@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.
Files changed (107) hide show
  1. package/.babelrc +16 -0
  2. package/.circleci/config.yml +89 -0
  3. package/.eslintignore +5 -0
  4. package/.eslintrc.yaml +114 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  8. package/.github/pull_request_template.md +21 -0
  9. package/.github/workflows/stale.yml +8 -0
  10. package/.hound.yml +33 -0
  11. package/.ldrelease/config.yml +28 -0
  12. package/.prettierrc +6 -0
  13. package/CHANGELOG.md +603 -0
  14. package/CODEOWNERS +2 -0
  15. package/CONTRIBUTING.md +55 -0
  16. package/LICENSE.txt +13 -0
  17. package/README.md +36 -0
  18. package/SECURITY.md +5 -0
  19. package/attribute_reference.js +217 -0
  20. package/big_segments.js +117 -0
  21. package/caching_store_wrapper.js +240 -0
  22. package/changes.json +30 -0
  23. package/configuration.js +235 -0
  24. package/context.js +98 -0
  25. package/context_filter.js +137 -0
  26. package/contract-tests/README.md +7 -0
  27. package/contract-tests/index.js +109 -0
  28. package/contract-tests/log.js +23 -0
  29. package/contract-tests/package.json +15 -0
  30. package/contract-tests/sdkClientEntity.js +110 -0
  31. package/contract-tests/testharness-suppressions.txt +2 -0
  32. package/diagnostic_events.js +151 -0
  33. package/docs/typedoc.js +10 -0
  34. package/errors.js +26 -0
  35. package/evaluator.js +822 -0
  36. package/event_factory.js +121 -0
  37. package/event_processor.js +320 -0
  38. package/event_summarizer.js +101 -0
  39. package/feature_store.js +120 -0
  40. package/feature_store_event_wrapper.js +258 -0
  41. package/file_data_source.js +192 -0
  42. package/flags_state.js +46 -0
  43. package/index.d.ts +2426 -0
  44. package/index.js +452 -0
  45. package/integrations.js +7 -0
  46. package/interfaces.js +2 -0
  47. package/loggers.js +125 -0
  48. package/messages.js +31 -0
  49. package/operators.js +106 -0
  50. package/package.json +105 -0
  51. package/polling.js +70 -0
  52. package/requestor.js +62 -0
  53. package/scripts/better-audit.sh +76 -0
  54. package/sharedtest/big_segment_store_tests.js +86 -0
  55. package/sharedtest/feature_store_tests.js +177 -0
  56. package/sharedtest/persistent_feature_store_tests.js +183 -0
  57. package/sharedtest/store_tests.js +7 -0
  58. package/streaming.js +179 -0
  59. package/test/LDClient-big-segments-test.js +92 -0
  60. package/test/LDClient-end-to-end-test.js +218 -0
  61. package/test/LDClient-evaluation-all-flags-test.js +226 -0
  62. package/test/LDClient-evaluation-test.js +204 -0
  63. package/test/LDClient-events-test.js +502 -0
  64. package/test/LDClient-listeners-test.js +180 -0
  65. package/test/LDClient-test.js +96 -0
  66. package/test/LDClient-tls-test.js +110 -0
  67. package/test/attribute_reference-test.js +494 -0
  68. package/test/big_segments-test.js +182 -0
  69. package/test/caching_store_wrapper-test.js +434 -0
  70. package/test/configuration-test.js +249 -0
  71. package/test/context-test.js +93 -0
  72. package/test/context_filter-test.js +424 -0
  73. package/test/diagnostic_events-test.js +152 -0
  74. package/test/evaluator-big-segments-test.js +301 -0
  75. package/test/evaluator-bucketing-test.js +333 -0
  76. package/test/evaluator-clause-test.js +277 -0
  77. package/test/evaluator-flag-test.js +452 -0
  78. package/test/evaluator-pre-conditions-test.js +105 -0
  79. package/test/evaluator-rule-test.js +131 -0
  80. package/test/evaluator-segment-match-test.js +310 -0
  81. package/test/evaluator_helpers.js +106 -0
  82. package/test/event_processor-test.js +680 -0
  83. package/test/event_summarizer-test.js +146 -0
  84. package/test/feature_store-test.js +42 -0
  85. package/test/feature_store_event_wrapper-test.js +182 -0
  86. package/test/feature_store_test_base.js +60 -0
  87. package/test/file_data_source-test.js +255 -0
  88. package/test/loggers-test.js +126 -0
  89. package/test/operators-test.js +102 -0
  90. package/test/polling-test.js +158 -0
  91. package/test/requestor-test.js +60 -0
  92. package/test/store_tests_big_segments-test.js +61 -0
  93. package/test/streaming-test.js +323 -0
  94. package/test/stubs.js +107 -0
  95. package/test/test_data-test.js +341 -0
  96. package/test/update_queue-test.js +61 -0
  97. package/test-types.ts +210 -0
  98. package/test_data.js +323 -0
  99. package/tsconfig.json +14 -0
  100. package/update_queue.js +28 -0
  101. package/utils/__tests__/httpUtils-test.js +39 -0
  102. package/utils/__tests__/wrapPromiseCallback-test.js +33 -0
  103. package/utils/asyncUtils.js +32 -0
  104. package/utils/httpUtils.js +105 -0
  105. package/utils/stringifyAttrs.js +14 -0
  106. package/utils/wrapPromiseCallback.js +36 -0
  107. package/versioned_data_kind.js +34 -0
