product-channel.e2e-spec.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import {
  3. createErrorResultGuard,
  4. createTestEnvironment,
  5. E2E_DEFAULT_CHANNEL_TOKEN,
  6. ErrorResultGuard,
  7. } from '@vendure/testing';
  8. import { fail } from 'assert';
  9. import gql from 'graphql-tag';
  10. import path from 'path';
  11. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  12. import { initialData } from '../../../e2e-common/e2e-initial-data';
  13. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  14. import {
  15. AssignProductsToChannelDocument,
  16. AssignProductVariantsToChannelDocument,
  17. ChannelFragment,
  18. CreateAdministratorDocument,
  19. CreateChannelDocument,
  20. CreateProductDocument,
  21. CreateProductMutation,
  22. CreateProductVariantsDocument,
  23. CreateRoleDocument,
  24. CreateRoleMutation,
  25. CurrencyCode,
  26. GetChannelsDocument,
  27. GetProductVariantListDocument,
  28. GetProductWithVariantsDocument,
  29. GetProductWithVariantsQuery,
  30. LanguageCode,
  31. Permission,
  32. ProductVariantFragment,
  33. RemoveProductsFromChannelDocument,
  34. RemoveProductVariantsFromChannelDocument,
  35. UpdateChannelDocument,
  36. UpdateProductDocument,
  37. UpdateProductVariantsDocument,
  38. } from './graphql/generated-e2e-admin-types';
  39. import { AddItemToOrderMutation, AddItemToOrderMutationVariables } from './graphql/generated-e2e-shop-types';
  40. import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
  41. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  42. describe('ChannelAware Products and ProductVariants', () => {
  43. const { server, adminClient, shopClient } = createTestEnvironment(testConfig());
  44. const SECOND_CHANNEL_TOKEN = 'second_channel_token';
  45. const THIRD_CHANNEL_TOKEN = 'third_channel_token';
  46. let secondChannelAdminRole: CreateRoleMutation['createRole'];
  47. const orderResultGuard: ErrorResultGuard<{ lines: Array<{ id: string }> }> = createErrorResultGuard(
  48. input => !!input.lines,
  49. );
  50. beforeAll(async () => {
  51. await server.init({
  52. initialData,
  53. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  54. customerCount: 1,
  55. });
  56. await adminClient.asSuperAdmin();
  57. await adminClient.query(CreateChannelDocument, {
  58. input: {
  59. code: 'second-channel',
  60. token: SECOND_CHANNEL_TOKEN,
  61. defaultLanguageCode: LanguageCode.en,
  62. currencyCode: CurrencyCode.GBP,
  63. pricesIncludeTax: true,
  64. defaultShippingZoneId: 'T_1',
  65. defaultTaxZoneId: 'T_1',
  66. },
  67. });
  68. await adminClient.query(CreateChannelDocument, {
  69. input: {
  70. code: 'third-channel',
  71. token: THIRD_CHANNEL_TOKEN,
  72. defaultLanguageCode: LanguageCode.en,
  73. currencyCode: CurrencyCode.EUR,
  74. pricesIncludeTax: true,
  75. defaultShippingZoneId: 'T_1',
  76. defaultTaxZoneId: 'T_1',
  77. },
  78. });
  79. const { createRole } = await adminClient.query(CreateRoleDocument, {
  80. input: {
  81. description: 'second channel admin',
  82. code: 'second-channel-admin',
  83. channelIds: ['T_2'],
  84. permissions: [
  85. Permission.ReadCatalog,
  86. Permission.ReadSettings,
  87. Permission.ReadAdministrator,
  88. Permission.CreateAdministrator,
  89. Permission.UpdateAdministrator,
  90. ],
  91. },
  92. });
  93. secondChannelAdminRole = createRole;
  94. await adminClient.query(CreateAdministratorDocument, {
  95. input: {
  96. firstName: 'Admin',
  97. lastName: 'Two',
  98. emailAddress: 'admin2@test.com',
  99. password: 'test',
  100. roleIds: [secondChannelAdminRole.id],
  101. },
  102. });
  103. }, TEST_SETUP_TIMEOUT_MS);
  104. afterAll(async () => {
  105. await server.destroy();
  106. });
  107. describe('assigning Product to Channels', () => {
  108. let product1: NonNullable<GetProductWithVariantsQuery['product']>;
  109. beforeAll(async () => {
  110. await adminClient.asSuperAdmin();
  111. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  112. const { product } = await adminClient.query(GetProductWithVariantsDocument, {
  113. id: 'T_1',
  114. });
  115. product1 = product!;
  116. });
  117. it(
  118. 'throws if attempting to assign Product to channel to which the admin has no access',
  119. assertThrowsWithMessage(async () => {
  120. await adminClient.asUserWithCredentials('admin2@test.com', 'test');
  121. await adminClient.query(AssignProductsToChannelDocument, {
  122. input: {
  123. channelId: 'T_3',
  124. productIds: [product1.id],
  125. },
  126. });
  127. }, 'You are not currently authorized to perform this action'),
  128. );
  129. it('assigns Product to Channel and applies price factor', async () => {
  130. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  131. const PRICE_FACTOR = 0.5;
  132. await adminClient.asSuperAdmin();
  133. const { assignProductsToChannel } = await adminClient.query(AssignProductsToChannelDocument, {
  134. input: {
  135. channelId: 'T_2',
  136. productIds: [product1.id],
  137. priceFactor: PRICE_FACTOR,
  138. },
  139. });
  140. expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
  141. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  142. const { product } = await adminClient.query(GetProductWithVariantsDocument, {
  143. id: product1.id,
  144. });
  145. expect(product!.variants.map(v => v.price)).toEqual(
  146. product1.variants.map(v => Math.round(v.price * PRICE_FACTOR)),
  147. );
  148. // Second Channel is configured to include taxes in price, so they should be the same.
  149. expect(product!.variants.map(v => v.priceWithTax)).toEqual(
  150. product1.variants.map(v => Math.round(v.priceWithTax * PRICE_FACTOR)),
  151. );
  152. // Second Channel has the default currency of GBP, so the prices should be the same.
  153. expect(product!.variants.map(v => v.currencyCode)).toEqual(['GBP', 'GBP', 'GBP', 'GBP']);
  154. });
  155. it('ProductVariant.channels includes all Channels from default Channel', async () => {
  156. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  157. const { product } = await adminClient.query(GetProductWithVariantsDocument, {
  158. id: product1.id,
  159. });
  160. expect(product?.variants[0].channels.map(c => c.id)).toEqual(['T_1', 'T_2']);
  161. });
  162. it('ProductVariant.channels includes only current Channel from non-default Channel', async () => {
  163. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  164. const { product } = await adminClient.query(GetProductWithVariantsDocument, {
  165. id: product1.id,
  166. });
  167. expect(product?.variants[0].channels.map(c => c.id)).toEqual(['T_2']);
  168. });
  169. it('does not assign Product to same channel twice', async () => {
  170. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  171. const { assignProductsToChannel } = await adminClient.query(AssignProductsToChannelDocument, {
  172. input: {
  173. channelId: 'T_2',
  174. productIds: [product1.id],
  175. },
  176. });
  177. expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
  178. });
  179. it(
  180. 'throws if attempting to remove Product from default Channel',
  181. assertThrowsWithMessage(async () => {
  182. await adminClient.query(RemoveProductsFromChannelDocument, {
  183. input: {
  184. productIds: [product1.id],
  185. channelId: 'T_1',
  186. },
  187. });
  188. }, 'Items cannot be removed from the default Channel'),
  189. );
  190. it('removes Product from Channel', async () => {
  191. await adminClient.asSuperAdmin();
  192. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  193. const { removeProductsFromChannel } = await adminClient.query(RemoveProductsFromChannelDocument, {
  194. input: {
  195. productIds: [product1.id],
  196. channelId: 'T_2',
  197. },
  198. });
  199. expect(removeProductsFromChannel[0].channels.map(c => c.id)).toEqual(['T_1']);
  200. });
  201. // https://github.com/vendurehq/vendure/issues/2716
  202. it('querying an Order with a variant that was since removed from the channel', async () => {
  203. await adminClient.query(AssignProductsToChannelDocument, {
  204. input: {
  205. channelId: 'T_2',
  206. productIds: [product1.id],
  207. priceFactor: 1,
  208. },
  209. });
  210. // Create an order in the second channel with the variant just assigned
  211. shopClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  212. const { addItemToOrder } = await shopClient.query<
  213. AddItemToOrderMutation,
  214. AddItemToOrderMutationVariables
  215. >(ADD_ITEM_TO_ORDER, {
  216. productVariantId: product1.variants[0].id,
  217. quantity: 1,
  218. });
  219. orderResultGuard.assertSuccess(addItemToOrder);
  220. // Now remove that variant from the second channel
  221. await adminClient.query(RemoveProductsFromChannelDocument, {
  222. input: {
  223. productIds: [product1.id],
  224. channelId: 'T_2',
  225. },
  226. });
  227. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  228. // If no price fields are requested on the ProductVariant, then the query will
  229. // succeed even if the ProductVariant is no longer assigned to the channel.
  230. const GET_ORDER_WITHOUT_VARIANT_PRICE = `
  231. query GetOrderWithoutVariantPrice($id: ID!) {
  232. order(id: $id) {
  233. id
  234. lines {
  235. id
  236. linePrice
  237. productVariant {
  238. id
  239. name
  240. }
  241. }
  242. }
  243. }`;
  244. const { order } = await adminClient.query(gql(GET_ORDER_WITHOUT_VARIANT_PRICE), {
  245. id: addItemToOrder.id,
  246. });
  247. expect(order).toEqual({
  248. id: 'T_1',
  249. lines: [
  250. {
  251. id: 'T_1',
  252. linePrice: 129900,
  253. productVariant: {
  254. id: 'T_1',
  255. name: 'Laptop 13 inch 8GB',
  256. },
  257. },
  258. ],
  259. });
  260. try {
  261. // The API will only throw if one of the price fields is requested in the query
  262. const GET_ORDER_WITH_VARIANT_PRICE = `
  263. query GetOrderWithVariantPrice($id: ID!) {
  264. order(id: $id) {
  265. id
  266. lines {
  267. id
  268. linePrice
  269. productVariant {
  270. id
  271. name
  272. price
  273. }
  274. }
  275. }
  276. }`;
  277. await adminClient.query(gql(GET_ORDER_WITH_VARIANT_PRICE), {
  278. id: addItemToOrder.id,
  279. });
  280. fail(`Should have thrown`);
  281. } catch (e: any) {
  282. expect(e.message).toContain(
  283. 'No price information was found for ProductVariant ID "1" in the Channel "second-channel"',
  284. );
  285. }
  286. });
  287. });
  288. describe('assigning ProductVariant to Channels', () => {
  289. let product1: NonNullable<GetProductWithVariantsQuery['product']>;
  290. beforeAll(async () => {
  291. await adminClient.asSuperAdmin();
  292. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  293. const { product } = await adminClient.query(GetProductWithVariantsDocument, {
  294. id: 'T_2',
  295. });
  296. product1 = product!;
  297. });
  298. it(
  299. 'throws if attempting to assign ProductVariant to channel to which the admin has no access',
  300. assertThrowsWithMessage(async () => {
  301. await adminClient.asUserWithCredentials('admin2@test.com', 'test');
  302. await adminClient.query(AssignProductVariantsToChannelDocument, {
  303. input: {
  304. channelId: 'T_3',
  305. productVariantIds: [product1.variants[0].id],
  306. },
  307. });
  308. }, 'You are not currently authorized to perform this action'),
  309. );
  310. it('assigns ProductVariant to Channel and applies price factor', async () => {
  311. const PRICE_FACTOR = 0.5;
  312. await adminClient.asSuperAdmin();
  313. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  314. const { assignProductVariantsToChannel } = await adminClient.query(
  315. AssignProductVariantsToChannelDocument,
  316. {
  317. input: {
  318. channelId: 'T_3',
  319. productVariantIds: [product1.variants[0].id],
  320. priceFactor: PRICE_FACTOR,
  321. },
  322. },
  323. );
  324. expect(assignProductVariantsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  325. adminClient.setChannelToken(THIRD_CHANNEL_TOKEN);
  326. const { product } = await adminClient.query(GetProductWithVariantsDocument, {
  327. id: product1.id,
  328. });
  329. expect(product!.channels.map(c => c.id).sort()).toEqual(['T_3']);
  330. // Third Channel is configured to include taxes in price, so they should be the same.
  331. expect(product!.variants.map(v => v.priceWithTax)).toEqual([
  332. Math.round(product1.variants[0].priceWithTax * PRICE_FACTOR),
  333. ]);
  334. // Third Channel has the default currency EUR
  335. expect(product!.variants.map(v => v.currencyCode)).toEqual(['EUR']);
  336. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  337. const { product: check } = await adminClient.query(GetProductWithVariantsDocument, {
  338. id: product1.id,
  339. });
  340. // from the default channel, all channels are visible
  341. expect(check?.channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  342. expect(check?.variants[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  343. expect(check?.variants[1].channels.map(c => c.id).sort()).toEqual(['T_1']);
  344. });
  345. it('does not assign ProductVariant to same channel twice', async () => {
  346. await adminClient.asSuperAdmin();
  347. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  348. const { assignProductVariantsToChannel } = await adminClient.query(
  349. AssignProductVariantsToChannelDocument,
  350. {
  351. input: {
  352. channelId: 'T_3',
  353. productVariantIds: [product1.variants[0].id],
  354. },
  355. },
  356. );
  357. expect(assignProductVariantsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  358. });
  359. it(
  360. 'throws if attempting to remove ProductVariant from default Channel',
  361. assertThrowsWithMessage(async () => {
  362. await adminClient.query(RemoveProductVariantsFromChannelDocument, {
  363. input: {
  364. productVariantIds: [product1.variants[0].id],
  365. channelId: 'T_1',
  366. },
  367. });
  368. }, 'Items cannot be removed from the default Channel'),
  369. );
  370. it('removes ProductVariant but not Product from Channel', async () => {
  371. await adminClient.asSuperAdmin();
  372. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  373. const { assignProductVariantsToChannel } = await adminClient.query(
  374. AssignProductVariantsToChannelDocument,
  375. {
  376. input: {
  377. channelId: 'T_3',
  378. productVariantIds: [product1.variants[1].id],
  379. },
  380. },
  381. );
  382. expect(assignProductVariantsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  383. const { removeProductVariantsFromChannel } = await adminClient.query(
  384. RemoveProductVariantsFromChannelDocument,
  385. {
  386. input: {
  387. productVariantIds: [product1.variants[1].id],
  388. channelId: 'T_3',
  389. },
  390. },
  391. );
  392. expect(removeProductVariantsFromChannel[0].channels.map(c => c.id)).toEqual(['T_1']);
  393. const { product } = await adminClient.query(GetProductWithVariantsDocument, {
  394. id: product1.id,
  395. });
  396. expect(product!.channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  397. });
  398. it('removes ProductVariant and Product from Channel', async () => {
  399. await adminClient.asSuperAdmin();
  400. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  401. const { removeProductVariantsFromChannel } = await adminClient.query(
  402. RemoveProductVariantsFromChannelDocument,
  403. {
  404. input: {
  405. productVariantIds: [product1.variants[0].id],
  406. channelId: 'T_3',
  407. },
  408. },
  409. );
  410. expect(removeProductVariantsFromChannel[0].channels.map(c => c.id)).toEqual(['T_1']);
  411. const { product } = await adminClient.query(GetProductWithVariantsDocument, {
  412. id: product1.id,
  413. });
  414. expect(product!.channels.map(c => c.id).sort()).toEqual(['T_1']);
  415. });
  416. });
  417. describe('creating Product in sub-channel', () => {
  418. let createdProduct: CreateProductMutation['createProduct'];
  419. let createdVariant: ProductVariantFragment;
  420. it('creates a Product in sub-channel', async () => {
  421. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  422. const { createProduct } = await adminClient.query(CreateProductDocument, {
  423. input: {
  424. translations: [
  425. {
  426. languageCode: LanguageCode.en,
  427. name: 'Channel Product',
  428. slug: 'channel-product',
  429. description: 'Channel product',
  430. },
  431. ],
  432. },
  433. });
  434. const { createProductVariants } = await adminClient.query(CreateProductVariantsDocument, {
  435. input: [
  436. {
  437. productId: createProduct.id,
  438. sku: 'PV1',
  439. optionIds: [],
  440. translations: [{ languageCode: LanguageCode.en, name: 'Variant 1' }],
  441. },
  442. ],
  443. });
  444. createdProduct = createProduct;
  445. createdVariant = createProductVariants[0]!;
  446. // from sub-channel, only that channel is visible
  447. expect(createdProduct.channels.map(c => c.id).sort()).toEqual(['T_2']);
  448. expect(createdVariant.channels.map(c => c.id).sort()).toEqual(['T_2']);
  449. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  450. const { product } = await adminClient.query(GetProductWithVariantsDocument, {
  451. id: createProduct.id,
  452. });
  453. // from the default channel, all channels are visible
  454. expect(product?.channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
  455. expect(product?.variants[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
  456. });
  457. });
  458. describe('updating Product in sub-channel', () => {
  459. it(
  460. 'throws if attempting to update a Product which is not assigned to that Channel',
  461. assertThrowsWithMessage(async () => {
  462. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  463. await adminClient.query(UpdateProductDocument, {
  464. input: {
  465. id: 'T_2',
  466. translations: [{ languageCode: LanguageCode.en, name: 'xyz' }],
  467. },
  468. });
  469. }, 'No Product with the id "2" could be found'),
  470. );
  471. });
  472. describe('updating channel defaultCurrencyCode', () => {
  473. let secondChannelId: string;
  474. const channelGuard: ErrorResultGuard<ChannelFragment> = createErrorResultGuard(input => !!input.id);
  475. beforeAll(async () => {
  476. const { channels } = await adminClient.query(GetChannelsDocument);
  477. secondChannelId = channels.items.find(c => c.token === SECOND_CHANNEL_TOKEN)!.id;
  478. });
  479. it('updates variant prices from old default to new', async () => {
  480. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  481. const { productVariants } = await adminClient.query(GetProductVariantListDocument, {});
  482. expect(productVariants.items.map(i => i.currencyCode)).toEqual(['GBP']);
  483. const { updateChannel } = await adminClient.query(UpdateChannelDocument, {
  484. input: {
  485. id: secondChannelId,
  486. availableCurrencyCodes: [CurrencyCode.MYR, CurrencyCode.GBP, CurrencyCode.EUR],
  487. defaultCurrencyCode: CurrencyCode.MYR,
  488. },
  489. });
  490. channelGuard.assertSuccess(updateChannel);
  491. expect(updateChannel.defaultCurrencyCode).toBe(CurrencyCode.MYR);
  492. const { productVariants: variantsAfter } = await adminClient.query(
  493. GetProductVariantListDocument,
  494. {},
  495. );
  496. expect(variantsAfter.items.map(i => i.currencyCode)).toEqual(['MYR']);
  497. });
  498. it('does not change prices in other currencies', async () => {
  499. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  500. const { productVariants } = await adminClient.query(GetProductVariantListDocument, {});
  501. const { updateProductVariants } = await adminClient.query(UpdateProductVariantsDocument, {
  502. input: productVariants.items.map(i => ({
  503. id: i.id,
  504. prices: [
  505. { price: 100, currencyCode: CurrencyCode.GBP },
  506. { price: 200, currencyCode: CurrencyCode.MYR },
  507. { price: 300, currencyCode: CurrencyCode.EUR },
  508. ],
  509. })),
  510. });
  511. expect(updateProductVariants[0]?.prices.sort((a, b) => a.price - b.price)).toEqual([
  512. { currencyCode: 'GBP', price: 100 },
  513. { currencyCode: 'MYR', price: 200 },
  514. { currencyCode: 'EUR', price: 300 },
  515. ]);
  516. expect(updateProductVariants[0]?.currencyCode).toBe('MYR');
  517. await adminClient.query(UpdateChannelDocument, {
  518. input: {
  519. id: secondChannelId,
  520. availableCurrencyCodes: [
  521. CurrencyCode.MYR,
  522. CurrencyCode.GBP,
  523. CurrencyCode.EUR,
  524. CurrencyCode.AUD,
  525. ],
  526. defaultCurrencyCode: CurrencyCode.AUD,
  527. },
  528. });
  529. const { productVariants: after } = await adminClient.query(GetProductVariantListDocument, {});
  530. expect(after.items.map(i => i.currencyCode)).toEqual(['AUD']);
  531. expect(after.items[0]?.prices.sort((a, b) => a.price - b.price)).toEqual([
  532. { currencyCode: 'GBP', price: 100 },
  533. { currencyCode: 'AUD', price: 200 },
  534. { currencyCode: 'EUR', price: 300 },
  535. ]);
  536. });
  537. // https://github.com/vendurehq/vendure/issues/2391
  538. it('does not duplicate an existing price', async () => {
  539. await adminClient.query(UpdateChannelDocument, {
  540. input: {
  541. id: secondChannelId,
  542. defaultCurrencyCode: CurrencyCode.GBP,
  543. },
  544. });
  545. const { productVariants: after } = await adminClient.query(GetProductVariantListDocument, {});
  546. expect(after.items.map(i => i.currencyCode)).toEqual(['GBP']);
  547. expect(after.items[0]?.prices.sort((a, b) => a.price - b.price)).toEqual([
  548. { currencyCode: 'GBP', price: 100 },
  549. { currencyCode: 'AUD', price: 200 },
  550. { currencyCode: 'EUR', price: 300 },
  551. ]);
  552. });
  553. });
  554. describe('querying products', () => {
  555. // https://github.com/vendurehq/vendure/issues/2924
  556. it('find by slug with multiple channels', async () => {
  557. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  558. const { createProduct: secondChannelProduct } = await adminClient.query(CreateProductDocument, {
  559. input: {
  560. translations: [
  561. {
  562. languageCode: LanguageCode.en,
  563. name: 'Channel 2 Product',
  564. slug: 'unique-slug',
  565. description: 'Channel 2 product',
  566. },
  567. ],
  568. },
  569. });
  570. expect(secondChannelProduct.slug).toBe('unique-slug');
  571. adminClient.setChannelToken(THIRD_CHANNEL_TOKEN);
  572. const { createProduct: thirdChannelProduct } = await adminClient.query(CreateProductDocument, {
  573. input: {
  574. translations: [
  575. {
  576. languageCode: LanguageCode.en,
  577. name: 'Channel 3 Product',
  578. slug: 'unique-slug',
  579. description: 'Channel 3 product',
  580. },
  581. ],
  582. },
  583. });
  584. expect(thirdChannelProduct.slug).toBe('unique-slug');
  585. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  586. const { product: result1 } = await adminClient.query(GetProductWithVariantsDocument, {
  587. slug: 'unique-slug',
  588. });
  589. expect(result1?.name).toBe('Channel 2 Product');
  590. adminClient.setChannelToken(THIRD_CHANNEL_TOKEN);
  591. const { product: result2 } = await adminClient.query(GetProductWithVariantsDocument, {
  592. slug: 'unique-slug',
  593. });
  594. expect(result2?.name).toBe('Channel 3 Product');
  595. });
  596. });
  597. });