product.e2e-spec.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  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].price).toBe(745);
  131. expect(result.product.variants[0].taxCategory).toEqual({
  132. id: 'T_1',
  133. name: 'Standard Tax',
  134. });
  135. });
  136. it('returns null when id not found', async () => {
  137. const result = await client.query<GetProductWithVariants.Query, GetProductWithVariants.Variables>(
  138. GET_PRODUCT_WITH_VARIANTS,
  139. {
  140. languageCode: LanguageCode.en,
  141. id: 'bad_id',
  142. },
  143. );
  144. expect(result.product).toBeNull();
  145. });
  146. });
  147. describe('product mutation', () => {
  148. let newProduct: ProductWithVariants.Fragment;
  149. it('createProduct creates a new Product', async () => {
  150. const result = await client.query<CreateProduct.Mutation, CreateProduct.Variables>(
  151. CREATE_PRODUCT,
  152. {
  153. input: {
  154. translations: [
  155. {
  156. languageCode: LanguageCode.en,
  157. name: 'en Baked Potato',
  158. slug: 'en-baked-potato',
  159. description: 'A baked potato',
  160. },
  161. {
  162. languageCode: LanguageCode.de,
  163. name: 'de Baked Potato',
  164. slug: 'de-baked-potato',
  165. description: 'Eine baked Erdapfel',
  166. },
  167. ],
  168. },
  169. },
  170. );
  171. newProduct = result.createProduct;
  172. expect(newProduct).toMatchSnapshot();
  173. });
  174. it('createProduct creates a new Product with assets', async () => {
  175. const assetsResult = await client.query<GetAssetList.Query, GetAssetList.Variables>(
  176. GET_ASSET_LIST,
  177. );
  178. const assetIds = assetsResult.assets.items.slice(0, 2).map(a => a.id);
  179. const featuredAssetId = assetsResult.assets.items[0].id;
  180. const result = await client.query<CreateProduct.Mutation, CreateProduct.Variables>(
  181. CREATE_PRODUCT,
  182. {
  183. input: {
  184. assetIds,
  185. featuredAssetId,
  186. translations: [
  187. {
  188. languageCode: LanguageCode.en,
  189. name: 'en Has Assets',
  190. slug: 'en-has-assets',
  191. description: 'A product with assets',
  192. },
  193. ],
  194. },
  195. },
  196. );
  197. expect(result.createProduct.assets.map(a => a.id)).toEqual(assetIds);
  198. expect(result.createProduct.featuredAsset!.id).toBe(featuredAssetId);
  199. });
  200. it('updateProduct updates a Product', async () => {
  201. const result = await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(
  202. UPDATE_PRODUCT,
  203. {
  204. input: {
  205. id: newProduct.id,
  206. translations: [
  207. {
  208. languageCode: LanguageCode.en,
  209. name: 'en Mashed Potato',
  210. slug: 'en-mashed-potato',
  211. description: 'A blob of mashed potato',
  212. },
  213. {
  214. languageCode: LanguageCode.de,
  215. name: 'de Mashed Potato',
  216. slug: 'de-mashed-potato',
  217. description: 'Eine blob von gemashed Erdapfel',
  218. },
  219. ],
  220. },
  221. },
  222. );
  223. expect(result.updateProduct).toMatchSnapshot();
  224. });
  225. it('updateProduct accepts partial input', async () => {
  226. const result = await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(
  227. UPDATE_PRODUCT,
  228. {
  229. input: {
  230. id: newProduct.id,
  231. translations: [
  232. {
  233. languageCode: LanguageCode.en,
  234. name: 'en Very Mashed Potato',
  235. },
  236. ],
  237. },
  238. },
  239. );
  240. expect(result.updateProduct.translations.length).toBe(2);
  241. expect(result.updateProduct.translations[0].name).toBe('en Very Mashed Potato');
  242. expect(result.updateProduct.translations[0].description).toBe('A blob of mashed potato');
  243. expect(result.updateProduct.translations[1].name).toBe('de Mashed Potato');
  244. });
  245. it('updateProduct adds Assets to a product and sets featured asset', async () => {
  246. const assetsResult = await client.query<GetAssetList.Query, GetAssetList.Variables>(
  247. GET_ASSET_LIST,
  248. );
  249. const assetIds = assetsResult.assets.items.map(a => a.id);
  250. const featuredAssetId = assetsResult.assets.items[2].id;
  251. const result = await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(
  252. UPDATE_PRODUCT,
  253. {
  254. input: {
  255. id: newProduct.id,
  256. assetIds,
  257. featuredAssetId,
  258. },
  259. },
  260. );
  261. expect(result.updateProduct.assets.map(a => a.id)).toEqual(assetIds);
  262. expect(result.updateProduct.featuredAsset!.id).toBe(featuredAssetId);
  263. });
  264. it('updateProduct sets a featured asset', async () => {
  265. const productResult = await client.query<
  266. GetProductWithVariants.Query,
  267. GetProductWithVariants.Variables
  268. >(GET_PRODUCT_WITH_VARIANTS, {
  269. id: newProduct.id,
  270. languageCode: LanguageCode.en,
  271. });
  272. const assets = productResult.product!.assets;
  273. const result = await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(
  274. UPDATE_PRODUCT,
  275. {
  276. input: {
  277. id: newProduct.id,
  278. featuredAssetId: assets[0].id,
  279. },
  280. },
  281. );
  282. expect(result.updateProduct.featuredAsset!.id).toBe(assets[0].id);
  283. });
  284. it('updateProduct errors with an invalid productId', async () => {
  285. try {
  286. await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  287. input: {
  288. id: '999',
  289. translations: [
  290. {
  291. languageCode: LanguageCode.en,
  292. name: 'en Mashed Potato',
  293. slug: 'en-mashed-potato',
  294. description: 'A blob of mashed potato',
  295. },
  296. {
  297. languageCode: LanguageCode.de,
  298. name: 'de Mashed Potato',
  299. slug: 'de-mashed-potato',
  300. description: 'Eine blob von gemashed Erdapfel',
  301. },
  302. ],
  303. },
  304. });
  305. fail('Should have thrown');
  306. } catch (err) {
  307. expect(err.message).toEqual(
  308. expect.stringContaining(`No Product with the id '999' could be found`),
  309. );
  310. }
  311. });
  312. it('addOptionGroupToProduct adds an option group', async () => {
  313. const result = await client.query<
  314. AddOptionGroupToProduct.Mutation,
  315. AddOptionGroupToProduct.Variables
  316. >(ADD_OPTION_GROUP_TO_PRODUCT, {
  317. optionGroupId: 'T_1',
  318. productId: newProduct.id,
  319. });
  320. expect(result.addOptionGroupToProduct.optionGroups.length).toBe(1);
  321. expect(result.addOptionGroupToProduct.optionGroups[0].id).toBe('T_1');
  322. });
  323. it('addOptionGroupToProduct errors with an invalid productId', async () => {
  324. try {
  325. await client.query<AddOptionGroupToProduct.Mutation, AddOptionGroupToProduct.Variables>(
  326. ADD_OPTION_GROUP_TO_PRODUCT,
  327. {
  328. optionGroupId: 'T_1',
  329. productId: '999',
  330. },
  331. );
  332. fail('Should have thrown');
  333. } catch (err) {
  334. expect(err.message).toEqual(
  335. expect.stringContaining(`No Product with the id '999' could be found`),
  336. );
  337. }
  338. });
  339. it('addOptionGroupToProduct errors with an invalid optionGroupId', async () => {
  340. try {
  341. await client.query<AddOptionGroupToProduct.Mutation, AddOptionGroupToProduct.Variables>(
  342. ADD_OPTION_GROUP_TO_PRODUCT,
  343. {
  344. optionGroupId: '999',
  345. productId: newProduct.id,
  346. },
  347. );
  348. fail('Should have thrown');
  349. } catch (err) {
  350. expect(err.message).toEqual(
  351. expect.stringContaining(`No OptionGroup with the id '999' could be found`),
  352. );
  353. }
  354. });
  355. it('removeOptionGroupFromProduct removes an option group', async () => {
  356. const result = await client.query<
  357. RemoveOptionGroupFromProduct.Mutation,
  358. RemoveOptionGroupFromProduct.Variables
  359. >(REMOVE_OPTION_GROUP_FROM_PRODUCT, {
  360. optionGroupId: '1',
  361. productId: '1',
  362. });
  363. expect(result.removeOptionGroupFromProduct.optionGroups.length).toBe(0);
  364. });
  365. it('removeOptionGroupFromProduct errors with an invalid productId', async () => {
  366. try {
  367. await client.query<
  368. RemoveOptionGroupFromProduct.Mutation,
  369. RemoveOptionGroupFromProduct.Variables
  370. >(REMOVE_OPTION_GROUP_FROM_PRODUCT, {
  371. optionGroupId: '1',
  372. productId: '999',
  373. });
  374. fail('Should have thrown');
  375. } catch (err) {
  376. expect(err.message).toEqual(
  377. expect.stringContaining(`No Product with the id '999' could be found`),
  378. );
  379. }
  380. });
  381. describe('variants', () => {
  382. let variants: ProductWithVariants.Variants[];
  383. it('generateVariantsForProduct generates variants', async () => {
  384. const result = await client.query<
  385. GenerateProductVariants.Mutation,
  386. GenerateProductVariants.Variables
  387. >(GENERATE_PRODUCT_VARIANTS, {
  388. productId: newProduct.id,
  389. defaultPrice: 123,
  390. defaultSku: 'ABC',
  391. });
  392. variants = result.generateVariantsForProduct.variants;
  393. expect(variants.length).toBe(2);
  394. expect(variants[0].options.length).toBe(1);
  395. expect(variants[1].options.length).toBe(1);
  396. });
  397. it('generateVariantsForProduct throws with an invalid productId', async () => {
  398. try {
  399. await client.query<GenerateProductVariants.Mutation, GenerateProductVariants.Variables>(
  400. GENERATE_PRODUCT_VARIANTS,
  401. {
  402. productId: '999',
  403. },
  404. );
  405. fail('Should have thrown');
  406. } catch (err) {
  407. expect(err.message).toEqual(
  408. expect.stringContaining(`No Product with the id '999' could be found`),
  409. );
  410. }
  411. });
  412. it('updateProductVariants updates variants', async () => {
  413. const firstVariant = variants[0];
  414. const result = await client.query<
  415. UpdateProductVariants.Mutation,
  416. UpdateProductVariants.Variables
  417. >(UPDATE_PRODUCT_VARIANTS, {
  418. input: [
  419. {
  420. id: firstVariant.id,
  421. translations: firstVariant.translations,
  422. sku: 'ABC',
  423. price: 432,
  424. },
  425. ],
  426. });
  427. const updatedVariant = result.updateProductVariants[0];
  428. if (!updatedVariant) {
  429. fail('no updated variant returned.');
  430. return;
  431. }
  432. expect(updatedVariant.sku).toBe('ABC');
  433. expect(updatedVariant.price).toBe(432);
  434. });
  435. it('updateProductVariants updates taxCategory and priceBeforeTax', async () => {
  436. const firstVariant = variants[0];
  437. const result = await client.query<
  438. UpdateProductVariants.Mutation,
  439. UpdateProductVariants.Variables
  440. >(UPDATE_PRODUCT_VARIANTS, {
  441. input: [
  442. {
  443. id: firstVariant.id,
  444. price: 105,
  445. taxCategoryId: 'T_2',
  446. },
  447. ],
  448. });
  449. const updatedVariant = result.updateProductVariants[0];
  450. if (!updatedVariant) {
  451. fail('no updated variant returned.');
  452. return;
  453. }
  454. expect(updatedVariant.price).toBe(105);
  455. expect(updatedVariant.taxCategory.id).toBe('T_2');
  456. });
  457. it('updateProductVariants throws with an invalid variant id', async () => {
  458. try {
  459. await client.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  460. UPDATE_PRODUCT_VARIANTS,
  461. {
  462. input: [
  463. {
  464. id: '999',
  465. translations: variants[0].translations,
  466. sku: 'ABC',
  467. price: 432,
  468. },
  469. ],
  470. },
  471. );
  472. fail('Should have thrown');
  473. } catch (err) {
  474. expect(err.message).toEqual(
  475. expect.stringContaining(`No ProductVariant with the id '999' could be found`),
  476. );
  477. }
  478. });
  479. it('applyFacetValuesToProductVariants adds facets to variants', async () => {
  480. const result = await client.query<
  481. ApplyFacetValuesToProductVariants.Mutation,
  482. ApplyFacetValuesToProductVariants.Variables
  483. >(APPLY_FACET_VALUE_TO_PRODUCT_VARIANTS, {
  484. facetValueIds: ['1', '3', '5'],
  485. productVariantIds: variants.map(v => v.id),
  486. });
  487. expect(result.applyFacetValuesToProductVariants.length).toBe(2);
  488. expect(result.applyFacetValuesToProductVariants[0].facetValues).toMatchSnapshot();
  489. expect(result.applyFacetValuesToProductVariants[1].facetValues).toMatchSnapshot();
  490. });
  491. it('applyFacetValuesToProductVariants errors with invalid facet value id', async () => {
  492. try {
  493. await client.query<
  494. ApplyFacetValuesToProductVariants.Mutation,
  495. ApplyFacetValuesToProductVariants.Variables
  496. >(APPLY_FACET_VALUE_TO_PRODUCT_VARIANTS, {
  497. facetValueIds: ['999', '888'],
  498. productVariantIds: variants.map(v => v.id),
  499. });
  500. fail('Should have thrown');
  501. } catch (err) {
  502. expect(err.message).toEqual(
  503. expect.stringContaining(`No FacetValue with the id '999' could be found`),
  504. );
  505. }
  506. });
  507. it('applyFacetValuesToProductVariants errors with invalid variant id', async () => {
  508. try {
  509. await client.query<
  510. ApplyFacetValuesToProductVariants.Mutation,
  511. ApplyFacetValuesToProductVariants.Variables
  512. >(APPLY_FACET_VALUE_TO_PRODUCT_VARIANTS, {
  513. facetValueIds: ['1', '3', '5'],
  514. productVariantIds: ['999'],
  515. });
  516. fail('Should have thrown');
  517. } catch (err) {
  518. expect(err.message).toEqual(
  519. expect.stringContaining(`No ProductVariant with the id '999' could be found`),
  520. );
  521. }
  522. });
  523. });
  524. });
  525. });