product.e2e-spec.ts 22 KB

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