launchdarkly-js-sdk-common 4.3.2 → 5.0.0-alpha.1
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/package.json +3 -1
- package/src/AnonymousContextProcessor.js +95 -0
- package/src/ContextFilter.js +147 -0
- package/src/EventProcessor.js +30 -13
- package/src/EventSender.js +1 -1
- package/src/Identity.js +10 -11
- package/src/PersistentFlagStore.js +3 -3
- package/src/Requestor.js +4 -4
- package/src/Stream.js +5 -5
- package/src/__tests__/ContextFilter-test.js +470 -0
- package/src/__tests__/EventProcessor-test.js +50 -121
- package/src/__tests__/LDClient-events-test.js +9 -152
- package/src/__tests__/LDClient-inspectors-test.js +1 -1
- package/src/__tests__/LDClient-test.js +18 -15
- package/src/__tests__/TransientContextProcessor-test.js +115 -0
- package/src/__tests__/attributeReference-test.js +400 -0
- package/src/__tests__/configuration-test.js +20 -21
- package/src/__tests__/context-test.js +93 -0
- package/src/__tests__/diagnosticEvents-test.js +0 -4
- package/src/__tests__/utils-test.js +3 -9
- package/src/attributeReference.js +143 -0
- package/src/configuration.js +4 -8
- package/src/context.js +96 -0
- package/src/diagnosticEvents.js +0 -2
- package/src/index.js +76 -89
- package/src/messages.js +8 -8
- package/src/utils.js +18 -15
- package/test-types.ts +3 -7
- package/typings.d.ts +140 -76
- package/src/UserFilter.js +0 -75
- package/src/UserValidator.js +0 -56
- package/src/__tests__/UserFilter-test.js +0 -93
- package/src/__tests__/UserValidator-test.js +0 -57
|
@@ -127,7 +127,7 @@ describe('LDClient', () => {
|
|
|
127
127
|
await withServers(async baseConfig => {
|
|
128
128
|
await withClient(numericUser, baseConfig, async client => {
|
|
129
129
|
await client.waitForInitialization();
|
|
130
|
-
expect(client.
|
|
130
|
+
expect(client.getContext()).toEqual(stringifiedNumericUser);
|
|
131
131
|
});
|
|
132
132
|
});
|
|
133
133
|
});
|
|
@@ -139,14 +139,14 @@ describe('LDClient', () => {
|
|
|
139
139
|
await withClient(anonUser, baseConfig, async client0 => {
|
|
140
140
|
await client0.waitForInitialization();
|
|
141
141
|
|
|
142
|
-
generatedUser = client0.
|
|
142
|
+
generatedUser = client0.getContext();
|
|
143
143
|
expect(generatedUser.key).toEqual(expect.anything());
|
|
144
144
|
expect(generatedUser).toMatchObject(anonUser);
|
|
145
145
|
});
|
|
146
146
|
await withClient(anonUser, baseConfig, async client1 => {
|
|
147
147
|
await client1.waitForInitialization();
|
|
148
148
|
|
|
149
|
-
const newUser1 = client1.
|
|
149
|
+
const newUser1 = client1.getContext();
|
|
150
150
|
expect(newUser1).toEqual(generatedUser);
|
|
151
151
|
});
|
|
152
152
|
});
|
|
@@ -161,7 +161,7 @@ describe('LDClient', () => {
|
|
|
161
161
|
await withClient(anonUser, baseConfig, async client0 => {
|
|
162
162
|
await client0.waitForInitialization();
|
|
163
163
|
|
|
164
|
-
generatedUser = client0.
|
|
164
|
+
generatedUser = client0.getContext();
|
|
165
165
|
expect(generatedUser.key).toEqual(expect.anything());
|
|
166
166
|
expect(generatedUser).toMatchObject(anonUser);
|
|
167
167
|
});
|
|
@@ -169,7 +169,7 @@ describe('LDClient', () => {
|
|
|
169
169
|
await withClient(anonUser, baseConfig, async client1 => {
|
|
170
170
|
await client1.waitForInitialization();
|
|
171
171
|
|
|
172
|
-
const newUser1 = client1.
|
|
172
|
+
const newUser1 = client1.getContext();
|
|
173
173
|
expect(newUser1.key).toEqual(expect.anything());
|
|
174
174
|
expect(newUser1.key).not.toEqual(generatedUser.key);
|
|
175
175
|
expect(newUser1).toMatchObject(anonUser);
|
|
@@ -466,12 +466,12 @@ describe('LDClient', () => {
|
|
|
466
466
|
|
|
467
467
|
const identifyPromise = client.identify(user2);
|
|
468
468
|
await sleepAsync(100); // sleep to jump some async ticks
|
|
469
|
-
expect(client.
|
|
469
|
+
expect(client.getContext()).toEqual(user);
|
|
470
470
|
|
|
471
471
|
signal.add();
|
|
472
472
|
await identifyPromise;
|
|
473
473
|
|
|
474
|
-
expect(client.
|
|
474
|
+
expect(client.getContext()).toEqual(user2);
|
|
475
475
|
});
|
|
476
476
|
});
|
|
477
477
|
});
|
|
@@ -498,7 +498,11 @@ describe('LDClient', () => {
|
|
|
498
498
|
});
|
|
499
499
|
});
|
|
500
500
|
|
|
501
|
-
it(
|
|
501
|
+
it.each([
|
|
502
|
+
{ country: 'US' }, // Legacy user with no key, and not anonymous.
|
|
503
|
+
{ kind: 'user' }, // A single kind that is not anonymous and has no key.
|
|
504
|
+
{ kind: 'multi', app: { anonymous: true }, org: {}, user: { key: 'yes' } }, // Multi kind with 1 non-anonymous context without a key.
|
|
505
|
+
])('returns an error and does not update flags when identify is called with invalid contexts', async badContext => {
|
|
502
506
|
const flags0 = { 'enable-foo': { value: false } };
|
|
503
507
|
const flags1 = { 'enable-foo': { value: true } };
|
|
504
508
|
await withServers(async (baseConfig, pollServer) => {
|
|
@@ -516,8 +520,7 @@ describe('LDClient', () => {
|
|
|
516
520
|
expect(client.variation('enable-foo')).toBe(false);
|
|
517
521
|
expect(pollServer.requests.length()).toEqual(1);
|
|
518
522
|
|
|
519
|
-
|
|
520
|
-
await expect(client.identify(userWithNoKey)).rejects.toThrow();
|
|
523
|
+
await expect(client.identify(badContext)).rejects.toThrow();
|
|
521
524
|
|
|
522
525
|
expect(client.variation('enable-foo')).toBe(false);
|
|
523
526
|
expect(pollServer.requests.length()).toEqual(1);
|
|
@@ -533,7 +536,7 @@ describe('LDClient', () => {
|
|
|
533
536
|
const anonUser = { anonymous: true, country: 'US' };
|
|
534
537
|
await client.identify(anonUser);
|
|
535
538
|
|
|
536
|
-
const newUser = client.
|
|
539
|
+
const newUser = client.getContext();
|
|
537
540
|
expect(newUser.key).toEqual(expect.anything());
|
|
538
541
|
expect(newUser).toMatchObject(anonUser);
|
|
539
542
|
});
|
|
@@ -546,7 +549,7 @@ describe('LDClient', () => {
|
|
|
546
549
|
const user = { key: 'user' };
|
|
547
550
|
const state = {
|
|
548
551
|
environment: 'env',
|
|
549
|
-
|
|
552
|
+
context: user,
|
|
550
553
|
flags: { flagkey: { value: 'value' } },
|
|
551
554
|
};
|
|
552
555
|
const sp = stubPlatform.mockStateProvider(state);
|
|
@@ -575,7 +578,7 @@ describe('LDClient', () => {
|
|
|
575
578
|
const user = { key: 'user' };
|
|
576
579
|
const state = {
|
|
577
580
|
environment: 'env',
|
|
578
|
-
|
|
581
|
+
context: user,
|
|
579
582
|
flags: { flagkey: { value: 'value' } },
|
|
580
583
|
};
|
|
581
584
|
const sp = stubPlatform.mockStateProvider(null);
|
|
@@ -592,7 +595,7 @@ describe('LDClient', () => {
|
|
|
592
595
|
const user = { key: 'user' };
|
|
593
596
|
const state0 = {
|
|
594
597
|
environment: 'env',
|
|
595
|
-
|
|
598
|
+
context: user,
|
|
596
599
|
flags: { flagkey: { value: 'value0' } },
|
|
597
600
|
};
|
|
598
601
|
const sp = stubPlatform.mockStateProvider(state0);
|
|
@@ -618,7 +621,7 @@ describe('LDClient', () => {
|
|
|
618
621
|
it('disables identify()', async () => {
|
|
619
622
|
const user = { key: 'user' };
|
|
620
623
|
const user1 = { key: 'user1' };
|
|
621
|
-
const state = { environment: 'env',
|
|
624
|
+
const state = { environment: 'env', context: user, flags: { flagkey: { value: 'value' } } };
|
|
622
625
|
const sp = stubPlatform.mockStateProvider(state);
|
|
623
626
|
|
|
624
627
|
await withServers(async (baseConfig, pollServer) => {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import AnonymousContextProcessor from '../AnonymousContextProcessor';
|
|
2
|
+
|
|
3
|
+
describe('AnonymousContextProcessor', () => {
|
|
4
|
+
let localStorage;
|
|
5
|
+
let logger;
|
|
6
|
+
let uv;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
localStorage = {};
|
|
10
|
+
logger = {
|
|
11
|
+
warn: jest.fn(),
|
|
12
|
+
};
|
|
13
|
+
uv = new AnonymousContextProcessor(localStorage, logger);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('rejects null user', async () => {
|
|
17
|
+
await expect(uv.processContext(null)).rejects.toThrow();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('leaves user with string key unchanged', async () => {
|
|
21
|
+
const u = { key: 'someone', name: 'me' };
|
|
22
|
+
expect(await uv.processContext(u)).toEqual(u);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('stringifies non-string key', async () => {
|
|
26
|
+
const u0 = { key: 123, name: 'me' };
|
|
27
|
+
const u1 = { key: '123', name: 'me' };
|
|
28
|
+
expect(await uv.processContext(u0)).toEqual(u1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('uses cached key for anonymous user', async () => {
|
|
32
|
+
const cachedKey = 'thing';
|
|
33
|
+
let storageKey;
|
|
34
|
+
localStorage.get = async key => {
|
|
35
|
+
storageKey = key;
|
|
36
|
+
return cachedKey;
|
|
37
|
+
};
|
|
38
|
+
const u = { anonymous: true };
|
|
39
|
+
expect(await uv.processContext(u)).toEqual({ key: cachedKey, anonymous: true });
|
|
40
|
+
expect(storageKey).toEqual('ld:$anonUserId');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('generates and stores key for anonymous user', async () => {
|
|
44
|
+
let storageKey;
|
|
45
|
+
let storedValue;
|
|
46
|
+
localStorage.get = async () => null;
|
|
47
|
+
localStorage.set = async (key, value) => {
|
|
48
|
+
storageKey = key;
|
|
49
|
+
storedValue = value;
|
|
50
|
+
};
|
|
51
|
+
const u0 = { anonymous: true };
|
|
52
|
+
const u1 = await uv.processContext(u0);
|
|
53
|
+
expect(storedValue).toEqual(expect.anything());
|
|
54
|
+
expect(u1).toEqual({ key: storedValue, anonymous: true });
|
|
55
|
+
expect(storageKey).toEqual('ld:$anonUserId');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('generates and stores a key for each anonymous context in a multi-kind context', async () => {
|
|
59
|
+
const context = {
|
|
60
|
+
kind: 'multi',
|
|
61
|
+
user: { anonymous: true },
|
|
62
|
+
org: { anonymous: true },
|
|
63
|
+
app: { key: 'app' },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const storage = {};
|
|
67
|
+
localStorage.get = async key => storage[key];
|
|
68
|
+
localStorage.set = async (key, value) => {
|
|
69
|
+
storage[key] = value;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const processed = await uv.processContext(context);
|
|
73
|
+
expect(processed.user.key).toBeDefined();
|
|
74
|
+
expect(processed.user.key).not.toEqual(processed.org.key);
|
|
75
|
+
expect(processed.org.key).toBeDefined();
|
|
76
|
+
expect(processed.app.key).toEqual('app');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('uses cached keys for context kinds that have already been generated', async () => {
|
|
80
|
+
const context = {
|
|
81
|
+
kind: 'multi',
|
|
82
|
+
user: { anonymous: true },
|
|
83
|
+
org: { anonymous: true },
|
|
84
|
+
another: { anonymous: true },
|
|
85
|
+
app: { key: 'app' },
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const storage = {
|
|
89
|
+
'ld:$contextKey:org': 'cachedOrgKey',
|
|
90
|
+
'ld:$anonUserId': 'cachedUserKey',
|
|
91
|
+
};
|
|
92
|
+
localStorage.get = async key => storage[key];
|
|
93
|
+
localStorage.set = async (key, value) => {
|
|
94
|
+
storage[key] = value;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const processed = await uv.processContext(context);
|
|
98
|
+
expect(processed.user.key).toEqual('cachedUserKey');
|
|
99
|
+
expect(processed.org.key).toEqual('cachedOrgKey');
|
|
100
|
+
expect(processed.another.key).toBeDefined();
|
|
101
|
+
expect(processed.app.key).toEqual('app');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it.each([{ anonymous: true }, { kind: 'user', anonymous: true }, { kind: 'multi', user: { anonymous: true } }])(
|
|
105
|
+
'uses the same key to store any user context (legacy, single, multi)',
|
|
106
|
+
async context => {
|
|
107
|
+
const storage = {};
|
|
108
|
+
localStorage.get = async key => expect(key).toEqual('ld:$anonUserId');
|
|
109
|
+
localStorage.set = async (key, value) => {
|
|
110
|
+
storage[key] = value;
|
|
111
|
+
};
|
|
112
|
+
await uv.processContext(context);
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
});
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
const AttributeReference = require('../attributeReference');
|
|
2
|
+
|
|
3
|
+
describe('when filtering attributes by reference', () => {
|
|
4
|
+
it('should be able to remove a top level value', () => {
|
|
5
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
6
|
+
{
|
|
7
|
+
launchdarkly: {
|
|
8
|
+
u2c: true,
|
|
9
|
+
},
|
|
10
|
+
ld: false,
|
|
11
|
+
foo: ['bar', 'baz'],
|
|
12
|
+
'a/b': 1,
|
|
13
|
+
'm~n': 2,
|
|
14
|
+
' ': ' ',
|
|
15
|
+
null: null,
|
|
16
|
+
},
|
|
17
|
+
['ld']
|
|
18
|
+
);
|
|
19
|
+
expect(cloned).toEqual({
|
|
20
|
+
launchdarkly: {
|
|
21
|
+
u2c: true,
|
|
22
|
+
},
|
|
23
|
+
foo: ['bar', 'baz'],
|
|
24
|
+
'a/b': 1,
|
|
25
|
+
'm~n': 2,
|
|
26
|
+
' ': ' ',
|
|
27
|
+
null: null,
|
|
28
|
+
});
|
|
29
|
+
expect(excluded).toEqual(['/ld']);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should be able to exclude a nested value', () => {
|
|
33
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
34
|
+
{
|
|
35
|
+
launchdarkly: {
|
|
36
|
+
u2c: true,
|
|
37
|
+
},
|
|
38
|
+
ld: false,
|
|
39
|
+
foo: ['bar', 'baz'],
|
|
40
|
+
'a/b': 1,
|
|
41
|
+
'm~n': 2,
|
|
42
|
+
' ': ' ',
|
|
43
|
+
null: null,
|
|
44
|
+
},
|
|
45
|
+
['/launchdarkly/u2c']
|
|
46
|
+
);
|
|
47
|
+
expect(cloned).toEqual({
|
|
48
|
+
launchdarkly: {},
|
|
49
|
+
ld: false,
|
|
50
|
+
foo: ['bar', 'baz'],
|
|
51
|
+
'a/b': 1,
|
|
52
|
+
'm~n': 2,
|
|
53
|
+
' ': ' ',
|
|
54
|
+
null: null,
|
|
55
|
+
});
|
|
56
|
+
expect(excluded).toEqual(['/launchdarkly/u2c']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('sould be able to exclude an object', () => {
|
|
60
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
61
|
+
{
|
|
62
|
+
launchdarkly: {
|
|
63
|
+
u2c: true,
|
|
64
|
+
},
|
|
65
|
+
ld: false,
|
|
66
|
+
foo: ['bar', 'baz'],
|
|
67
|
+
'a/b': 1,
|
|
68
|
+
'm~n': 2,
|
|
69
|
+
' ': ' ',
|
|
70
|
+
null: null,
|
|
71
|
+
},
|
|
72
|
+
['launchdarkly']
|
|
73
|
+
);
|
|
74
|
+
expect(cloned).toEqual({
|
|
75
|
+
ld: false,
|
|
76
|
+
foo: ['bar', 'baz'],
|
|
77
|
+
'a/b': 1,
|
|
78
|
+
'm~n': 2,
|
|
79
|
+
' ': ' ',
|
|
80
|
+
null: null,
|
|
81
|
+
});
|
|
82
|
+
expect(excluded).toEqual(['/launchdarkly']);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('sould be able to exclude an array', () => {
|
|
86
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
87
|
+
{
|
|
88
|
+
launchdarkly: {
|
|
89
|
+
u2c: true,
|
|
90
|
+
},
|
|
91
|
+
ld: false,
|
|
92
|
+
foo: ['bar', 'baz'],
|
|
93
|
+
'a/b': 1,
|
|
94
|
+
'm~n': 2,
|
|
95
|
+
' ': ' ',
|
|
96
|
+
null: null,
|
|
97
|
+
},
|
|
98
|
+
['foo']
|
|
99
|
+
);
|
|
100
|
+
expect(cloned).toEqual({
|
|
101
|
+
launchdarkly: {
|
|
102
|
+
u2c: true,
|
|
103
|
+
},
|
|
104
|
+
ld: false,
|
|
105
|
+
'a/b': 1,
|
|
106
|
+
'm~n': 2,
|
|
107
|
+
' ': ' ',
|
|
108
|
+
null: null,
|
|
109
|
+
});
|
|
110
|
+
expect(excluded).toEqual(['/foo']);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should not allow exclude an array index', () => {
|
|
114
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
115
|
+
{
|
|
116
|
+
launchdarkly: {
|
|
117
|
+
u2c: true,
|
|
118
|
+
},
|
|
119
|
+
ld: false,
|
|
120
|
+
foo: ['bar', 'baz'],
|
|
121
|
+
'a/b': 1,
|
|
122
|
+
'm~n': 2,
|
|
123
|
+
' ': ' ',
|
|
124
|
+
null: null,
|
|
125
|
+
},
|
|
126
|
+
['foo/0']
|
|
127
|
+
);
|
|
128
|
+
expect(cloned).toEqual({
|
|
129
|
+
launchdarkly: {
|
|
130
|
+
u2c: true,
|
|
131
|
+
},
|
|
132
|
+
ld: false,
|
|
133
|
+
foo: ['bar', 'baz'],
|
|
134
|
+
'a/b': 1,
|
|
135
|
+
'm~n': 2,
|
|
136
|
+
' ': ' ',
|
|
137
|
+
null: null,
|
|
138
|
+
});
|
|
139
|
+
expect(excluded).toEqual([]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should not allow exclude a property inside an index of an array.', () => {
|
|
143
|
+
const objWithArrayOfObjects = {
|
|
144
|
+
array: [
|
|
145
|
+
{
|
|
146
|
+
toRemove: true,
|
|
147
|
+
toLeave: true,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(objWithArrayOfObjects, ['array/0/toRemove']);
|
|
153
|
+
expect(cloned).toEqual(objWithArrayOfObjects);
|
|
154
|
+
expect(excluded).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should not allow exclude the root object', () => {
|
|
158
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
159
|
+
{
|
|
160
|
+
launchdarkly: {
|
|
161
|
+
u2c: true,
|
|
162
|
+
},
|
|
163
|
+
ld: false,
|
|
164
|
+
foo: ['bar', 'baz'],
|
|
165
|
+
'a/b': 1,
|
|
166
|
+
'm~n': 2,
|
|
167
|
+
' ': ' ',
|
|
168
|
+
null: null,
|
|
169
|
+
},
|
|
170
|
+
['/']
|
|
171
|
+
);
|
|
172
|
+
expect(cloned).toEqual({
|
|
173
|
+
launchdarkly: {
|
|
174
|
+
u2c: true,
|
|
175
|
+
},
|
|
176
|
+
ld: false,
|
|
177
|
+
foo: ['bar', 'baz'],
|
|
178
|
+
'a/b': 1,
|
|
179
|
+
'm~n': 2,
|
|
180
|
+
' ': ' ',
|
|
181
|
+
null: null,
|
|
182
|
+
});
|
|
183
|
+
expect(excluded).toEqual([]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should allow exclude a null value', () => {
|
|
187
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
188
|
+
{
|
|
189
|
+
launchdarkly: {
|
|
190
|
+
u2c: true,
|
|
191
|
+
},
|
|
192
|
+
ld: false,
|
|
193
|
+
foo: ['bar', 'baz'],
|
|
194
|
+
'a/b': 1,
|
|
195
|
+
'm~n': 2,
|
|
196
|
+
' ': ' ',
|
|
197
|
+
null: null,
|
|
198
|
+
},
|
|
199
|
+
['null']
|
|
200
|
+
);
|
|
201
|
+
expect(cloned).toEqual({
|
|
202
|
+
launchdarkly: {
|
|
203
|
+
u2c: true,
|
|
204
|
+
},
|
|
205
|
+
ld: false,
|
|
206
|
+
foo: ['bar', 'baz'],
|
|
207
|
+
'a/b': 1,
|
|
208
|
+
'm~n': 2,
|
|
209
|
+
' ': ' ',
|
|
210
|
+
});
|
|
211
|
+
expect(excluded).toEqual(['/null']);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should not allow exclude a value inside null', () => {
|
|
215
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
216
|
+
{
|
|
217
|
+
launchdarkly: {
|
|
218
|
+
u2c: true,
|
|
219
|
+
},
|
|
220
|
+
ld: false,
|
|
221
|
+
foo: ['bar', 'baz'],
|
|
222
|
+
'a/b': 1,
|
|
223
|
+
'm~n': 2,
|
|
224
|
+
' ': ' ',
|
|
225
|
+
null: null,
|
|
226
|
+
},
|
|
227
|
+
['/null/null']
|
|
228
|
+
);
|
|
229
|
+
expect(cloned).toEqual({
|
|
230
|
+
launchdarkly: {
|
|
231
|
+
u2c: true,
|
|
232
|
+
},
|
|
233
|
+
ld: false,
|
|
234
|
+
foo: ['bar', 'baz'],
|
|
235
|
+
'a/b': 1,
|
|
236
|
+
'm~n': 2,
|
|
237
|
+
' ': ' ',
|
|
238
|
+
null: null,
|
|
239
|
+
});
|
|
240
|
+
expect(excluded).toEqual([]);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should not allow exclude a value inside explicit undefined', () => {
|
|
244
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
245
|
+
{
|
|
246
|
+
launchdarkly: {
|
|
247
|
+
u2c: true,
|
|
248
|
+
},
|
|
249
|
+
ld: false,
|
|
250
|
+
foo: ['bar', 'baz'],
|
|
251
|
+
'a/b': 1,
|
|
252
|
+
'm~n': 2,
|
|
253
|
+
' ': ' ',
|
|
254
|
+
null: null,
|
|
255
|
+
},
|
|
256
|
+
['undefined/null']
|
|
257
|
+
);
|
|
258
|
+
expect(cloned).toEqual({
|
|
259
|
+
launchdarkly: {
|
|
260
|
+
u2c: true,
|
|
261
|
+
},
|
|
262
|
+
ld: false,
|
|
263
|
+
foo: ['bar', 'baz'],
|
|
264
|
+
'a/b': 1,
|
|
265
|
+
'm~n': 2,
|
|
266
|
+
' ': ' ',
|
|
267
|
+
null: null,
|
|
268
|
+
});
|
|
269
|
+
expect(excluded).toEqual([]);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should allow removing an explicit undefined value', () => {
|
|
273
|
+
const objToClone = { undefined: undefined };
|
|
274
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(objToClone, ['undefined']);
|
|
275
|
+
expect(cloned).toEqual({});
|
|
276
|
+
expect(excluded).toEqual(['/undefined']);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should allow removing references with escape characters', () => {
|
|
280
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
281
|
+
{
|
|
282
|
+
launchdarkly: {
|
|
283
|
+
u2c: true,
|
|
284
|
+
},
|
|
285
|
+
ld: false,
|
|
286
|
+
foo: ['bar', 'baz'],
|
|
287
|
+
'a/b': 1,
|
|
288
|
+
'm~n': 2,
|
|
289
|
+
' ': ' ',
|
|
290
|
+
null: null,
|
|
291
|
+
},
|
|
292
|
+
['/a~1b', '/m~0n']
|
|
293
|
+
);
|
|
294
|
+
expect(cloned).toEqual({
|
|
295
|
+
launchdarkly: {
|
|
296
|
+
u2c: true,
|
|
297
|
+
},
|
|
298
|
+
ld: false,
|
|
299
|
+
foo: ['bar', 'baz'],
|
|
300
|
+
' ': ' ',
|
|
301
|
+
null: null,
|
|
302
|
+
});
|
|
303
|
+
expect(excluded).toEqual(['/a~1b', '/m~0n']);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should allow removing literals without escape characters', () => {
|
|
307
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
308
|
+
{
|
|
309
|
+
launchdarkly: {
|
|
310
|
+
u2c: true,
|
|
311
|
+
},
|
|
312
|
+
ld: false,
|
|
313
|
+
foo: ['bar', 'baz'],
|
|
314
|
+
'a/b': 1,
|
|
315
|
+
'm~n': 2,
|
|
316
|
+
' ': ' ',
|
|
317
|
+
null: null,
|
|
318
|
+
},
|
|
319
|
+
['a/b', 'm~n']
|
|
320
|
+
);
|
|
321
|
+
expect(cloned).toEqual({
|
|
322
|
+
launchdarkly: {
|
|
323
|
+
u2c: true,
|
|
324
|
+
},
|
|
325
|
+
ld: false,
|
|
326
|
+
foo: ['bar', 'baz'],
|
|
327
|
+
' ': ' ',
|
|
328
|
+
null: null,
|
|
329
|
+
});
|
|
330
|
+
expect(excluded).toEqual(['/a~1b', '/m~0n']);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should handle cycles', () => {
|
|
334
|
+
const item = {};
|
|
335
|
+
const objWithCycle = {
|
|
336
|
+
item,
|
|
337
|
+
name: 'test',
|
|
338
|
+
remove: 'remove',
|
|
339
|
+
};
|
|
340
|
+
item.parent = objWithCycle;
|
|
341
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(objWithCycle, ['remove']);
|
|
342
|
+
expect(cloned).toEqual({
|
|
343
|
+
item: {},
|
|
344
|
+
name: 'test',
|
|
345
|
+
});
|
|
346
|
+
expect(excluded).toEqual(['/remove']);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should allow non-circular reference and should treat them independently for filtering', () => {
|
|
350
|
+
const item = { value: 'value' };
|
|
351
|
+
const objWithSharedPeer = {
|
|
352
|
+
item: item,
|
|
353
|
+
second: item,
|
|
354
|
+
third: item,
|
|
355
|
+
fourth: item,
|
|
356
|
+
};
|
|
357
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(objWithSharedPeer, ['third', '/second/value']);
|
|
358
|
+
expect(cloned).toEqual({
|
|
359
|
+
item: { value: 'value' },
|
|
360
|
+
second: {},
|
|
361
|
+
fourth: { value: 'value' },
|
|
362
|
+
});
|
|
363
|
+
expect(excluded).toEqual(['/second/value', '/third']);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should allow for an empty reference list', () => {
|
|
367
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(
|
|
368
|
+
{
|
|
369
|
+
launchdarkly: {
|
|
370
|
+
u2c: true,
|
|
371
|
+
},
|
|
372
|
+
ld: false,
|
|
373
|
+
foo: ['bar', 'baz'],
|
|
374
|
+
'a/b': 1,
|
|
375
|
+
'm~n': 2,
|
|
376
|
+
' ': ' ',
|
|
377
|
+
null: null,
|
|
378
|
+
},
|
|
379
|
+
[]
|
|
380
|
+
);
|
|
381
|
+
expect(cloned).toEqual({
|
|
382
|
+
launchdarkly: {
|
|
383
|
+
u2c: true,
|
|
384
|
+
},
|
|
385
|
+
ld: false,
|
|
386
|
+
foo: ['bar', 'baz'],
|
|
387
|
+
'a/b': 1,
|
|
388
|
+
'm~n': 2,
|
|
389
|
+
' ': ' ',
|
|
390
|
+
null: null,
|
|
391
|
+
});
|
|
392
|
+
expect(excluded).toEqual([]);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe('when given a literal', () => {
|
|
397
|
+
it('can convert it to a reference', () => {
|
|
398
|
+
expect(AttributeReference.literalToReference('/~why')).toEqual('/~1~0why');
|
|
399
|
+
});
|
|
400
|
+
});
|
|
@@ -44,24 +44,26 @@ describe('configuration', () => {
|
|
|
44
44
|
await listener.expectNoErrors();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
// As of the latest major version, there are no deprecated options. This logic can be restored
|
|
48
|
+
// the next time we deprecate something.
|
|
49
|
+
// function checkDeprecated(oldName, newName, value) {
|
|
50
|
+
// const desc = newName
|
|
51
|
+
// ? 'allows "' + oldName + '" as a deprecated equivalent to "' + newName + '"'
|
|
52
|
+
// : 'warns that "' + oldName + '" is deprecated';
|
|
53
|
+
// it(desc, async () => {
|
|
54
|
+
// const listener = errorListener();
|
|
55
|
+
// const config0 = {};
|
|
56
|
+
// config0[oldName] = value;
|
|
57
|
+
// const config1 = configuration.validate(config0, listener.emitter, null, listener.logger);
|
|
58
|
+
// if (newName) {
|
|
59
|
+
// expect(config1[newName]).toBe(value);
|
|
60
|
+
// expect(config1[oldName]).toBeUndefined();
|
|
61
|
+
// } else {
|
|
62
|
+
// expect(config1[oldName]).toEqual(value);
|
|
63
|
+
// }
|
|
64
|
+
// await listener.expectWarningOnly(messages.deprecated(oldName, newName));
|
|
65
|
+
// });
|
|
66
|
+
// }
|
|
65
67
|
|
|
66
68
|
function checkBooleanProperty(name) {
|
|
67
69
|
it('enforces boolean type and default for "' + name + '"', async () => {
|
|
@@ -100,15 +102,12 @@ describe('configuration', () => {
|
|
|
100
102
|
checkBooleanProperty('sendEvents');
|
|
101
103
|
checkBooleanProperty('allAttributesPrivate');
|
|
102
104
|
checkBooleanProperty('sendLDHeaders');
|
|
103
|
-
checkBooleanProperty('inlineUsersInEvents');
|
|
104
105
|
checkBooleanProperty('sendEventsOnlyForVariation');
|
|
105
106
|
checkBooleanProperty('useReport');
|
|
106
107
|
checkBooleanProperty('evaluationReasons');
|
|
107
108
|
checkBooleanProperty('diagnosticOptOut');
|
|
108
109
|
checkBooleanProperty('streaming');
|
|
109
110
|
|
|
110
|
-
checkDeprecated('allowFrequentDuplicateEvents', undefined, true);
|
|
111
|
-
|
|
112
111
|
function checkNumericProperty(name, validValue) {
|
|
113
112
|
it('enforces numeric type and default for "' + name + '"', async () => {
|
|
114
113
|
await expectDefault(name);
|