@adobe/spacecat-shared-tier-client 1.3.9 → 1.3.11

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [@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)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * adds additional check for dangling enrollments ([#1296](https://github.com/adobe/spacecat-shared/issues/1296)) ([e5a0de3](https://github.com/adobe/spacecat-shared/commit/e5a0de32112dbf81e3da37d7b90a747d993497da))
7
+
8
+ # [@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)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * no override paid tier ([#1221](https://github.com/adobe/spacecat-shared/issues/1221)) ([a528e1e](https://github.com/adobe/spacecat-shared/commit/a528e1e345cce2c542b32dc43c221b49e3f86464))
14
+
1
15
  # [@adobe/spacecat-shared-tier-client-v1.3.9](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tier-client-v1.3.8...@adobe/spacecat-shared-tier-client-v1.3.9) (2025-11-30)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-tier-client",
3
- "version": "1.3.9",
3
+ "version": "1.3.11",
4
4
  "description": "Shared modules of the Spacecat Services - Tier Client",
5
5
  "type": "module",
6
6
  "engines": {
@@ -148,8 +148,8 @@ class TierClient {
148
148
  if (existing.entitlement) {
149
149
  const currentTier = existing.entitlement.getTier();
150
150
 
151
- // If tier doesn't match, update it
152
- if (currentTier !== tier) {
151
+ // If currentTier doesn't match with given tier and is not PAID, update it
152
+ if (currentTier !== tier && currentTier !== EntitlementModel.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}`);
@@ -80,6 +80,7 @@ describe('TierClient', () => {
80
80
  },
81
81
  Site: {
82
82
  findById: sandbox.stub(),
83
+ batchGetByKeys: sandbox.stub(),
83
84
  },
84
85
  };
85
86
 
@@ -417,7 +418,7 @@ describe('TierClient', () => {
417
418
  it('should update tier when entitlement exists with different tier', async () => {
418
419
  const mockEntitlementWithDifferentTier = {
419
420
  ...mockEntitlement,
420
- getTier: () => 'PAID',
421
+ getTier: () => 'FREE_TRIAL', // current non-PAID
421
422
  setTier: sandbox.stub().returnsThis(),
422
423
  save: sandbox.stub().resolves(),
423
424
  };
@@ -426,9 +427,9 @@ describe('TierClient', () => {
426
427
  .findByOrganizationIdAndProductCode.resolves(mockEntitlementWithDifferentTier);
427
428
  mockDataAccess.SiteEnrollment.allBySiteId.resolves([mockSiteEnrollment]);
428
429
 
429
- const result = await tierClient.createEntitlement('FREE_TRIAL');
430
+ const result = await tierClient.createEntitlement('PAID');
430
431
 
431
- expect(mockEntitlementWithDifferentTier.setTier).to.have.been.calledWith('FREE_TRIAL');
432
+ expect(mockEntitlementWithDifferentTier.setTier).to.have.been.calledWith('PAID');
432
433
  expect(mockEntitlementWithDifferentTier.save).to.have.been.called;
433
434
  expect(result).to.deep.equal({
434
435
  entitlement: mockEntitlementWithDifferentTier,
@@ -437,6 +438,28 @@ describe('TierClient', () => {
437
438
  expect(mockDataAccess.Entitlement.create).to.not.have.been.called;
438
439
  });
439
440
 
441
+ it('should not update tier when entitlement exists with same tier', async () => {
442
+ const mockEntitlementWithSameTier = {
443
+ ...mockEntitlement,
444
+ getTier: () => 'FREE_TRIAL',
445
+ setTier: sandbox.stub().returnsThis(),
446
+ save: sandbox.stub().resolves(),
447
+ };
448
+
449
+ mockDataAccess.Entitlement
450
+ .findByOrganizationIdAndProductCode.resolves(mockEntitlementWithSameTier);
451
+ mockDataAccess.SiteEnrollment.allBySiteId.resolves([mockSiteEnrollment]);
452
+
453
+ const result = await tierClient.createEntitlement('FREE_TRIAL');
454
+
455
+ expect(mockEntitlementWithSameTier.setTier).to.not.have.been.called;
456
+ expect(mockEntitlementWithSameTier.save).to.not.have.been.called;
457
+ expect(result).to.deep.equal({
458
+ entitlement: mockEntitlementWithSameTier,
459
+ siteEnrollment: mockSiteEnrollment,
460
+ });
461
+ });
462
+
440
463
  it('should throw error when organization not found', async () => {
441
464
  mockDataAccess.Organization.findById.resolves(null);
442
465
 
@@ -704,11 +727,25 @@ describe('TierClient', () => {
704
727
  getEntitlementId: () => 'entitlement-123',
705
728
  };
706
729
 
730
+ const mockSiteForEnrollment1 = {
731
+ getId: () => siteId,
732
+ getOrganizationId: () => orgId,
733
+ };
734
+
735
+ const mockSiteForEnrollment2 = {
736
+ getId: () => '789-site-id',
737
+ getOrganizationId: () => orgId,
738
+ };
739
+
707
740
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
708
741
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
709
742
  mockSiteEnrollment,
710
743
  mockEnrollment2,
711
744
  ]);
745
+ mockDataAccess.Site.batchGetByKeys.resolves({
746
+ data: [mockSiteForEnrollment1, mockSiteForEnrollment2],
747
+ unprocessed: [],
748
+ });
712
749
 
713
750
  const result = await tierClientWithoutSite.getAllEnrollment();
714
751
 
@@ -720,6 +757,10 @@ describe('TierClient', () => {
720
757
  .to.have.been.calledWith(orgId, productCode);
721
758
  expect(mockDataAccess.SiteEnrollment.allByEntitlementId)
722
759
  .to.have.been.calledWith('entitlement-123');
760
+ expect(mockDataAccess.Site.batchGetByKeys).to.have.been.calledWith([
761
+ { siteId },
762
+ { siteId: '789-site-id' },
763
+ ]);
723
764
  });
724
765
 
725
766
  it('should return filtered enrollments when site is provided', async () => {
@@ -729,11 +770,25 @@ describe('TierClient', () => {
729
770
  getEntitlementId: () => 'entitlement-123',
730
771
  };
731
772
 
773
+ const mockSiteForEnrollment1 = {
774
+ getId: () => siteId,
775
+ getOrganizationId: () => orgId,
776
+ };
777
+
778
+ const mockSiteForEnrollment2 = {
779
+ getId: () => 'other-site-id',
780
+ getOrganizationId: () => orgId,
781
+ };
782
+
732
783
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
733
784
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
734
785
  mockSiteEnrollment,
735
786
  mockEnrollment2,
736
787
  ]);
788
+ mockDataAccess.Site.batchGetByKeys.resolves({
789
+ data: [mockSiteForEnrollment1, mockSiteForEnrollment2],
790
+ unprocessed: [],
791
+ });
737
792
 
738
793
  const result = await tierClient.getAllEnrollment();
739
794
 
@@ -789,18 +844,96 @@ describe('TierClient', () => {
789
844
  getEntitlementId: () => 'entitlement-123',
790
845
  };
791
846
 
847
+ const mockSiteForEnrollment1 = {
848
+ getId: () => siteId,
849
+ getOrganizationId: () => orgId,
850
+ };
851
+
852
+ const mockSiteForEnrollment2 = {
853
+ getId: () => 'different-site-id',
854
+ getOrganizationId: () => orgId,
855
+ };
856
+
792
857
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
793
858
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
794
859
  mockSiteEnrollment,
795
860
  mockEnrollment2,
796
861
  mockEnrollment3,
797
862
  ]);
863
+ mockDataAccess.Site.batchGetByKeys.resolves({
864
+ data: [mockSiteForEnrollment1, mockSiteForEnrollment2, mockSiteForEnrollment1],
865
+ unprocessed: [],
866
+ });
798
867
 
799
868
  const result = await tierClient.getAllEnrollment();
800
869
 
801
870
  expect(result.enrollments).to.have.lengthOf(2);
802
871
  expect(result.enrollments).to.deep.equal([mockSiteEnrollment, mockEnrollment3]);
803
872
  });
873
+
874
+ it('should filter out enrollments with mismatching orgId', async () => {
875
+ const tierClientWithoutSite = new TierClient(
876
+ mockContext,
877
+ organizationInstance,
878
+ null,
879
+ productCode,
880
+ );
881
+
882
+ const mismatchingOrgId = 'different-org-id';
883
+ const mockEnrollment2 = {
884
+ getId: () => 'enrollment-456',
885
+ getSiteId: () => 'site-with-wrong-org',
886
+ getEntitlementId: () => 'entitlement-123',
887
+ };
888
+
889
+ const mockSiteForEnrollment1 = {
890
+ getId: () => siteId,
891
+ getOrganizationId: () => orgId,
892
+ };
893
+
894
+ const mockSiteForEnrollment2 = {
895
+ getId: () => 'site-with-wrong-org',
896
+ getOrganizationId: () => mismatchingOrgId,
897
+ };
898
+
899
+ mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
900
+ mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
901
+ mockSiteEnrollment,
902
+ mockEnrollment2,
903
+ ]);
904
+ mockDataAccess.Site.batchGetByKeys.resolves({
905
+ data: [mockSiteForEnrollment1, mockSiteForEnrollment2],
906
+ unprocessed: [],
907
+ });
908
+
909
+ const result = await tierClientWithoutSite.getAllEnrollment();
910
+
911
+ expect(result.enrollments).to.have.lengthOf(1);
912
+ expect(result.enrollments[0].getId()).to.equal('enrollment-123');
913
+ });
914
+
915
+ it('should log warning when site not found for enrollment', async () => {
916
+ const tierClientWithoutSite = new TierClient(
917
+ mockContext,
918
+ organizationInstance,
919
+ null,
920
+ productCode,
921
+ );
922
+
923
+ mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
924
+ mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
925
+ mockDataAccess.Site.batchGetByKeys.resolves({
926
+ data: [],
927
+ unprocessed: [],
928
+ });
929
+
930
+ const result = await tierClientWithoutSite.getAllEnrollment();
931
+
932
+ expect(result.enrollments).to.have.lengthOf(0);
933
+ expect(mockContext.log.warn).to.have.been.calledWith(
934
+ `Site not found for enrollment ${mockSiteEnrollment.getId()} with siteId ${siteId}`,
935
+ );
936
+ });
804
937
  });
805
938
 
806
939
  describe('getFirstEnrollment', () => {
@@ -812,10 +945,15 @@ describe('TierClient', () => {
812
945
  const mockSiteObject = {
813
946
  getId: () => siteId,
814
947
  getName: () => 'Test Site',
948
+ getOrganizationId: () => orgId,
815
949
  };
816
950
 
817
951
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
818
952
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
953
+ mockDataAccess.Site.batchGetByKeys.resolves({
954
+ data: [mockSiteObject],
955
+ unprocessed: [],
956
+ });
819
957
  mockDataAccess.Site.findById.resolves(mockSiteObject);
820
958
 
821
959
  const tierClientWithoutSite = new TierClient(
@@ -839,6 +977,13 @@ describe('TierClient', () => {
839
977
  const mockSiteObject = {
840
978
  getId: () => siteId,
841
979
  getName: () => 'Test Site',
980
+ getOrganizationId: () => orgId,
981
+ };
982
+
983
+ const mockSiteObject2 = {
984
+ getId: () => 'other-site-id',
985
+ getName: () => 'Other Site',
986
+ getOrganizationId: () => orgId,
842
987
  };
843
988
 
844
989
  const mockEnrollment2 = {
@@ -852,6 +997,10 @@ describe('TierClient', () => {
852
997
  mockSiteEnrollment,
853
998
  mockEnrollment2,
854
999
  ]);
1000
+ mockDataAccess.Site.batchGetByKeys.resolves({
1001
+ data: [mockSiteObject, mockSiteObject2],
1002
+ unprocessed: [],
1003
+ });
855
1004
  mockDataAccess.Site.findById.resolves(mockSiteObject);
856
1005
 
857
1006
  const tierClientWithoutSite = new TierClient(
@@ -895,8 +1044,18 @@ describe('TierClient', () => {
895
1044
  });
896
1045
 
897
1046
  it('should return null site when site not found in database', async () => {
1047
+ const mockSiteObject = {
1048
+ getId: () => siteId,
1049
+ getName: () => 'Test Site',
1050
+ getOrganizationId: () => orgId,
1051
+ };
1052
+
898
1053
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
899
1054
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
1055
+ mockDataAccess.Site.batchGetByKeys.resolves({
1056
+ data: [mockSiteObject],
1057
+ unprocessed: [],
1058
+ });
900
1059
  mockDataAccess.Site.findById.resolves(null);
901
1060
 
902
1061
  const tierClientWithoutSite = new TierClient(
@@ -929,10 +1088,15 @@ describe('TierClient', () => {
929
1088
  const mockSiteObject = {
930
1089
  getId: () => siteId,
931
1090
  getName: () => 'Test Site',
1091
+ getOrganizationId: () => orgId,
932
1092
  };
933
1093
 
934
1094
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
935
1095
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
1096
+ mockDataAccess.Site.batchGetByKeys.resolves({
1097
+ data: [mockSiteObject],
1098
+ unprocessed: [],
1099
+ });
936
1100
  mockDataAccess.Site.findById.resolves(mockSiteObject);
937
1101
 
938
1102
  const result = await tierClient.getFirstEnrollment();
@@ -944,10 +1108,10 @@ describe('TierClient', () => {
944
1108
  });
945
1109
  });
946
1110
 
947
- it('should handle error when fetching site', async () => {
1111
+ it('should handle error when fetching site via batchGetByKeys', async () => {
948
1112
  mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
949
1113
  mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
950
- mockDataAccess.Site.findById.rejects(new Error('Site fetch error'));
1114
+ mockDataAccess.Site.batchGetByKeys.rejects(new Error('Site fetch error'));
951
1115
 
952
1116
  await expect(tierClient.getFirstEnrollment()).to.be.rejectedWith('Site fetch error');
953
1117
  });