@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.
- package/dist/cjs/services/PlanService.js +7 -1
- package/dist/esm/index.js +1100 -180
- package/dist/esm/services/CollabService.js +961 -52
- package/dist/esm/services/PlanService.js +7 -1
- package/dist/esm/services/TrackingService.js +71 -66
- package/dist/esm/services/index.js +1097 -177
- package/dist/esm/utils/CollabClient.js +948 -39
- package/dist/esm/utils/jsonDiff.js +886 -13
- package/dist/node/services/PlanService.js +7 -1
- package/package.json +6 -6
- package/src/services/PlanService.js +7 -1
- package/src/services/tests/PlanService/createPlanWithValidation.test.js +137 -10
- package/src/services/tests/PlanService/getActivePlans.test.js +125 -0
- package/src/services/tests/PlanService/updatePlanWithValidation.test.js +393 -62
|
@@ -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
|
|
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.
|
|
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.
|
|
50
|
-
"@domql/utils": "^2.
|
|
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.
|
|
54
|
-
"@symbo.ls/socket": "^2.
|
|
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": "
|
|
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
|
|
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
|
-
`
|
|
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
|
-
'
|
|
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
|
-
`
|
|
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(`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
369
|
-
)
|
|
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
|
-
|
|
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
|