@symbo.ls/sdk 2.33.41 → 2.34.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.
@@ -270,6 +270,9 @@ class PlanService extends BaseService {
270
270
  }
271
271
  });
272
272
  }
273
+ if (Object.hasOwn(planData, "key") && planData.key == null) {
274
+ throw new Error("Plan key must be a valid string");
275
+ }
273
276
  if (planData.key && !/^[a-z0-9-]+$/u.test(planData.key)) {
274
277
  throw new Error(
275
278
  "Plan key must contain only lowercase letters, numbers, and hyphens"
@@ -324,7 +327,7 @@ class PlanService extends BaseService {
324
327
  `Pricing option '${key}' must have a non-negative numeric 'amount'`
325
328
  );
326
329
  }
327
- if (interval != null && !allowedIntervals.has(interval)) {
330
+ if (interval !== null && !allowedIntervals.has(interval)) {
328
331
  throw new Error(
329
332
  `Pricing option '${key}' has invalid interval '${interval}'. Allowed: month, year, week, day or null`
330
333
  );
@@ -336,6 +339,9 @@ class PlanService extends BaseService {
336
339
  }
337
340
  });
338
341
  }
342
+ if (Object.hasOwn(planData, "key") && planData.key == null) {
343
+ throw new Error("Plan key must be a valid string");
344
+ }
339
345
  if (planData.key && !/^[a-z0-9-]+$/u.test(planData.key)) {
340
346
  throw new Error(
341
347
  "Plan key must contain only lowercase letters, numbers, and hyphens"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/sdk",
3
- "version": "2.33.41",
3
+ "version": "2.34.0",
4
4
  "type": "module",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -46,12 +46,12 @@
46
46
  "test:user": "cross-env NODE_ENV=$NODE_ENV npx tape integration-tests/index.js integration-tests/user/*.test.js | tap-spec"
47
47
  },
48
48
  "dependencies": {
49
- "@domql/element": "^2.33.41",
50
- "@domql/utils": "^2.33.41",
49
+ "@domql/element": "^2.34.0",
50
+ "@domql/utils": "^2.34.0",
51
51
  "@grafana/faro-web-sdk": "^1.19.0",
52
52
  "@grafana/faro-web-tracing": "^1.19.0",
53
- "@symbo.ls/router": "^2.33.41",
54
- "@symbo.ls/socket": "^2.33.41",
53
+ "@symbo.ls/router": "^2.34.0",
54
+ "@symbo.ls/socket": "^2.34.0",
55
55
  "acorn": "^8.14.0",
56
56
  "acorn-walk": "^8.3.4",
57
57
  "dexie": "^4.0.11",
@@ -74,5 +74,5 @@
74
74
  "tap-spec": "^5.0.0",
75
75
  "tape": "^5.9.0"
76
76
  },
77
- "gitHead": "1dffcfb13a8308462abc598b71292a2f58dc58f5"
77
+ "gitHead": "36ad7eff7cf19c4b684e754e04fa80c88448c3aa"
78
78
  }
@@ -305,6 +305,9 @@ export class PlanService extends BaseService {
305
305
  }
306
306
 
307
307
  // Optional: validate top-level key if provided (legacy support)
