@adobe/spacecat-shared-tier-client 1.0.0 → 1.1.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-tier-client-v1.1.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tier-client-v1.0.1...@adobe/spacecat-shared-tier-client-v1.1.0) (2025-09-22)
2
+
3
+
4
+ ### Features
5
+
6
+ * entitlement with out site and upgrade ([#971](https://github.com/adobe/spacecat-shared/issues/971)) ([1f042df](https://github.com/adobe/spacecat-shared/commit/1f042df62439383ff4a7cea6b0eb75649439e72a))
7
+
8
+ # [@adobe/spacecat-shared-tier-client-v1.0.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tier-client-v1.0.0...@adobe/spacecat-shared-tier-client-v1.0.1) (2025-09-18)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * fix in create org entity ([#965](https://github.com/adobe/spacecat-shared/issues/965)) ([406cbf3](https://github.com/adobe/spacecat-shared/commit/406cbf3cd214f3ec3b332ca48d8a743c123a2ddd))
14
+
1
15
  # @adobe/spacecat-shared-tier-client-v1.0.0 (2025-09-16)
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.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Shared modules of the Spacecat Services - Tier Client",
5
5
  "type": "module",
6
6
  "engines": {
@@ -58,7 +58,8 @@ class TierClient {
58
58
  if (!isNonEmptyObject(context)) {
59
59
  throw new Error('Context is required');
60
60
  }
61
- const organization = await site.getOrganization();
61
+ const organizationId = await site.getOrganizationId();
62
+ const organization = await context.dataAccess.Organization.findById(organizationId);
62
63
  return new TierClient(context, organization, site, productCode);
63
64
  }
64
65
 
@@ -148,9 +149,10 @@ class TierClient {
148
149
 
149
150
  /**
150
151
  * Creates entitlement for organization and site enrollment for site.
151
- * First validates that org and site don't already have an entitlement for this product.
152
+ * If entitlement exists with different tier, updates the tier.
152
153
  * @param {string} tier - Entitlement tier.
153
- * @returns {Promise<object>} Object with created entitlement and siteEnrollment.
154
+ * @returns {Promise<object>} Object with created/updated
155
+ * entitlement and siteEnrollment (if site provided).
154
156
  */
155
157
  async createEntitlement(tier) {
156
158
  try {
@@ -158,42 +160,41 @@ class TierClient {
158
160
  throw new Error(`Invalid tier: ${tier}. Valid tiers: ${Object.values(EntitlementModel.TIERS).join(', ')}`);
159
161
  }
160
162
 
161
- if (!this.site) {
162
- throw new Error('Site required for creating entitlements');
163
- }
164
-
165
163
  const orgId = this.organization.getId();
166
- const siteId = this.site.getId();
167
- this.log.info(`Creating entitlement for org ${orgId}, site ${siteId}, product ${this.productCode}, tier ${tier}`);
164
+ this.log.info(`Creating/updating entitlement for org ${orgId}, product ${this.productCode}, tier ${tier}`);
168
165
 
169
166
  // Check what already exists
170
167
  const existing = await this.checkValidEntitlement();
171
168
 
172
- // If both entitlement and site enrollment exist, return them
173
- if (existing.entitlement && existing.siteEnrollment) {
174
- this.log.info(`Entitlement and site enrollment already exist for org ${orgId}, site ${siteId} and product ${this.productCode}`);
175
- return existing;
176
- }
177
-
178
- // If only entitlement exists, we need to create site enrollment
179
- if (existing.entitlement && !existing.siteEnrollment) {
180
- this.log.info(`Entitlement exists but site enrollment missing for org ${orgId}, site ${siteId} and product ${this.productCode}`);
169
+ // If entitlement exists, handle tier update and return
170
+ if (existing.entitlement) {
171
+ const currentTier = existing.entitlement.getTier();
181
172
 
182
- // Create site enrollment for existing entitlement
183
- const siteEnrollment = await this.SiteEnrollment.create({
184
- siteId,
185
- entitlementId: existing.entitlement.getId(),
186
- });
173
+ // If tier doesn't match, update it
174
+ if (currentTier !== tier) {
175
+ this.log.info(`Updating entitlement tier from ${currentTier} to ${tier} for org ${orgId}`);
176
+ existing.entitlement.setTier(tier);
177
+ await existing.entitlement.save();
178
+ }
187
179
 
188
- this.log.info(`Created site enrollment: ${siteEnrollment.getId()}`);
180
+ // If site provided but no site enrollment, create it
181
+ if (this.site && !existing.siteEnrollment) {
182
+ const siteId = this.site.getId();
183
+ const siteEnrollment = await this.SiteEnrollment.create({
184
+ siteId,
185
+ entitlementId: existing.entitlement.getId(),
186
+ });
187
+ this.log.info(`Created site enrollment: ${siteEnrollment.getId()}`);
188
+ return {
189
+ entitlement: existing.entitlement,
190
+ siteEnrollment,
191
+ };
192
+ }
189
193
 
190
- return {
191
- entitlement: existing.entitlement,
192
- siteEnrollment,
193
- };
194
+ return existing;
194
195
  }
195
196
 
196
- // Create entitlement
197
+ // No existing entitlement, create new one
197
198
  const entitlement = await this.Entitlement.create({
198
199
  organizationId: orgId,
199
200
  productCode: this.productCode,
@@ -204,9 +205,15 @@ class TierClient {
204
205
  },
205
206
  });
206
207
 
207
- this.log.info(`Created entitlement: ${entitlement.getId()}`);
208
+ this.log.info(`Created new entitlement: ${entitlement.getId()}`);
209
+
210
+ // If no site provided, return entitlement only
211
+ if (!this.site) {
212
+ return { entitlement };
213
+ }
208
214
 
209
215
  // Create site enrollment
216
+ const siteId = this.site.getId();
210
217
  const siteEnrollment = await this.SiteEnrollment.create({
211
218
  siteId,
212
219
  entitlementId: entitlement.getId(),
@@ -219,7 +226,7 @@ class TierClient {
219
226
  siteEnrollment,
220
227
  };
221
228
  } catch (error) {
222
- this.log.error(`Error creating entitlement and site enrollment: ${error.message}`);
229
+ this.log.error(`Error creating/updating entitlement: ${error.message}`);
223
230
  throw error;
224
231
  }
225
232
  }
@@ -128,7 +128,7 @@ describe('TierClient', () => {
128
128
  const testSiteWithOrgRef = Object.create(Site.prototype);
129
129
  Object.assign(testSiteWithOrgRef, {
130
130
  getId: () => siteId,
131
- getOrganization: () => testOrganization,
131
+ getOrganizationId: () => orgId,
132
132
  });
133
133
 
134
134
  describe('createForOrg', () => {
@@ -165,19 +165,23 @@ describe('TierClient', () => {
165
165
 
166
166
  describe('createForSite', () => {
167
167
  it('should create TierClient for site with getOrganizationId', async () => {
168
+ mockDataAccess.Organization.findById.resolves(testOrganization);
168
169
  const client = await TierClient.createForSite(mockContext, testSite, productCode);
169
170
 
170
171
  expect(client).to.be.an('object');
171
172
  expect(client.checkValidEntitlement).to.be.a('function');
172
173
  expect(client.createEntitlement).to.be.a('function');
174
+ expect(mockDataAccess.Organization.findById).to.have.been.calledWith(orgId);
173
175
  });
174
176
 
175
- it('should create TierClient for site with getOrganization', async () => {
177
+ it('should create TierClient for site with getOrganizationId (alternative)', async () => {
178
+ mockDataAccess.Organization.findById.resolves(testOrganization);
176
179
  const client = await TierClient.createForSite(mockContext, testSiteWithOrgRef, productCode);
177
180
 
178
181
  expect(client).to.be.an('object');
179
182
  expect(client.checkValidEntitlement).to.be.a('function');
180
183
  expect(client.createEntitlement).to.be.a('function');
184
+ expect(mockDataAccess.Organization.findById).to.have.been.calledWith(orgId);
181
185
  });
182
186
 
183
187
  it('should throw error when site is not provided', async () => {
@@ -199,7 +203,7 @@ describe('TierClient', () => {
199
203
 
200
204
  it('should throw error when dataAccess is missing', async () => {
201
205
  const invalidContext = { log: {} };
202
- await expect(TierClient.createForSite(invalidContext, testSite, productCode)).to.be.rejectedWith('Cannot destructure property');
206
+ await expect(TierClient.createForSite(invalidContext, testSite, productCode)).to.be.rejectedWith('Cannot read properties of undefined');
203
207
  });
204
208
  });
205
209
  });
@@ -347,7 +351,33 @@ describe('TierClient', () => {
347
351
  await expect(tierClient.createEntitlement('INVALID_TIER')).to.be.rejectedWith('Invalid tier: INVALID_TIER');
348
352
  });
349
353
 
350
- it('should throw error when site is not provided for createEntitlement', async () => {
354
+ it('should work without site for createEntitlement (organization only)', async () => {
355
+ // Create a TierClient without site
356
+ const tierClientWithoutSite = new TierClient(
357
+ mockContext,
358
+ organizationInstance,
359
+ null,
360
+ productCode,
361
+ );
362
+
363
+ mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(null);
364
+ mockDataAccess.Entitlement.create.resolves(mockEntitlement);
365
+
366
+ const result = await tierClientWithoutSite.createEntitlement('FREE_TRIAL');
367
+
368
+ expect(result).to.deep.equal({
369
+ entitlement: mockEntitlement,
370
+ });
371
+ expect(mockDataAccess.Entitlement.create).to.have.been.calledWith({
372
+ organizationId: orgId,
373
+ productCode,
374
+ tier: 'FREE_TRIAL',
375
+ quotas: { llmo_trial_prompts: 200, llmo_trial_prompts_consumed: 0 },
376
+ });
377
+ expect(mockDataAccess.SiteEnrollment.create).to.not.have.been.called;
378
+ });
379
+
380
+ it('should return existing entitlement when site is not provided and entitlement exists', async () => {
351
381
  // Create a TierClient without site
352
382
  const tierClientWithoutSite = new TierClient(
353
383
  mockContext,
@@ -356,7 +386,38 @@ describe('TierClient', () => {
356
386
  productCode,
357
387
  );
358
388
 
359
- await expect(tierClientWithoutSite.createEntitlement('FREE_TRIAL')).to.be.rejectedWith('Site required for creating entitlements');
389
+ mockDataAccess.Entitlement.findByOrganizationIdAndProductCode.resolves(mockEntitlement);
390
+
391
+ const result = await tierClientWithoutSite.createEntitlement('FREE_TRIAL');
392
+
393
+ expect(result).to.deep.equal({
394
+ entitlement: mockEntitlement,
395
+ });
396
+ expect(mockDataAccess.Entitlement.create).to.not.have.been.called;
397
+ expect(mockDataAccess.SiteEnrollment.create).to.not.have.been.called;
398
+ });
399
+
400
+ it('should update tier when entitlement exists with different tier', async () => {
401
+ const mockEntitlementWithDifferentTier = {
402
+ ...mockEntitlement,
403
+ getTier: () => 'PAID',
404
+ setTier: sandbox.stub().returnsThis(),
405
+ save: sandbox.stub().resolves(),
406
+ };
407
+
408
+ mockDataAccess.Entitlement
409
+ .findByOrganizationIdAndProductCode.resolves(mockEntitlementWithDifferentTier);
410
+ mockDataAccess.SiteEnrollment.allBySiteId.resolves([mockSiteEnrollment]);
411
+
412
+ const result = await tierClient.createEntitlement('FREE_TRIAL');
413
+
414
+ expect(mockEntitlementWithDifferentTier.setTier).to.have.been.calledWith('FREE_TRIAL');
415
+ expect(mockEntitlementWithDifferentTier.save).to.have.been.called;
416
+ expect(result).to.deep.equal({
417
+ entitlement: mockEntitlementWithDifferentTier,
418
+ siteEnrollment: mockSiteEnrollment,
419
+ });
420
+ expect(mockDataAccess.Entitlement.create).to.not.have.been.called;
360
421
  });
361
422
 
362
423
  it('should throw error when organization not found', async () => {