@adobe/spacecat-shared-tier-client 1.3.10 → 1.3.12

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/.releaserc.cjs CHANGED
@@ -1,8 +1,12 @@
1
1
  module.exports = {
2
2
  extends: "semantic-release-monorepo",
3
3
  plugins: [
4
- "@semantic-release/commit-analyzer",
5
- "@semantic-release/release-notes-generator",
4
+ ["@semantic-release/commit-analyzer", {
5
+ "preset": "conventionalcommits",
6
+ }],
7
+ ["@semantic-release/release-notes-generator", {
8
+ "preset": "conventionalcommits",
9
+ }],
6
10
  ["@semantic-release/changelog", {
7
11
  "changelogFile": "CHANGELOG.md",
8
12
  }],
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [@adobe/spacecat-shared-tier-client-v1.3.12](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tier-client-v1.3.11...@adobe/spacecat-shared-tier-client-v1.3.12) (2026-02-17)
2
+
3
+ ### Bug Fixes
4
+
5
+ * **data-access:** decouple shared packages for v2/v3 alias wrapper rollout ([#1355](https://github.com/adobe/spacecat-shared/issues/1355)) ([ba48df7](https://github.com/adobe/spacecat-shared/commit/ba48df710e0030c1cb3ef4f90661cff1b548d42f))
6
+
7
+ # [@adobe/spacecat-shared-tier-client-v1.3.11](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tier-client-v1.3.10...@adobe/spacecat-shared-tier-client-v1.3.11) (2026-02-06)
8
+
9
+
10
+ ### Bug Fixes
11
+
12
+ * adds additional check for dangling enrollments ([#1296](https://github.com/adobe/spacecat-shared/issues/1296)) ([e5a0de3](https://github.com/adobe/spacecat-shared/commit/e5a0de32112dbf81e3da37d7b90a747d993497da))
13
+
1
14
  # [@adobe/spacecat-shared-tier-client-v1.3.10](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tier-client-v1.3.9...@adobe/spacecat-shared-tier-client-v1.3.10) (2025-12-05)
2
15
 
3
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-tier-client",
3
- "version": "1.3.10",
3
+ "version": "1.3.12",
4
4
  "description": "Shared modules of the Spacecat Services - Tier Client",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@adobe/spacecat-shared-utils": "1.81.1",
39
- "@adobe/spacecat-shared-data-access": "2.88.7"
39
+ "@mysticat/data-service-types": "git+https://github.com/adobe/mysticat-data-service.git#types-ts-v1.11.1"
40
40
  },
41
41
  "devDependencies": {
42
42
  "c8": "10.1.3",
@@ -11,9 +11,9 @@
11
11
  */
12
12
 
13
13
  import { isNonEmptyObject, hasText } from '@adobe/spacecat-shared-utils';
14
- import {
15
- Entitlement as EntitlementModel,
16
- } from '@adobe/spacecat-shared-data-access';
14
+ import { MYSTICAT_ENUMS_BY_TYPE } from '@mysticat/data-service-types';
15
+
16
+ const ENTITLEMENT_TIERS = MYSTICAT_ENUMS_BY_TYPE.ENTITLEMENT_TIER;
17
17
  /**
18
18
  * TierClient provides methods to manage entitlements and site enrollments.
19
19
  */
@@ -137,8 +137,8 @@ class TierClient {
137
137
  */
138
138
  async createEntitlement(tier) {
139
139
  try {
140
- if (!Object.values(EntitlementModel.TIERS).includes(tier)) {
141
- throw new Error(`Invalid tier: ${tier}. Valid tiers: ${Object.values(EntitlementModel.TIERS).join(', ')}`);
140
+ if (!Object.values(ENTITLEMENT_TIERS).includes(tier)) {
141
+ throw new Error(`Invalid tier: ${tier}. Valid tiers: ${Object.values(ENTITLEMENT_TIERS).join(', ')}`);
142
142
  }
143
143
  const orgId = this.organization.getId();
144
144
  // Check what already exists
@@ -149,7 +149,7 @@ class TierClient {
149
149
  const currentTier = existing.entitlement.getTier();
150
150
 
151
151
  // If currentTier doesn't match with given tier and is not PAID, update it
152
- if (currentTier !== tier && currentTier !== EntitlementModel.TIERS.PAID) {
152
+ if (currentTier !== tier && currentTier !== ENTITLEMENT_TIERS.PAID) {
153
153
  existing.entitlement.setTier(tier);
154
154
  await existing.entitlement.save();
155
155
  }
@@ -220,6 +220,7 @@ class TierClient {
220
220
  * Gets all enrollments based on context, filtered by productCode.
221
221
  * - If site is provided: returns site enrollment for the entitlement matching productCode
222
222
  * - If org-only: returns all site enrollments for the entitlement matching productCode
223
+ * - Filters out enrollments where the site's orgId doesn't match the entitlement's orgId
223
224
  * @returns {Promise<object>} Object with entitlement and enrollments array.
224
225
  */
225
226
  async getAllEnrollment() {
@@ -234,16 +235,41 @@ class TierClient {
234
235
 
235
236
  const allEnrollments = await this.SiteEnrollment.allByEntitlementId(entitlement.getId());
236
237
 
238
+ if (allEnrollments.length === 0) {
239
+ return { entitlement, enrollments: [] };
240
+ }
241
+
242
+ // Fetch all sites using batchGetByKeys
243
+ const siteKeys = allEnrollments.map((enrollment) => ({ siteId: enrollment.getSiteId() }));
244
+ const sitesResult = await this.Site.batchGetByKeys(siteKeys);
245
+ const sitesMap = new Map(sitesResult.data.map((site) => [site.getId(), site]));
246
+
247
+ // Filter enrollments where site's orgId matches the entitlement's orgId
248
+ const validEnrollments = [];
249
+
250
+ for (const enrollment of allEnrollments) {
251
+ const site = sitesMap.get(enrollment.getSiteId());
252
+ if (!site) {
253
+ // Site not found, log warning and skip
254
+ this.log.warn(`Site not found for enrollment ${enrollment.getId()} with siteId ${enrollment.getSiteId()}`);
255
+ } else {
256
+ const siteOrgId = site.getOrganizationId();
257
+ if (siteOrgId === orgId) {
258
+ validEnrollments.push(enrollment);
259
+ }
260
+ }
261
+ }
262
+
237
263
  if (this.site) {
238
264
  // Return site enrollments matching the entitlement and site
239
265
  const siteId = this.site.getId();
240
- const matchingEnrollments = allEnrollments.filter(
266
+ const matchingEnrollments = validEnrollments.filter(
241
267
  (se) => se.getSiteId() === siteId,
242
268
  );
243
269
  return { entitlement, enrollments: matchingEnrollments };
244
270
  } else {
245
- // Return all enrollments for the entitlement
246
- return { entitlement, enrollments: allEnrollments };
271
+ // Return all valid enrollments for the entitlement
272
+ return { entitlement, enrollments: validEnrollments };
247
273
  }
248
274
  } catch (error) {
249
275
  this.log.error(`Error getting all enrollments: ${error.message}`);
@@ -288,7 +314,7 @@ class TierClient {
288
314
  async revokeEntitlement() {
289
315
  const existing = await this.checkValidEntitlement();
290
316
  if (existing.entitlement) {
291
- if (existing.entitlement.getTier() === EntitlementModel.TIERS.PAID) {
317
+ if (existing.entitlement.getTier() === ENTITLEMENT_TIERS.PAID) {
292
318
  throw new Error('Paid entitlement cannot be revoked');
293
319
  }
294
320
  await existing.entitlement.remove();
@@ -17,7 +17,6 @@ import chaiAsPromised from 'chai-as-promised';
17
17
  import sinonChai from 'sinon-chai';
18
18
  import sinon from 'sinon';
19
19
 
20
- import { Organization, Site } from '@adobe/spacecat-shared-data-access';
21
20
  import TierClient from '../src/tier-client.js';
22
21
 
23
22
  use(chaiAsPromised);
@@ -49,11 +48,7 @@ describe('TierClient', () => {
49
48
  };
50
49
 
51
50
  // Create actual Organization instance for instanceof checks
52
- const organizationInstance = Object.create(Organization.prototype);
53
- Object.assign(
54
- organizationInstance,
55
- { entityName: Organization.ENTITY_NAME, ...mockOrganization },
56
- );
51
+ const organizationInstance = { ...mockOrganization };
57
52
 
58
53
  const mockSite = {
59
54
  getId: () => siteId,
@@ -62,8 +57,7 @@ describe('TierClient', () => {
62
57
  };
63
58
 
64
59
  // Create actual Site instance for instanceof checks
65
- const siteInstance = Object.create(Site.prototype);
66
- Object.assign(siteInstance, { entityName: Site.ENTITY_NAME, ...mockSite });
60
+ const siteInstance = { ...mockSite };
67
61
 
68
62
  const mockDataAccess = {
69
63
  Entitlement: {
@@ -80,6 +74,7 @@ describe('TierClient', () => {
80
74
  },
81
75
  Site: {
82
76
  findById: sandbox.stub(),
77
+ batchGetByKeys: sandbox.stub(),
83
78
  },
84
79
  };
85
80
 
@@ -120,23 +115,18 @@ describe('TierClient', () => {
120
115
  });
121
116
 
122
117
  describe('Static Factory Methods', () => {
123
- const testOrganization = Object.create(Organization.prototype);
124
- Object.assign(testOrganization, { entityName: Organization.ENTITY_NAME, getId: () => orgId });
118
+ const testOrganization = { getId: () => orgId };
125
119
 
126
- const testSite = Object.create(Site.prototype);
127
- Object.assign(testSite, {
128
- entityName: Site.ENTITY_NAME,
120
+ const testSite = {
129
121
  getId: () => siteId,
130
122
  getOrganizationId: () => orgId,
131
123
  getOrganization: () => testOrganization,
132
- });
124
+ };
133
125
 
134
- const testSiteWithOrgRef = Object.create(Site.prototype);
135
- Object.assign(testSiteWithOrgRef, {
136
- entityName: Site.ENTITY_NAME,
126
+ const testSiteWithOrgRef = {
137
127
  getId: () => siteId,
138
128
  getOrganizationId: () => orgId,
139
- });
129
+ };
140
130
 
141
131
  describe('createForOrg', () => {
142
132
  it('should create TierClient for organization', () => {
@@ -726,11 +716,25 @@ describe('TierClient', () => {
726
716
  getEntitlementId: () => 'entitlement-123',
727
717
  };
728
718
 
719
+ const mockSiteForEnrollment1 = {
720
+ getId: () => siteId,
721
+ getOrganizationId: () => orgId,
722
+ };
723
+
724
+ const mockSiteForEnrollment2 = {
725
+ getId: () => '789-site-id',
726
+ getOrganizationId: () => orgId,
727
+ };
728
+
729
729
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
730
730
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
731
731
  mockSiteEnrollment,
732
732
  mockEnrollment2,
733
733
  ]);
734
+ mockDataAccess.Site.batchGetByKeys.resolves({
735
+ data: [mockSiteForEnrollment1, mockSiteForEnrollment2],
736
+ unprocessed: [],
737
+ });
734
738
 
735
739
  const result = await tierClientWithoutSite.getAllEnrollment();
736
740
 
@@ -742,6 +746,10 @@ describe('TierClient', () => {
742
746
  .to.have.been.calledWith(orgId, productCode);
743
747
  expect(mockDataAccess.SiteEnrollment.allByEntitlementId)
744
748
  .to.have.been.calledWith('entitlement-123');
749
+ expect(mockDataAccess.Site.batchGetByKeys).to.have.been.calledWith([
750
+ { siteId },
751
+ { siteId: '789-site-id' },
752
+ ]);
745
753
  });
746
754
 
747
755
  it('should return filtered enrollments when site is provided', async () => {
@@ -751,11 +759,25 @@ describe('TierClient', () => {
751
759
  getEntitlementId: () => 'entitlement-123',
752
760
  };
753
761
 
762
+ const mockSiteForEnrollment1 = {
763
+ getId: () => siteId,
764
+ getOrganizationId: () => orgId,
765
+ };
766
+
767
+ const mockSiteForEnrollment2 = {
768
+ getId: () => 'other-site-id',
769
+ getOrganizationId: () => orgId,
770
+ };
771
+
754
772
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
755
773
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
756
774
  mockSiteEnrollment,
757
775
  mockEnrollment2,
758
776
  ]);
777
+ mockDataAccess.Site.batchGetByKeys.resolves({
778
+ data: [mockSiteForEnrollment1, mockSiteForEnrollment2],
779
+ unprocessed: [],
780
+ });
759
781
 
760
782
  const result = await tierClient.getAllEnrollment();
761
783
 
@@ -811,18 +833,96 @@ describe('TierClient', () => {
811
833
  getEntitlementId: () => 'entitlement-123',
812
834
  };
813
835
 
836
+ const mockSiteForEnrollment1 = {
837
+ getId: () => siteId,
838
+ getOrganizationId: () => orgId,
839
+ };
840
+
841
+ const mockSiteForEnrollment2 = {
842
+ getId: () => 'different-site-id',
843
+ getOrganizationId: () => orgId,
844
+ };
845
+
814
846
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
815
847
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
816
848
  mockSiteEnrollment,
817
849
  mockEnrollment2,
818
850
  mockEnrollment3,
819
851
  ]);
852
+ mockDataAccess.Site.batchGetByKeys.resolves({
853
+ data: [mockSiteForEnrollment1, mockSiteForEnrollment2, mockSiteForEnrollment1],
854
+ unprocessed: [],
855
+ });
820
856
 
821
857
  const result = await tierClient.getAllEnrollment();
822
858
 
823
859
  expect(result.enrollments).to.have.lengthOf(2);
824
860
  expect(result.enrollments).to.deep.equal([mockSiteEnrollment, mockEnrollment3]);
825
861
  });
862
+
863
+ it('should filter out enrollments with mismatching orgId', async () => {
864
+ const tierClientWithoutSite = new TierClient(
865
+ mockContext,
866
+ organizationInstance,
867
+ null,
868
+ productCode,
869
+ );
870
+
871
+ const mismatchingOrgId = 'different-org-id';
872
+ const mockEnrollment2 = {
873
+ getId: () => 'enrollment-456',
874
+ getSiteId: () => 'site-with-wrong-org',
875
+ getEntitlementId: () => 'entitlement-123',
876
+ };
877
+
878
+ const mockSiteForEnrollment1 = {
879
+ getId: () => siteId,
880
+ getOrganizationId: () => orgId,
881
+ };
882
+
883
+ const mockSiteForEnrollment2 = {
884
+ getId: () => 'site-with-wrong-org',
885
+ getOrganizationId: () => mismatchingOrgId,
886
+ };
887
+
888
+ mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
889
+ mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
890
+ mockSiteEnrollment,
891
+ mockEnrollment2,
892
+ ]);
893
+ mockDataAccess.Site.batchGetByKeys.resolves({
894
+ data: [mockSiteForEnrollment1, mockSiteForEnrollment2],
895
+ unprocessed: [],
896
+ });
897
+
898
+ const result = await tierClientWithoutSite.getAllEnrollment();
899
+
900
+ expect(result.enrollments).to.have.lengthOf(1);
901
+ expect(result.enrollments[0].getId()).to.equal('enrollment-123');
902
+ });
903
+
904
+ it('should log warning when site not found for enrollment', async () => {
905
+ const tierClientWithoutSite = new TierClient(
906
+ mockContext,
907
+ organizationInstance,
908
+ null,
909
+ productCode,
910
+ );
911
+
912
+ mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
913
+ mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
914
+ mockDataAccess.Site.batchGetByKeys.resolves({
915
+ data: [],
916
+ unprocessed: [],
917
+ });
918
+
919
+ const result = await tierClientWithoutSite.getAllEnrollment();
920
+
921
+ expect(result.enrollments).to.have.lengthOf(0);
922
+ expect(mockContext.log.warn).to.have.been.calledWith(
923
+ `Site not found for enrollment ${mockSiteEnrollment.getId()} with siteId ${siteId}`,
924
+ );
925
+ });
826
926
  });
827
927
 
828
928
  describe('getFirstEnrollment', () => {
@@ -834,10 +934,15 @@ describe('TierClient', () => {
834
934
  const mockSiteObject = {
835
935
  getId: () => siteId,
836
936
  getName: () => 'Test Site',
937
+ getOrganizationId: () => orgId,
837
938
  };
838
939
 
839
940
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
840
941
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
942
+ mockDataAccess.Site.batchGetByKeys.resolves({
943
+ data: [mockSiteObject],
944
+ unprocessed: [],
945
+ });
841
946
  mockDataAccess.Site.findById.resolves(mockSiteObject);
842
947
 
843
948
  const tierClientWithoutSite = new TierClient(
@@ -861,6 +966,13 @@ describe('TierClient', () => {
861
966
  const mockSiteObject = {
862
967
  getId: () => siteId,
863
968
  getName: () => 'Test Site',
969
+ getOrganizationId: () => orgId,
970
+ };
971
+
972
+ const mockSiteObject2 = {
973
+ getId: () => 'other-site-id',
974
+ getName: () => 'Other Site',
975
+ getOrganizationId: () => orgId,
864
976
  };
865
977
 
866
978
  const mockEnrollment2 = {
@@ -874,6 +986,10 @@ describe('TierClient', () => {
874
986
  mockSiteEnrollment,
875
987
  mockEnrollment2,
876
988
  ]);
989
+ mockDataAccess.Site.batchGetByKeys.resolves({
990
+ data: [mockSiteObject, mockSiteObject2],
991
+ unprocessed: [],
992
+ });
877
993
  mockDataAccess.Site.findById.resolves(mockSiteObject);
878
994
 
879
995
  const tierClientWithoutSite = new TierClient(
@@ -917,8 +1033,18 @@ describe('TierClient', () => {
917
1033
  });
918
1034
 
919
1035
  it('should return null site when site not found in database', async () => {
1036
+ const mockSiteObject = {
1037
+ getId: () => siteId,
1038
+ getName: () => 'Test Site',
1039
+ getOrganizationId: () => orgId,
1040
+ };
1041
+
920
1042
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
921
1043
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
1044
+ mockDataAccess.Site.batchGetByKeys.resolves({
1045
+ data: [mockSiteObject],
1046
+ unprocessed: [],
1047
+ });
922
1048
  mockDataAccess.Site.findById.resolves(null);
923
1049
 
924
1050
  const tierClientWithoutSite = new TierClient(
@@ -951,10 +1077,15 @@ describe('TierClient', () => {
951
1077
  const mockSiteObject = {
952
1078
  getId: () => siteId,
953
1079
  getName: () => 'Test Site',
1080
+ getOrganizationId: () => orgId,
954
1081
  };
955
1082
 
956
1083
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
957
1084
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
1085
+ mockDataAccess.Site.batchGetByKeys.resolves({
1086
+ data: [mockSiteObject],
1087
+ unprocessed: [],
1088
+ });
958
1089
  mockDataAccess.Site.findById.resolves(mockSiteObject);
959
1090
 
960
1091
  const result = await tierClient.getFirstEnrollment();
@@ -966,10 +1097,10 @@ describe('TierClient', () => {
966
1097
  });
967
1098
  });
968
1099
 
969
- it('should handle error when fetching site', async () => {
1100
+ it('should handle error when fetching site via batchGetByKeys', async () => {
970
1101
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
971
1102
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
972
- mockDataAccess.Site.findById.rejects(new Error('Site fetch error'));
1103
+ mockDataAccess.Site.batchGetByKeys.rejects(new Error('Site fetch error'));
973
1104
 
974
1105
  await expect(tierClient.getFirstEnrollment()).to.be.rejectedWith('Site fetch error');
975
1106
  });