product.e2e-spec.ts 22 KB

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