@adobe/spacecat-shared-tier-client 1.3.11 → 1.3.13
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 +6 -2
- package/CHANGELOG.md +12 -0
- package/package.json +2 -2
- package/src/tier-client.js +77 -38
- package/test/tier-client.test.js +129 -82
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
|
-
|
|
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,15 @@
|
|
|
1
|
+
## [@adobe/spacecat-shared-tier-client-v1.3.13](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tier-client-v1.3.12...@adobe/spacecat-shared-tier-client-v1.3.13) (2026-03-01)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* **tier-client:** avoid 414 URI Too Large on orgs with many enrollments ([#1390](https://github.com/adobe/spacecat-shared/issues/1390)) ([2e9de5e](https://github.com/adobe/spacecat-shared/commit/2e9de5e685c2a25801d4d50ad7317773ee706ef0))
|
|
6
|
+
|
|
7
|
+
## [@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)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **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))
|
|
12
|
+
|
|
1
13
|
# [@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
14
|
|
|
3
15
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/spacecat-shared-tier-client",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.13",
|
|
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
|
-
"@
|
|
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",
|
package/src/tier-client.js
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { isNonEmptyObject, hasText } from '@adobe/spacecat-shared-utils';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
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(
|
|
141
|
-
throw new Error(`Invalid tier: ${tier}. Valid tiers: ${Object.values(
|
|
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 !==
|
|
152
|
+
if (currentTier !== tier && currentTier !== ENTITLEMENT_TIERS.PAID) {
|
|
153
153
|
existing.entitlement.setTier(tier);
|
|
154
154
|
await existing.entitlement.save();
|
|
155
155
|
}
|
|
@@ -239,10 +239,40 @@ class TierClient {
|
|
|
239
239
|
return { entitlement, enrollments: [] };
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
//
|
|
242
|
+
// When a specific site is provided, skip batch fetch entirely.
|
|
243
|
+
// Just filter enrollments by site ID and verify org ownership with a single lookup.
|
|
244
|
+
if (this.site) {
|
|
245
|
+
const targetSiteId = this.site.getId();
|
|
246
|
+
const matchingEnrollments = allEnrollments.filter(
|
|
247
|
+
(se) => se.getSiteId() === targetSiteId,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (matchingEnrollments.length === 0) {
|
|
251
|
+
return { entitlement, enrollments: [] };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const site = await this.Site.findById(targetSiteId);
|
|
255
|
+
if (!site || site.getOrganizationId() !== orgId) {
|
|
256
|
+
return { entitlement, enrollments: [] };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { entitlement, enrollments: matchingEnrollments };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Org-only path: fetch sites in chunks to avoid 414 URI Too Large.
|
|
263
|
+
// PostgREST uses GET with ?id=in.(...) which has URL length limits.
|
|
264
|
+
const CHUNK_SIZE = 50;
|
|
243
265
|
const siteKeys = allEnrollments.map((enrollment) => ({ siteId: enrollment.getSiteId() }));
|
|
244
|
-
const
|
|
245
|
-
|
|
266
|
+
const sitesMap = new Map();
|
|
267
|
+
|
|
268
|
+
for (let i = 0; i < siteKeys.length; i += CHUNK_SIZE) {
|
|
269
|
+
const chunk = siteKeys.slice(i, i + CHUNK_SIZE);
|
|
270
|
+
// eslint-disable-next-line no-await-in-loop
|
|
271
|
+
const sitesResult = await this.Site.batchGetByKeys(chunk);
|
|
272
|
+
for (const site of sitesResult.data) {
|
|
273
|
+
sitesMap.set(site.getId(), site);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
246
276
|
|
|
247
277
|
// Filter enrollments where site's orgId matches the entitlement's orgId
|
|
248
278
|
const validEnrollments = [];
|
|
@@ -250,27 +280,13 @@ class TierClient {
|
|
|
250
280
|
for (const enrollment of allEnrollments) {
|
|
251
281
|
const site = sitesMap.get(enrollment.getSiteId());
|
|
252
282
|
if (!site) {
|
|
253
|
-
// Site not found, log warning and skip
|
|
254
283
|
this.log.warn(`Site not found for enrollment ${enrollment.getId()} with siteId ${enrollment.getSiteId()}`);
|
|
255
|
-
} else {
|
|
256
|
-
|
|
257
|
-
if (siteOrgId === orgId) {
|
|
258
|
-
validEnrollments.push(enrollment);
|
|
259
|
-
}
|
|
284
|
+
} else if (site.getOrganizationId() === orgId) {
|
|
285
|
+
validEnrollments.push(enrollment);
|
|
260
286
|
}
|
|
261
287
|
}
|
|
262
288
|
|
|
263
|
-
|
|
264
|
-
// Return site enrollments matching the entitlement and site
|
|
265
|
-
const siteId = this.site.getId();
|
|
266
|
-
const matchingEnrollments = validEnrollments.filter(
|
|
267
|
-
(se) => se.getSiteId() === siteId,
|
|
268
|
-
);
|
|
269
|
-
return { entitlement, enrollments: matchingEnrollments };
|
|
270
|
-
} else {
|
|
271
|
-
// Return all valid enrollments for the entitlement
|
|
272
|
-
return { entitlement, enrollments: validEnrollments };
|
|
273
|
-
}
|
|
289
|
+
return { entitlement, enrollments: validEnrollments };
|
|
274
290
|
} catch (error) {
|
|
275
291
|
this.log.error(`Error getting all enrollments: ${error.message}`);
|
|
276
292
|
throw error;
|
|
@@ -279,28 +295,51 @@ class TierClient {
|
|
|
279
295
|
|
|
280
296
|
/**
|
|
281
297
|
* Gets the first enrollment and its site, filtered by productCode.
|
|
282
|
-
* - If site is provided:
|
|
283
|
-
* - If org-only:
|
|
298
|
+
* - If site is provided: finds matching enrollment and returns this.site directly
|
|
299
|
+
* - If org-only: iterates enrollments, fetches sites one at a time, returns first org match
|
|
284
300
|
* @returns {Promise<object>} Object with entitlement, enrollment, and site.
|
|
285
301
|
*/
|
|
286
302
|
async getFirstEnrollment() {
|
|
287
303
|
try {
|
|
288
|
-
const
|
|
304
|
+
const orgId = this.organization.getId();
|
|
305
|
+
const entitlement = await this.Entitlement
|
|
306
|
+
.findByOrganizationIdAndProductCode(orgId, this.productCode);
|
|
289
307
|
|
|
290
|
-
if (!entitlement
|
|
308
|
+
if (!entitlement) {
|
|
291
309
|
return { entitlement: null, enrollment: null, site: null };
|
|
292
310
|
}
|
|
293
311
|
|
|
294
|
-
const
|
|
295
|
-
const enrollmentSiteId = firstEnrollment.getSiteId();
|
|
296
|
-
const site = await this.Site.findById(enrollmentSiteId);
|
|
312
|
+
const allEnrollments = await this.SiteEnrollment.allByEntitlementId(entitlement.getId());
|
|
297
313
|
|
|
298
|
-
if (!
|
|
299
|
-
|
|
300
|
-
|
|
314
|
+
if (!allEnrollments || allEnrollments.length === 0) {
|
|
315
|
+
return { entitlement: null, enrollment: null, site: null };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// When a specific site is set, find its enrollment in memory — no fetch needed.
|
|
319
|
+
if (this.site) {
|
|
320
|
+
const targetSiteId = this.site.getId();
|
|
321
|
+
const matchingEnrollment = allEnrollments.find(
|
|
322
|
+
(se) => se.getSiteId() === targetSiteId,
|
|
323
|
+
);
|
|
324
|
+
if (matchingEnrollment) {
|
|
325
|
+
return { entitlement, enrollment: matchingEnrollment, site: this.site };
|
|
326
|
+
}
|
|
327
|
+
return { entitlement: null, enrollment: null, site: null };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Org-only: iterate enrollments, fetch site one at a time, return first org match.
|
|
331
|
+
// This avoids batch-fetching all sites (which causes 414 on large sets).
|
|
332
|
+
for (const enrollment of allEnrollments) {
|
|
333
|
+
// eslint-disable-next-line no-await-in-loop
|
|
334
|
+
const site = await this.Site.findById(enrollment.getSiteId());
|
|
335
|
+
if (!site) {
|
|
336
|
+
this.log.warn(`Site not found for enrollment ${enrollment.getId()} with siteId ${enrollment.getSiteId()}`);
|
|
337
|
+
} else if (site.getOrganizationId() === orgId) {
|
|
338
|
+
return { entitlement, enrollment, site };
|
|
339
|
+
}
|
|
301
340
|
}
|
|
302
341
|
|
|
303
|
-
return { entitlement, enrollment:
|
|
342
|
+
return { entitlement: null, enrollment: null, site: null };
|
|
304
343
|
} catch (error) {
|
|
305
344
|
this.log.error(`Error getting first enrollment: ${error.message}`);
|
|
306
345
|
throw error;
|
|
@@ -314,7 +353,7 @@ class TierClient {
|
|
|
314
353
|
async revokeEntitlement() {
|
|
315
354
|
const existing = await this.checkValidEntitlement();
|
|
316
355
|
if (existing.entitlement) {
|
|
317
|
-
if (existing.entitlement.getTier() ===
|
|
356
|
+
if (existing.entitlement.getTier() === ENTITLEMENT_TIERS.PAID) {
|
|
318
357
|
throw new Error('Paid entitlement cannot be revoked');
|
|
319
358
|
}
|
|
320
359
|
await existing.entitlement.remove();
|
package/test/tier-client.test.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
66
|
-
Object.assign(siteInstance, { entityName: Site.ENTITY_NAME, ...mockSite });
|
|
60
|
+
const siteInstance = { ...mockSite };
|
|
67
61
|
|
|
68
62
|
const mockDataAccess = {
|
|
69
63
|
Entitlement: {
|
|
@@ -121,23 +115,18 @@ describe('TierClient', () => {
|
|
|
121
115
|
});
|
|
122
116
|
|
|
123
117
|
describe('Static Factory Methods', () => {
|
|
124
|
-
const testOrganization =
|
|
125
|
-
Object.assign(testOrganization, { entityName: Organization.ENTITY_NAME, getId: () => orgId });
|
|
118
|
+
const testOrganization = { getId: () => orgId };
|
|
126
119
|
|
|
127
|
-
const testSite =
|
|
128
|
-
Object.assign(testSite, {
|
|
129
|
-
entityName: Site.ENTITY_NAME,
|
|
120
|
+
const testSite = {
|
|
130
121
|
getId: () => siteId,
|
|
131
122
|
getOrganizationId: () => orgId,
|
|
132
123
|
getOrganization: () => testOrganization,
|
|
133
|
-
}
|
|
124
|
+
};
|
|
134
125
|
|
|
135
|
-
const testSiteWithOrgRef =
|
|
136
|
-
Object.assign(testSiteWithOrgRef, {
|
|
137
|
-
entityName: Site.ENTITY_NAME,
|
|
126
|
+
const testSiteWithOrgRef = {
|
|
138
127
|
getId: () => siteId,
|
|
139
128
|
getOrganizationId: () => orgId,
|
|
140
|
-
}
|
|
129
|
+
};
|
|
141
130
|
|
|
142
131
|
describe('createForOrg', () => {
|
|
143
132
|
it('should create TierClient for organization', () => {
|
|
@@ -775,20 +764,12 @@ describe('TierClient', () => {
|
|
|
775
764
|
getOrganizationId: () => orgId,
|
|
776
765
|
};
|
|
777
766
|
|
|
778
|
-
const mockSiteForEnrollment2 = {
|
|
779
|
-
getId: () => 'other-site-id',
|
|
780
|
-
getOrganizationId: () => orgId,
|
|
781
|
-
};
|
|
782
|
-
|
|
783
767
|
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
784
768
|
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
|
|
785
769
|
mockSiteEnrollment,
|
|
786
770
|
mockEnrollment2,
|
|
787
771
|
]);
|
|
788
|
-
mockDataAccess.Site.
|
|
789
|
-
data: [mockSiteForEnrollment1, mockSiteForEnrollment2],
|
|
790
|
-
unprocessed: [],
|
|
791
|
-
});
|
|
772
|
+
mockDataAccess.Site.findById.resolves(mockSiteForEnrollment1);
|
|
792
773
|
|
|
793
774
|
const result = await tierClient.getAllEnrollment();
|
|
794
775
|
|
|
@@ -796,8 +777,8 @@ describe('TierClient', () => {
|
|
|
796
777
|
entitlement: mockEntitlement,
|
|
797
778
|
enrollments: [mockSiteEnrollment],
|
|
798
779
|
});
|
|
799
|
-
expect(mockDataAccess.
|
|
800
|
-
|
|
780
|
+
expect(mockDataAccess.Site.batchGetByKeys).to.not.have.been.called;
|
|
781
|
+
expect(mockDataAccess.Site.findById).to.have.been.calledWith(siteId);
|
|
801
782
|
});
|
|
802
783
|
|
|
803
784
|
it('should return null entitlement and empty enrollments when no entitlement exists', async () => {
|
|
@@ -831,6 +812,38 @@ describe('TierClient', () => {
|
|
|
831
812
|
expect(mockContext.log.error).to.have.been.calledWith('Error getting all enrollments: Database error');
|
|
832
813
|
});
|
|
833
814
|
|
|
815
|
+
it('should return empty enrollments when site has no matching enrollments', async () => {
|
|
816
|
+
const nonMatchingEnrollment = {
|
|
817
|
+
getId: () => 'enrollment-999',
|
|
818
|
+
getSiteId: () => 'other-site-id',
|
|
819
|
+
getEntitlementId: () => 'entitlement-123',
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
823
|
+
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([nonMatchingEnrollment]);
|
|
824
|
+
|
|
825
|
+
const result = await tierClient.getAllEnrollment();
|
|
826
|
+
|
|
827
|
+
expect(result).to.deep.equal({ entitlement: mockEntitlement, enrollments: [] });
|
|
828
|
+
expect(mockDataAccess.Site.findById).to.not.have.been.called;
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it('should return empty enrollments when site org does not match', async () => {
|
|
832
|
+
const wrongOrgSite = {
|
|
833
|
+
getId: () => siteId,
|
|
834
|
+
getOrganizationId: () => 'wrong-org-id',
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
838
|
+
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
|
|
839
|
+
mockDataAccess.Site.findById.resolves(wrongOrgSite);
|
|
840
|
+
|
|
841
|
+
const result = await tierClient.getAllEnrollment();
|
|
842
|
+
|
|
843
|
+
expect(result).to.deep.equal({ entitlement: mockEntitlement, enrollments: [] });
|
|
844
|
+
expect(mockDataAccess.Site.findById).to.have.been.calledWith(siteId);
|
|
845
|
+
});
|
|
846
|
+
|
|
834
847
|
it('should filter out enrollments not matching site ID', async () => {
|
|
835
848
|
const mockEnrollment2 = {
|
|
836
849
|
getId: () => 'enrollment-456',
|
|
@@ -849,26 +862,20 @@ describe('TierClient', () => {
|
|
|
849
862
|
getOrganizationId: () => orgId,
|
|
850
863
|
};
|
|
851
864
|
|
|
852
|
-
const mockSiteForEnrollment2 = {
|
|
853
|
-
getId: () => 'different-site-id',
|
|
854
|
-
getOrganizationId: () => orgId,
|
|
855
|
-
};
|
|
856
|
-
|
|
857
865
|
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
858
866
|
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
|
|
859
867
|
mockSiteEnrollment,
|
|
860
868
|
mockEnrollment2,
|
|
861
869
|
mockEnrollment3,
|
|
862
870
|
]);
|
|
863
|
-
mockDataAccess.Site.
|
|
864
|
-
data: [mockSiteForEnrollment1, mockSiteForEnrollment2, mockSiteForEnrollment1],
|
|
865
|
-
unprocessed: [],
|
|
866
|
-
});
|
|
871
|
+
mockDataAccess.Site.findById.resolves(mockSiteForEnrollment1);
|
|
867
872
|
|
|
868
873
|
const result = await tierClient.getAllEnrollment();
|
|
869
874
|
|
|
870
875
|
expect(result.enrollments).to.have.lengthOf(2);
|
|
871
876
|
expect(result.enrollments).to.deep.equal([mockSiteEnrollment, mockEnrollment3]);
|
|
877
|
+
expect(mockDataAccess.Site.batchGetByKeys).to.not.have.been.called;
|
|
878
|
+
expect(mockDataAccess.Site.findById).to.have.been.calledWith(siteId);
|
|
872
879
|
});
|
|
873
880
|
|
|
874
881
|
it('should filter out enrollments with mismatching orgId', async () => {
|
|
@@ -950,10 +957,6 @@ describe('TierClient', () => {
|
|
|
950
957
|
|
|
951
958
|
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
952
959
|
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
|
|
953
|
-
mockDataAccess.Site.batchGetByKeys.resolves({
|
|
954
|
-
data: [mockSiteObject],
|
|
955
|
-
unprocessed: [],
|
|
956
|
-
});
|
|
957
960
|
mockDataAccess.Site.findById.resolves(mockSiteObject);
|
|
958
961
|
|
|
959
962
|
const tierClientWithoutSite = new TierClient(
|
|
@@ -971,6 +974,7 @@ describe('TierClient', () => {
|
|
|
971
974
|
site: mockSiteObject,
|
|
972
975
|
});
|
|
973
976
|
expect(mockDataAccess.Site.findById).to.have.been.calledWith(siteId);
|
|
977
|
+
expect(mockDataAccess.Site.batchGetByKeys).to.not.have.been.called;
|
|
974
978
|
});
|
|
975
979
|
|
|
976
980
|
it('should return first enrollment when multiple exist', async () => {
|
|
@@ -980,12 +984,6 @@ describe('TierClient', () => {
|
|
|
980
984
|
getOrganizationId: () => orgId,
|
|
981
985
|
};
|
|
982
986
|
|
|
983
|
-
const mockSiteObject2 = {
|
|
984
|
-
getId: () => 'other-site-id',
|
|
985
|
-
getName: () => 'Other Site',
|
|
986
|
-
getOrganizationId: () => orgId,
|
|
987
|
-
};
|
|
988
|
-
|
|
989
987
|
const mockEnrollment2 = {
|
|
990
988
|
getId: () => 'enrollment-456',
|
|
991
989
|
getSiteId: () => 'other-site-id',
|
|
@@ -997,10 +995,6 @@ describe('TierClient', () => {
|
|
|
997
995
|
mockSiteEnrollment,
|
|
998
996
|
mockEnrollment2,
|
|
999
997
|
]);
|
|
1000
|
-
mockDataAccess.Site.batchGetByKeys.resolves({
|
|
1001
|
-
data: [mockSiteObject, mockSiteObject2],
|
|
1002
|
-
unprocessed: [],
|
|
1003
|
-
});
|
|
1004
998
|
mockDataAccess.Site.findById.resolves(mockSiteObject);
|
|
1005
999
|
|
|
1006
1000
|
const tierClientWithoutSite = new TierClient(
|
|
@@ -1013,7 +1007,9 @@ describe('TierClient', () => {
|
|
|
1013
1007
|
const result = await tierClientWithoutSite.getFirstEnrollment();
|
|
1014
1008
|
|
|
1015
1009
|
expect(result.enrollment).to.equal(mockSiteEnrollment);
|
|
1016
|
-
expect(
|
|
1010
|
+
expect(result.site).to.equal(mockSiteObject);
|
|
1011
|
+
expect(mockDataAccess.Site.findById).to.have.been.calledOnceWith(siteId);
|
|
1012
|
+
expect(mockDataAccess.Site.batchGetByKeys).to.not.have.been.called;
|
|
1017
1013
|
});
|
|
1018
1014
|
|
|
1019
1015
|
it('should return nulls when no entitlement exists', async () => {
|
|
@@ -1043,19 +1039,9 @@ describe('TierClient', () => {
|
|
|
1043
1039
|
expect(mockDataAccess.Site.findById).to.not.have.been.called;
|
|
1044
1040
|
});
|
|
1045
1041
|
|
|
1046
|
-
it('should
|
|
1047
|
-
const mockSiteObject = {
|
|
1048
|
-
getId: () => siteId,
|
|
1049
|
-
getName: () => 'Test Site',
|
|
1050
|
-
getOrganizationId: () => orgId,
|
|
1051
|
-
};
|
|
1052
|
-
|
|
1042
|
+
it('should skip enrollment when site not found and return nulls', async () => {
|
|
1053
1043
|
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
1054
1044
|
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
|
|
1055
|
-
mockDataAccess.Site.batchGetByKeys.resolves({
|
|
1056
|
-
data: [mockSiteObject],
|
|
1057
|
-
unprocessed: [],
|
|
1058
|
-
});
|
|
1059
1045
|
mockDataAccess.Site.findById.resolves(null);
|
|
1060
1046
|
|
|
1061
1047
|
const tierClientWithoutSite = new TierClient(
|
|
@@ -1068,12 +1054,12 @@ describe('TierClient', () => {
|
|
|
1068
1054
|
const result = await tierClientWithoutSite.getFirstEnrollment();
|
|
1069
1055
|
|
|
1070
1056
|
expect(result).to.deep.equal({
|
|
1071
|
-
entitlement:
|
|
1072
|
-
enrollment:
|
|
1057
|
+
entitlement: null,
|
|
1058
|
+
enrollment: null,
|
|
1073
1059
|
site: null,
|
|
1074
1060
|
});
|
|
1075
1061
|
expect(mockContext.log.warn).to.have.been.calledWith(
|
|
1076
|
-
`Site not found for enrollment ${mockSiteEnrollment.getId()} with
|
|
1062
|
+
`Site not found for enrollment ${mockSiteEnrollment.getId()} with siteId ${siteId}`,
|
|
1077
1063
|
);
|
|
1078
1064
|
});
|
|
1079
1065
|
|
|
@@ -1085,35 +1071,96 @@ describe('TierClient', () => {
|
|
|
1085
1071
|
});
|
|
1086
1072
|
|
|
1087
1073
|
it('should work with site-specific client', async () => {
|
|
1088
|
-
const mockSiteObject = {
|
|
1089
|
-
getId: () => siteId,
|
|
1090
|
-
getName: () => 'Test Site',
|
|
1091
|
-
getOrganizationId: () => orgId,
|
|
1092
|
-
};
|
|
1093
|
-
|
|
1094
1074
|
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
1095
1075
|
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
|
|
1096
|
-
mockDataAccess.Site.batchGetByKeys.resolves({
|
|
1097
|
-
data: [mockSiteObject],
|
|
1098
|
-
unprocessed: [],
|
|
1099
|
-
});
|
|
1100
|
-
mockDataAccess.Site.findById.resolves(mockSiteObject);
|
|
1101
1076
|
|
|
1102
1077
|
const result = await tierClient.getFirstEnrollment();
|
|
1103
1078
|
|
|
1104
1079
|
expect(result).to.deep.equal({
|
|
1105
1080
|
entitlement: mockEntitlement,
|
|
1106
1081
|
enrollment: mockSiteEnrollment,
|
|
1107
|
-
site:
|
|
1082
|
+
site: siteInstance,
|
|
1108
1083
|
});
|
|
1084
|
+
expect(mockDataAccess.Site.findById).to.not.have.been.called;
|
|
1085
|
+
expect(mockDataAccess.Site.batchGetByKeys).to.not.have.been.called;
|
|
1109
1086
|
});
|
|
1110
1087
|
|
|
1111
|
-
it('should handle error when fetching site via
|
|
1088
|
+
it('should handle error when fetching site via findById', async () => {
|
|
1112
1089
|
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
1113
1090
|
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([mockSiteEnrollment]);
|
|
1114
|
-
mockDataAccess.Site.
|
|
1091
|
+
mockDataAccess.Site.findById.rejects(new Error('Site fetch error'));
|
|
1115
1092
|
|
|
1116
|
-
|
|
1093
|
+
const tierClientWithoutSite = new TierClient(
|
|
1094
|
+
mockContext,
|
|
1095
|
+
organizationInstance,
|
|
1096
|
+
null,
|
|
1097
|
+
productCode,
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
await expect(tierClientWithoutSite.getFirstEnrollment()).to.be.rejectedWith('Site fetch error');
|
|
1101
|
+
expect(mockDataAccess.Site.batchGetByKeys).to.not.have.been.called;
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
it('should return nulls when site-specific client has no matching enrollment', async () => {
|
|
1105
|
+
const nonMatchingEnrollment = {
|
|
1106
|
+
getId: () => 'enrollment-999',
|
|
1107
|
+
getSiteId: () => 'other-site-id',
|
|
1108
|
+
getEntitlementId: () => 'entitlement-123',
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
1112
|
+
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([nonMatchingEnrollment]);
|
|
1113
|
+
|
|
1114
|
+
const result = await tierClient.getFirstEnrollment();
|
|
1115
|
+
|
|
1116
|
+
expect(result).to.deep.equal({
|
|
1117
|
+
entitlement: null,
|
|
1118
|
+
enrollment: null,
|
|
1119
|
+
site: null,
|
|
1120
|
+
});
|
|
1121
|
+
expect(mockDataAccess.Site.findById).to.not.have.been.called;
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
it('should skip enrollments with mismatching orgId and return first match', async () => {
|
|
1125
|
+
const wrongOrgSite = {
|
|
1126
|
+
getId: () => siteId,
|
|
1127
|
+
getOrganizationId: () => 'wrong-org-id',
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
const correctSite = {
|
|
1131
|
+
getId: () => 'correct-site-id',
|
|
1132
|
+
getOrganizationId: () => orgId,
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
const enrollment2 = {
|
|
1136
|
+
getId: () => 'enrollment-456',
|
|
1137
|
+
getSiteId: () => 'correct-site-id',
|
|
1138
|
+
getEntitlementId: () => 'entitlement-123',
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
|
|
1142
|
+
mockDataAccess.SiteEnrollment.allByEntitlementId.resolves([
|
|
1143
|
+
mockSiteEnrollment,
|
|
1144
|
+
enrollment2,
|
|
1145
|
+
]);
|
|
1146
|
+
mockDataAccess.Site.findById.onFirstCall().resolves(wrongOrgSite);
|
|
1147
|
+
mockDataAccess.Site.findById.onSecondCall().resolves(correctSite);
|
|
1148
|
+
|
|
1149
|
+
const tierClientWithoutSite = new TierClient(
|
|
1150
|
+
mockContext,
|
|
1151
|
+
organizationInstance,
|
|
1152
|
+
null,
|
|
1153
|
+
productCode,
|
|
1154
|
+
);
|
|
1155
|
+
|
|
1156
|
+
const result = await tierClientWithoutSite.getFirstEnrollment();
|
|
1157
|
+
|
|
1158
|
+
expect(result).to.deep.equal({
|
|
1159
|
+
entitlement: mockEntitlement,
|
|
1160
|
+
enrollment: enrollment2,
|
|
1161
|
+
site: correctSite,
|
|
1162
|
+
});
|
|
1163
|
+
expect(mockDataAccess.Site.findById).to.have.been.calledTwice;
|
|
1117
1164
|
});
|
|
1118
1165
|
});
|
|
1119
1166
|
});
|