@depup/launchdarkly-node-server-sdk 7.0.4-depup.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/.babelrc +16 -0
- package/.circleci/config.yml +89 -0
- package/.eslintignore +5 -0
- package/.eslintrc.yaml +114 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/pull_request_template.md +21 -0
- package/.github/workflows/stale.yml +8 -0
- package/.hound.yml +33 -0
- package/.ldrelease/config.yml +28 -0
- package/.prettierrc +6 -0
- package/CHANGELOG.md +603 -0
- package/CODEOWNERS +2 -0
- package/CONTRIBUTING.md +55 -0
- package/LICENSE.txt +13 -0
- package/README.md +36 -0
- package/SECURITY.md +5 -0
- package/attribute_reference.js +217 -0
- package/big_segments.js +117 -0
- package/caching_store_wrapper.js +240 -0
- package/changes.json +30 -0
- package/configuration.js +235 -0
- package/context.js +98 -0
- package/context_filter.js +137 -0
- package/contract-tests/README.md +7 -0
- package/contract-tests/index.js +109 -0
- package/contract-tests/log.js +23 -0
- package/contract-tests/package.json +15 -0
- package/contract-tests/sdkClientEntity.js +110 -0
- package/contract-tests/testharness-suppressions.txt +2 -0
- package/diagnostic_events.js +151 -0
- package/docs/typedoc.js +10 -0
- package/errors.js +26 -0
- package/evaluator.js +822 -0
- package/event_factory.js +121 -0
- package/event_processor.js +320 -0
- package/event_summarizer.js +101 -0
- package/feature_store.js +120 -0
- package/feature_store_event_wrapper.js +258 -0
- package/file_data_source.js +192 -0
- package/flags_state.js +46 -0
- package/index.d.ts +2426 -0
- package/index.js +452 -0
- package/integrations.js +7 -0
- package/interfaces.js +2 -0
- package/loggers.js +125 -0
- package/messages.js +31 -0
- package/operators.js +106 -0
- package/package.json +105 -0
- package/polling.js +70 -0
- package/requestor.js +62 -0
- package/scripts/better-audit.sh +76 -0
- package/sharedtest/big_segment_store_tests.js +86 -0
- package/sharedtest/feature_store_tests.js +177 -0
- package/sharedtest/persistent_feature_store_tests.js +183 -0
- package/sharedtest/store_tests.js +7 -0
- package/streaming.js +179 -0
- package/test/LDClient-big-segments-test.js +92 -0
- package/test/LDClient-end-to-end-test.js +218 -0
- package/test/LDClient-evaluation-all-flags-test.js +226 -0
- package/test/LDClient-evaluation-test.js +204 -0
- package/test/LDClient-events-test.js +502 -0
- package/test/LDClient-listeners-test.js +180 -0
- package/test/LDClient-test.js +96 -0
- package/test/LDClient-tls-test.js +110 -0
- package/test/attribute_reference-test.js +494 -0
- package/test/big_segments-test.js +182 -0
- package/test/caching_store_wrapper-test.js +434 -0
- package/test/configuration-test.js +249 -0
- package/test/context-test.js +93 -0
- package/test/context_filter-test.js +424 -0
- package/test/diagnostic_events-test.js +152 -0
- package/test/evaluator-big-segments-test.js +301 -0
- package/test/evaluator-bucketing-test.js +333 -0
- package/test/evaluator-clause-test.js +277 -0
- package/test/evaluator-flag-test.js +452 -0
- package/test/evaluator-pre-conditions-test.js +105 -0
- package/test/evaluator-rule-test.js +131 -0
- package/test/evaluator-segment-match-test.js +310 -0
- package/test/evaluator_helpers.js +106 -0
- package/test/event_processor-test.js +680 -0
- package/test/event_summarizer-test.js +146 -0
- package/test/feature_store-test.js +42 -0
- package/test/feature_store_event_wrapper-test.js +182 -0
- package/test/feature_store_test_base.js +60 -0
- package/test/file_data_source-test.js +255 -0
- package/test/loggers-test.js +126 -0
- package/test/operators-test.js +102 -0
- package/test/polling-test.js +158 -0
- package/test/requestor-test.js +60 -0
- package/test/store_tests_big_segments-test.js +61 -0
- package/test/streaming-test.js +323 -0
- package/test/stubs.js +107 -0
- package/test/test_data-test.js +341 -0
- package/test/update_queue-test.js +61 -0
- package/test-types.ts +210 -0
- package/test_data.js +323 -0
- package/tsconfig.json +14 -0
- package/update_queue.js +28 -0
- package/utils/__tests__/httpUtils-test.js +39 -0
- package/utils/__tests__/wrapPromiseCallback-test.js +33 -0
- package/utils/asyncUtils.js +32 -0
- package/utils/httpUtils.js +105 -0
- package/utils/stringifyAttrs.js +14 -0
- package/utils/wrapPromiseCallback.js +36 -0
- package/versioned_data_kind.js +34 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
var LDClient = require('../index.js');
|
|
2
|
+
var messages = require('../messages');
|
|
3
|
+
var stubs = require('./stubs');
|
|
4
|
+
|
|
5
|
+
describe('LDClient', () => {
|
|
6
|
+
|
|
7
|
+
describe('ready event', () => {
|
|
8
|
+
it('is fired in offline mode', done => {
|
|
9
|
+
var client = LDClient.init('sdk_key', { offline: true });
|
|
10
|
+
client.on('ready', () => {
|
|
11
|
+
done();
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('failed event', () => {
|
|
17
|
+
it('is fired if initialization fails', done => {
|
|
18
|
+
var updateProcessor = stubs.stubUpdateProcessor();
|
|
19
|
+
updateProcessor.error = { status: 403 };
|
|
20
|
+
var client = stubs.createClient({ updateProcessor: updateProcessor }, {});
|
|
21
|
+
|
|
22
|
+
client.on('failed', err => {
|
|
23
|
+
expect(err).toEqual(updateProcessor.error);
|
|
24
|
+
done();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('isOffline()', () => {
|
|
30
|
+
it('returns true in offline mode', done => {
|
|
31
|
+
var client = LDClient.init('sdk_key', {offline: true});
|
|
32
|
+
client.on('ready', () => {
|
|
33
|
+
expect(client.isOffline()).toEqual(true);
|
|
34
|
+
done();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('secureModeHash()', () => {
|
|
40
|
+
it('correctly computes hash for a known message and secret', () => {
|
|
41
|
+
var client = LDClient.init('secret', {offline: true});
|
|
42
|
+
var hash = client.secureModeHash({"key": "Message"});
|
|
43
|
+
expect(hash).toEqual("aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it.each([
|
|
47
|
+
[{key: 'Message'}, 'aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597'],
|
|
48
|
+
[{kind: 'user', key: 'Message'}, 'aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597'],
|
|
49
|
+
[{kind: 'org', key: 'orgtest'}, '40bc9b2e66a842e269ab98dad813e4e15203bbbfd91e8c96b92f3ae6f3f5e223'],
|
|
50
|
+
[{kind: 'multi', user: {key: 'user:test'}, org: {key: 'org:test'}}, '607cc91526c615823e320dabca7967ce544fbe83bcb2b7287163f2d1c7aa210f']
|
|
51
|
+
])('it uses the canonical key', (context, expectedHash) => {
|
|
52
|
+
const client = LDClient.init('secret', {offline: true});
|
|
53
|
+
const hash = client.secureModeHash(context);
|
|
54
|
+
|
|
55
|
+
expect(hash).toEqual(expectedHash);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('waitForInitialization()', () => {
|
|
60
|
+
it('resolves when ready', async () => {
|
|
61
|
+
var client = stubs.createClient({}, {});
|
|
62
|
+
await client.waitForInitialization();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('resolves immediately if the client is already ready', async () => {
|
|
66
|
+
var client = stubs.createClient({}, {});
|
|
67
|
+
await client.waitForInitialization();
|
|
68
|
+
await client.waitForInitialization();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('is rejected if initialization fails', async () => {
|
|
72
|
+
var err = { status: 403 };
|
|
73
|
+
var updateProcessor = stubs.stubUpdateProcessor();
|
|
74
|
+
updateProcessor.error = err;
|
|
75
|
+
var client = stubs.createClient({ updateProcessor: updateProcessor }, {});
|
|
76
|
+
await expect(client.waitForInitialization()).rejects.toBe(err);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('creates only one Promise', async () => {
|
|
80
|
+
const updateProcessor = stubs.stubUpdateProcessor();
|
|
81
|
+
updateProcessor.shouldInitialize = false;
|
|
82
|
+
const client = stubs.createClient({ updateProcessor: updateProcessor }, {});
|
|
83
|
+
const p1 = client.waitForInitialization();
|
|
84
|
+
const p2 = client.waitForInitialization();
|
|
85
|
+
expect(p2).toBe(p1);
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('close()', () => {
|
|
90
|
+
it('does not crash when closing an offline client', done => {
|
|
91
|
+
var client = LDClient.init('sdk_key', {offline: true});
|
|
92
|
+
expect(() => client.close()).not.toThrow();
|
|
93
|
+
done();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as LDClient from '../index';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AsyncQueue,
|
|
5
|
+
TestHttpHandlers,
|
|
6
|
+
TestHttpServer,
|
|
7
|
+
failOnTimeout,
|
|
8
|
+
withCloseable
|
|
9
|
+
} from 'launchdarkly-js-test-helpers';
|
|
10
|
+
import * as stubs from './stubs';
|
|
11
|
+
|
|
12
|
+
describe('LDClient TLS configuration', () => {
|
|
13
|
+
const sdkKey = 'secret';
|
|
14
|
+
let logger = stubs.stubLogger();
|
|
15
|
+
|
|
16
|
+
it('can connect via HTTPS to a server with a self-signed certificate, if CA is specified', async () => {
|
|
17
|
+
await withCloseable(TestHttpServer.startSecure, async server => {
|
|
18
|
+
server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson({}));
|
|
19
|
+
|
|
20
|
+
const config = {
|
|
21
|
+
baseUri: server.url,
|
|
22
|
+
sendEvents: false,
|
|
23
|
+
stream: false,
|
|
24
|
+
logger: stubs.stubLogger(),
|
|
25
|
+
tlsParams: { ca: server.certificate },
|
|
26
|
+
diagnosticOptOut: true,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
await withCloseable(LDClient.init(sdkKey, config), async client => {
|
|
30
|
+
await client.waitForInitialization();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('cannot connect via HTTPS to a server with a self-signed certificate, using default config', async () => {
|
|
36
|
+
await withCloseable(TestHttpServer.startSecure, async server => {
|
|
37
|
+
server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson({}));
|
|
38
|
+
|
|
39
|
+
const logCapture = stubs.asyncLogCapture();
|
|
40
|
+
const config = {
|
|
41
|
+
baseUri: server.url,
|
|
42
|
+
sendEvents: false,
|
|
43
|
+
stream: false,
|
|
44
|
+
logger: logCapture.logger,
|
|
45
|
+
diagnosticOptOut: true,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
await withCloseable(LDClient.init(sdkKey, config), async client => {
|
|
49
|
+
const message1 = await failOnTimeout(logCapture.warn.take(), 1000, 'timed out waiting for log message');
|
|
50
|
+
expect(message1).toMatch(/only disable the streaming API/); // irrelevant message due to our use of polling mode
|
|
51
|
+
const message2 = await failOnTimeout(logCapture.warn.take(), 1000, 'timed out waiting for log message');
|
|
52
|
+
expect(message2).toMatch(/self.signed/);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('can use custom TLS options for streaming as well as polling', async () => {
|
|
58
|
+
await withCloseable(TestHttpServer.startSecure, async server => {
|
|
59
|
+
const eventData = { data: { flags: { flag: { version: 1 } }, segments: {} } };
|
|
60
|
+
await withCloseable(new AsyncQueue(), async events => {
|
|
61
|
+
events.add({ type: 'put', data: JSON.stringify(eventData) });
|
|
62
|
+
server.forMethodAndPath('get', '/stream/all', TestHttpHandlers.sseStream(events));
|
|
63
|
+
|
|
64
|
+
const config = {
|
|
65
|
+
baseUri: server.url,
|
|
66
|
+
streamUri: server.url + '/stream',
|
|
67
|
+
sendEvents: false,
|
|
68
|
+
logger: logger,
|
|
69
|
+
tlsParams: { ca: server.certificate },
|
|
70
|
+
diagnosticOptOut: true,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
await withCloseable(LDClient.init(sdkKey, config), async client => {
|
|
74
|
+
await client.waitForInitialization(); // this won't return until the stream receives the "put" event
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('can use custom TLS options for posting events', async () => {
|
|
81
|
+
await withCloseable(TestHttpServer.startSecure, async server => {
|
|
82
|
+
server.forMethodAndPath('post', '/events/bulk', TestHttpHandlers.respond(200));
|
|
83
|
+
server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson({}));
|
|
84
|
+
|
|
85
|
+
const config = {
|
|
86
|
+
baseUri: server.url,
|
|
87
|
+
eventsUri: server.url + '/events',
|
|
88
|
+
stream: false,
|
|
89
|
+
logger: stubs.stubLogger(),
|
|
90
|
+
tlsParams: { ca: server.certificate },
|
|
91
|
+
diagnosticOptOut: true,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
await withCloseable(LDClient.init(sdkKey, config), async client => {
|
|
95
|
+
await client.waitForInitialization();
|
|
96
|
+
client.identify({ key: 'user' });
|
|
97
|
+
await client.flush();
|
|
98
|
+
|
|
99
|
+
const flagsRequest = await server.nextRequest();
|
|
100
|
+
expect(flagsRequest.path).toEqual('/sdk/latest-all');
|
|
101
|
+
|
|
102
|
+
const eventsRequest = await server.nextRequest();
|
|
103
|
+
expect(eventsRequest.path).toEqual('/events/bulk');
|
|
104
|
+
const eventData = JSON.parse(eventsRequest.body);
|
|
105
|
+
expect(eventData.length).toEqual(1);
|
|
106
|
+
expect(eventData[0].kind).toEqual('identify');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
const AttributeReference = require('../attribute_reference');
|
|
2
|
+
|
|
3
|
+
describe('when getting attributes by reference', () => {
|
|
4
|
+
it('should handle an empty reference', () => {
|
|
5
|
+
expect(AttributeReference.get({
|
|
6
|
+
'launchdarkly': {
|
|
7
|
+
'u2c': true
|
|
8
|
+
},
|
|
9
|
+
'ld': false,
|
|
10
|
+
'foo': ['bar', 'baz'],
|
|
11
|
+
'a/b': 1,
|
|
12
|
+
'm~n': 2,
|
|
13
|
+
' ': ' ',
|
|
14
|
+
'null': null
|
|
15
|
+
}, '')).toBeUndefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should handle a reference to the root element', () => {
|
|
19
|
+
expect(AttributeReference.get({
|
|
20
|
+
'launchdarkly': {
|
|
21
|
+
'u2c': true
|
|
22
|
+
},
|
|
23
|
+
'ld': false,
|
|
24
|
+
'foo': ['bar', 'baz'],
|
|
25
|
+
'a/b': 1,
|
|
26
|
+
'm~n': 2,
|
|
27
|
+
' ': ' ',
|
|
28
|
+
'null': null
|
|
29
|
+
}, '/')).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should handle a reference to a top level attribute without a leading slash', () => {
|
|
33
|
+
expect(AttributeReference.get({ ld: false }, 'ld')).toEqual(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should handle a reference to an array', () => {
|
|
37
|
+
expect(AttributeReference.get({ foo: ['bar', 'baz'] }, '/foo')).toEqual(['bar', 'baz']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should not allow indexing an array', () => {
|
|
41
|
+
expect(AttributeReference.get({ foo: ['bar', 'baz'] }, '/foo/0')).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should not allow indexing a non-array object', () => {
|
|
45
|
+
expect(AttributeReference.get({
|
|
46
|
+
'launchdarkly': {
|
|
47
|
+
'u2c': true
|
|
48
|
+
}
|
|
49
|
+
}, 'launchdarkly')).toEqual({
|
|
50
|
+
'u2c': true
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle indexing into a nested property', () => {
|
|
55
|
+
expect(AttributeReference.get({
|
|
56
|
+
'launchdarkly': {
|
|
57
|
+
'u2c': true
|
|
58
|
+
}
|
|
59
|
+
}, '/launchdarkly/u2c')).toEqual(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should not treat object literals as indexing into nested objects', () => {
|
|
63
|
+
expect(AttributeReference.get({
|
|
64
|
+
'launchdarkly': {
|
|
65
|
+
'u2c': true
|
|
66
|
+
}
|
|
67
|
+
}, 'launchdarkly/u2c')).toEqual(undefined);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should allow indexing of whitepace keys', () => {
|
|
71
|
+
expect(AttributeReference.get({ ' ': ' ' }, '/ ')).toEqual(' ');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
it('should handle escaped slashes in keys', () => {
|
|
76
|
+
expect(AttributeReference.get({ 'a/b': 1 }, '/a~1b')).toEqual(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle escaped tilde in keys', () => {
|
|
80
|
+
expect(AttributeReference.get({ 'm~n': 2 }, '/m~0n')).toEqual(2);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should handle literal with unescaped /', () => {
|
|
84
|
+
expect(AttributeReference.get({ 'a/b': 1 }, 'a/b')).toEqual(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle literal with unescaped ~', () => {
|
|
88
|
+
expect(AttributeReference.get({ 'm~n': 2 }, 'm~n')).toEqual(2);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle accessing a null value', () => {
|
|
92
|
+
expect(AttributeReference.get({ 'null': null }, 'null')).toEqual(null);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should handle an attempt to access inside a null value', () => {
|
|
96
|
+
expect(AttributeReference.get({ 'null': null }, '/null/null')).toBeUndefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle an attempt to access something that doesn\'t exist', () => {
|
|
100
|
+
expect(AttributeReference.get({ whatever: true }, 'badkey')).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should be able to get a top level key starting with /', () => {
|
|
104
|
+
expect(AttributeReference.get({ '/why': 'because', 'why': 'not' }, '/~1why')).toEqual("because");
|
|
105
|
+
expect(AttributeReference.get({ '/why': 'because', 'why': 'not' }, '/why')).toEqual("not");
|
|
106
|
+
expect(AttributeReference.get({ '/~why': 'because', 'why': 'not' }, '/~1~0why')).toEqual("because");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should allow indexing a key that has leading spaces before a slash', () => {
|
|
110
|
+
expect(AttributeReference.get({ ' /': 'a' }, ' /')).toEqual('a');
|
|
111
|
+
expect(AttributeReference.get({ ' /': 'a' }, '/ ~1')).toEqual('a');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should not allow indexing into string', () => {
|
|
115
|
+
expect(AttributeReference.get({attr: 'string'}, '/attr/0')).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('when filtering attributes by reference', () => {
|
|
120
|
+
|
|
121
|
+
it('should be able to remove a top level value', () => {
|
|
122
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
123
|
+
'launchdarkly': {
|
|
124
|
+
'u2c': true
|
|
125
|
+
},
|
|
126
|
+
'ld': false,
|
|
127
|
+
'foo': ['bar', 'baz'],
|
|
128
|
+
'a/b': 1,
|
|
129
|
+
'm~n': 2,
|
|
130
|
+
' ': ' ',
|
|
131
|
+
'null': null
|
|
132
|
+
}, ['ld']);
|
|
133
|
+
expect(cloned).toEqual({
|
|
134
|
+
'launchdarkly': {
|
|
135
|
+
'u2c': true
|
|
136
|
+
},
|
|
137
|
+
'foo': ['bar', 'baz'],
|
|
138
|
+
'a/b': 1,
|
|
139
|
+
'm~n': 2,
|
|
140
|
+
' ': ' ',
|
|
141
|
+
'null': null
|
|
142
|
+
});
|
|
143
|
+
expect(excluded).toEqual(['/ld']);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should be able to exclude a nested value', () => {
|
|
147
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
148
|
+
'launchdarkly': {
|
|
149
|
+
'u2c': true
|
|
150
|
+
},
|
|
151
|
+
'ld': false,
|
|
152
|
+
'foo': ['bar', 'baz'],
|
|
153
|
+
'a/b': 1,
|
|
154
|
+
'm~n': 2,
|
|
155
|
+
' ': ' ',
|
|
156
|
+
'null': null
|
|
157
|
+
}, ['/launchdarkly/u2c']);
|
|
158
|
+
expect(cloned).toEqual({
|
|
159
|
+
'launchdarkly': {
|
|
160
|
+
},
|
|
161
|
+
'ld': false,
|
|
162
|
+
'foo': ['bar', 'baz'],
|
|
163
|
+
'a/b': 1,
|
|
164
|
+
'm~n': 2,
|
|
165
|
+
' ': ' ',
|
|
166
|
+
'null': null
|
|
167
|
+
});
|
|
168
|
+
expect(excluded).toEqual(['/launchdarkly/u2c']);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('sould be able to exclude an object', () => {
|
|
172
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
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
|
+
}, ['launchdarkly']);
|
|
183
|
+
expect(cloned).toEqual({
|
|
184
|
+
'ld': false,
|
|
185
|
+
'foo': ['bar', 'baz'],
|
|
186
|
+
'a/b': 1,
|
|
187
|
+
'm~n': 2,
|
|
188
|
+
' ': ' ',
|
|
189
|
+
'null': null
|
|
190
|
+
});
|
|
191
|
+
expect(excluded).toEqual(['/launchdarkly']);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('sould be able to exclude an array', () => {
|
|
195
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
196
|
+
'launchdarkly': {
|
|
197
|
+
'u2c': true
|
|
198
|
+
},
|
|
199
|
+
'ld': false,
|
|
200
|
+
'foo': ['bar', 'baz'],
|
|
201
|
+
'a/b': 1,
|
|
202
|
+
'm~n': 2,
|
|
203
|
+
' ': ' ',
|
|
204
|
+
'null': null
|
|
205
|
+
}, ['foo']);
|
|
206
|
+
expect(cloned).toEqual({
|
|
207
|
+
'launchdarkly': {
|
|
208
|
+
'u2c': true
|
|
209
|
+
},
|
|
210
|
+
'ld': false,
|
|
211
|
+
'a/b': 1,
|
|
212
|
+
'm~n': 2,
|
|
213
|
+
' ': ' ',
|
|
214
|
+
'null': null
|
|
215
|
+
});
|
|
216
|
+
expect(excluded).toEqual(['/foo']);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should not allow exclude an array index', () => {
|
|
220
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
221
|
+
'launchdarkly': {
|
|
222
|
+
'u2c': true
|
|
223
|
+
},
|
|
224
|
+
'ld': false,
|
|
225
|
+
'foo': ['bar', 'baz'],
|
|
226
|
+
'a/b': 1,
|
|
227
|
+
'm~n': 2,
|
|
228
|
+
' ': ' ',
|
|
229
|
+
'null': null
|
|
230
|
+
}, ['foo/0']);
|
|
231
|
+
expect(cloned).toEqual({
|
|
232
|
+
'launchdarkly': {
|
|
233
|
+
'u2c': true
|
|
234
|
+
},
|
|
235
|
+
'ld': false,
|
|
236
|
+
'foo': ['bar', 'baz'],
|
|
237
|
+
'a/b': 1,
|
|
238
|
+
'm~n': 2,
|
|
239
|
+
' ': ' ',
|
|
240
|
+
'null': null
|
|
241
|
+
});
|
|
242
|
+
expect(excluded).toEqual([]);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should not allow exclude a property inside an index of an array.', () => {
|
|
246
|
+
const objWithArrayOfObjects = {
|
|
247
|
+
array: [{
|
|
248
|
+
toRemove: true,
|
|
249
|
+
toLeave: true,
|
|
250
|
+
}]
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(objWithArrayOfObjects, ['array/0/toRemove']);
|
|
254
|
+
expect(cloned).toEqual(objWithArrayOfObjects);
|
|
255
|
+
expect(excluded).toEqual([]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should not allow exclude the root object', () => {
|
|
259
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
260
|
+
'launchdarkly': {
|
|
261
|
+
'u2c': true
|
|
262
|
+
},
|
|
263
|
+
'ld': false,
|
|
264
|
+
'foo': ['bar', 'baz'],
|
|
265
|
+
'a/b': 1,
|
|
266
|
+
'm~n': 2,
|
|
267
|
+
' ': ' ',
|
|
268
|
+
'null': null
|
|
269
|
+
}, ['/']);
|
|
270
|
+
expect(cloned).toEqual({
|
|
271
|
+
'launchdarkly': {
|
|
272
|
+
'u2c': true
|
|
273
|
+
},
|
|
274
|
+
'ld': false,
|
|
275
|
+
'foo': ['bar', 'baz'],
|
|
276
|
+
'a/b': 1,
|
|
277
|
+
'm~n': 2,
|
|
278
|
+
' ': ' ',
|
|
279
|
+
'null': null
|
|
280
|
+
});
|
|
281
|
+
expect(excluded).toEqual([]);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should allow exclude a null value', () => {
|
|
285
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
286
|
+
'launchdarkly': {
|
|
287
|
+
'u2c': true
|
|
288
|
+
},
|
|
289
|
+
'ld': false,
|
|
290
|
+
'foo': ['bar', 'baz'],
|
|
291
|
+
'a/b': 1,
|
|
292
|
+
'm~n': 2,
|
|
293
|
+
' ': ' ',
|
|
294
|
+
'null': null
|
|
295
|
+
}, ['null']);
|
|
296
|
+
expect(cloned).toEqual({
|
|
297
|
+
'launchdarkly': {
|
|
298
|
+
'u2c': true
|
|
299
|
+
},
|
|
300
|
+
'ld': false,
|
|
301
|
+
'foo': ['bar', 'baz'],
|
|
302
|
+
'a/b': 1,
|
|
303
|
+
'm~n': 2,
|
|
304
|
+
' ': ' ',
|
|
305
|
+
});
|
|
306
|
+
expect(excluded).toEqual(['/null']);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should not allow exclude a value inside null', () => {
|
|
310
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
311
|
+
'launchdarkly': {
|
|
312
|
+
'u2c': true
|
|
313
|
+
},
|
|
314
|
+
'ld': false,
|
|
315
|
+
'foo': ['bar', 'baz'],
|
|
316
|
+
'a/b': 1,
|
|
317
|
+
'm~n': 2,
|
|
318
|
+
' ': ' ',
|
|
319
|
+
'null': null
|
|
320
|
+
}, ['/null/null']);
|
|
321
|
+
expect(cloned).toEqual({
|
|
322
|
+
'launchdarkly': {
|
|
323
|
+
'u2c': true
|
|
324
|
+
},
|
|
325
|
+
'ld': false,
|
|
326
|
+
'foo': ['bar', 'baz'],
|
|
327
|
+
'a/b': 1,
|
|
328
|
+
'm~n': 2,
|
|
329
|
+
' ': ' ',
|
|
330
|
+
'null': null
|
|
331
|
+
});
|
|
332
|
+
expect(excluded).toEqual([]);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should not allow exclude a value inside explicit undefined', () => {
|
|
336
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
337
|
+
'launchdarkly': {
|
|
338
|
+
'u2c': true
|
|
339
|
+
},
|
|
340
|
+
'ld': false,
|
|
341
|
+
'foo': ['bar', 'baz'],
|
|
342
|
+
'a/b': 1,
|
|
343
|
+
'm~n': 2,
|
|
344
|
+
' ': ' ',
|
|
345
|
+
'null': null
|
|
346
|
+
}, ['undefined/null']);
|
|
347
|
+
expect(cloned).toEqual({
|
|
348
|
+
'launchdarkly': {
|
|
349
|
+
'u2c': true
|
|
350
|
+
},
|
|
351
|
+
'ld': false,
|
|
352
|
+
'foo': ['bar', 'baz'],
|
|
353
|
+
'a/b': 1,
|
|
354
|
+
'm~n': 2,
|
|
355
|
+
' ': ' ',
|
|
356
|
+
'null': null
|
|
357
|
+
});
|
|
358
|
+
expect(excluded).toEqual([]);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should allow removing an explicit undefined value', () => {
|
|
362
|
+
const objToClone = { undefined: undefined };
|
|
363
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(objToClone, ['undefined']);
|
|
364
|
+
expect(cloned).toEqual({});
|
|
365
|
+
expect(excluded).toEqual(['/undefined']);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should allow removing references with escape characters', () => {
|
|
369
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
370
|
+
'launchdarkly': {
|
|
371
|
+
'u2c': true
|
|
372
|
+
},
|
|
373
|
+
'ld': false,
|
|
374
|
+
'foo': ['bar', 'baz'],
|
|
375
|
+
'a/b': 1,
|
|
376
|
+
'm~n': 2,
|
|
377
|
+
' ': ' ',
|
|
378
|
+
'null': null
|
|
379
|
+
}, ['/a~1b', '/m~0n']);
|
|
380
|
+
expect(cloned).toEqual({
|
|
381
|
+
'launchdarkly': {
|
|
382
|
+
'u2c': true
|
|
383
|
+
},
|
|
384
|
+
'ld': false,
|
|
385
|
+
'foo': ['bar', 'baz'],
|
|
386
|
+
' ': ' ',
|
|
387
|
+
'null': null
|
|
388
|
+
});
|
|
389
|
+
expect(excluded).toEqual(['/a~1b', '/m~0n']);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should allow removing literals without escape characters', () => {
|
|
393
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
394
|
+
'launchdarkly': {
|
|
395
|
+
'u2c': true
|
|
396
|
+
},
|
|
397
|
+
'ld': false,
|
|
398
|
+
'foo': ['bar', 'baz'],
|
|
399
|
+
'a/b': 1,
|
|
400
|
+
'm~n': 2,
|
|
401
|
+
' ': ' ',
|
|
402
|
+
'null': null
|
|
403
|
+
}, ['a/b', 'm~n']);
|
|
404
|
+
expect(cloned).toEqual({
|
|
405
|
+
'launchdarkly': {
|
|
406
|
+
'u2c': true
|
|
407
|
+
},
|
|
408
|
+
'ld': false,
|
|
409
|
+
'foo': ['bar', 'baz'],
|
|
410
|
+
' ': ' ',
|
|
411
|
+
'null': null
|
|
412
|
+
});
|
|
413
|
+
expect(excluded).toEqual(['/a~1b', '/m~0n']);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
it('should handle cycles', () => {
|
|
418
|
+
const item = {};
|
|
419
|
+
const objWithCycle = {
|
|
420
|
+
item,
|
|
421
|
+
name: 'test',
|
|
422
|
+
remove: 'remove'
|
|
423
|
+
};
|
|
424
|
+
item.parent = objWithCycle;
|
|
425
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(objWithCycle, ['remove']);
|
|
426
|
+
expect(cloned).toEqual({
|
|
427
|
+
item: {},
|
|
428
|
+
name: 'test',
|
|
429
|
+
});
|
|
430
|
+
expect(excluded).toEqual(['/remove']);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('should allow non-circular reference and should treat them independently for filtering', () => {
|
|
434
|
+
const item = { value: 'value' };
|
|
435
|
+
const objWithSharedPeer = {
|
|
436
|
+
item: item,
|
|
437
|
+
second: item,
|
|
438
|
+
third: item,
|
|
439
|
+
fourth: item
|
|
440
|
+
};
|
|
441
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding(objWithSharedPeer, ['third', '/second/value']);
|
|
442
|
+
expect(cloned).toEqual({
|
|
443
|
+
item: { value: 'value' },
|
|
444
|
+
second: {},
|
|
445
|
+
fourth: { value: 'value' },
|
|
446
|
+
});
|
|
447
|
+
expect(excluded).toEqual(['/second/value', '/third']);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should allow for an empty reference list', () => {
|
|
451
|
+
const { cloned, excluded } = AttributeReference.cloneExcluding({
|
|
452
|
+
'launchdarkly': {
|
|
453
|
+
'u2c': true
|
|
454
|
+
},
|
|
455
|
+
'ld': false,
|
|
456
|
+
'foo': ['bar', 'baz'],
|
|
457
|
+
'a/b': 1,
|
|
458
|
+
'm~n': 2,
|
|
459
|
+
' ': ' ',
|
|
460
|
+
'null': null
|
|
461
|
+
}, []);
|
|
462
|
+
expect(cloned).toEqual({
|
|
463
|
+
'launchdarkly': {
|
|
464
|
+
'u2c': true
|
|
465
|
+
},
|
|
466
|
+
'ld': false,
|
|
467
|
+
'foo': ['bar', 'baz'],
|
|
468
|
+
'a/b': 1,
|
|
469
|
+
'm~n': 2,
|
|
470
|
+
' ': ' ',
|
|
471
|
+
'null': null
|
|
472
|
+
});
|
|
473
|
+
expect(excluded).toEqual([]);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('when given a literal', () => {
|
|
478
|
+
it('can convert it to a reference', () => {
|
|
479
|
+
expect(AttributeReference.literalToReference("/~why")).toEqual("/~1~0why");
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it.each([
|
|
484
|
+
['kind', true],
|
|
485
|
+
['/kind', true],
|
|
486
|
+
['potato', false],
|
|
487
|
+
['/potato', false],
|
|
488
|
+
['', false],
|
|
489
|
+
[undefined, false],
|
|
490
|
+
['//', false],
|
|
491
|
+
['/', false],
|
|
492
|
+
])('can check if reference isKind', (ref, is) => {
|
|
493
|
+
expect(AttributeReference.isKind(ref)).toEqual(is);
|
|
494
|
+
});
|