@labdigital/commercetools-mock 2.1.0 → 2.3.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.
@@ -18,6 +18,23 @@ import type {
18
18
  ProductChangePriceAction,
19
19
  ProductAddPriceAction,
20
20
  ProductRemovePriceAction,
21
+ CategoryReference,
22
+ InvalidJsonInputError,
23
+ InvalidOperationError,
24
+ TaxCategoryReference,
25
+ StateReference,
26
+ ProductChangeNameAction,
27
+ ProductChangeSlugAction,
28
+ ProductSetMetaTitleAction,
29
+ ProductSetMetaDescriptionAction,
30
+ ProductSetMetaKeywordsAction,
31
+ ProductAddVariantAction,
32
+ ProductRemoveVariantAction,
33
+ ProductChangeMasterVariantAction,
34
+ ProductSetTaxCategoryAction,
35
+ ProductAddToCategoryAction,
36
+ ProductRemoveFromCategoryAction,
37
+ ProductTransitionStateAction,
21
38
  } from '@commercetools/platform-sdk'
22
39
  import { v4 as uuidv4 } from 'uuid'
23
40
  import type { Writable } from '../types.js'
@@ -28,6 +45,7 @@ import {
28
45
  getReferenceFromResourceIdentifier,
29
46
  } from './helpers.js'
30
47
  import deepEqual from 'deep-equal'
48
+ import { CommercetoolsError } from '../exceptions.js'
31
49
 
32
50
  export class ProductRepository extends AbstractResourceRepository<'product'> {
33
51
  getTypeId() {
@@ -57,16 +75,64 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
57
75
  }
58
76
  }
59
77
 
78
+ // Resolve Product categories
79
+ const categoryReferences: CategoryReference[] = []
80
+ draft.categories?.forEach((category) => {
81
+ if (category) {
82
+ categoryReferences.push(
83
+ getReferenceFromResourceIdentifier<CategoryReference>(
84
+ category,
85
+ context.projectKey,
86
+ this._storage
87
+ )
88
+ )
89
+ } else {
90
+ throw new CommercetoolsError<InvalidJsonInputError>(
91
+ {
92
+ code: 'InvalidJsonInput',
93
+ message: 'Request body does not contain valid JSON.',
94
+ detailedErrorMessage: 'categories: JSON object expected.',
95
+ },
96
+ 400
97
+ )
98
+ }
99
+ })
100
+
101
+ // Resolve Tax category
102
+ let taxCategoryReference: TaxCategoryReference | undefined = undefined
103
+ if (draft.taxCategory) {
104
+ taxCategoryReference =
105
+ getReferenceFromResourceIdentifier<TaxCategoryReference>(
106
+ draft.taxCategory,
107
+ context.projectKey,
108
+ this._storage
109
+ )
110
+ }
111
+
112
+ // Resolve Product State
113
+ let productStateReference: StateReference | undefined = undefined
114
+ if (draft.state) {
115
+ productStateReference =
116
+ getReferenceFromResourceIdentifier<StateReference>(
117
+ draft.state,
118
+ context.projectKey,
119
+ this._storage
120
+ )
121
+ }
122
+
60
123
  const productData: ProductData = {
61
124
  name: draft.name,
62
125
  slug: draft.slug,
63
- categories: [],
126
+ description: draft.description,
127
+ categories: categoryReferences,
64
128
  masterVariant: variantFromDraft(1, draft.masterVariant),
65
129
  variants:
66
130
  draft.variants?.map((variant, index) =>
67
131
  variantFromDraft(index + 2, variant)
68
132
  ) ?? [],
69
-
133
+ metaTitle: draft.metaTitle,
134
+ metaDescription: draft.metaDescription,
135
+ metaKeywords: draft.metaKeywords,
70
136
  searchKeywords: draft.searchKeywords ?? {},
71
137
  }
72
138
 
@@ -74,6 +140,8 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
74
140
  ...getBaseResourceProperties(),
75
141
  key: draft.key,
76
142
  productType: productType,
