@labdigital/commercetools-mock 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@labdigital/commercetools-mock",
3
3
  "author": "Michael van Tellingen",
4
- "version": "2.2.0",
4
+ "version": "2.4.0",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
@@ -4,6 +4,8 @@ import type {
4
4
  Category,
5
5
  CategoryAddAssetAction,
6
6
  CategoryChangeAssetNameAction,
7
+ CategoryChangeNameAction,
8
+ CategoryChangeParentAction,
7
9
  CategoryChangeSlugAction,
8
10
  CategoryDraft,
9
11
  CategoryRemoveAssetAction,
@@ -98,6 +100,30 @@ export class CategoryRepository extends AbstractResourceRepository<'category'> {
98
100
  ) => {
99
101
  resource.slug = slug
100
102
  },
103
+ changeName: (
104
+ context: RepositoryContext,
105
+ resource: Writable<Category>,
106
+ { name }: CategoryChangeNameAction
107
+ ) => {
108
+ resource.name = name
109
+ },
110
+ changeParent: (
111
+ context: RepositoryContext,
112
+ resource: Writable<Category>,
113
+ { parent }: CategoryChangeParentAction
114
+ ) => {
115
+ const category = this._storage.getByResourceIdentifier(
116
+ context.projectKey,
117
+ parent
118
+ )
119
+ if (!category) {
120
+ throw new Error('No category found for reference')
121
+ }
122
+ resource.parent = {
123
+ typeId: 'category',
124
+ id: category.id,
125
+ }
126
+ },
101
127
  setKey: (
102
128
  context: RepositoryContext,
103
129
  resource: Writable<Category>,
@@ -6,6 +6,7 @@ import type {
6
6
  ProductDraft,
7
7
  ProductPublishAction,
8
8
  ProductSetAttributeAction,
9
+ ProductSetAttributeInAllVariantsAction,
9
10
  ProductSetDescriptionAction,
10
11
  ProductAddExternalImageAction,
11
12
  ProductRemoveImageAction,
@@ -18,6 +19,23 @@ import type {
18
19
  ProductChangePriceAction,
19
20
  ProductAddPriceAction,
20
21
  ProductRemovePriceAction,
22
+ CategoryReference,
23
+ InvalidJsonInputError,
24
+ InvalidOperationError,
25
+ TaxCategoryReference,
26
+ StateReference,
27
+ ProductChangeNameAction,
28
+ ProductChangeSlugAction,
29
+ ProductSetMetaTitleAction,
30
+ ProductSetMetaDescriptionAction,
31
+ ProductSetMetaKeywordsAction,
32
+ ProductAddVariantAction,
33
+ ProductRemoveVariantAction,
34
+ ProductChangeMasterVariantAction,
35
+ ProductSetTaxCategoryAction,
36
+ ProductAddToCategoryAction,
37
+ ProductRemoveFromCategoryAction,
38
+ ProductTransitionStateAction,
21
39
  } from '@commercetools/platform-sdk'
22
40
  import { v4 as uuidv4 } from 'uuid'
23
41
  import type { Writable } from '../types.js'
@@ -28,6 +46,7 @@ import {
28
46
  getReferenceFromResourceIdentifier,
29
47
  } from './helpers.js'
30
48
  import deepEqual from 'deep-equal'
49
+ import { CommercetoolsError } from '../exceptions.js'
31
50
 
32
51
  export class ProductRepository extends AbstractResourceRepository<'product'> {
33
52
  getTypeId() {
@@ -57,16 +76,64 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
57
76
  }
58
77
  }
59
78
 
79
+ // Resolve Product categories
80
+ const categoryReferences: CategoryReference[] = []
81
+ draft.categories?.forEach((category) => {
82
+ if (category) {
83
+ categoryReferences.push(
84
+ getReferenceFromResourceIdentifier<CategoryReference>(
85
+ category,
86
+ context.projectKey,
87
+ this._storage
88
+ )
89
+ )
90
+ } else {
91
+ throw new CommercetoolsError<InvalidJsonInputError>(
92
+ {
93
+ code: 'InvalidJsonInput',
94
+ message: 'Request body does not contain valid JSON.',
95
+ detailedErrorMessage: 'categories: JSON object expected.',
96
+ },
97
+ 400
98
+ )
99
+ }
100
+ })
101
+
102
+ // Resolve Tax category
103
+ let taxCategoryReference: TaxCategoryReference | undefined = undefined
104
+ if (draft.taxCategory) {
105
+ taxCategoryReference =
106
+ getReferenceFromResourceIdentifier<TaxCategoryReference>(
107
+ draft.taxCategory,
108
+ context.projectKey,
109
+ this._storage
110
+ )
111
+ }
112
+
113
+ // Resolve Product State
114
+ let productStateReference: StateReference | undefined = undefined
115
+ if (draft.state) {
116
+ productStateReference =
117
+ getReferenceFromResourceIdentifier<StateReference>(
118
+ draft.state,
119
+ context.projectKey,
120
+ this._storage
121
+ )
122
+ }
123
+
60
124
  const productData: ProductData = {
61
125
  name: draft.name,
62
126
  slug: draft.slug,
63
- categories: [],
127
+ description: draft.description,
128
+ categories: categoryReferences,
64
129
  masterVariant: variantFromDraft(1, draft.masterVariant),
65
130
  variants:
66
131
  draft.variants?.map((variant, index) =>
67
132
  variantFromDraft(index + 2, variant)
68
133
  ) ?? [],
69
-
134
+ metaTitle: draft.metaTitle,
135
+ metaDescription: draft.metaDescription,
136
+ metaKeywords: draft.metaKeywords,
70
137
  searchKeywords: draft.searchKeywords ?? {},
71
138
  }
72
139
 
@@ -74,6 +141,8 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
74
141
  ...getBaseResourceProperties(),
75
142
  key: draft.key,
76
143
  productType: productType,
144
+ taxCategory: taxCategoryReference,
145
+ state: productStateReference,
77
146
  masterData: {
78
147
  current: productData,
79
148
  staged: productData,
@@ -170,6 +239,65 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
170
239
 
171
240
  return resource
172
241
  },
242
+ setAttributeInAllVariants: (
243
+ context: RepositoryContext,
244
+ resource: Writable<Product>,
245
+ { name, value, staged }: ProductSetAttributeInAllVariantsAction
246
+ ) => {
247
+ const setAttrInAllVariants = (data: Writable<ProductData>) => {
248
+ if (!data.masterVariant.attributes) {
249
+ data.masterVariant.attributes = []
250
+ }
251
+
252
+ const existingAttr = data.masterVariant.attributes?.find(
253
+ (attr) => attr.name === name
254
+ )
255
+
256
+ if (existingAttr) {
257
+ existingAttr.value = value
258
+ } else {
259
+ data.masterVariant.attributes.push({
260
+ name,
261
+ value,
262
+ })
263
+ }
264
+
265
+ data.variants.forEach((variant) => {
266
+ if (!variant.attributes) {
267
+ variant.attributes = []
268
+ }
269
+
270
+ const existingAttr = variant.attributes.find(
271
+ (attr) => attr.name === name
272
+ )
273
+ if (existingAttr) {
274
+ existingAttr.value = value
275
+ } else {
276
+ variant.attributes.push({
277
+ name,
278
+ value,
279
+ })
280
+ }
281
+ })
282
+ }
283
+
284
+ // If true, only the staged Attribute is set. If false, both current and
285
+ // staged Attribute is set. Default is true
286
+ const onlyStaged = staged !== undefined ? staged : true
287
+
288
+ // Write the attribute to the staged data
289
+ setAttrInAllVariants(resource.masterData.staged)
290
+
291
+ // Also write to published data is isStaged = false
292
+ // if isStaged is false we set the attribute on both the staged and
293
+ // published data.
294
+ if (!onlyStaged) {
295
+ setAttrInAllVariants(resource.masterData.current)
296
+ }
297
+ checkForStagedChanges(resource)
298
+
299
+ return resource
300
+ },
173
301
  setDescription: (
174
302
  context: RepositoryContext,
175
303
  resource: Writable<Product>,
@@ -366,13 +494,15 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
366
494
 
367
495
  return resource
368
496
  },
369
-
370
497
  addPrice: (
371
498
  context: RepositoryContext,
372
499
  resource: Writable<Product>,
373
500
  { variantId, sku, price, staged }: ProductAddPriceAction
374
501
  ) => {
375
- const addVariantPrice = (data: Writable<ProductData>) => {
502
+ const addVariantPrice = (
503
+ data: Writable<ProductData>,
504
+ priceToAdd: Price
505
+ ) => {
376
506
  const { variant, isMasterVariant, variantIndex } = getVariant(
377
507
  data,
378
508
  variantId,
@@ -385,9 +515,9 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
385
515
  }
386
516
 
387
517
  if (variant.prices === undefined) {
388
- variant.prices = [priceFromDraft(price)]
518
+ variant.prices = [priceToAdd]
389
519
  } else {
390
- variant.prices.push(priceFromDraft(price))
520
+ variant.prices.push(priceToAdd)
391
521
  }
392
522
 
393
523
  if (isMasterVariant) {
@@ -397,18 +527,21 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
397
527
  }
398
528
  }
399
529
 
530
+ // Pre-creating the price object ensures consistency between staged and current versions
531
+ const priceToAdd = priceFromDraft(price)
532
+
400
533
  // If true, only the staged Attribute is set. If false, both current and
401
534
  // staged Attribute is set. Default is true
402
535
  const onlyStaged = staged !== undefined ? staged : true
403
536
 
404
537
  // Write the attribute to the staged data
405
- addVariantPrice(resource.masterData.staged)
538
+ addVariantPrice(resource.masterData.staged, priceToAdd)
406
539
 
407
540
  // Also write to published data is isStaged = false
408
541
  // if isStaged is false we set the attribute on both the staged and
409
542
  // published data.
410
543
  if (!onlyStaged) {
411
- addVariantPrice(resource.masterData.current)
544
+ addVariantPrice(resource.masterData.current, priceToAdd)
412
545
  }
413
546
  checkForStagedChanges(resource)
414
547
 
@@ -525,21 +658,354 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
525
658
 
526
659
  return resource
527
660
  },
661
+ changeName: (
662
+ context: RepositoryContext,
663
+ resource: Writable<Product>,
664
+ { name, staged }: ProductChangeNameAction
665
+ ) => {
666
+ const onlyStaged = staged !== undefined ? staged : true
667
+ resource.masterData.staged.name = name
668
+ if (!onlyStaged) {
669
+ resource.masterData.current.name = name
670
+ }
671
+ checkForStagedChanges(resource)
672
+ return resource
673
+ },
674
+ changeSlug: (
675
+ context: RepositoryContext,
676
+ resource: Writable<Product>,
677
+ { slug, staged }: ProductChangeSlugAction
678
+ ) => {
679
+ const onlyStaged = staged !== undefined ? staged : true
680
+ resource.masterData.staged.slug = slug
681
+ if (!onlyStaged) {
682
+ resource.masterData.current.slug = slug
683
+ }
684
+ checkForStagedChanges(resource)
685
+ return resource
686
+ },
687
+ setMetaTitle: (
688
+ context: RepositoryContext,
689
+ resource: Writable<Product>,
690
+ { metaTitle, staged }: ProductSetMetaTitleAction
691
+ ) => {
692
+ const onlyStaged = staged !== undefined ? staged : true
693
+ resource.masterData.staged.metaTitle = metaTitle
694
+ if (!onlyStaged) {
695
+ resource.masterData.current.metaTitle = metaTitle
696
+ }
697
+ checkForStagedChanges(resource)
698
+ return resource
699
+ },
700
+ setMetaDescription: (
701
+ context: RepositoryContext,
702
+ resource: Writable<Product>,
703
+ { metaDescription, staged }: ProductSetMetaDescriptionAction
704
+ ) => {
705
+ const onlyStaged = staged !== undefined ? staged : true
706
+ resource.masterData.staged.metaDescription = metaDescription
707
+ if (!onlyStaged) {
708
+ resource.masterData.current.metaDescription = metaDescription
709
+ }
710
+ checkForStagedChanges(resource)
711
+ return resource
712
+ },
713
+ setMetaKeywords: (
714
+ context: RepositoryContext,
715
+ resource: Writable<Product>,
716
+ { metaKeywords, staged }: ProductSetMetaKeywordsAction
717
+ ) => {
718
+ const onlyStaged = staged !== undefined ? staged : true
719
+ resource.masterData.staged.metaKeywords = metaKeywords
720
+ if (!onlyStaged) {
721
+ resource.masterData.current.metaKeywords = metaKeywords
722
+ }
723
+ checkForStagedChanges(resource)
724
+ return resource
725
+ },
726
+ addVariant: (
727
+ context: RepositoryContext,
728
+ resource: Writable<Product>,
729
+ {
730
+ sku,
731
+ key,
732
+ prices,
733
+ images,
734
+ attributes,
735
+ staged,
736
+ assets,
737
+ }: ProductAddVariantAction
738
+ ) => {
739
+ const variantDraft: ProductVariantDraft = {
740
+ sku: sku,
741
+ key: key,
742
+ prices: prices,
743
+ images: images,
744
+ attributes: attributes,
745
+ assets: assets,
746
+ }
747
+
748
+ const dataStaged = resource.masterData.staged
749
+ const allVariants = [
750
+ dataStaged.masterVariant,
751
+ ...(dataStaged.variants ?? []),
752
+ ]
753
+ const maxId = allVariants.reduce(
754
+ (max, element) => (element.id > max ? element.id : max),
755
+ 0
756
+ )
757
+ const variant = variantFromDraft(maxId + 1, variantDraft)
758
+ dataStaged.variants.push(variant)
759
+
760
+ const onlyStaged = staged !== undefined ? staged : true
761
+
762
+ if (!onlyStaged) {
763
+ resource.masterData.current.variants.push(variant)
764
+ }
765
+ checkForStagedChanges(resource)
766
+
767
+ return resource
768
+ },
769
+ removeVariant: (
770
+ context: RepositoryContext,
771
+ resource: Writable<Product>,
772
+ { id, sku, staged }: ProductRemoveVariantAction
773
+ ) => {
774
+ const removeVariant = (data: Writable<ProductData>) => {
775
+ const { variant, isMasterVariant, variantIndex } = getVariant(
776
+ data,
777
+ id,
778
+ sku
779
+ )
780
+ if (!variant) {
781
+ throw new Error(
782
+ `Variant with id ${id} or sku ${sku} not found on product ${resource.id}`
783
+ )
784
+ }
785
+ if (isMasterVariant) {
786
+ throw new Error(
787
+ `Can not remove the variant [ID:${id}] for [Product:${resource.id}] since it's the master variant`
788
+ )
789
+ }
790
+
791
+ data.variants.splice(variantIndex, 1)
792
+ }
793
+
794
+ const onlyStaged = staged !== undefined ? staged : true
795
+
796
+ removeVariant(resource.masterData.staged)
797
+
798
+ if (!onlyStaged) {
799
+ removeVariant(resource.masterData.current)
800
+ }
801
+ checkForStagedChanges(resource)
802
+
803
+ return resource
804
+ },
805
+ changeMasterVariant: (
806
+ context: RepositoryContext,
807
+ resource: Writable<Product>,
808
+ { variantId, sku, staged }: ProductChangeMasterVariantAction
809
+ ) => {
810
+ const setMaster = (data: Writable<ProductData>) => {
811
+ const { variant, isMasterVariant, variantIndex } = getVariant(
812
+ data,
813
+ variantId,
814
+ sku
815
+ )
816
+ if (!variant) {
817
+ throw new Error(
818
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
819
+ )
820
+ }
821
+
822
+ if (!isMasterVariant) {
823
+ // Save previous master variant
824
+ const masterVariantPrev = data.masterVariant
825
+ data.masterVariant = variant
826
+ // Remove new master from variants
827
+ data.variants.splice(variantIndex, 1)
828
+ // Add previous master to variants
829
+ data.variants.push(masterVariantPrev)
830
+ }
831
+ }
832
+
833
+ const onlyStaged = staged !== undefined ? staged : true
834
+
835
+ setMaster(resource.masterData.staged)
836
+
837
+ if (!onlyStaged) {
838
+ setMaster(resource.masterData.current)
839
+ }
840
+ checkForStagedChanges(resource)
841
+
842
+ return resource
843
+ },
844
+ setTaxCategory: (
845
+ context: RepositoryContext,
846
+ resource: Writable<Product>,
847
+ { taxCategory }: ProductSetTaxCategoryAction
848
+ ) => {
849
+ let taxCategoryReference: TaxCategoryReference | undefined = undefined
850
+ if (taxCategory) {
851
+ taxCategoryReference =
852
+ getReferenceFromResourceIdentifier<TaxCategoryReference>(
853
+ taxCategory,
854
+ context.projectKey,
855
+ this._storage
856
+ )
857
+ } else {
858
+ throw new CommercetoolsError<InvalidJsonInputError>(
859
+ {
860
+ code: 'InvalidJsonInput',
861
+ message: 'Request body does not contain valid JSON.',
862
+ detailedErrorMessage:
863
+ 'actions -> taxCategory: Missing required value',
864
+ },
865
+ 400
866
+ )
867
+ }
868
+ resource.taxCategory = taxCategoryReference
869
+ return resource
870
+ },
871
+ addToCategory: (
872
+ context: RepositoryContext,
873
+ resource: Writable<Product>,
874
+ { category, staged, orderHint }: ProductAddToCategoryAction
875
+ ) => {
876
+ const addCategory = (data: Writable<ProductData>) => {
877
+ if (category) {
878
+ data.categories.push(
879
+ getReferenceFromResourceIdentifier<CategoryReference>(
880
+ category,
881
+ context.projectKey,
882
+ this._storage
883
+ )
884
+ )
885
+ } else {
886
+ throw new CommercetoolsError<InvalidJsonInputError>(
887
+ {
888
+ code: 'InvalidJsonInput',
889
+ message: 'Request body does not contain valid JSON.',
890
+ detailedErrorMessage:
891
+ 'actions -> category: Missing required value',
892
+ },
893
+ 400
894
+ )
895
+ }
896
+ }
897
+
898
+ const onlyStaged = staged !== undefined ? staged : true
899
+
900
+ addCategory(resource.masterData.staged)
901
+
902
+ if (!onlyStaged) {
903
+ addCategory(resource.masterData.current)
904
+ }
905
+ checkForStagedChanges(resource)
906
+
907
+ return resource
908
+ },
909
+ removeFromCategory: (
910
+ context: RepositoryContext,
911
+ resource: Writable<Product>,
912
+ { category, staged }: ProductRemoveFromCategoryAction
913
+ ) => {
914
+ const removeCategory = (data: Writable<ProductData>) => {
915
+ if (category) {
916
+ const resolvedCategory =
917
+ getReferenceFromResourceIdentifier<CategoryReference>(
918
+ category,
919
+ context.projectKey,
920
+ this._storage
921
+ )
922
+
923
+ const foundCategory = data.categories.find(
924
+ (productCategory: CategoryReference) => {
925
+ if (productCategory.id == resolvedCategory.id) {
926
+ return productCategory
927
+ }
928
+ return false
929
+ }
930
+ )
931
+
932
+ if (!foundCategory) {
933
+ throw new CommercetoolsError<InvalidOperationError>(
934
+ {
935
+ code: 'InvalidOperation',
936
+ message:
937
+ `Cannot remove from category '${resolvedCategory.id}' because product ` +
938
+ `'${resource.masterData.current.name}' is not in that category.`,
939
+ },
940
+ 400
941
+ )
942
+ }
943
+
944
+ data.categories = data.categories.filter(
945
+ (productCategory: CategoryReference) => {
946
+ if (productCategory.id == resolvedCategory.id) {
947
+ return false
948
+ }
949
+ return true
950
+ }
951
+ )
952
+ } else {
953
+ throw new CommercetoolsError<InvalidJsonInputError>(
954
+ {
955
+ code: 'InvalidJsonInput',
956
+ message: 'Request body does not contain valid JSON.',
957
+ detailedErrorMessage:
958
+ 'actions -> category: Missing required value',
959
+ },
960
+ 400
961
+ )
962
+ }
963
+ }
964
+
965
+ const onlyStaged = staged !== undefined ? staged : true
966
+ removeCategory(resource.masterData.staged)
967
+
968
+ if (!onlyStaged) {
969
+ removeCategory(resource.masterData.current)
970
+ }
971
+ checkForStagedChanges(resource)
972
+
973
+ return resource
974
+ },
975
+ transitionState: (
976
+ context: RepositoryContext,
977
+ resource: Writable<Product>,
978
+ { state, force }: ProductTransitionStateAction
979
+ ) => {
980
+ let productStateReference: StateReference | undefined = undefined
981
+ if (state) {
982
+ productStateReference =
983
+ getReferenceFromResourceIdentifier<StateReference>(
984
+ state,
985
+ context.projectKey,
986
+ this._storage
987
+ )
988
+ resource.state = productStateReference
989
+ } else {
990
+ throw new CommercetoolsError<InvalidJsonInputError>(
991
+ {
992
+ code: 'InvalidJsonInput',
993
+ message: 'Request body does not contain valid JSON.',
994
+ detailedErrorMessage: 'actions -> state: Missing required value',
995
+ },
996
+ 400
997
+ )
998
+ }
999
+
1000
+ return resource
1001
+ },
528
1002
 
529
- // 'changeName': () => {},
530
- // 'changeSlug': () => {},
531
- // 'addVariant': () => {},
532
- // 'removeVariant': () => {},
533
- // 'changeMasterVariant': () => {},
534
1003
  // 'setPrices': () => {},
535
1004
  // 'setProductPriceCustomType': () => {},
536
1005
  // 'setProductPriceCustomField': () => {},
537
1006
  // 'setDiscountedPrice': () => {},
538
1007
  // 'setAttributeInAllVariants': () => {},
539
- // 'addToCategory': () => {},
540
1008
  // 'setCategoryOrderHint': () => {},
541
- // 'removeFromCategory': () => {},
542
- // 'setTaxCategory': () => {},
543
1009
  // 'setSku': () => {},
544
1010
  // 'setProductVariantKey': () => {},
545
1011
  // 'setImageLabel': () => {},
@@ -554,12 +1020,8 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
554
1020
  // 'setAssetCustomType': () => {},
555
1021
  // 'setAssetCustomField': () => {},
556
1022
  // 'setSearchKeywords': () => {},
557
- // 'setMetaTitle': () => {},
558
- // 'setMetaDescription': () => {},
559
- // 'setMetaKeywords': () => {},
560
1023
  // 'revertStagedChanges': () => {},
561
1024
  // 'revertStagedVariantChanges': () => {},
562
- // 'transitionState': () => {},
563
1025
  }
564
1026
  }
565
1027
 
@@ -616,6 +1078,7 @@ const variantFromDraft = (
616
1078
  ): ProductVariant => ({
617
1079
  id: variantId,
618
1080
  sku: variant?.sku,
1081
+ key: variant?.key,
619
1082
  attributes: variant?.attributes ?? [],
620
1083
  prices: variant?.prices?.map(priceFromDraft),
621
1084
  assets: [],
@@ -624,6 +1087,7 @@ const variantFromDraft = (
624
1087
 
625
1088
  const priceFromDraft = (draft: PriceDraft): Price => ({
626
1089
  id: uuidv4(),
1090
+ key: draft.key,
627
1091
  country: draft.country,
628
1092
  value: createTypedMoney(draft.value),
629
1093
  })