@@ -0,0 +1,323 @@
1
+ const { DiagnosticId, DiagnosticsManager } = require('../diagnostic_events');
2
+ const InMemoryFeatureStore = require('../feature_store');
3
+ const StreamProcessor = require('../streaming');
4
+ import * as httpUtils from '../utils/httpUtils';
5
+ const dataKind = require('../versioned_data_kind');
6
+
7
+ const { failOnResolve, failOnTimeout, promisifySingle } = require('launchdarkly-js-test-helpers');
8
+ const stubs = require('./stubs');
9
+
10
+ describe('StreamProcessor', () => {
11
+ const sdkKey = 'SDK_KEY';
12
+
13
+ function fakeEventSource() {
14
+ var es = { handlers: {} };
15
+ es.constructor = function(url, options) {
16
+ es.url = url;
17
+ es.options = options;
18
+ this.addEventListener = (type, handler) => {
19
+ es.handlers[type] = handler;
20
+ };
21
+ this.close = () => {
22
+ es.closed = true;
23
+ };
24
+ this.simulateError = e => {
25
+ const shouldRetry = es.options.errorFilter(e);
26
+ if (!shouldRetry) {
27
+ es.closed = true;
28
+ }
29
+ }
30
+ es.instance = this;
31
+ };
32
+ return es;
33
+ }
34
+
35
+ function createProcessor(config, es, diagnosticsManager) {
36
+ return StreamProcessor(sdkKey, config, null, diagnosticsManager, es.constructor);
37
+ }
38
+
39
+ function expectJsonError(err, config) {
40
+ expect(err).not.toBe(undefined);
41
+ expect(err.message).toEqual('Malformed JSON data in event stream');
42
+ expect(config.logger.error).toHaveBeenCalled();
43
+ }
44
+
45
+ it('uses expected URL', function() {
46
+ var config = { streamUri: 'http://test' };
47
+ var es = fakeEventSource();
48
+ var sp = createProcessor(config, es);
49
+ sp.start();
50
+ expect(es.url).toEqual(config.streamUri + '/all');
51
+ });
52
+
53
+ it('sets expected headers', function() {
54
+ var config = { streamUri: 'http://test' };
55
+ var es = fakeEventSource();
56
+ var sp = createProcessor(config, es);
57
+ sp.start();
58
+ expect(es.options.headers).toMatchObject(httpUtils.getDefaultHeaders(sdkKey, config));
59
+ });
60
+
61
+ describe('put message', function() {
62
+ var putData = {
63
+ data: {
64
+ flags: {
65
+ flagkey: { key: 'flagkey', version: 1 }
66
+ },
67
+ segments: {
68
+ segkey: { key: 'segkey', version: 2 }
69
+ }
70
+ }
71
+ };
72
+
73
+ it('causes flags and segments to be stored', async () => {
74
+ var featureStore = InMemoryFeatureStore();
75
+ var config = { featureStore: featureStore, logger: stubs.stubLogger() };
76
+ var es = fakeEventSource();
77
+ var sp = createProcessor(config, es);
78
+ sp.start();
79
+
80
+ es.handlers.put({ data: JSON.stringify(putData) });
81
+
82
+ var flag = await promisifySingle(featureStore.initialized)();
83
+ expect(flag).toEqual(true);
84
+
85
+ var f = await promisifySingle(featureStore.get)(dataKind.features, 'flagkey');
86
+ expect(f.version).toEqual(1);
87
+ var s = await promisifySingle(featureStore.get)(dataKind.segments, 'segkey');
88
+ expect(s.version).toEqual(2);
89
+ });
90
+
91
+ it('calls initialization callback', async () => {
92
+ var featureStore = InMemoryFeatureStore();
93
+ var config = { featureStore: featureStore, logger: stubs.stubLogger() };
94
+ var es = fakeEventSource();
95
+ var sp = createProcessor(config, es);
96
+
97
+ var waitUntilStarted = promisifySingle(sp.start)();
98
+ es.handlers.put({ data: JSON.stringify(putData) });
99
+ var result = await waitUntilStarted;
100
+ expect(result).toBe(undefined);
101
+ });
102
+
103
+ it('passes error to callback if data is invalid', async () => {
104
+ var featureStore = InMemoryFeatureStore();
105
+ var config = { featureStore: featureStore, logger: stubs.stubLogger() };
106
+ var es = fakeEventSource();
107
+ var sp = createProcessor(config, es);
108
+
109
+ var waitUntilStarted = promisifySingle(sp.start)();
110
+ es.handlers.put({ data: '{not-good' });
111
+ var result = await waitUntilStarted;
112
+ expectJsonError(result, config);
113
+ });
114
+
115
+ it('updates diagnostic stats', async () => {
116
+ const featureStore = InMemoryFeatureStore();
117
+ const config = { featureStore: featureStore, logger: stubs.stubLogger() };
118
+
119
+ const id = DiagnosticId('sdk-key');
120
+ const manager = DiagnosticsManager(config, id, 100000);
121
+ const startTime = new Date().getTime();
122
+
123
+ const es = fakeEventSource();
124
+ const sp = createProcessor(config, es, manager);
125
+
126
+ const waitUntilStarted = promisifySingle(sp.start)();
127
+ es.handlers.put({ data: JSON.stringify(putData) });
128
+ await waitUntilStarted;
129
+
130
+ const event = manager.createStatsEventAndReset(0, 0, 0);
131
+ expect(event.streamInits.length).toEqual(1);
132
+ const si = event.streamInits[0];
133
+ expect(si.timestamp).toBeGreaterThanOrEqual(startTime);
134
+ expect(si.failed).not.toBeTruthy();
135
+ expect(si.durationMillis).toBeGreaterThanOrEqual(0);
136
+ });
137
+ });
138
+
139
+ describe('patch message', function() {
140
+ it('updates flag', async () => {
141
+ var featureStore = InMemoryFeatureStore();
142
+ var config = { featureStore: featureStore, logger: stubs.stubLogger() };
143
+ var es = fakeEventSource();
144
+ var sp = createProcessor(config, es);
145
+
146
+ var patchData = {
147
+ path: '/flags/flagkey',
148
+ data: { key: 'flagkey', version: 1 }
149
+ };
150
+
151
+ sp.start();
152
+ es.handlers.patch({ data: JSON.stringify(patchData) });
153
+
154
+ var f = await promisifySingle(featureStore.get)(dataKind.features, 'flagkey');
155
+ expect(f.version).toEqual(1);
156
+ });
157
+
158
+ it('updates segment', async () => {
159
+ var featureStore = InMemoryFeatureStore();
160
+ var config = { featureStore: featureStore, logger: stubs.stubLogger() };
161
+ var es = fakeEventSource();
162
+ var sp = createProcessor(config, es);
163
+
164
+ var patchData = {
165
+ path: '/segments/segkey',
166
+ data: { key: 'segkey', version: 1 }
167
+ };
168
+
169
+ sp.start();
170
+ es.handlers.patch({ data: JSON.stringify(patchData) });
171
+
172
+ var s = await promisifySingle(featureStore.get)(dataKind.segments, 'segkey');
173
+ expect(s.version).toEqual(1);
174
+ });
175
+
176
+ it('passes error to callback if data is invalid', async () => {
177
+ var featureStore = InMemoryFeatureStore();
178
+ var config = { featureStore: featureStore, logger: stubs.stubLogger() };
179
+ var es = fakeEventSource();
180
+ var sp = createProcessor(config, es);
181
+
182
+ var waitForCallback = promisifySingle(sp.start)();
183
+ es.handlers.patch({ data: '{not-good' });
184
+ var result = await waitForCallback;
185
+ expectJsonError(result, config);
186
+ });
187
+ });
188
+
189
+ describe('delete message', function() {
190
+ it('deletes flag', async () => {
191
+ var featureStore = InMemoryFeatureStore();
192
+ var config = { featureStore: featureStore, logger: stubs.stubLogger() };
193
+ var es = fakeEventSource();
194
+ var sp = createProcessor(config, es);
195
+
196
+ sp.start();
197
+
198
+ var flag = { key: 'flagkey', version: 1 }
199
+ await promisifySingle(featureStore.upsert)(dataKind.features, flag);
200
+ var f = await promisifySingle(featureStore.get)(dataKind.features, flag.key);
201
+ expect(f).toEqual(flag);
202
+
203
+ var deleteData = { path: '/flags/' + flag.key, version: 2 };
204
+ es.handlers.delete({ data: JSON.stringify(deleteData) });
205
+
206
+ var f = await promisifySingle(featureStore.get)(dataKind.features, flag.key);
207
+ expect(f).toBe(null);
208
+ });
209
+
210
+ it('deletes segment', async () => {
211
+ var featureStore = InMemoryFeatureStore();
212
+ var config = { featureStore: featureStore, logger: stubs.stubLogger() };
213
+ var es = fakeEventSource();
214
+ var sp = createProcessor(config, es);
215
+
216
+ sp.start();
217
+
218
+ var segment = { key: 'segkey', version: 1 }
219
+ await promisifySingle(featureStore.upsert)(dataKind.segments, segment);
220
+ var s = await promisifySingle(featureStore.get)(dataKind.segments, segment.key);
221
+ expect(s).toEqual(segment);
222
+
223
+ var deleteData = { path: '/segments/' + segment.key, version: 2 };
224
+ es.handlers.delete({ data: JSON.stringify(deleteData) });
225
+
226
+ s = await promisifySingle(featureStore.get)(dataKind.segments, segment.key);
227
+ expect(s).toBe(null);
228
+ });
229
+
230
+ it('passes error to callback if data is invalid', async () => {
231
+ var featureStore = InMemoryFeatureStore();
232
+ var config = { featureStore: featureStore, logger: stubs.stubLogger() };
233
+ var es = fakeEventSource();
234
+ var sp = createProcessor(config, es);
235
+
236
+ var waitForResult = promisifySingle(sp.start)();
237
+ es.handlers.delete({ data: '{not-good' });
238
+ var result = await waitForResult;
239
+ expectJsonError(result, config);
240
+ });
241
+ });
242
+
243
+ async function testRecoverableError(err) {
244
+ const logCapture = stubs.asyncLogCapture();
245
+ const featureStore = InMemoryFeatureStore();
246
+ const config = { featureStore: featureStore, logger: logCapture.logger };
247
+ const id = DiagnosticId('sdk-key');
248
+ const manager = DiagnosticsManager(config, id, 100000);
249
+ const startTime = new Date().getTime();
250
+
251
+ const es = fakeEventSource();
252
+ const sp = createProcessor(config, es, manager);
253
+
254
+ // Note that the callback for start() will *not* be called; it only reports failure and provides the
255
+ // error object if it's an unrecovable error.
256
+ const waitForStart = promisifySingle(sp.start)();
257
+
258
+ es.instance.simulateError(err);
259
+
260
+ await failOnTimeout(logCapture.warn.take(), 1000, 'timed out waiting for log message');
261
+
262
+ await failOnResolve(waitForStart, 50, 'initialization completed unexpectedly');
263
+ await failOnResolve(logCapture.error.take(), 50, 'got unexpected log error');
264
+
265
+ expect(es.closed).not.toBeTruthy();
266
+
267
+ const event = manager.createStatsEventAndReset(0, 0, 0);
268
+ expect(event.streamInits.length).toEqual(1);
269
+ const si = event.streamInits[0];
270
+ expect(si.timestamp).toBeGreaterThanOrEqual(startTime);
271
+ expect(si.failed).toBeTruthy();
272
+ expect(si.durationMillis).toBeGreaterThanOrEqual(0);
273
+ }
274
+
275
+ it.each([400, 408, 429, 500, 503])(
276
+ 'continues retrying after error %d',
277
+ async (status) => {
278
+ const err = new Error('sorry');
279
+ err.status = status;
280
+ await testRecoverableError(err);
281
+ }
282
+ );
283
+
284
+ it('continues retrying after I/O error', async () => await testRecoverableError(new Error('sorry')));
285
+
286
+ async function testUnrecoverableError(err) {
287
+ const logCapture = stubs.asyncLogCapture();
288
+ const featureStore = InMemoryFeatureStore();
289
+ const config = { featureStore: featureStore, logger: logCapture.logger };
290
+ const id = DiagnosticId('sdk-key');
291
+ const manager = DiagnosticsManager(config, id, 100000);
292
+ const startTime = new Date().getTime();
293
+
294
+ const es = fakeEventSource();
295
+ const sp = createProcessor(config, es, manager);
296
+
297
+ const waitForStart = promisifySingle(sp.start)();
298
+ es.instance.simulateError(err);
299
+ const errReceived = await failOnTimeout(waitForStart, 1000, 'timed out waiting for error result');
300
+
301
+ expect(errReceived).toEqual(err);
302
+
303
+ await failOnTimeout(logCapture.error.take(), 50, 'timed out waiting for log error');
304
+
305
+ expect(es.closed).toBe(true);
306
+
307
+ const event = manager.createStatsEventAndReset(0, 0, 0);
308
+ expect(event.streamInits.length).toEqual(1);
309
+ const si = event.streamInits[0];
310
+ expect(si.timestamp).toBeGreaterThanOrEqual(startTime);
311
+ expect(si.failed).toBeTruthy();
312
+ expect(si.durationMillis).toBeGreaterThanOrEqual(0);
313
+ }
314
+
315
+ it.each([401, 403])(
316
+ 'stops retrying after error %d',
317
+ async (status) => {
318
+ const err = new Error('sorry');
319
+ err.status = status;
320
+ await testUnrecoverableError(err);
321
+ }
322
+ );
323
+ });
package/test/stubs.js ADDED
@@ -0,0 +1,107 @@
1
+ const { withCloseable } = require('launchdarkly-js-test-helpers');
2
+ var InMemoryFeatureStore = require('../feature_store');
3
+ var LDClient = require('../index.js');
4
+ var dataKind = require('../versioned_data_kind');
5
+
6
+ import { AsyncQueue } from 'launchdarkly-js-test-helpers';
7
+
8
+ import { format } from 'util';
9
+
10
+ function stubEventProcessor() {
11
+ var eventProcessor = {
12
+ events: [],
13
+ sendEvent: function(event) {
14
+ eventProcessor.events.push(event);
15
+ },
16
+ flush: function(callback) {
17
+ if (callback) {
18
+ setImmediate(callback);
19
+ } else {
20
+ return Promise.resolve(null);
21
+ }
22
+ },
23
+ close: function() {}
24
+ };
25
+ return eventProcessor;
26
+ }
27
+
28
+ function stubLogger() {
29
+ return {
30
+ debug: jest.fn(),
31
+ info: jest.fn(),
32
+ warn: jest.fn(),
33
+ error: jest.fn()
34
+ };
35
+ }
36
+
37
+ function asyncLogCapture() {
38
+ const logCapture = {all: new AsyncQueue()};
39
+ const logger = {};
40
+ for (const level of ['debug', 'info', 'warn', 'error']) {
41
+ logCapture[level] = new AsyncQueue();
42
+ logger[level] = function(fmt, ...args) {
43
+ const message = format(fmt, ...args);
44
+ logCapture[level].add(message);
45
+ logCapture.all.add({ level: level, message: message });
46
+ }
47
+ }
48
+ logCapture.logger = logger;
49
+ return logCapture;
50
+ }
51
+
52
+ function stubUpdateProcessor() {
53
+ var updateProcessor = {
54
+ start: function(callback) {
55
+ if (updateProcessor.shouldInitialize) {
56
+ setImmediate(callback, updateProcessor.error);
57
+ }
58
+ },
59
+ shouldInitialize: true
60
+ };
61
+ return updateProcessor;
62
+ }
63
+
64
+ function createClient(overrideOptions) {
65
+ var defaults = {
66
+ eventProcessor: stubEventProcessor(),
67
+ updateProcessor: stubUpdateProcessor(),
68
+ logger: stubLogger()
69
+ };
70
+ return LDClient.init('secret', Object.assign({}, defaults, overrideOptions));
71
+ }
72
+
73
+ async function withClient(overrideOptions, callback) {
74
+ return withCloseable(createClient(overrideOptions), callback);
75
+ }
76
+
77
+ function initializedStoreWithFlags(...flags) {
78
+ const flagsMap = {};
79
+ for (const f of flags) {
80
+ flagsMap[f.key] = f;
81
+ }
82
+ const store = InMemoryFeatureStore();
83
+ store.init({
84
+ [dataKind.features.namespace]: flagsMap,
85
+ [dataKind.segments.namespace]: {}
86
+ });
87
+ return store;
88
+ }
89
+
90
+ function uninitializedStoreWithFlags(...flags) {
91
+ const store = InMemoryFeatureStore();
92
+ for (const f of flags) {
93
+ store.upsert(dataKind.features, f);
94
+ }
95
+ return store;
96
+ }
97
+
98
+ module.exports = {
99
+ asyncLogCapture,
100
+ createClient,
101
+ initializedStoreWithFlags,
102
+ stubEventProcessor,
103
+ stubLogger,
104
+ stubUpdateProcessor,
105
+ uninitializedStoreWithFlags,
106
+ withClient,
107
+ };