@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 +14 -0
- package/package.json +1 -1
- package/src/tier-client.js +31 -5
- package/test/tier-client.test.js +169 -5
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
package/src/tier-client.js
CHANGED
|
@@ -148,8 +148,8 @@ class TierClient {
|
|
|
148
148
|
if (existing.entitlement) {
|
|
149
149
|
const currentTier = existing.entitlement.getTier();
|
|
150
150
|
|
|
151
|
-
// If
|
|
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 =
|
|
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:
|
|
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}`);
|
package/test/tier-client.test.js
CHANGED
|
@@ -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: () => '
|
|
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('
|
|
430
|
+
const result = await tierClient.createEntitlement('PAID');
|
|
430
431
|
|
|
431
|
-
expect(mockEntitlementWithDifferentTier.setTier).to.have.been.calledWith('
|
|
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.
|
|
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
|
});
|