product.e2e-spec.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  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 errors with an invalid productId', async () => {
  284. try {
  285. await client.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  286. input: {
  287. id: '999',
  288. translations: [
  289. {
  290. languageCode: LanguageCode.en,
  291. name: 'en Mashed Potato',
  292. slug: 'en-mashed-potato',
  293. description: 'A blob of mashed potato',
  294. },
  295. {
  296. languageCode: LanguageCode.de,
  297. name: 'de Mashed Potato',
  298. slug: 'de-mashed-potato',
  299. description: 'Eine blob von gemashed Erdapfel',
  300. },
  301. ],
  302. },
  303. });
  304. fail('Should have thrown');
  305. } catch (err) {
  306. expect(err.message).toEqual(
  307. expect.stringContaining(`No Product with the id '999' could be found`),
  308. );
  309. }
  310. });
  311. it('addOptionGroupToProduct adds an option group', async () => {
  312. const result = await client.query<
  313. AddOptionGroupToProduct.Mutation,
  314. AddOptionGroupToProduct.Variables
  315. >(ADD_OPTION_GROUP_TO_PRODUCT, {
  316. optionGroupId: 'T_1',
  317. productId: newProduct.id,
  318. });
  319. expect(result.addOptionGroupToProduct.optionGroups.length).toBe(1);
  320. expect(result.addOptionGroupToProduct.optionGroups[0].id).toBe('T_1');
  321. });
  322. it('addOptionGroupToProduct errors with an invalid productId', async () => {
  323. try {
  324. await client.query<AddOptionGroupToProduct.Mutation, AddOptionGroupToProduct.Variables>(
  325. ADD_OPTION_GROUP_TO_PRODUCT,
  326. {
  327. optionGroupId: 'T_1',
  328. productId: '999',
  329. },
  330. );
  331. fail('Should have thrown');
  332. } catch (err) {
  333. expect(err.message).toEqual(
  334. expect.stringContaining(`No Product with the id '999' could be found`),
  335. );
  336. }
  337. });
  338. it('addOptionGroupToProduct errors with an invalid optionGroupId', async () => {
  339. try {
  340. await client.query<AddOptionGroupToProduct.Mutation, AddOptionGroupToProduct.Variables>(
  341. ADD_OPTION_GROUP_TO_PRODUCT,
  342. {
  343. optionGroupId: '999',
  344. productId: newProduct.id,
  345. },
  346. );
  347. fail('Should have thrown');
  348. } catch (err) {
  349. expect(err.message).toEqual(
  350. expect.stringContaining(`No ProductOptionGroup with the id '999' could be found`),
  351. );
  352. }
  353. });
  354. it('removeOptionGroupFromProduct removes an option group', async () => {
  355. const result = await client.query<
  356. RemoveOptionGroupFromProduct.Mutation,
  357. RemoveOptionGroupFromProduct.Variables
  358. >(REMOVE_OPTION_GROUP_FROM_PRODUCT, {
  359. optionGroupId: '1',
  360. productId: '1',
  361. });
  362. expect(result.removeOptionGroupFromProduct.optionGroups.length).toBe(0);
  363. });
  364. it('removeOptionGroupFromProduct errors with an invalid productId', async () => {
  365. try {
  366. await client.query<
  367. RemoveOptionGroupFromProduct.Mutation,
  368. RemoveOptionGroupFromProduct.Variables
  369. >(REMOVE_OPTION_GROUP_FROM_PRODUCT, {
  370. optionGroupId: '1',
  371. productId: '999',
  372. });
  373. fail('Should have thrown');
  374. } catch (err) {
  375. expect(err.message).toEqual(
  376. expect.stringContaining(`No Product with the id '999' could be found`),
  377. );
  378. }
  379. });
  380. describe('variants', () => {
  381. let variants: ProductWithVariants.Variants[];
  382. it('generateVariantsForProduct generates variants', async () => {
  383. const result = await client.query<
  384. GenerateProductVariants.Mutation,
  385. GenerateProductVariants.Variables
  386. >(GENERATE_PRODUCT_VARIANTS, {
  387. productId: newProduct.id,
  388. defaultPrice: 123,
  389. defaultSku: 'ABC',
  390. });
  391. variants = result.generateVariantsForProduct.variants;
  392. expect(variants.length).toBe(2);
  393. expect(variants[0].options.length).toBe(1);
  394. expect(variants[1].options.length).toBe(1);
  395. });
  396. it('generateVariantsForProduct throws with an invalid productId', async () => {
  397. try {
  398. await client.query<GenerateProductVariants.Mutation, GenerateProductVariants.Variables>(
  399. GENERATE_PRODUCT_VARIANTS,
  400. {
  401. productId: '999',
  402. },
  403. );
  404. fail('Should have thrown');
  405. } catch (err) {
  406. expect(err.message).toEqual(
  407. expect.stringContaining(`No Product with the id '999' could be found`),
  408. );
  409. }
  410. });
  411. it('updateProductVariants updates variants', async () => {
  412. const firstVariant = variants[0];
  413. const result = await client.query<
  414. UpdateProductVariants.Mutation,
  415. UpdateProductVariants.Variables
  416. >(UPDATE_PRODUCT_VARIANTS, {
  417. input: [
  418. {
  419. id: firstVariant.id,
  420. translations: firstVariant.translations,
  421. sku: 'ABC',
  422. price: 432,
  423. },
  424. ],
  425. });
  426. const updatedVariant = result.updateProductVariants[0];
  427. if (!updatedVariant) {
  428. fail('no updated variant returned.');
  429. return;
  430. }
  431. expect(updatedVariant.sku).toBe('ABC');
  432. expect(updatedVariant.price).toBe(432);
  433. });
  434. it('updateProductVariants updates taxCategory and priceBeforeTax', async () => {
  435. const firstVariant = variants[0];
  436. const result = await client.query<
  437. UpdateProductVariants.Mutation,
  438. UpdateProductVariants.Variables
  439. >(UPDATE_PRODUCT_VARIANTS, {
  440. input: [
  441. {
  442. id: firstVariant.id,
  443. price: 105,
  444. taxCategoryId: 'T_2',
  445. },
  446. ],
  447. });
  448. const updatedVariant = result.updateProductVariants[0];
  449. if (!updatedVariant) {
  450. fail('no updated variant returned.');
  451. return;
  452. }
  453. expect(updatedVariant.price).toBe(105);
  454. expect(updatedVariant.taxCategory.id).toBe('T_2');
  455. });
  456. it('updateProductVariants updates facetValues', async () => {
  457. const firstVariant = variants[0];
  458. const result = await client.query<
  459. UpdateProductVariants.Mutation,
  460. UpdateProductVariants.Variables
  461. >(UPDATE_PRODUCT_VARIANTS, {
  462. input: [
  463. {
  464. id: firstVariant.id,
  465. facetValueIds: ['T_1'],
  466. },
  467. ],
  468. });
  469. const updatedVariant = result.updateProductVariants[0];
  470. if (!updatedVariant) {
  471. fail('no updated variant returned.');
  472. return;
  473. }
  474. expect(updatedVariant.facetValues.length).toBe(1);
  475. expect(updatedVariant.facetValues[0].id).toBe('T_1');
  476. });
  477. it('updateProductVariants throws with an invalid variant id', async () => {
  478. try {
  479. await client.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  480. UPDATE_PRODUCT_VARIANTS,
  481. {
  482. input: [
  483. {
  484. id: 'T_999',
  485. translations: variants[0].translations,
  486. sku: 'ABC',
  487. price: 432,
  488. },
  489. ],
  490. },
  491. );
  492. fail('Should have thrown');
  493. } catch (err) {
  494. expect(err.message).toEqual(
  495. expect.stringContaining(`No ProductVariant with the id '999' could be found`),
  496. );
  497. }
  498. });
  499. });
  500. });
  501. });