1
0

duplicate-entity.e2e-spec.ts 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { pick } from '@vendure/common/lib/pick';
  3. import {
  4. Collection,
  5. CollectionService,
  6. CurrencyCode,
  7. defaultEntityDuplicators,
  8. EntityDuplicator,
  9. freeShipping,
  10. LanguageCode,
  11. mergeConfig,
  12. minimumOrderAmount,
  13. Permission,
  14. PermissionDefinition,
  15. TransactionalConnection,
  16. variantIdCollectionFilter,
  17. } from '@vendure/core';
  18. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  19. import path from 'path';
  20. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  21. import { initialData } from '../../../e2e-common/e2e-initial-data';
  22. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  23. import { channelFragment, promotionFragment, roleFragment } from './graphql/fragments-admin';
  24. import { FragmentOf, ResultOf } from './graphql/graphql-admin';
  25. import {
  26. assignProductToChannelDocument,
  27. createAdministratorDocument,
  28. createChannelDocument,
  29. createCollectionDocument,
  30. createPromotionDocument,
  31. createRoleDocument,
  32. duplicateEntityDocument,
  33. getCollectionDocument,
  34. getCollectionsDocument,
  35. getEntityDuplicatorsDocument,
  36. getFacetWithValuesDocument,
  37. getProductWithVariantsDocument,
  38. getPromotionDocument,
  39. updateProductVariantsDocument,
  40. } from './graphql/shared-definitions';
  41. const customPermission = new PermissionDefinition({
  42. name: 'custom',
  43. });
  44. let collectionService: CollectionService;
  45. let connection: TransactionalConnection;
  46. const customCollectionDuplicator = new EntityDuplicator({
  47. code: 'custom-collection-duplicator',
  48. description: [{ languageCode: LanguageCode.en, value: 'Custom Collection Duplicator' }],
  49. args: {
  50. throwError: {
  51. type: 'boolean',
  52. defaultValue: false,
  53. },
  54. },
  55. forEntities: ['Collection'],
  56. requiresPermission: customPermission.Permission,
  57. init(injector) {
  58. collectionService = injector.get(CollectionService);
  59. connection = injector.get(TransactionalConnection);
  60. },
  61. duplicate: async input => {
  62. const { ctx, id, args } = input;
  63. const original = await connection.getEntityOrThrow(ctx, Collection, id, {
  64. relations: {
  65. assets: true,
  66. featuredAsset: true,
  67. },
  68. });
  69. const newCollection = await collectionService.create(ctx, {
  70. isPrivate: original.isPrivate,
  71. customFields: original.customFields,
  72. assetIds: original.assets.map(a => a.id),
  73. featuredAssetId: original.featuredAsset?.id,
  74. parentId: original.parentId,
  75. filters: original.filters.map(f => ({
  76. code: f.code,
  77. arguments: f.args,
  78. })),
  79. inheritFilters: original.inheritFilters,
  80. translations: original.translations.map(t => ({
  81. languageCode: t.languageCode,
  82. name: `${t.name} (copy)`,
  83. slug: `${t.slug}-copy`,
  84. description: t.description,
  85. customFields: t.customFields,
  86. })),
  87. });
  88. if (args.throwError) {
  89. throw new Error('Dummy error');
  90. }
  91. return newCollection;
  92. },
  93. });
  94. describe('Duplicating entities', () => {
  95. const { server, adminClient } = createTestEnvironment(
  96. mergeConfig(testConfig(), {
  97. authOptions: {
  98. customPermissions: [customPermission],
  99. },
  100. entityOptions: {
  101. entityDuplicators: [...defaultEntityDuplicators, customCollectionDuplicator],
  102. },
  103. }),
  104. );
  105. const duplicateEntityGuard: ErrorResultGuard<{ newEntityId: string }> = createErrorResultGuard(
  106. result => !!result.newEntityId,
  107. );
  108. const createChannelGuard: ErrorResultGuard<FragmentOf<typeof channelFragment>> = createErrorResultGuard(
  109. result => !!result.id,
  110. );
  111. let testRole: FragmentOf<typeof roleFragment>;
  112. let testAdmin: ResultOf<typeof createAdministratorDocument>['createAdministrator'];
  113. let newEntityId: string;
  114. beforeAll(async () => {
  115. await server.init({
  116. initialData,
  117. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  118. customerCount: 1,
  119. });
  120. await adminClient.asSuperAdmin();
  121. // create a new role and Admin and sign in as that Admin
  122. const { createRole } = await adminClient.query(createRoleDocument, {
  123. input: {
  124. channelIds: ['T_1'],
  125. code: 'test-role',
  126. description: 'Testing custom permissions',
  127. permissions: [
  128. Permission.CreateCollection,
  129. Permission.UpdateCollection,
  130. Permission.ReadCollection,
  131. ],
  132. },
  133. });
  134. testRole = createRole;
  135. const { createAdministrator } = await adminClient.query(createAdministratorDocument, {
  136. input: {
  137. firstName: 'Test',
  138. lastName: 'Admin',
  139. emailAddress: 'test@admin.com',
  140. password: 'test',
  141. roleIds: [testRole.id],
  142. },
  143. });
  144. testAdmin = createAdministrator;
  145. }, TEST_SETUP_TIMEOUT_MS);
  146. afterAll(async () => {
  147. await server.destroy();
  148. });
  149. it('get entity duplicators', async () => {
  150. const { entityDuplicators } = await adminClient.query(getEntityDuplicatorsDocument);
  151. expect(entityDuplicators.find(d => d.code === 'custom-collection-duplicator')).toEqual({
  152. args: [
  153. {
  154. defaultValue: false,
  155. name: 'throwError',
  156. type: 'boolean',
  157. },
  158. ],
  159. code: 'custom-collection-duplicator',
  160. description: 'Custom Collection Duplicator',
  161. forEntities: ['Collection'],
  162. requiresPermission: ['custom'],
  163. });
  164. });
  165. it('cannot duplicate if lacking permissions', async () => {
  166. await adminClient.asUserWithCredentials(testAdmin.emailAddress, 'test');
  167. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  168. input: {
  169. entityName: 'Collection',
  170. entityId: 'T_2',
  171. duplicatorInput: {
  172. code: 'custom-collection-duplicator',
  173. arguments: [
  174. {
  175. name: 'throwError',
  176. value: 'false',
  177. },
  178. ],
  179. },
  180. },
  181. });
  182. duplicateEntityGuard.assertErrorResult(duplicateEntity);
  183. expect(duplicateEntity.message).toBe('The entity could not be duplicated');
  184. expect(duplicateEntity.duplicationError).toBe(
  185. 'You do not have the required permissions to duplicate this entity',
  186. );
  187. });
  188. it('errors thrown in duplicator cause ErrorResult', async () => {
  189. await adminClient.asSuperAdmin();
  190. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  191. input: {
  192. entityName: 'Collection',
  193. entityId: 'T_2',
  194. duplicatorInput: {
  195. code: 'custom-collection-duplicator',
  196. arguments: [
  197. {
  198. name: 'throwError',
  199. value: 'true',
  200. },
  201. ],
  202. },
  203. },
  204. });
  205. duplicateEntityGuard.assertErrorResult(duplicateEntity);
  206. expect(duplicateEntity.message).toBe('The entity could not be duplicated');
  207. expect(duplicateEntity.duplicationError).toBe('Dummy error');
  208. });
  209. it('errors thrown cause all DB changes to be rolled back', async () => {
  210. await adminClient.asSuperAdmin();
  211. const { collections } = await adminClient.query(getCollectionsDocument);
  212. expect(collections.items.length).toBe(1);
  213. expect(collections.items.map(i => i.name)).toEqual(['Plants']);
  214. });
  215. it('returns ID of new entity', async () => {
  216. await adminClient.asSuperAdmin();
  217. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  218. input: {
  219. entityName: 'Collection',
  220. entityId: 'T_2',
  221. duplicatorInput: {
  222. code: 'custom-collection-duplicator',
  223. arguments: [
  224. {
  225. name: 'throwError',
  226. value: 'false',
  227. },
  228. ],
  229. },
  230. },
  231. });
  232. duplicateEntityGuard.assertSuccess(duplicateEntity);
  233. expect(duplicateEntity.newEntityId).toBeDefined();
  234. newEntityId = duplicateEntity.newEntityId;
  235. });
  236. it('duplicate gets created', async () => {
  237. const { collection } = await adminClient.query(getCollectionDocument, {
  238. id: newEntityId,
  239. });
  240. expect(pick(collection!, ['id', 'name', 'slug'])).toEqual({
  241. id: newEntityId,
  242. name: 'Plants (copy)',
  243. slug: 'plants-copy',
  244. });
  245. });
  246. describe('default entity duplicators', () => {
  247. describe('Product duplicator', () => {
  248. let originalProduct: NonNullable<ResultOf<typeof getProductWithVariantsDocument>['product']>;
  249. let originalFirstVariant: NonNullable<
  250. ResultOf<typeof getProductWithVariantsDocument>['product']
  251. >['variants'][0];
  252. let newProduct1Id: string;
  253. let newProduct2Id: string;
  254. beforeAll(async () => {
  255. await adminClient.asSuperAdmin();
  256. // Add asset and facet values to the first product variant
  257. await adminClient.query(updateProductVariantsDocument, {
  258. input: [
  259. {
  260. id: 'T_1',
  261. assetIds: ['T_1'],
  262. featuredAssetId: 'T_1',
  263. facetValueIds: ['T_1', 'T_2'],
  264. },
  265. ],
  266. });
  267. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  268. id: 'T_1',
  269. });
  270. originalProduct = product!;
  271. originalFirstVariant = product!.variants.find(v => v.id === 'T_1')!;
  272. });
  273. it('duplicate product without variants', async () => {
  274. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  275. input: {
  276. entityName: 'Product',
  277. entityId: 'T_1',
  278. duplicatorInput: {
  279. code: 'product-duplicator',
  280. arguments: [
  281. {
  282. name: 'includeVariants',
  283. value: 'false',
  284. },
  285. ],
  286. },
  287. },
  288. });
  289. duplicateEntityGuard.assertSuccess(duplicateEntity);
  290. newProduct1Id = duplicateEntity.newEntityId;
  291. expect(newProduct1Id).toBe('T_2');
  292. });
  293. it('new product has no variants', async () => {
  294. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  295. id: newProduct1Id,
  296. });
  297. expect(product?.variants.length).toBe(0);
  298. });
  299. it('is initially disabled', async () => {
  300. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  301. id: newProduct1Id,
  302. });
  303. expect(product?.enabled).toBe(false);
  304. });
  305. it('assets are duplicated', async () => {
  306. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  307. id: newProduct1Id,
  308. });
  309. expect(product?.featuredAsset).toEqual(originalProduct.featuredAsset);
  310. expect(product?.assets.length).toBe(1);
  311. expect(product?.assets).toEqual(originalProduct.assets);
  312. });
  313. it('facet values are duplicated', async () => {
  314. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  315. id: newProduct1Id,
  316. });
  317. expect(product?.facetValues).toEqual(originalProduct.facetValues);
  318. expect(product?.facetValues.map(fv => fv.name).sort()).toEqual(['computers', 'electronics']);
  319. });
  320. it('duplicate product with variants', async () => {
  321. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  322. input: {
  323. entityName: 'Product',
  324. entityId: 'T_1',
  325. duplicatorInput: {
  326. code: 'product-duplicator',
  327. arguments: [
  328. {
  329. name: 'includeVariants',
  330. value: 'true',
  331. },
  332. ],
  333. },
  334. },
  335. });
  336. duplicateEntityGuard.assertSuccess(duplicateEntity);
  337. newProduct2Id = duplicateEntity.newEntityId;
  338. expect(newProduct2Id).toBe('T_3');
  339. });
  340. it('new product has variants', async () => {
  341. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  342. id: newProduct2Id,
  343. });
  344. expect(product?.variants.length).toBe(4);
  345. expect(product?.variants.length).toBe(originalProduct.variants.length);
  346. expect(product?.variants.map(v => v.name).sort()).toEqual(
  347. originalProduct.variants.map(v => v.name).sort(),
  348. );
  349. });
  350. it('product name is suffixed', async () => {
  351. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  352. id: newProduct2Id,
  353. });
  354. expect(product?.name).toBe('Laptop (copy)');
  355. });
  356. it('variant SKUs are suffixed', async () => {
  357. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  358. id: newProduct2Id,
  359. });
  360. expect(product?.variants.map(v => v.sku).sort()).toEqual([
  361. 'L2201308-copy',
  362. 'L2201316-copy',
  363. 'L2201508-copy',
  364. 'L2201516-copy',
  365. ]);
  366. });
  367. it('variant assets are preserved', async () => {
  368. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  369. id: newProduct2Id,
  370. });
  371. expect(product?.variants.find(v => v.name === originalFirstVariant.name)?.assets).toEqual(
  372. originalFirstVariant.assets,
  373. );
  374. expect(
  375. product?.variants.find(v => v.name === originalFirstVariant.name)?.featuredAsset,
  376. ).toEqual(originalFirstVariant.featuredAsset);
  377. });
  378. it('variant facet values are preserved', async () => {
  379. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  380. id: newProduct2Id,
  381. });
  382. expect(
  383. product?.variants.find(v => v.name === originalFirstVariant.name)?.facetValues.length,
  384. ).toBe(2);
  385. expect(
  386. product?.variants.find(v => v.name === originalFirstVariant.name)?.facetValues,
  387. ).toEqual(originalFirstVariant.facetValues);
  388. });
  389. it('variant stock levels are preserved', async () => {
  390. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  391. id: newProduct2Id,
  392. });
  393. expect(product?.variants.find(v => v.name === originalFirstVariant.name)?.stockOnHand).toBe(
  394. 100,
  395. );
  396. });
  397. it('variant prices are duplicated', async () => {
  398. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  399. input: {
  400. entityName: 'Product',
  401. entityId: 'T_1',
  402. duplicatorInput: {
  403. code: 'product-duplicator',
  404. arguments: [
  405. {
  406. name: 'includeVariants',
  407. value: 'true',
  408. },
  409. ],
  410. },
  411. },
  412. });
  413. duplicateEntityGuard.assertSuccess(duplicateEntity);
  414. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  415. id: duplicateEntity.newEntityId,
  416. });
  417. const variant = product?.variants.find(v => v.sku.startsWith(originalFirstVariant.sku));
  418. expect(variant).not.toBeUndefined();
  419. expect(originalFirstVariant.price).toBeGreaterThan(0);
  420. expect(variant!.price).toBe(originalFirstVariant.price);
  421. });
  422. it('variant prices are duplicated on a channel specific basis', async () => {
  423. const { createChannel } = await adminClient.query(createChannelDocument, {
  424. input: {
  425. code: 'second-channel',
  426. token: 'second-channel',
  427. defaultLanguageCode: LanguageCode.en,
  428. currencyCode: CurrencyCode.USD,
  429. pricesIncludeTax: false,
  430. defaultShippingZoneId: 'T_1',
  431. defaultTaxZoneId: 'T_1',
  432. },
  433. });
  434. createChannelGuard.assertSuccess(createChannel);
  435. await adminClient.query(assignProductToChannelDocument, {
  436. input: {
  437. channelId: createChannel.id,
  438. productIds: ['T_1'],
  439. },
  440. });
  441. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  442. id: 'T_1',
  443. });
  444. const productVariant = product!.variants[0];
  445. adminClient.setChannelToken('second-channel');
  446. await adminClient.query(updateProductVariantsDocument, {
  447. input: [
  448. {
  449. id: productVariant.id,
  450. price: productVariant.price + 150,
  451. },
  452. ],
  453. });
  454. adminClient.setChannelToken('e2e-default-channel');
  455. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  456. input: {
  457. entityName: 'Product',
  458. entityId: 'T_1',
  459. duplicatorInput: {
  460. code: 'product-duplicator',
  461. arguments: [
  462. {
  463. name: 'includeVariants',
  464. value: 'true',
  465. },
  466. ],
  467. },
  468. },
  469. });
  470. duplicateEntityGuard.assertSuccess(duplicateEntity);
  471. const { product: productWithVariantChannelNull } = await adminClient.query(
  472. getProductWithVariantsDocument,
  473. {
  474. id: duplicateEntity.newEntityId,
  475. },
  476. );
  477. const productVariantChannelNull = productWithVariantChannelNull!.variants.find(v =>
  478. v.sku.startsWith(productVariant.sku),
  479. );
  480. expect(productVariantChannelNull!.price).toEqual(productVariant.price);
  481. adminClient.setChannelToken('second-channel');
  482. const { duplicateEntity: duplicateEntitySecondChannel } = await adminClient.query(
  483. duplicateEntityDocument,
  484. {
  485. input: {
  486. entityName: 'Product',
  487. entityId: 'T_1',
  488. duplicatorInput: {
  489. code: 'product-duplicator',
  490. arguments: [
  491. {
  492. name: 'includeVariants',
  493. value: 'true',
  494. },
  495. ],
  496. },
  497. },
  498. },
  499. );
  500. duplicateEntityGuard.assertSuccess(duplicateEntitySecondChannel);
  501. const { product: productWithVariantChannel2 } = await adminClient.query(
  502. getProductWithVariantsDocument,
  503. {
  504. id: duplicateEntitySecondChannel.newEntityId,
  505. },
  506. );
  507. const productVariantChannel2 = productWithVariantChannel2!.variants.find(v =>
  508. v.sku.startsWith(productVariant.sku),
  509. );
  510. expect(productVariantChannel2!.price).toEqual(productVariant.price + 150);
  511. });
  512. it('tax categories are duplicated', async () => {
  513. // update existing variant with a non 1 first tax category
  514. // bc tax category defaults to the first available
  515. const { product } = await adminClient.query(getProductWithVariantsDocument, {
  516. id: 'T_1',
  517. });
  518. await adminClient.query(updateProductVariantsDocument, {
  519. input: [{ id: product!.variants[0].id, taxCategoryId: 'T_2' }],
  520. });
  521. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  522. input: {
  523. entityName: 'Product',
  524. entityId: 'T_1',
  525. duplicatorInput: {
  526. code: 'product-duplicator',
  527. arguments: [
  528. {
  529. name: 'includeVariants',
  530. value: 'true',
  531. },
  532. ],
  533. },
  534. },
  535. });
  536. duplicateEntityGuard.assertSuccess(duplicateEntity);
  537. const { product: productReloaded } = await adminClient.query(getProductWithVariantsDocument, {
  538. id: duplicateEntity.newEntityId,
  539. });
  540. const variant = productReloaded?.variants.find(v =>
  541. v.sku.startsWith(product!.variants[0].sku),
  542. );
  543. expect(variant).not.toBeUndefined();
  544. expect(variant!.taxCategory.id).toEqual('T_2');
  545. });
  546. });
  547. describe('Collection duplicator', () => {
  548. let testCollection: ResultOf<typeof createCollectionDocument>['createCollection'];
  549. let duplicatedCollectionId: string;
  550. beforeAll(async () => {
  551. await adminClient.asSuperAdmin();
  552. const { createCollection } = await adminClient.query(createCollectionDocument, {
  553. input: {
  554. parentId: 'T_2',
  555. assetIds: ['T_1'],
  556. featuredAssetId: 'T_1',
  557. isPrivate: false,
  558. inheritFilters: false,
  559. translations: [
  560. {
  561. languageCode: LanguageCode.en,
  562. name: 'Test Collection',
  563. description: 'Test Collection description',
  564. slug: 'test-collection',
  565. },
  566. ],
  567. filters: [
  568. {
  569. code: variantIdCollectionFilter.code,
  570. arguments: [
  571. {
  572. name: 'variantIds',
  573. value: '["T_1"]',
  574. },
  575. {
  576. name: 'combineWithAnd',
  577. value: 'true',
  578. },
  579. ],
  580. },
  581. ],
  582. },
  583. });
  584. testCollection = createCollection;
  585. });
  586. it('duplicate collection', async () => {
  587. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  588. input: {
  589. entityName: 'Collection',
  590. entityId: testCollection.id,
  591. duplicatorInput: {
  592. code: 'collection-duplicator',
  593. arguments: [],
  594. },
  595. },
  596. });
  597. duplicateEntityGuard.assertSuccess(duplicateEntity);
  598. expect(duplicateEntity.newEntityId).toBeDefined();
  599. duplicatedCollectionId = duplicateEntity.newEntityId;
  600. });
  601. it('collection name is suffixed', async () => {
  602. const { collection } = await adminClient.query(getCollectionDocument, {
  603. id: duplicatedCollectionId,
  604. });
  605. expect(collection?.name).toBe('Test Collection (copy)');
  606. });
  607. it('is initially private', async () => {
  608. const { collection } = await adminClient.query(getCollectionDocument, {
  609. id: duplicatedCollectionId,
  610. });
  611. expect(collection?.isPrivate).toBe(true);
  612. });
  613. it('assets are duplicated', async () => {
  614. const { collection } = await adminClient.query(getCollectionDocument, {
  615. id: duplicatedCollectionId,
  616. });
  617. expect(collection?.featuredAsset).toEqual(testCollection.featuredAsset);
  618. expect(collection?.assets.length).toBe(1);
  619. expect(collection?.assets).toEqual(testCollection.assets);
  620. });
  621. it('parentId matches', async () => {
  622. const { collection } = await adminClient.query(getCollectionDocument, {
  623. id: duplicatedCollectionId,
  624. });
  625. expect(collection?.parent?.id).toBe(testCollection.parent?.id);
  626. });
  627. it('filters are duplicated', async () => {
  628. const { collection } = await adminClient.query(getCollectionDocument, {
  629. id: duplicatedCollectionId,
  630. });
  631. expect(collection?.filters).toEqual(testCollection.filters);
  632. });
  633. });
  634. describe('Facet duplicator', () => {
  635. let newFacetId: string;
  636. it('duplicate facet', async () => {
  637. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  638. input: {
  639. entityName: 'Facet',
  640. entityId: 'T_1',
  641. duplicatorInput: {
  642. code: 'facet-duplicator',
  643. arguments: [
  644. {
  645. name: 'includeFacetValues',
  646. value: 'true',
  647. },
  648. ],
  649. },
  650. },
  651. });
  652. duplicateEntityGuard.assertSuccess(duplicateEntity);
  653. expect(duplicateEntity.newEntityId).toBe('T_2');
  654. newFacetId = duplicateEntity.newEntityId;
  655. });
  656. it('facet name is suffixed', async () => {
  657. const { facet } = await adminClient.query(getFacetWithValuesDocument, {
  658. id: newFacetId,
  659. });
  660. expect(facet?.name).toBe('category (copy)');
  661. });
  662. it('is initially private', async () => {
  663. const { facet } = await adminClient.query(getFacetWithValuesDocument, {
  664. id: newFacetId,
  665. });
  666. expect(facet?.isPrivate).toBe(true);
  667. });
  668. it('facet values are duplicated', async () => {
  669. const { facet } = await adminClient.query(getFacetWithValuesDocument, {
  670. id: newFacetId,
  671. });
  672. expect(facet?.values.map(v => v.name).sort()).toEqual([
  673. 'computers (copy)',
  674. 'electronics (copy)',
  675. ]);
  676. });
  677. });
  678. describe('Promotion duplicator', () => {
  679. let testPromotion: FragmentOf<typeof promotionFragment>;
  680. let duplicatedPromotionId: string;
  681. const promotionGuard: ErrorResultGuard<{ id: string }> = createErrorResultGuard(
  682. result => !!result.id,
  683. );
  684. beforeAll(async () => {
  685. await adminClient.asSuperAdmin();
  686. const { createPromotion } = await adminClient.query(createPromotionDocument, {
  687. input: {
  688. enabled: true,
  689. couponCode: 'TEST',
  690. perCustomerUsageLimit: 1,
  691. usageLimit: 100,
  692. startsAt: new Date().toISOString(),
  693. endsAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30).toISOString(),
  694. translations: [
  695. {
  696. name: 'Test Promotion',
  697. description: 'Test Promotion description',
  698. languageCode: LanguageCode.en,
  699. },
  700. ],
  701. conditions: [
  702. {
  703. code: minimumOrderAmount.code,
  704. arguments: [
  705. {
  706. name: 'amount',
  707. value: '1000',
  708. },
  709. {
  710. name: 'taxInclusive',
  711. value: 'true',
  712. },
  713. ],
  714. },
  715. ],
  716. actions: [
  717. {
  718. code: freeShipping.code,
  719. arguments: [],
  720. },
  721. ],
  722. },
  723. });
  724. promotionGuard.assertSuccess(createPromotion);
  725. testPromotion = createPromotion;
  726. });
  727. it('duplicate promotion', async () => {
  728. const { duplicateEntity } = await adminClient.query(duplicateEntityDocument, {
  729. input: {
  730. entityName: 'Promotion',
  731. entityId: testPromotion.id,
  732. duplicatorInput: {
  733. code: 'promotion-duplicator',
  734. arguments: [],
  735. },
  736. },
  737. });
  738. duplicateEntityGuard.assertSuccess(duplicateEntity);
  739. expect(testPromotion.id).toBe('T_1');
  740. expect(duplicateEntity.newEntityId).toBe('T_2');
  741. duplicatedPromotionId = duplicateEntity.newEntityId;
  742. });
  743. it('promotion name is suffixed', async () => {
  744. const { promotion } = await adminClient.query(getPromotionDocument, {
  745. id: duplicatedPromotionId,
  746. });
  747. expect(promotion?.name).toBe('Test Promotion (copy)');
  748. });
  749. it('is initially disabled', async () => {
  750. const { promotion } = await adminClient.query(getPromotionDocument, {
  751. id: duplicatedPromotionId,
  752. });
  753. expect(promotion?.enabled).toBe(false);
  754. });
  755. it('properties are duplicated', async () => {
  756. const { promotion } = await adminClient.query(getPromotionDocument, {
  757. id: duplicatedPromotionId,
  758. });
  759. expect(promotion?.startsAt).toBe(testPromotion.startsAt);
  760. expect(promotion?.endsAt).toBe(testPromotion.endsAt);
  761. expect(promotion?.couponCode).toBe(testPromotion.couponCode);
  762. expect(promotion?.perCustomerUsageLimit).toBe(testPromotion.perCustomerUsageLimit);
  763. expect(promotion?.usageLimit).toBe(testPromotion.usageLimit);
  764. });
  765. it('conditions are duplicated', async () => {
  766. const { promotion } = await adminClient.query(getPromotionDocument, {
  767. id: duplicatedPromotionId,
  768. });
  769. expect(promotion?.conditions).toEqual(testPromotion.conditions);
  770. });
  771. it('actions are duplicated', async () => {
  772. const { promotion } = await adminClient.query(getPromotionDocument, {
  773. id: duplicatedPromotionId,
  774. });
  775. expect(promotion?.actions).toEqual(testPromotion.actions);
  776. });
  777. });
  778. });
  779. });