@adobe/spacecat-shared-tier-client 1.0.1 → 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,10 @@
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
+
1
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)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-tier-client",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Shared modules of the Spacecat Services - Tier Client",
5
5
  "type": "module",
6
6
  "engines": {
@@ -149,9 +149,10 @@ class TierClient {
149
149
 
150
150
  /**
151
151
  * Creates entitlement for organization and site enrollment for site.
152
- * 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.
153
153
  * @param {string} tier - Entitlement tier.
154
- * @returns {Promise<object>} Object with created entitlement and siteEnrollment.
154
+ * @returns {Promise<object>} Object with created/updated
155
+ * entitlement and siteEnrollment (if site provided).
155
156
  */
156
157
  async createEntitlement(tier) {
157
158
  try {
@@ -159,42 +160,41 @@ class TierClient {
159
160
  throw new Error(`Invalid tier: ${tier}. Valid tiers: ${Object.values(EntitlementModel.TIERS).join(', ')}`);
160
161
  }
161
162
 
162
- if (!this.site) {
163
- throw new Error('Site required for creating entitlements');
164
- }
165
-
166
163
  const orgId = this.organization.getId();
167
- const siteId = this.site.getId();
168
- 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}`);
169
165
 
170
166
  // Check what already exists
171
167
  const existing = await this.checkValidEntitlement();
172
168
 
173
- // If both entitlement and site enrollment exist, return them
174
- if (existing.entitlement && existing.siteEnrollment) {
175
- this.log.info(`Entitlement and site enrollment already exist for org ${orgId}, site ${siteId} and product ${this.productCode}`);
176
- return existing;
177
- }
178
-
179
- // If only entitlement exists, we need to create site enrollment
180
- if (existing.entitlement && !existing.siteEnrollment) {
181
- 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();
182
172
 
183
- // Create site enrollment for existing entitlement
184
- const siteEnrollment = await this.SiteEnrollment.create({
185
- siteId,
186
- entitlementId: existing.entitlement.getId(),
187
- });
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
+ }
188
179
 
189
- 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
+ }
190
193
 
191
- return {
192
- entitlement: existing.entitlement,
193
- siteEnrollment,
194
- };
194
+ return existing;
195
195
  }
196
196
 
197
- // Create entitlement
197
+ // No existing entitlement, create new one
198
198
  const entitlement = await this.Entitlement.create({
199
199
  organizationId: orgId,
200
200
  productCode: this.productCode,
@@ -205,9 +205,15 @@ class TierClient {
205
205
  },
206
206
  });
207
207
 
208
- 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
+ }
209
214
 
210
215
  // Create site enrollment
216
+ const siteId = this.site.getId();
211
217
  const siteEnrollment = await this.SiteEnrollment.create({
212
218
  siteId,
213
219
  entitlementId: entitlement.getId(),
@@ -220,7 +226,7 @@ class TierClient {
220
226
  siteEnrollment,
221
227
  };
222
228
  } catch (error) {
223
- this.log.error(`Error creating entitlement and site enrollment: ${error.message}`);
229
+ this.log.error(`Error creating/updating entitlement: ${error.message}`);
224
230
  throw error;
225
231
  }
226
232
  }
@@ -351,7 +351,7 @@ describe('TierClient', () => {
351
351
  await expect(tierClient.createEntitlement('INVALID_TIER')).to.be.rejectedWith('Invalid tier: INVALID_TIER');
352
352
  });
353
353
 
354
- it('should throw error when site is not provided for createEntitlement', async () => {
354
+ it('should work without site for createEntitlement (organization only)', async () => {
355
355
  // Create a TierClient without site
356
356
  const tierClientWithoutSite = new TierClient(
357
357
  mockContext,
@@ -360,7 +360,64 @@ describe('TierClient', () => {
360
360
  productCode,
361
361
  );
362
362
 
363
- await expect(tierClientWithoutSite.createEntitlement('FREE_TRIAL')).to.be.rejectedWith('Site required for creating entitlements');
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 () => {
381
+ // Create a TierClient without site
382
+ const tierClientWithoutSite = new TierClient(
383
+ mockContext,
384
+ organizationInstance,
385
+ null,
386
+ productCode,
387
+ );
388
+
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;
364
421
  });
365
422
 
366
423
  it('should throw error when organization not found', async () => {