duplicate-entity.e2e-spec.ts 32 KB

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