product.e2e-spec.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. import {
  2. AddOptionGroupToProduct,
  3. ApplyFacetValuesToProductVariants,
  4. CreateProduct,
  5. GenerateProductVariants,
  6. GetAssetList,
  7. GetProductList,
  8. GetProductWithVariants,
  9. LanguageCode,
  10. ProductWithVariants,
  11. RemoveOptionGroupFromProduct,
  12. SortOrder,
  13. UpdateProduct,
  14. UpdateProductVariants,
  15. } from 'shared/generated-types';
  16. import { omit } from 'shared/omit';
  17. import {
  18. ADD_OPTION_GROUP_TO_PRODUCT,
  19. APPLY_FACET_VALUE_TO_PRODUCT_VARIANTS,
  20. CREATE_PRODUCT,
  21. GENERATE_PRODUCT_VARIANTS,
  22. GET_ASSET_LIST,
  23. GET_PRODUCT_LIST,
  24. GET_PRODUCT_WITH_VARIANTS,
  25. REMOVE_OPTION_GROUP_FROM_PRODUCT,
  26. UPDATE_PRODUCT,
  27. UPDATE_PRODUCT_VARIANTS,
  28. } from '../../admin-ui/src/app/data/definitions/product-definitions';
  29. import { TestClient } from './test-client';
  30. import { TestServer } from './test-server';
  31. // tslint:disable:no-non-null-assertion
  32. describe('Product resolver', () => {
  33. const client = new TestClient();
  34. const server = new TestServer();
  35. beforeAll(async () => {
  36. const token = await server.init({
  37. productCount: 20,
  38. customerCount: 1,
  39. });
  40. await client.init();
  41. }, 60000);
  42. afterAll(async () => {
  43. await server.destroy();
  44. });
  45. describe('products list query', () => {
  46. it('returns all products when no options passed', async () => {
  47. const result = await client.query<GetProductList.Query, GetProductList.Variables>(
  48. GET_PRODUCT_LIST,
  49. {
  50. languageCode: LanguageCode.en,
  51. },
  52. );
  53. expect(result.products.items.length).toBe(20);
  54. expect(result.products.totalItems).toBe(20);
  55. });
  56. it('limits result set with skip & take', async () => {
  57. const result = await client.query<GetProductList.Query, GetProductList.Variables>(
  58. GET_PRODUCT_LIST,
  59. {
  60. languageCode: LanguageCode.en,
  61. options: {
  62. skip: 0,
  63. take: 3,
  64. },
  65. },
  66. );
  67. expect(result.products.items.length).toBe(3);
  68. expect(result.products.totalItems).toBe(20);
  69. });
  70. it('filters by name', async () => {
  71. const result = await client.query<GetProductList.Query, GetProductList.Variables>(
  72. GET_PRODUCT_LIST,
  73. {
  74. languageCode: LanguageCode.en,
  75. options: {
  76. filter: {
  77. name: {
  78. contains: 'fish',
  79. },
  80. },
  81. },
  82. },
  83. );
  84. expect(result.products.items.length).toBe(1);
  85. expect(result.products.items[0].name).toBe('en Practical Frozen Fish');
  86. });
  87. it('sorts by name', async () => {
  88. const result = await client.query<GetProductList.Query, GetProductList.Variables>(
  89. GET_PRODUCT_LIST,
  90. {
  91. languageCode: LanguageCode.en,
  92. options: {
  93. sort: {
  94. name: SortOrder.ASC,
  95. },
  96. },
  97. },
  98. );
  99. expect(result.products.items.map(p => p.name)).toMatchSnapshot();
  100. });
  101. });
  102. describe('product query', () => {
  103. it('returns expected properties', async () => {
  104. const result = await client.query<GetProductWithVariants.Query, GetProductWithVariants.Variables>(
  105. GET_PRODUCT_WITH_VARIANTS,
  106. {
  107. languageCode: LanguageCode.en,
  108. id: 'T_2',
  109. },
  110. );
  111. if (!result.product) {
  112. fail('Product not found');
  113. return;
  114. }
  115. expect(omit(result.product, ['variants'])).toMatchSnapshot();
  116. expect(result.product.variants.length).toBe(2);
  117. });
  118. it('ProductVariant price properties are correct', async () => {
  119. const result = await client.query<GetProductWithVariants.Query, GetProductWithVariants.Variables>(
  120. GET_PRODUCT_WITH_VARIANTS,
  121. {
  122. languageCode: LanguageCode.en,
  123. id: 'T_2',
  124. },
  125. );
  126. if (!result.product) {
  127. fail('Product not found');
  128. return;
  129. }
  130. expect(result.product.variants[0].priceBeforeTax).toBe(621);
  131. expect(result.product.variants[0].price).toBe(745);
  132. expect(result.product.variants[0].taxCategory).toEqual({
  133. id: 'T_1',
  134. name: 'Standard Tax',
  135. taxRate: 20,
  136. });
  137. });
  138. it('returns null when id not found', async () => {
  139. const result = await client.query<GetProductWithVariants.Query, GetProductWithVariants.Variables>(
  140. GET_PRODUCT_WITH_VARIANTS,
  141. {
  142. languageCode: LanguageCode.en,
  143. id: 'bad_id',
  144. },
  145. );
  146. expect(result.product).toBeNull();
  147. });
  148. });
  149. describe('product mutation', () => {
  150. let newProduct: ProductWithVariants.Fragment;
  151. it('createProduct creates a new Product', async () => {
  152. const result = await client.query<CreateProduct.Mutation, CreateProduct.Variables>(
  153. CREATE_PRODUCT,
  154. {
  155. input: {
  156. translations: [
  157. {
  158. languageCode: LanguageCode.en,
  159. name: 'en Baked Potato',
  160. slug: 'en-baked-potato',
  161. description: 'A baked potato',
  162. },
  163. {
  164. languageCode: LanguageCode.de,
  165. name: 'de Baked Potato',
  166. slug: 'de-baked-potato',
  167. description: 'Eine baked Erdapfel',
  168. },
  169. ],
  170. },
  171. },
  172. );
  173. newProduct = result.createProduct;
  174. expect(newProduct).toMatchSnapshot();
  175. });
  176. it('createProduct creates a new Product with assets', async () => {
  177. const assetsResult = await client.query<GetAssetList.Query, GetAssetList.Variables>(
  178. GET_ASSET_LIST,
  179. );
  180. const assetIds = assetsResult.assets.items.slice(0, 2).map(a => a.id);
  181. const featuredAssetId = assetsResult.assets.items[0].id;
  182. const result = await client.query<CreateProduct.Mutation, CreateProduct.Variables>(
  183. CREATE_PRODUCT,
  184. {
  185. input: {
  186. assetIds,
  187. featuredAssetId,
  188. translations: [
  189. {
  190. languageCode: LanguageCode.en,
  191. name: 'en Has Assets',
  192. slug: 'en-has-assets',
  193. description: 'A product with assets',
  194. },
  195. ],
  196. },
  197. },
  198. );
  199. expect(result.createProduct.assets.map(a => a.id)).toEqual(assetIds);
  200. expect(result.createProduct.featuredAsset!.id).toBe(featuredAssetId);
  201. });
  202. it('updateProduct updates a Product', async () => {
  203. const result = await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(
  204. UPDATE_PRODUCT,
  205. {
  206. input: {
  207. id: newProduct.id,
  208. translations: [
  209. {
  210. languageCode: LanguageCode.en,
  211. name: 'en Mashed Potato',
  212. slug: 'en-mashed-potato',
  213. description: 'A blob of mashed potato',
  214. },
  215. {
  216. languageCode: LanguageCode.de,
  217. name: 'de Mashed Potato',
  218. slug: 'de-mashed-potato',
  219. description: 'Eine blob von gemashed Erdapfel',
  220. },
  221. ],
  222. },
  223. },
  224. );
  225. expect(result.updateProduct).toMatchSnapshot();
  226. });
  227. it('updateProduct accepts partial input', async () => {
  228. const result = await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(
  229. UPDATE_PRODUCT,
  230. {
  231. input: {
  232. id: newProduct.id,
  233. translations: [
  234. {
  235. languageCode: LanguageCode.en,
  236. name: 'en Very Mashed Potato',
  237. },
  238. ],
  239. },
  240. },
  241. );
  242. expect(result.updateProduct.translations.length).toBe(2);
  243. expect(result.updateProduct.translations[0].name).toBe('en Very Mashed Potato');
  244. expect(result.updateProduct.translations[0].description).toBe('A blob of mashed potato');
  245. expect(result.updateProduct.translations[1].name).toBe('de Mashed Potato');
  246. });
  247. it('updateProduct adds Assets to a product and sets featured asset', async () => {
  248. const assetsResult = await client.query<GetAssetList.Query, GetAssetList.Variables>(
  249. GET_ASSET_LIST,
  250. );
  251. const assetIds = assetsResult.assets.items.map(a => a.id);
  252. const featuredAssetId = assetsResult.assets.items[2].id;
  253. const result = await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(
  254. UPDATE_PRODUCT,
  255. {
  256. input: {
  257. id: newProduct.id,
  258. assetIds,
  259. featuredAssetId,
  260. },
  261. },
  262. );
  263. expect(result.updateProduct.assets.map(a => a.id)).toEqual(assetIds);
  264. expect(result.updateProduct.featuredAsset!.id).toBe(featuredAssetId);
  265. });
  266. it('updateProduct sets a featured asset', async () => {
  267. const productResult = await client.query<
  268. GetProductWithVariants.Query,
  269. GetProductWithVariants.Variables
  270. >(GET_PRODUCT_WITH_VARIANTS, {
  271. id: newProduct.id,
  272. languageCode: LanguageCode.en,
  273. });
  274. const assets = productResult.product!.assets;
  275. const result = await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(
  276. UPDATE_PRODUCT,
  277. {
  278. input: {
  279. id: newProduct.id,
  280. featuredAssetId: assets[0].id,
  281. },
  282. },
  283. );
  284. expect(result.updateProduct.featuredAsset!.id).toBe(assets[0].id);
  285. });
  286. it('updateProduct errors with an invalid productId', async () => {
  287. try {
  288. await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  289. input: {
  290. id: '999',
  291. translations: [
  292. {
  293. languageCode: LanguageCode.en,
  294. name: 'en Mashed Potato',
  295. slug: 'en-mashed-potato',
  296. description: 'A blob of mashed potato',
  297. },
  298. {
  299. languageCode: LanguageCode.de,
  300. name: 'de Mashed Potato',
  301. slug: 'de-mashed-potato',
  302. description: 'Eine blob von gemashed Erdapfel',
  303. },
  304. ],
  305. },
  306. });
  307. fail('Should have thrown');
  308. } catch (err) {
  309. expect(err.message).toEqual(
  310. expect.stringContaining(`No Product with the id '999' could be found`),
  311. );
  312. }
  313. });
  314. it('addOptionGroupToProduct adds an option group', async () => {
  315. const result = await client.query<
  316. AddOptionGroupToProduct.Mutation,
  317. AddOptionGroupToProduct.Variables
  318. >(ADD_OPTION_GROUP_TO_PRODUCT, {
  319. optionGroupId: 'T_1',
  320. productId: newProduct.id,
  321. });
  322. expect(result.addOptionGroupToProduct.optionGroups.length).toBe(1);
  323. expect(result.addOptionGroupToProduct.optionGroups[0].id).toBe('T_1');
  324. });
  325. it('addOptionGroupToProduct errors with an invalid productId', async () => {
  326. try {
  327. await client.query<AddOptionGroupToProduct.Mutation, AddOptionGroupToProduct.Variables>(
  328. ADD_OPTION_GROUP_TO_PRODUCT,
  329. {
  330. optionGroupId: 'T_1',
  331. productId: '999',
  332. },
  333. );
  334. fail('Should have thrown');
  335. } catch (err) {
  336. expect(err.message).toEqual(
  337. expect.stringContaining(`No Product with the id '999' could be found`),
  338. );
  339. }
  340. });
  341. it('addOptionGroupToProduct errors with an invalid optionGroupId', async () => {
  342. try {
  343. await client.query<AddOptionGroupToProduct.Mutation, AddOptionGroupToProduct.Variables>(
  344. ADD_OPTION_GROUP_TO_PRODUCT,
  345. {
  346. optionGroupId: '999',
  347. productId: newProduct.id,
  348. },
  349. );
  350. fail('Should have thrown');
  351. } catch (err) {
  352. expect(err.message).toEqual(
  353. expect.stringContaining(`No OptionGroup with the id '999' could be found`),
  354. );
  355. }
  356. });
  357. it('removeOptionGroupFromProduct removes an option group', async () => {
  358. const result = await client.query<
  359. RemoveOptionGroupFromProduct.Mutation,
  360. RemoveOptionGroupFromProduct.Variables
  361. >(REMOVE_OPTION_GROUP_FROM_PRODUCT, {
  362. optionGroupId: '1',
  363. productId: '1',
  364. });
  365. expect(result.removeOptionGroupFromProduct.optionGroups.length).toBe(0);
  366. });
  367. it('removeOptionGroupFromProduct errors with an invalid productId', async () => {
  368. try {
  369. await client.query<
  370. RemoveOptionGroupFromProduct.Mutation,
  371. RemoveOptionGroupFromProduct.Variables
  372. >(REMOVE_OPTION_GROUP_FROM_PRODUCT, {
  373. optionGroupId: '1',
  374. productId: '999',
  375. });
  376. fail('Should have thrown');
  377. } catch (err) {
  378. expect(err.message).toEqual(
  379. expect.stringContaining(`No Product with the id '999' could be found`),
  380. );
  381. }
  382. });
  383. describe('variants', () => {
  384. let variants: ProductWithVariants.Variants[];
  385. it('generateVariantsForProduct generates variants', async () => {
  386. const result = await client.query<
  387. GenerateProductVariants.Mutation,
  388. GenerateProductVariants.Variables
  389. >(GENERATE_PRODUCT_VARIANTS, {
  390. productId: newProduct.id,
  391. defaultPrice: 123,
  392. defaultSku: 'ABC',
  393. });
  394. variants = result.generateVariantsForProduct.variants;
  395. expect(variants.length).toBe(2);
  396. expect(variants[0].options.length).toBe(1);
  397. expect(variants[1].options.length).toBe(1);
  398. });
  399. it('generateVariantsForProduct throws with an invalid productId', async () => {
  400. try {
  401. await client.query<GenerateProductVariants.Mutation, GenerateProductVariants.Variables>(
  402. GENERATE_PRODUCT_VARIANTS,
  403. {
  404. productId: '999',
  405. },
  406. );
  407. fail('Should have thrown');
  408. } catch (err) {
  409. expect(err.message).toEqual(
  410. expect.stringContaining(`No Product with the id '999' could be found`),
  411. );
  412. }
  413. });
  414. it('updateProductVariants updates variants', async () => {
  415. const firstVariant = variants[0];
  416. const result = await client.query<
  417. UpdateProductVariants.Mutation,
  418. UpdateProductVariants.Variables
  419. >(UPDATE_PRODUCT_VARIANTS, {
  420. input: [
  421. {
  422. id: firstVariant.id,
  423. translations: firstVariant.translations,
  424. sku: 'ABC',
  425. price: 432,
  426. },
  427. ],
  428. });
  429. const updatedVariant = result.updateProductVariants[0];
  430. if (!updatedVariant) {
  431. fail('no updated variant returned.');
  432. return;
  433. }
  434. expect(updatedVariant.sku).toBe('ABC');
  435. expect(updatedVariant.price).toBe(432);
  436. });
  437. it('updateProductVariants updates taxCategory and priceBeforeTax', async () => {
  438. const firstVariant = variants[0];
  439. const result = await client.query<
  440. UpdateProductVariants.Mutation,
  441. UpdateProductVariants.Variables
  442. >(UPDATE_PRODUCT_VARIANTS, {
  443. input: [
  444. {
  445. id: firstVariant.id,
  446. price: 105,
  447. taxCategoryId: 'T_2',
  448. },
  449. ],
  450. });
  451. const updatedVariant = result.updateProductVariants[0];
  452. if (!updatedVariant) {
  453. fail('no updated variant returned.');
  454. return;
  455. }
  456. expect(updatedVariant.price).toBe(105);
  457. expect(updatedVariant.taxCategory.id).toBe('T_2');
  458. expect(updatedVariant.priceBeforeTax).toBe(100);
  459. });
  460. it('updateProductVariants throws with an invalid variant id', async () => {
  461. try {
  462. await client.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  463. UPDATE_PRODUCT_VARIANTS,
  464. {
  465. input: [
  466. {
  467. id: '999',
  468. translations: variants[0].translations,
  469. sku: 'ABC',
  470. price: 432,
  471. },
  472. ],
  473. },
  474. );
  475. fail('Should have thrown');
  476. } catch (err) {
  477. expect(err.message).toEqual(
  478. expect.stringContaining(`No ProductVariant with the id '999' could be found`),
  479. );
  480. }
  481. });
  482. it('applyFacetValuesToProductVariants adds facets to variants', async () => {
  483. const result = await client.query<
  484. ApplyFacetValuesToProductVariants.Mutation,
  485. ApplyFacetValuesToProductVariants.Variables
  486. >(APPLY_FACET_VALUE_TO_PRODUCT_VARIANTS, {
  487. facetValueIds: ['1', '3', '5'],
  488. productVariantIds: variants.map(v => v.id),
  489. });
  490. expect(result.applyFacetValuesToProductVariants.length).toBe(2);
  491. expect(result.applyFacetValuesToProductVariants[0].facetValues).toMatchSnapshot();
  492. expect(result.applyFacetValuesToProductVariants[1].facetValues).toMatchSnapshot();
  493. });
  494. it('applyFacetValuesToProductVariants errors with invalid facet value id', async () => {
  495. try {
  496. await client.query<
  497. ApplyFacetValuesToProductVariants.Mutation,
  498. ApplyFacetValuesToProductVariants.Variables
  499. >(APPLY_FACET_VALUE_TO_PRODUCT_VARIANTS, {
  500. facetValueIds: ['999', '888'],
  501. productVariantIds: variants.map(v => v.id),
  502. });
  503. fail('Should have thrown');
  504. } catch (err) {
  505. expect(err.message).toEqual(
  506. expect.stringContaining(`No FacetValue with the id '999' could be found`),
  507. );
  508. }
  509. });
  510. it('applyFacetValuesToProductVariants errors with invalid variant id', async () => {
  511. try {
  512. await client.query<
  513. ApplyFacetValuesToProductVariants.Mutation,
  514. ApplyFacetValuesToProductVariants.Variables
  515. >(APPLY_FACET_VALUE_TO_PRODUCT_VARIANTS, {
  516. facetValueIds: ['1', '3', '5'],
  517. productVariantIds: ['999'],
  518. });
  519. fail('Should have thrown');
  520. } catch (err) {
  521. expect(err.message).toEqual(
  522. expect.stringContaining(`No ProductVariant with the id '999' could be found`),
  523. );
  524. }
  525. });
  526. });
  527. });
  528. });