product-channel.e2e-spec.ts 28 KB

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