308
+ if (Object.hasOwn(planData, 'key') && planData.key == null) {
309
+ throw new Error('Plan key must be a valid string')
310
+ }
308
311
  if (planData.key && !/^[a-z0-9-]+$/u.test(planData.key)) {
309
312
  throw new Error(
310
313
  'Plan key must contain only lowercase letters, numbers, and hyphens'
@@ -376,7 +379,7 @@ export class PlanService extends BaseService {
376
379
  )
377
380
  }
378
381
 
379
- if (interval != null && !allowedIntervals.has(interval)) {
382
+ if (interval !== null && !allowedIntervals.has(interval)) {
380
383
  throw new Error(
381
384
  `Pricing option '${key}' has invalid interval '${interval}'. Allowed: month, year, week, day or null`
382
385
  )
@@ -391,6 +394,9 @@ export class PlanService extends BaseService {
391
394
  }
392
395
 
393
396
  // Validate key format if provided
397
+ if (Object.hasOwn(planData, 'key') && planData.key == null) {
398
+ throw new Error('Plan key must be a valid string')
399
+ }
394
400
  if (planData.key && !/^[a-z0-9-]+$/u.test(planData.key)) {
395
401
  throw new Error(
396
402
  'Plan key must contain only lowercase letters, numbers, and hyphens'
@@ -84,7 +84,7 @@ function requiredFieldsMissing () {
84
84
  t.equal(
85
85
  err.toString(),
86
86
  `Error: Required field \'${field}\' is missing`,
87
- `Validation failed on required field: ${field} successfully`
87
+ `Required field validation failed on required field: ${field} successfully`
88
88
  )
89
89
  }
90
90
  sandbox.restore()
@@ -110,7 +110,7 @@ test('Price validation should throw an error when price field is present', async
110
110
  t.equal(
111
111
  err.toString(),
112
112
  'Error: Field "price" is no longer supported. Use unified "pricingOptions" with "amount" instead.',
113
- 'planData validation detected unsupported price field'
113
+ 'Price validation detected unsupported price field'
114
114
  )
115
115
  }
116
116
  sandbox.restore()
@@ -145,7 +145,7 @@ function pricingOptionsValidation () {
145
145
  t.equal(
146
146
  err.toString(),
147
147
  'Error: pricingOptions must be a non-empty array when provided',
148
- `Validation failed successfully on bad pricingOptions data: ${field}`
148
+ `pricingOptions validation failed successfully on bad pricingOptions data: ${field}`
149
149
  )
150
150
  }
151
151
  sandbox.restore()
@@ -163,7 +163,7 @@ function keyTypeValidation () {
163
163
  { name: 'false boolean value', key: false }
164
164
  ]
165
165
  for (let ii = 0; ii < badKeyData.length; ii++) {
166
- test(`Key validation should throw an error checking for: ${badKeyData[ii].name}`, async t => {
166
+ test(`Key type validation should throw an error checking for: ${badKeyData[ii].name}`, async t => {
167
167
  t.plan(1)
168
168
  const planData = {
169
169
  name: 'testName',
@@ -201,7 +201,7 @@ function keyFormatValidation () {
201
201
  { name: 'punctuation', key: 'syntax!' }
202
202
  ]
203
203
  for (let ii = 0; ii < badKeyData.length; ii++) {
204
- test(`Key validation should throw an error checking for: ${badKeyData[ii].name}`, async t => {
204
+ test(`Key format validation should throw an error checking for: ${badKeyData[ii].name}`, async t => {
205
205
  t.plan(1)
206
206
  const planData = {
207
207
  name: 'testName',
@@ -240,7 +240,7 @@ function displayNameValidation () {
240
240
  { name: 'null value', displayName: null, key: 'null-value' }
241
241
  ]
242
242
  for (let ii = 0; ii < badDisplayNameData.length; ii++) {
243
- test(`Key validation should throw an error checking for: ${badDisplayNameData[ii].name}`, async t => {
243
+ test(`displayName validation should throw an error checking for: ${badDisplayNameData[ii].name}`, async t => {
244
244
  t.plan(1)
245
245
  const planData = {
246
246
  name: 'testName',
@@ -313,7 +313,7 @@ function amountValidation () {
313
313
  t.equal(
314
314
  err.toString(),
315
315
  `Error: Pricing option \'${badAmountData[ii].key}\' must have a non-negative numeric \'amount\'`,
316
- `displayName validation detected bad data: ${badAmountData[ii].name}`
316
+ `amount validation detected bad data: ${badAmountData[ii].name}`
317
317
  )
318
318
  }
319
319
  sandbox.restore()
@@ -365,8 +365,8 @@ function intervalValidation () {
365
365
  t.equal(
366
366
  err.toString(),
367
367
  `Error: Pricing option \'${badAmountData[ii].key}\' has invalid interval \'${badAmountData[ii].interval}\'. Allowed: month, year, week, day or null`,
368
- `displayName validation detected bad data: ${badAmountData[ii].name}`
369
- ) // `Pricing option '${key}' has invalid interval '${interval}'. Allowed: month, year, week, day or null`
368
+ `interval validation detected bad data: ${badAmountData[ii].name}`
369
+ )
370
370
  }
371
371
  sandbox.restore()
372
372
  t.end()
@@ -375,7 +375,132 @@ function intervalValidation () {
375
375
  }
376
376
 
377
377
  function lookupKeyValidation () {
378
- // TODO interval unit tests line 268
378
+ const badData = [
379
+ {
380
+ name: 'number value',
381
+ displayName: 'test displayname',
382
+ key: 'number-amount-value',
383
+ amount: 0,
384
+ interval: null,
385
+ lookupKey: 123
386
+ },
387
+ {
388
+ name: 'false boolean value',
389
+ displayName: 'test displayname',
390
+ key: 'letter-value',
391
+ amount: 0,
392
+ interval: null,
393
+ lookupKey: false
394
+ },
395
+ {
396
+ name: 'true boolean value',
397
+ displayName: 'test displayname',
398
+ key: 'letter-value',
399
+ amount: 0,
400
+ interval: null,
401
+ lookupKey: true
402
+ },
403
+ {
404
+ name: 'object value',
405
+ displayName: 'test displayname',
406
+ key: 'undefined-value',
407
+ amount: 0,
408
+ interval: null,
409
+ lookupKey: {}
410
+ },
411
+ {
412
+ name: 'undefined value',
413
+ displayName: 'test displayname',
414
+ key: 'undefined-value',
415
+ amount: 0,
416
+ interval: null,
417
+ lookupKey: undefined
418
+ },
419
+ {
420
+ name: 'null value',
421
+ displayName: 'test displayname',
422
+ key: 'undefined-value',
423
+ amount: 0,
424
+ interval: null,
425
+ lookupKey: null
426
+ }
427
+ ]
428
+ for (let ii = 0; ii < badData.length; ii++) {
429
+ test(`lookup key validation should throw an error checking for: ${badData[ii].name}`, async t => {
430
+ t.plan(1)
431
+ const planData = {
432
+ name: 'testName',
433
+ description: 'test description',
434
+ pricingOptions: []
435
+ }
436
+ planData.pricingOptions.push(badData[ii])
437
+ const responseStub = [sandbox.stub()]
438
+ const planServiceStub = new PlanService()
439
+ sandbox.stub(planServiceStub, 'createPlan').resolves(responseStub)
440
+ try {
441
+ await planServiceStub.createPlanWithValidation(planData)
442
+ t.fail('Key type validation successfully threw an error')
443
+ } catch (err) {
444
+ t.equal(
445
+ err.toString(),
446
+ `Error: Pricing option \'${badData[ii].key}\' is missing required field \'lookupKey\'`,
447
+ `lookup key validation detected bad data: ${badData[ii].name}`
448
+ )
449
+ }
450
+ sandbox.restore()
451
+ t.end()
452
+ })
453
+ }
454
+ }
455
+
456
+ function topLevelKeyValidation () {
457
+ const planData = [
458
+ {
459
+ name: 'Uppercase letters',
460
+ description: 'test description',
461
+ key: 'ALLCAPS'
462
+ },
463
+ {
464
+ name: 'Underscore character',
465
+ description: 'test description',
466
+ key: '_'
467
+ },
468
+ {
469
+ name: 'Special characters',
470
+ description: 'test description',
471
+ key: '!@#$%'
472
+ },
473
+ {
474
+ name: 'Object type',
475
+ description: 'test description',
476
+ key: {}
477
+ },
478
+ {
479
+ name: 'Null value',
480
+ description: 'test description',
481
+ key: null
482
+ }
483
+ ]
484
+ for (let ii = 0; ii < planData.length; ii++) {
485
+ test(`top-level key validation should throw an error checking for: ${planData[ii].name}`, async t => {
486
+ t.plan(1)
487
+ const responseStub = [sandbox.stub()]
488
+ const planServiceStub = new PlanService()
489
+ sandbox.stub(planServiceStub, 'createPlan').resolves(responseStub)
490
+ try {
491
+ await planServiceStub.createPlanWithValidation(planData[ii])
492
+ t.fail('Key type validation successfully threw an error')
493
+ } catch (err) {
494
+ t.equal(
495
+ err.toString(),
496
+ 'Error: Plan key must contain only lowercase letters, numbers, and hyphens',
497
+ `displayName validation detected bad data: ${planData[ii].name} with ${err}`
498
+ )
499
+ }
500
+ sandbox.restore()
501
+ t.end()
502
+ })
503
+ }
379
504
  }
380
505
 
381
506
  amountValidation()
@@ -383,9 +508,11 @@ displayNameValidation()
383
508
  intervalValidation()
384
509
  keyFormatValidation()
385
510
  keyTypeValidation()
511
+ lookupKeyValidation()
386
512
  planDataEmptyOrNotAnObject()
387
513
  pricingOptionsValidation()
388
514
  requiredFieldsMissing()
515
+ topLevelKeyValidation()
389
516
  // #endregion
390
517
 
391
518
  // #region Cleanup
@@ -0,0 +1,125 @@
1
+ /* eslint-disable no-undefined */
2
+ import test from 'tape'
3
+ import sinon from 'sinon'
4
+ import { PlanService } from '../../PlanService.js'
5
+
6
+ // #region Setup
7
+ const sandbox = sinon.createSandbox()
8
+ // #endregion
9
+
10
+ // #region Tests
11
+ test('getActivePlans should return active plans', async t => {
12
+ t.plan(3)
13
+ const numActivePlans = 2
14
+ const plansStub = [
15
+ {
16
+ plan: 'plan1',
17
+ status: 'active',
18
+ isVisible: true
19
+ },
20
+ {
21
+ plan: 'plan2',
22
+ status: 'inactive',
23
+ isVisible: false
24
+ },
25
+ {
26
+ plan: 'plan3',
27
+ status: 'active',
28
+ isVisible: true
29
+ }
30
+ ]
31
+ const planServiceStub = new PlanService()
32
+ sandbox.stub(planServiceStub, 'getPlans').resolves(plansStub)
33
+ const response = await planServiceStub.getActivePlans()
34
+
35
+ t.equal(
36
+ response.length,
37
+ numActivePlans,
38
+ 'Correct number of active plans returned'
39
+ )
40
+ t.equal(
41
+ response[0].plan,
42
+ plansStub[0].plan,
43
+ 'First plan successfully returned'
44
+ )
45
+ t.equal(
46
+ response[1].plan,
47
+ plansStub[2].plan,
48
+ 'Third plan successfully returned'
49
+ )
50
+ })
51
+
52
+ function planFilter () {
53
+ const badPlans = [
54
+ {
55
+ plan: 'status is inactive and isVisible is false',
56
+ status: 'inactive',
57
+ isVisible: false
58
+ },
59
+ {
60
+ plan: 'status is active and isVisible is false',
61
+ status: 'active',
62
+ isVisible: false
63
+ },
64
+ {
65
+ plan: 'status is undefined and isVisible is true',
66
+ status: undefined,
67
+ isVisible: true
68
+ },
69
+ {
70
+ plan: 'status is undefined and isVisible is false',
71
+ status: undefined,
72
+ isVisible: false
73
+ },
74
+ {
75
+ plan: 'status is null and isVisible is true',
76
+ status: null,
77
+ isVisible: true
78
+ },
79
+ {
80
+ plan: 'status is null and isVisible is false',
81
+ status: null,
82
+ isVisible: false
83
+ },
84
+ {
85
+ plan: 'status is active and isVisible is undefined',
86
+ status: 'active',
87
+ isVisible: undefined
88
+ },
89
+ {
90
+ plan: 'status is active and isVisible is null',
91
+ status: 'active',
92
+ isVisible: null
93
+ },
94
+ {
95
+ plan: 'status is a number and isVisible is true',
96
+ status: 123,
97
+ isVisible: true
98
+ }
99
+ ]
100
+ for (let ii = 0; ii < badPlans.length; ii++) {
101
+ test(`getActivePlans should return no plans using ${badPlans[ii].plan}`, async t => {
102
+ t.plan(1)
103
+ const planServiceStub = new PlanService()
104
+ sandbox.stub(planServiceStub, 'getPlans').resolves([badPlans[ii]])
105
+ const response = await planServiceStub.getActivePlans()
106
+ console.log('&&& response: ', response)
107
+
108
+ t.equal(
109
+ response.length,
110
+ 0,
111
+ `No active plans returned when ${badPlans[ii].plan}`
112
+ )
113
+ })
114
+ }
115
+ }
116
+
117
+ planFilter()
118
+ // #endregion
119
+
120
+ // #region Cleanup
121
+ test('teardown', t => {
122
+ sandbox.restore()
123
+ t.end()
124
+ })
125
+ // #endregion