product-channel.e2e-spec.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /* tslint:disable:no-non-null-assertion */
  2. import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN } from '@vendure/testing';
  3. import path from 'path';
  4. import { initialData } from '../../../e2e-common/e2e-initial-data';
  5. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  6. import {
  7. AssignProductsToChannel,
  8. AssignProductVariantsToChannel,
  9. CreateAdministrator,
  10. CreateChannel,
  11. CreateProduct,
  12. CreateProductVariants,
  13. CreateRole,
  14. CurrencyCode,
  15. GetProductWithVariants,
  16. LanguageCode,
  17. Permission,
  18. ProductVariantFragment,
  19. RemoveProductsFromChannel,
  20. RemoveProductVariantsFromChannel,
  21. UpdateChannel,
  22. UpdateProduct,
  23. } from './graphql/generated-e2e-admin-types';
  24. import {
  25. ASSIGN_PRODUCTVARIANT_TO_CHANNEL,
  26. ASSIGN_PRODUCT_TO_CHANNEL,
  27. CREATE_ADMINISTRATOR,
  28. CREATE_CHANNEL,
  29. CREATE_PRODUCT,
  30. CREATE_PRODUCT_VARIANTS,
  31. CREATE_ROLE,
  32. GET_PRODUCT_WITH_VARIANTS,
  33. REMOVE_PRODUCTVARIANT_FROM_CHANNEL,
  34. REMOVE_PRODUCT_FROM_CHANNEL,
  35. UPDATE_CHANNEL,
  36. UPDATE_PRODUCT,
  37. } from './graphql/shared-definitions';
  38. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  39. describe('ChannelAware Products and ProductVariants', () => {
  40. const { server, adminClient, shopClient } = createTestEnvironment(testConfig());
  41. const SECOND_CHANNEL_TOKEN = 'second_channel_token';
  42. const THIRD_CHANNEL_TOKEN = 'third_channel_token';
  43. let secondChannelAdminRole: CreateRole.CreateRole;
  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<CreateChannel.Mutation, CreateChannel.Variables>(CREATE_CHANNEL, {
  52. input: {
  53. code: 'second-channel',
  54. token: SECOND_CHANNEL_TOKEN,
  55. defaultLanguageCode: LanguageCode.en,
  56. currencyCode: CurrencyCode.USD,
  57. pricesIncludeTax: true,
  58. defaultShippingZoneId: 'T_1',
  59. defaultTaxZoneId: 'T_1',
  60. },
  61. });
  62. await adminClient.query<CreateChannel.Mutation, CreateChannel.Variables>(CREATE_CHANNEL, {
  63. input: {
  64. code: 'third-channel',
  65. token: THIRD_CHANNEL_TOKEN,
  66. defaultLanguageCode: LanguageCode.en,
  67. currencyCode: CurrencyCode.USD,
  68. pricesIncludeTax: true,
  69. defaultShippingZoneId: 'T_1',
  70. defaultTaxZoneId: 'T_1',
  71. },
  72. });
  73. const { createRole } = await adminClient.query<CreateRole.Mutation, CreateRole.Variables>(
  74. CREATE_ROLE,
  75. {
  76. input: {
  77. description: 'second channel admin',
  78. code: 'second-channel-admin',
  79. channelIds: ['T_2'],
  80. permissions: [
  81. Permission.ReadCatalog,
  82. Permission.ReadSettings,
  83. Permission.ReadAdministrator,
  84. Permission.CreateAdministrator,
  85. Permission.UpdateAdministrator,
  86. ],
  87. },
  88. },
  89. );
  90. secondChannelAdminRole = createRole;
  91. await adminClient.query<CreateAdministrator.Mutation, CreateAdministrator.Variables>(
  92. CREATE_ADMINISTRATOR,
  93. {
  94. input: {
  95. firstName: 'Admin',
  96. lastName: 'Two',
  97. emailAddress: 'admin2@test.com',
  98. password: 'test',
  99. roleIds: [secondChannelAdminRole.id],
  100. },
  101. },
  102. );
  103. }, TEST_SETUP_TIMEOUT_MS);
  104. afterAll(async () => {
  105. await server.destroy();
  106. });
  107. describe('assigning Product to Channels', () => {
  108. let product1: GetProductWithVariants.Product;
  109. beforeAll(async () => {
  110. await adminClient.asSuperAdmin();
  111. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  112. const { product } = await adminClient.query<
  113. GetProductWithVariants.Query,
  114. GetProductWithVariants.Variables
  115. >(GET_PRODUCT_WITH_VARIANTS, {
  116. id: 'T_1',
  117. });
  118. product1 = product!;
  119. });
  120. it(
  121. 'throws if attempting to assign Product to channel to which the admin has no access',
  122. assertThrowsWithMessage(async () => {
  123. await adminClient.asUserWithCredentials('admin2@test.com', 'test');
  124. await adminClient.query<AssignProductsToChannel.Mutation, AssignProductsToChannel.Variables>(
  125. ASSIGN_PRODUCT_TO_CHANNEL,
  126. {
  127. input: {
  128. channelId: 'T_3',
  129. productIds: [product1.id],
  130. },
  131. },
  132. );
  133. }, 'You are not currently authorized to perform this action'),
  134. );
  135. it('assigns Product to Channel and applies price factor', async () => {
  136. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  137. const PRICE_FACTOR = 0.5;
  138. await adminClient.asSuperAdmin();
  139. const { assignProductsToChannel } = await adminClient.query<
  140. AssignProductsToChannel.Mutation,
  141. AssignProductsToChannel.Variables
  142. >(ASSIGN_PRODUCT_TO_CHANNEL, {
  143. input: {
  144. channelId: 'T_2',
  145. productIds: [product1.id],
  146. priceFactor: PRICE_FACTOR,
  147. },
  148. });
  149. expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
  150. await adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  151. const { product } = await adminClient.query<
  152. GetProductWithVariants.Query,
  153. GetProductWithVariants.Variables
  154. >(GET_PRODUCT_WITH_VARIANTS, {
  155. id: product1.id,
  156. });
  157. expect(product!.variants.map(v => v.price)).toEqual(
  158. product1.variants.map(v => Math.round(v.price * PRICE_FACTOR)),
  159. );
  160. // Second Channel is configured to include taxes in price, so they should be the same.
  161. expect(product!.variants.map(v => v.priceWithTax)).toEqual(
  162. product1.variants.map(v => Math.round(v.priceWithTax * PRICE_FACTOR)),
  163. );
  164. });
  165. it('ProductVariant.channels includes all Channels from default Channel', async () => {
  166. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  167. const { product } = await adminClient.query<
  168. GetProductWithVariants.Query,
  169. GetProductWithVariants.Variables
  170. >(GET_PRODUCT_WITH_VARIANTS, {
  171. id: product1.id,
  172. });
  173. expect(product?.variants[0].channels.map(c => c.id)).toEqual(['T_1', 'T_2']);
  174. });
  175. it('ProductVariant.channels includes only current Channel from non-default Channel', async () => {
  176. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  177. const { product } = await adminClient.query<
  178. GetProductWithVariants.Query,
  179. GetProductWithVariants.Variables
  180. >(GET_PRODUCT_WITH_VARIANTS, {
  181. id: product1.id,
  182. });
  183. expect(product?.variants[0].channels.map(c => c.id)).toEqual(['T_2']);
  184. });
  185. it('does not assign Product to same channel twice', async () => {
  186. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  187. const { assignProductsToChannel } = await adminClient.query<
  188. AssignProductsToChannel.Mutation,
  189. AssignProductsToChannel.Variables
  190. >(ASSIGN_PRODUCT_TO_CHANNEL, {
  191. input: {
  192. channelId: 'T_2',
  193. productIds: [product1.id],
  194. },
  195. });
  196. expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
  197. });
  198. it(
  199. 'throws if attempting to remove Product from default Channel',
  200. assertThrowsWithMessage(async () => {
  201. await adminClient.query<
  202. RemoveProductsFromChannel.Mutation,
  203. RemoveProductsFromChannel.Variables
  204. >(REMOVE_PRODUCT_FROM_CHANNEL, {
  205. input: {
  206. productIds: [product1.id],
  207. channelId: 'T_1',
  208. },
  209. });
  210. }, 'Products cannot be removed from the default Channel'),
  211. );
  212. it('removes Product from Channel', async () => {
  213. await adminClient.asSuperAdmin();
  214. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  215. const { removeProductsFromChannel } = await adminClient.query<
  216. RemoveProductsFromChannel.Mutation,
  217. RemoveProductsFromChannel.Variables
  218. >(REMOVE_PRODUCT_FROM_CHANNEL, {
  219. input: {
  220. productIds: [product1.id],
  221. channelId: 'T_2',
  222. },
  223. });
  224. expect(removeProductsFromChannel[0].channels.map(c => c.id)).toEqual(['T_1']);
  225. });
  226. });
  227. describe('assigning ProductVariant to Channels', () => {
  228. let product1: GetProductWithVariants.Product;
  229. beforeAll(async () => {
  230. await adminClient.asSuperAdmin();
  231. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  232. const { product } = await adminClient.query<
  233. GetProductWithVariants.Query,
  234. GetProductWithVariants.Variables
  235. >(GET_PRODUCT_WITH_VARIANTS, {
  236. id: 'T_2',
  237. });
  238. product1 = product!;
  239. });
  240. it(
  241. 'throws if attempting to assign ProductVariant to channel to which the admin has no access',
  242. assertThrowsWithMessage(async () => {
  243. await adminClient.asUserWithCredentials('admin2@test.com', 'test');
  244. await adminClient.query<
  245. AssignProductVariantsToChannel.Mutation,
  246. AssignProductVariantsToChannel.Variables
  247. >(ASSIGN_PRODUCTVARIANT_TO_CHANNEL, {
  248. input: {
  249. channelId: 'T_3',
  250. productVariantIds: [product1.variants[0].id],
  251. },
  252. });
  253. }, 'You are not currently authorized to perform this action'),
  254. );
  255. it('assigns ProductVariant to Channel and applies price factor', async () => {
  256. const PRICE_FACTOR = 0.5;
  257. await adminClient.asSuperAdmin();
  258. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  259. const { assignProductVariantsToChannel } = await adminClient.query<
  260. AssignProductVariantsToChannel.Mutation,
  261. AssignProductVariantsToChannel.Variables
  262. >(ASSIGN_PRODUCTVARIANT_TO_CHANNEL, {
  263. input: {
  264. channelId: 'T_3',
  265. productVariantIds: [product1.variants[0].id],
  266. priceFactor: PRICE_FACTOR,
  267. },
  268. });
  269. expect(assignProductVariantsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  270. await adminClient.setChannelToken(THIRD_CHANNEL_TOKEN);
  271. const { product } = await adminClient.query<
  272. GetProductWithVariants.Query,
  273. GetProductWithVariants.Variables
  274. >(GET_PRODUCT_WITH_VARIANTS, {
  275. id: product1.id,
  276. });
  277. expect(product!.channels.map(c => c.id).sort()).toEqual(['T_3']);
  278. expect(product!.variants.map(v => v.price)).toEqual([
  279. Math.round(product1.variants[0].price * PRICE_FACTOR),
  280. ]);
  281. // Third Channel is configured to include taxes in price, so they should be the same.
  282. expect(product!.variants.map(v => v.priceWithTax)).toEqual([
  283. Math.round(product1.variants[0].priceWithTax * PRICE_FACTOR),
  284. ]);
  285. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  286. const { product: check } = await adminClient.query<
  287. GetProductWithVariants.Query,
  288. GetProductWithVariants.Variables
  289. >(GET_PRODUCT_WITH_VARIANTS, {
  290. id: product1.id,
  291. });
  292. // from the default channel, all channels are visible
  293. expect(check?.channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  294. expect(check?.variants[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  295. expect(check?.variants[1].channels.map(c => c.id).sort()).toEqual(['T_1']);
  296. });
  297. it('does not assign ProductVariant to same channel twice', async () => {
  298. await adminClient.asSuperAdmin();
  299. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  300. const { assignProductVariantsToChannel } = await adminClient.query<
  301. AssignProductVariantsToChannel.Mutation,
  302. AssignProductVariantsToChannel.Variables
  303. >(ASSIGN_PRODUCTVARIANT_TO_CHANNEL, {
  304. input: {
  305. channelId: 'T_3',
  306. productVariantIds: [product1.variants[0].id],
  307. },
  308. });
  309. expect(assignProductVariantsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  310. });
  311. it(
  312. 'throws if attempting to remove ProductVariant from default Channel',
  313. assertThrowsWithMessage(async () => {
  314. await adminClient.query<
  315. RemoveProductVariantsFromChannel.Mutation,
  316. RemoveProductVariantsFromChannel.Variables
  317. >(REMOVE_PRODUCTVARIANT_FROM_CHANNEL, {
  318. input: {
  319. productVariantIds: [product1.variants[0].id],
  320. channelId: 'T_1',
  321. },
  322. });
  323. }, 'Products cannot be removed from the default Channel'),
  324. );
  325. it('removes ProductVariant but not Product from Channel', async () => {
  326. await adminClient.asSuperAdmin();
  327. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  328. const { assignProductVariantsToChannel } = await adminClient.query<
  329. AssignProductVariantsToChannel.Mutation,
  330. AssignProductVariantsToChannel.Variables
  331. >(ASSIGN_PRODUCTVARIANT_TO_CHANNEL, {
  332. input: {
  333. channelId: 'T_3',
  334. productVariantIds: [product1.variants[1].id],
  335. },
  336. });
  337. expect(assignProductVariantsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  338. const { removeProductVariantsFromChannel } = await adminClient.query<
  339. RemoveProductVariantsFromChannel.Mutation,
  340. RemoveProductVariantsFromChannel.Variables
  341. >(REMOVE_PRODUCTVARIANT_FROM_CHANNEL, {
  342. input: {
  343. productVariantIds: [product1.variants[1].id],
  344. channelId: 'T_3',
  345. },
  346. });
  347. expect(removeProductVariantsFromChannel[0].channels.map(c => c.id)).toEqual(['T_1']);
  348. const { product } = await adminClient.query<
  349. GetProductWithVariants.Query,
  350. GetProductWithVariants.Variables
  351. >(GET_PRODUCT_WITH_VARIANTS, {
  352. id: product1.id,
  353. });
  354. expect(product!.channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
  355. });
  356. it('removes ProductVariant and Product from Channel', async () => {
  357. await adminClient.asSuperAdmin();
  358. await adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  359. const { removeProductVariantsFromChannel } = await adminClient.query<
  360. RemoveProductVariantsFromChannel.Mutation,
  361. RemoveProductVariantsFromChannel.Variables
  362. >(REMOVE_PRODUCTVARIANT_FROM_CHANNEL, {
  363. input: {
  364. productVariantIds: [product1.variants[0].id],
  365. channelId: 'T_3',
  366. },
  367. });
  368. expect(removeProductVariantsFromChannel[0].channels.map(c => c.id)).toEqual(['T_1']);
  369. const { product } = await adminClient.query<
  370. GetProductWithVariants.Query,
  371. GetProductWithVariants.Variables
  372. >(GET_PRODUCT_WITH_VARIANTS, {
  373. id: product1.id,
  374. });
  375. expect(product!.channels.map(c => c.id).sort()).toEqual(['T_1']);
  376. });
  377. });
  378. describe('creating Product in sub-channel', () => {
  379. let createdProduct: CreateProduct.CreateProduct;
  380. let createdVariant: ProductVariantFragment;
  381. it('creates a Product in sub-channel', async () => {
  382. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  383. const { createProduct } = await adminClient.query<
  384. CreateProduct.Mutation,
  385. CreateProduct.Variables
  386. >(CREATE_PRODUCT, {
  387. input: {
  388. translations: [
  389. {
  390. languageCode: LanguageCode.en,
  391. name: 'Channel Product',
  392. slug: 'channel-product',
  393. description: 'Channel product',
  394. },
  395. ],
  396. },
  397. });
  398. const { createProductVariants } = await adminClient.query<
  399. CreateProductVariants.Mutation,
  400. CreateProductVariants.Variables
  401. >(CREATE_PRODUCT_VARIANTS, {
  402. input: [
  403. {
  404. productId: createProduct.id,
  405. sku: 'PV1',
  406. optionIds: [],
  407. translations: [{ languageCode: LanguageCode.en, name: 'Variant 1' }],
  408. },
  409. ],
  410. });
  411. createdProduct = createProduct;
  412. createdVariant = createProductVariants[0]!;
  413. // from sub-channel, only that channel is visible
  414. expect(createdProduct.channels.map(c => c.id).sort()).toEqual(['T_2']);
  415. expect(createdVariant.channels.map(c => c.id).sort()).toEqual(['T_2']);
  416. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  417. const { product } = await adminClient.query<
  418. GetProductWithVariants.Query,
  419. GetProductWithVariants.Variables
  420. >(GET_PRODUCT_WITH_VARIANTS, {
  421. id: createProduct.id,
  422. });
  423. // from the default channel, all channels are visible
  424. expect(product?.channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
  425. expect(product?.variants[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
  426. });
  427. });
  428. describe('updating Product in sub-channel', () => {
  429. it(
  430. 'throws if attempting to update a Product which is not assigned to that Channel',
  431. assertThrowsWithMessage(async () => {
  432. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  433. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  434. input: {
  435. id: 'T_2',
  436. translations: [{ languageCode: LanguageCode.en, name: 'xyz' }],
  437. },
  438. });
  439. }, `No Product with the id '2' could be found`),
  440. );
  441. });
  442. });