product.e2e-spec.ts 20 KB

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