143
+ taxCategory: taxCategoryReference,
144
+ state: productStateReference,
77
145
  masterData: {
78
146
  current: productData,
79
147
  staged: productData,
@@ -366,13 +434,15 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
366
434
 
367
435
  return resource
368
436
  },
369
-
370
437
  addPrice: (
371
438
  context: RepositoryContext,
372
439
  resource: Writable<Product>,
373
440
  { variantId, sku, price, staged }: ProductAddPriceAction
374
441
  ) => {
375
- const addVariantPrice = (data: Writable<ProductData>) => {
442
+ const addVariantPrice = (
443
+ data: Writable<ProductData>,
444
+ priceToAdd: Price
445
+ ) => {
376
446
  const { variant, isMasterVariant, variantIndex } = getVariant(
377
447
  data,
378
448
  variantId,
@@ -385,9 +455,9 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
385
455
  }
386
456
 
387
457
  if (variant.prices === undefined) {
388
- variant.prices = [priceFromDraft(price)]
458
+ variant.prices = [priceToAdd]
389
459
  } else {
390
- variant.prices.push(priceFromDraft(price))
460
+ variant.prices.push(priceToAdd)
391
461
  }
392
462
 
393
463
  if (isMasterVariant) {
@@ -397,18 +467,21 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
397
467
  }
398
468
  }
399
469
 
470
+ // Pre-creating the price object ensures consistency between staged and current versions
471
+ const priceToAdd = priceFromDraft(price)
472
+
400
473
  // If true, only the staged Attribute is set. If false, both current and
401
474
  // staged Attribute is set. Default is true
402
475
  const onlyStaged = staged !== undefined ? staged : true
403
476
 
404
477
  // Write the attribute to the staged data
405
- addVariantPrice(resource.masterData.staged)
478
+ addVariantPrice(resource.masterData.staged, priceToAdd)
406
479
 
407
480
  // Also write to published data is isStaged = false
408
481
  // if isStaged is false we set the attribute on both the staged and
409
482
  // published data.
410
483
  if (!onlyStaged) {
411
- addVariantPrice(resource.masterData.current)
484
+ addVariantPrice(resource.masterData.current, priceToAdd)
412
485
  }
413
486
  checkForStagedChanges(resource)
414
487
 
@@ -525,21 +598,354 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
525
598
 
526
599
  return resource
527
600
  },
601
+ changeName: (
602
+ context: RepositoryContext,
603
+ resource: Writable<Product>,
604
+ { name, staged }: ProductChangeNameAction
605
+ ) => {
606
+ const onlyStaged = staged !== undefined ? staged : true
607
+ resource.masterData.staged.name = name
608
+ if (!onlyStaged) {
609
+ resource.masterData.current.name = name
610
+ }
611
+ checkForStagedChanges(resource)
612
+ return resource
613
+ },
614
+ changeSlug: (
615
+ context: RepositoryContext,
616
+ resource: Writable<Product>,
617
+ { slug, staged }: ProductChangeSlugAction
618
+ ) => {
619
+ const onlyStaged = staged !== undefined ? staged : true
620
+ resource.masterData.staged.slug = slug
621
+ if (!onlyStaged) {
622
+ resource.masterData.current.slug = slug
623
+ }
624
+ checkForStagedChanges(resource)
625
+ return resource
626
+ },
627
+ setMetaTitle: (
628
+ context: RepositoryContext,
629
+ resource: Writable<Product>,
630
+ { metaTitle, staged }: ProductSetMetaTitleAction
631
+ ) => {
632
+ const onlyStaged = staged !== undefined ? staged : true
633
+ resource.masterData.staged.metaTitle = metaTitle
634
+ if (!onlyStaged) {
635
+ resource.masterData.current.metaTitle = metaTitle
636
+ }
637
+ checkForStagedChanges(resource)
638
+ return resource
639
+ },
640
+ setMetaDescription: (
641
+ context: RepositoryContext,
642
+ resource: Writable<Product>,
643
+ { metaDescription, staged }: ProductSetMetaDescriptionAction
644
+ ) => {
645
+ const onlyStaged = staged !== undefined ? staged : true
646
+ resource.masterData.staged.metaDescription = metaDescription
647
+ if (!onlyStaged) {
648
+ resource.masterData.current.metaDescription = metaDescription
649
+ }
650
+ checkForStagedChanges(resource)
651
+ return resource
652
+ },
653
+ setMetaKeywords: (
654
+ context: RepositoryContext,
655
+ resource: Writable<Product>,
656
+ { metaKeywords, staged }: ProductSetMetaKeywordsAction
657
+ ) => {
658
+ const onlyStaged = staged !== undefined ? staged : true
659
+ resource.masterData.staged.metaKeywords = metaKeywords
660
+ if (!onlyStaged) {
661
+ resource.masterData.current.metaKeywords = metaKeywords
662
+ }
663
+ checkForStagedChanges(resource)
664
+ return resource
665
+ },
666
+ addVariant: (
667
+ context: RepositoryContext,
668
+ resource: Writable<Product>,
669
+ {
670
+ sku,
671
+ key,
672
+ prices,
673
+ images,
674
+ attributes,
675
+ staged,
676
+ assets,
677
+ }: ProductAddVariantAction
678
+ ) => {
679
+ const variantDraft: ProductVariantDraft = {
680
+ sku: sku,
681
+ key: key,
682
+ prices: prices,
683
+ images: images,
684
+ attributes: attributes,
685
+ assets: assets,
686
+ }
687
+
688
+ const dataStaged = resource.masterData.staged
689
+ const allVariants = [
690
+ dataStaged.masterVariant,
691
+ ...(dataStaged.variants ?? []),
692
+ ]
693
+ const maxId = allVariants.reduce(
694
+ (max, element) => (element.id > max ? element.id : max),
695
+ 0
696
+ )
697
+ const variant = variantFromDraft(maxId + 1, variantDraft)
698
+ dataStaged.variants.push(variant)
699
+
700
+ const onlyStaged = staged !== undefined ? staged : true
701
+
702
+ if (!onlyStaged) {
703
+ resource.masterData.current.variants.push(variant)
704
+ }
705
+ checkForStagedChanges(resource)
706
+
707
+ return resource
708
+ },
709
+ removeVariant: (
710
+ context: RepositoryContext,
711
+ resource: Writable<Product>,
712
+ { id, sku, staged }: ProductRemoveVariantAction
713
+ ) => {
714
+ const removeVariant = (data: Writable<ProductData>) => {
715
+ const { variant, isMasterVariant, variantIndex } = getVariant(
716
+ data,
717
+ id,
718
+ sku
719
+ )
720
+ if (!variant) {
721
+ throw new Error(
722
+ `Variant with id ${id} or sku ${sku} not found on product ${resource.id}`
723
+ )
724
+ }
725
+ if (isMasterVariant) {
726
+ throw new Error(
727
+ `Can not remove the variant [ID:${id}] for [Product:${resource.id}] since it's the master variant`
728
+ )
729
+ }
730
+
731
+ data.variants.splice(variantIndex, 1)
732
+ }
733
+
734
+ const onlyStaged = staged !== undefined ? staged : true
735
+
736
+ removeVariant(resource.masterData.staged)
737
+
738
+ if (!onlyStaged) {
739
+ removeVariant(resource.masterData.current)
740
+ }
741
+ checkForStagedChanges(resource)
742
+
743
+ return resource
744
+ },
745
+ changeMasterVariant: (
746
+ context: RepositoryContext,
747
+ resource: Writable<Product>,
748
+ { variantId, sku, staged }: ProductChangeMasterVariantAction
749
+ ) => {
750
+ const setMaster = (data: Writable<ProductData>) => {
751
+ const { variant, isMasterVariant, variantIndex } = getVariant(
752
+ data,
753
+ variantId,
754
+ sku
755
+ )
756
+ if (!variant) {
757
+ throw new Error(
758
+ `Variant with id ${variantId} or sku ${sku} not found on product ${resource.id}`
759
+ )
760
+ }
761
+
762
+ if (!isMasterVariant) {
763
+ // Save previous master variant
764
+ const masterVariantPrev = data.masterVariant
765
+ data.masterVariant = variant
766
+ // Remove new master from variants
767
+ data.variants.splice(variantIndex, 1)
768
+ // Add previous master to variants
769
+ data.variants.push(masterVariantPrev)
770
+ }
771
+ }
772
+
773
+ const onlyStaged = staged !== undefined ? staged : true
774
+
775
+ setMaster(resource.masterData.staged)
776
+
777
+ if (!onlyStaged) {
778
+ setMaster(resource.masterData.current)
779
+ }
780
+ checkForStagedChanges(resource)
781
+
782
+ return resource
783
+ },
784
+ setTaxCategory: (
785
+ context: RepositoryContext,
786
+ resource: Writable<Product>,
787
+ { taxCategory }: ProductSetTaxCategoryAction
788
+ ) => {
789
+ let taxCategoryReference: TaxCategoryReference | undefined = undefined
790
+ if (taxCategory) {
791
+ taxCategoryReference =
792
+ getReferenceFromResourceIdentifier<TaxCategoryReference>(
793
+ taxCategory,
794
+ context.projectKey,
795
+ this._storage
796
+ )
797
+ } else {
798
+ throw new CommercetoolsError<InvalidJsonInputError>(
799
+ {
800
+ code: 'InvalidJsonInput',
801
+ message: 'Request body does not contain valid JSON.',
802
+ detailedErrorMessage:
803
+ 'actions -> taxCategory: Missing required value',
804
+ },
805
+ 400
806
+ )
807
+ }
808
+ resource.taxCategory = taxCategoryReference
809
+ return resource
810
+ },
811
+ addToCategory: (
812
+ context: RepositoryContext,
813
+ resource: Writable<Product>,
814
+ { category, staged, orderHint }: ProductAddToCategoryAction
815
+ ) => {
816
+ const addCategory = (data: Writable<ProductData>) => {
817
+ if (category) {
818
+ data.categories.push(
819
+ getReferenceFromResourceIdentifier<CategoryReference>(
820
+ category,
821
+ context.projectKey,
822
+ this._storage
823
+ )
824
+ )
825
+ } else {
826
+ throw new CommercetoolsError<InvalidJsonInputError>(
827
+ {
828
+ code: 'InvalidJsonInput',
829
+ message: 'Request body does not contain valid JSON.',
830
+ detailedErrorMessage:
831
+ 'actions -> category: Missing required value',
832
+ },
833
+ 400
834
+ )
835
+ }
836
+ }
837
+
838
+ const onlyStaged = staged !== undefined ? staged : true
839
+
840
+ addCategory(resource.masterData.staged)
841
+
842
+ if (!onlyStaged) {
843
+ addCategory(resource.masterData.current)
844
+ }
845
+ checkForStagedChanges(resource)
846
+
847
+ return resource
848
+ },
849
+ removeFromCategory: (
850
+ context: RepositoryContext,
851
+ resource: Writable<Product>,
852
+ { category, staged }: ProductRemoveFromCategoryAction
853
+ ) => {
854
+ const removeCategory = (data: Writable<ProductData>) => {
855
+ if (category) {
856
+ const resolvedCategory =
857
+ getReferenceFromResourceIdentifier<CategoryReference>(
858
+ category,
859
+ context.projectKey,
860
+ this._storage
861
+ )
862
+
863
+ const foundCategory = data.categories.find(
864
+ (productCategory: CategoryReference) => {
865
+ if (productCategory.id == resolvedCategory.id) {
866
+ return productCategory
867
+ }
868
+ return false
869
+ }
870
+ )
871
+
872
+ if (!foundCategory) {
873
+ throw new CommercetoolsError<InvalidOperationError>(
874
+ {
875
+ code: 'InvalidOperation',
876
+ message:
877
+ `Cannot remove from category '${resolvedCategory.id}' because product ` +
878
+ `'${resource.masterData.current.name}' is not in that category.`,
879
+ },
880
+ 400
881
+ )
882
+ }
883
+
884
+ data.categories = data.categories.filter(
885
+ (productCategory: CategoryReference) => {
886
+ if (productCategory.id == resolvedCategory.id) {
887
+ return false
888
+ }
889
+ return true
890
+ }
891
+ )
892
+ } else {
893
+ throw new CommercetoolsError<InvalidJsonInputError>(
894
+ {
895
+ code: 'InvalidJsonInput',
896
+ message: 'Request body does not contain valid JSON.',
897
+ detailedErrorMessage:
898
+ 'actions -> category: Missing required value',
899
+ },
900
+ 400
901
+ )
902
+ }
903
+ }
904
+
905
+ const onlyStaged = staged !== undefined ? staged : true
906
+ removeCategory(resource.masterData.staged)
907
+
908
+ if (!onlyStaged) {
909
+ removeCategory(resource.masterData.current)
910
+ }
911
+ checkForStagedChanges(resource)
912
+
913
+ return resource
914
+ },
915
+ transitionState: (
916
+ context: RepositoryContext,
917
+ resource: Writable<Product>,
918
+ { state, force }: ProductTransitionStateAction
919
+ ) => {
920
+ let productStateReference: StateReference | undefined = undefined
921
+ if (state) {
922
+ productStateReference =
923
+ getReferenceFromResourceIdentifier<StateReference>(
924
+ state,
925
+ context.projectKey,
926
+ this._storage
927
+ )
928
+ resource.state = productStateReference
929
+ } else {
930
+ throw new CommercetoolsError<InvalidJsonInputError>(
931
+ {
932
+ code: 'InvalidJsonInput',
933
+ message: 'Request body does not contain valid JSON.',
934
+ detailedErrorMessage: 'actions -> state: Missing required value',
935
+ },
936
+ 400
937
+ )
938
+ }
939
+
940
+ return resource
941
+ },
528
942
 
529
- // 'changeName': () => {},
530
- // 'changeSlug': () => {},
531
- // 'addVariant': () => {},
532
- // 'removeVariant': () => {},
533
- // 'changeMasterVariant': () => {},
534
943
  // 'setPrices': () => {},
535
944
  // 'setProductPriceCustomType': () => {},
536
945
  // 'setProductPriceCustomField': () => {},
537
946
  // 'setDiscountedPrice': () => {},
538
947
  // 'setAttributeInAllVariants': () => {},
539
- // 'addToCategory': () => {},
540
948
  // 'setCategoryOrderHint': () => {},
541
- // 'removeFromCategory': () => {},
542
- // 'setTaxCategory': () => {},
543
949
  // 'setSku': () => {},
544
950
  // 'setProductVariantKey': () => {},
545
951
  // 'setImageLabel': () => {},
@@ -554,12 +960,8 @@ export class ProductRepository extends AbstractResourceRepository<'product'> {
554
960
  // 'setAssetCustomType': () => {},
555
961
  // 'setAssetCustomField': () => {},
556
962
  // 'setSearchKeywords': () => {},
557
- // 'setMetaTitle': () => {},
558
- // 'setMetaDescription': () => {},
559
- // 'setMetaKeywords': () => {},
560
963
  // 'revertStagedChanges': () => {},
561
964
  // 'revertStagedVariantChanges': () => {},
562
- // 'transitionState': () => {},
563
965
  }
564
966
  }
565
967
 
@@ -616,6 +1018,7 @@ const variantFromDraft = (
616
1018
  ): ProductVariant => ({
617
1019
  id: variantId,
618
1020
  sku: variant?.sku,
1021
+ key: variant?.key,
619
1022
  attributes: variant?.attributes ?? [],
620
1023
  prices: variant?.prices?.map(priceFromDraft),
621
1024
  assets: [],
@@ -624,6 +1027,7 @@ const variantFromDraft = (
624
1027
 
625
1028
  const priceFromDraft = (draft: PriceDraft): Price => ({
626
1029
  id: uuidv4(),
1030
+ key: draft.key,
627
1031
  country: draft.country,
628
1032
  value: createTypedMoney(draft.value),
629
1033
  })