@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,126 @@
1
+ const loggers = require('../loggers');
2
+
3
+ describe('basicLogger', () => {
4
+ it('uses console.error by default', () => {
5
+ const realConsoleError = console.error;
6
+ const mock = jest.fn();
7
+ try {
8
+ console.error = mock;
9
+ const logger = loggers.basicLogger();
10
+ logger.warn('hello');
11
+ expect(mock).toHaveBeenCalledWith('warn: [LaunchDarkly] hello');
12
+ } finally {
13
+ console.error = realConsoleError;
14
+ }
15
+ });
16
+
17
+ it('can write to an arbitrary function', () => {
18
+ const outputFn = jest.fn();
19
+ const logger = loggers.basicLogger({ destination: outputFn });
20
+ logger.warn('hello');
21
+ expect(outputFn).toHaveBeenCalledWith('warn: [LaunchDarkly] hello');
22
+ });
23
+
24
+ it('throws an exception immediately if destination is not a function', () => {
25
+ expect(() => loggers.basicLogger({ destination: 'Mars' })).toThrow();
26
+ });
27
+
28
+ it('does not use util.format if there is only one argument', () => {
29
+ const outputFn = jest.fn();
30
+ const logger = loggers.basicLogger({ destination: outputFn });
31
+ logger.warn('%d things');
32
+ expect(outputFn).toHaveBeenCalledWith('warn: [LaunchDarkly] %d things');
33
+ });
34
+
35
+ it('does not use util.format if there are multiple arguments', () => {
36
+ const outputFn = jest.fn();
37
+ const logger = loggers.basicLogger({ destination: outputFn });
38
+ logger.warn('%d things', 3);
39
+ expect(outputFn).toHaveBeenCalledWith('warn: [LaunchDarkly] 3 things');
40
+ });
41
+
42
+ describe('output filtering by level', () => {
43
+ const testLevel = (minLevel, enabledLevels) => {
44
+ it('level: ' + minLevel, () => {
45
+ const outputFn = jest.fn();
46
+ const config = { destination: outputFn };
47
+ if (minLevel) {
48
+ config.level = minLevel;
49
+ }
50
+ const logger = loggers.basicLogger({ level: minLevel, destination: outputFn });
51
+ logger.debug('some debug output');
52
+ logger.info('some info output');
53
+ logger.warn('some warn output');
54
+ logger.error('some error output');
55
+ for (const [level, shouldBeEnabled] of Object.entries(enabledLevels)) {
56
+ const line = level + ': [LaunchDarkly] some ' + level + ' output';
57
+ if (shouldBeEnabled) {
58
+ expect(outputFn).toHaveBeenCalledWith(line);
59
+ } else {
60
+ expect(outputFn).not.toHaveBeenCalledWith(line);
61
+ }
62
+ }
63
+ });
64
+ };
65
+
66
+ testLevel('debug', { 'debug': true, 'info': true, 'warn': true, 'error': true });
67
+ testLevel('info', { 'debug': false, 'info': true, 'warn': true, 'error': true });
68
+ testLevel('warn', { 'debug': false, 'info': false, 'warn': true, 'error': true });
69
+ testLevel('error', { 'debug': false, 'info': false, 'warn': false, 'error': true });
70
+ testLevel('none', { 'debug': false, 'info': false, 'warn': false, 'error': false });
71
+
72
+ // default is info
73
+ testLevel(undefined, { 'debug': false, 'info': true, 'warn': true, 'error': true });
74
+ });
75
+ });
76
+
77
+ describe('safeLogger', () => {
78
+ function mockLogger() {
79
+ return {
80
+ error: jest.fn(),
81
+ warn: jest.fn(),
82
+ info: jest.fn(),
83
+ debug: jest.fn(),
84
+ };
85
+ }
86
+
87
+ const levels = ['error', 'warn', 'info', 'debug'];
88
+
89
+ it('throws an error if you pass in a logger that does not conform to the LDLogger schema', () => {
90
+ const fallbackLogger = mockLogger();
91
+
92
+ // If the method does not exist
93
+ levels.forEach(method => {
94
+ const logger = mockLogger();
95
+ delete logger[method];
96
+ expect(() => loggers.safeLogger(logger, fallbackLogger)).toThrow(/Provided logger instance must support .* method/);
97
+ });
98
+
99
+ // If the method is not a function
100
+ levels.forEach(method => {
101
+ const logger = mockLogger();
102
+ logger[method] = 'invalid';
103
+ expect(() => loggers.safeLogger(logger, fallbackLogger)).toThrow(/Provided logger instance must support .* method/);
104
+ });
105
+ });
106
+
107
+ it('If a logger method throws an error, the error is caught and logged, then the fallback logger is called', () => {
108
+ const err = Error('Something bad happened');
109
+
110
+ levels.forEach(level => {
111
+ const logger = mockLogger();
112
+ logger[level] = jest.fn(() => {
113
+ throw err
114
+ });
115
+ const fallbackLogger = mockLogger();
116
+ const wrappedLogger = loggers.safeLogger(logger, fallbackLogger);
117
+
118
+ expect(() => wrappedLogger[level]('this is a logline', 'with multiple', 'arguments')).not.toThrow();
119
+
120
+ expect(fallbackLogger.error).toHaveBeenNthCalledWith(1, 'Error calling provided logger instance method ' + level + ': ' + err);
121
+
122
+ const nthCall = level === 'error' ? 2 : 1;
123
+ expect(fallbackLogger[level]).toHaveBeenNthCalledWith(nthCall, 'this is a logline', 'with multiple', 'arguments');
124
+ });
125
+ });
126
+ });
@@ -0,0 +1,102 @@
1
+ var assert = require('assert');
2
+ var operators = require('../operators');
3
+
4
+ describe('operators', function() {
5
+ const paramsTable = [
6
+ // numeric comparisons
7
+ [ 'in', 99, 99, true ],
8
+ [ 'in', 99.0001, 99.0001, true ],
9
+ [ 'in', 99, 99.0001, false ],
10
+ [ 'in', 99.0001, 99, false ],
11
+ [ 'lessThan', 99, 99.0001, true ],
12
+ [ 'lessThan', 99.0001, 99, false ],
13
+ [ 'lessThan', 99, 99, false ],
14
+ [ 'lessThanOrEqual', 99, 99.0001, true ],
15
+ [ 'lessThanOrEqual', 99.0001, 99, false ],
16
+ [ 'lessThanOrEqual', 99, 99, true ],
17
+ [ 'greaterThan', 99.0001, 99, true ],
18
+ [ 'greaterThan', 99, 99.0001, false ],
19
+ [ 'greaterThan', 99, 99, false ],
20
+ [ 'greaterThanOrEqual', 99.0001, 99, true ],
21
+ [ 'greaterThanOrEqual', 99, 99.0001, false ],
22
+ [ 'greaterThanOrEqual', 99, 99, true ],
23
+
24
+ // string comparisons
25
+ [ 'in', 'x', 'x', true ],
26
+ [ 'in', 'x', 'xyz', false ],
27
+ [ 'startsWith', 'xyz', 'x', true ],
28
+ [ 'startsWith', 'x', 'xyz', false ],
29
+ [ 'endsWith', 'xyz', 'z', true ],
30
+ [ 'endsWith', 'z', 'xyz', false ],
31
+ [ 'contains', 'xyz', 'y', true ],
32
+ [ 'contains', 'y', 'xyz', false ],
33
+
34
+ // mixed strings and numbers
35
+ [ 'in', '99', 99, false ],
36
+ [ 'in', 99, '99', false ],
37
+ [ 'contains', '99', 99, false ],
38
+ [ 'startsWith', '99', 99, false ],
39
+ [ 'endsWith', '99', 99, false ],
40
+ [ 'lessThanOrEqual', '99', 99, false ],
41
+ [ 'lessThanOrEqual', 99, '99', false ],
42
+ [ 'greaterThanOrEqual', '99', 99, false ],
43
+ [ 'greaterThanOrEqual', 99, '99', false ],
44
+
45
+ // regex
46
+ [ 'matches', 'hello world', 'hello.*rld', true ],
47
+ [ 'matches', 'hello world', 'hello.*rl', true ],
48
+ [ 'matches', 'hello world', 'l+', true ],
49
+ [ 'matches', 'hello world', '(world|planet)', true ],
50
+ [ 'matches', 'hello world', 'aloha', false ],
51
+ [ 'matches', 'hello world', '***not a regex', false ],
52
+ [ 'matches', 'hello world', 3, false ],
53
+ [ 'matches', 3, 'hello', false ],
54
+
55
+ // dates
56
+ [ 'before', 0, 1, true ],
57
+ [ 'before', -100, 0, true ],
58
+ [ 'before', '1970-01-01T00:00:00Z', 1000, true ],
59
+ [ 'before', '1970-01-01T00:00:00.500Z', 1000, true ],
60
+ [ 'before', true, 1000, false ], // wrong type
61
+ [ 'after', '1970-01-01T00:00:02.500Z', 1000, true ],
62
+ [ 'after', '1970-01-01 00:00:02.500Z', 1000, false ], // malformed timestamp
63
+ [ 'before', '1970-01-01T00:00:02+01:00', 1000, true ],
64
+ [ 'before', -1000, 1000, true ],
65
+ [ 'after', '1970-01-01T00:00:01.001Z', 1000, true ],
66
+ [ 'after', '1970-01-01T00:00:00-01:00', 1000, true ],
67
+
68
+ // semver
69
+ [ 'semVerEqual', '2.0.1', '2.0.1', true ],
70
+ [ 'semVerEqual', '2.0.1', '02.0.1', false ], // leading zeroes should be disallowed
71
+ [ 'semVerEqual', '2.0', '2.0.0', true ],
72
+ [ 'semVerEqual', '2', '2.0.0', true ],
73
+ [ 'semVerEqual', '2-rc1', '2.0.0-rc1', true ],
74
+ [ 'semVerEqual', '2+build2', '2.0.0+build2', true ],
75
+ [ 'semVerEqual', '2.0.0', '2.0.0+build2', true ], // build metadata should be ignored in comparison
76
+ [ 'semVerEqual', '2.0.0', '2.0.0-rc1', false ], // prerelease should not be ignored
77
+ [ 'semVerEqual', '2.0.0', '2.0.0+build_2', false ], // enforce allowable character set in build metadata
78
+ [ 'semVerEqual', '2.0.0', 'v2.0.0', false ], // disallow leading 'v'
79
+ [ 'semVerLessThan', '2.0.0', '2.0.1', true ],
80
+ [ 'semVerLessThan', '2.0', '2.0.1', true ],
81
+ [ 'semVerLessThan', '2.0.1', '2.0.0', false ],
82
+ [ 'semVerLessThan', '2.0.1', '2.0', false ],
83
+ [ 'semVerLessThan', '2.0.0-rc', '2.0.0-rc.beta', true ],
84
+ [ 'semVerLessThan', '2.0.0-rc', '2.0.0', true ],
85
+ [ 'semVerLessThan', '2.0.0-rc.3', '2.0.0-rc.29', true ],
86
+ [ 'semVerLessThan', '2.0.0-rc.x29', '2.0.0-rc.x3', true ],
87
+ [ 'semVerGreaterThan', '2.0.1', '2.0.0', true ],
88
+ [ 'semVerGreaterThan', '2.0.1', '2.0', true ],
89
+ [ 'semVerGreaterThan', '2.0.0', '2.0.1', false ],
90
+ [ 'semVerGreaterThan', '2.0', '2.0.1', false ],
91
+ [ 'semVerGreaterThan', '2.0.0-rc.1', '2.0.0-rc.0', true ],
92
+ [ 'semVerLessThan', '2.0.1', 'xbad%ver', false ],
93
+ [ 'semVerGreaterThan', '2.0.1', 'xbad%ver', false ]
94
+ ];
95
+
96
+ paramsTable.forEach(function(params) {
97
+ it('result is ' + params[3] + ' for ' + JSON.stringify(params[1]) + ' ' + params[0] + ' ' +
98
+ JSON.stringify(params[2]), function() {
99
+ assert.equal(operators.fn(params[0])(params[1], params[2]), params[3]);
100
+ });
101
+ });
102
+ });
@@ -0,0 +1,158 @@
1
+ const InMemoryFeatureStore = require('../feature_store');
2
+ const PollingProcessor = require('../polling');
3
+ const dataKind = require('../versioned_data_kind');
4
+ const { AsyncQueue, failOnResolve, failOnTimeout, promisify, promisifySingle } = require('launchdarkly-js-test-helpers');
5
+ const stubs = require('./stubs');
6
+
7
+ describe('PollingProcessor', () => {
8
+ const longInterval = 100000;
9
+ const allData = { flags: { flag: { version: 1 } }, segments: { segment: { version: 1 } } };
10
+ const jsonData = JSON.stringify(allData);
11
+
12
+ let store;
13
+ let config;
14
+ let processor;
15
+
16
+ beforeEach(() => {
17
+ store = InMemoryFeatureStore();
18
+ config = { featureStore: store, pollInterval: longInterval, logger: stubs.stubLogger() };
19
+ });
20
+
21
+ afterEach(() => {
22
+ processor && processor.stop();
23
+ });
24
+
25
+ it('makes no request before start', () => {
26
+ const requestor = {
27
+ requestAllData: jest.fn()
28
+ };
29
+ processor = PollingProcessor(config, requestor);
30
+
31
+ expect(requestor.requestAllData).not.toHaveBeenCalled();
32
+ });
33
+
34
+ it('polls immediately on start', () => {
35
+ const requestor = {
36
+ requestAllData: jest.fn()
37
+ };
38
+ processor = PollingProcessor(config, requestor);
39
+
40
+ processor.start(() => {});
41
+
42
+ expect(requestor.requestAllData).toHaveBeenCalledTimes(1);
43
+ });
44
+
45
+ it('calls callback on success', async () => {
46
+ const requestor = {
47
+ requestAllData: cb => cb(null, jsonData)
48
+ };
49
+ processor = PollingProcessor(config, requestor);
50
+
51
+ const err = await new Promise(resolve => processor.start(resolve));
52
+ expect(err).not.toBe(expect.anything());
53
+ });
54
+
55
+ it('initializes feature store', async () => {
56
+ const requestor = {
57
+ requestAllData: cb => cb(null, jsonData)
58
+ };
59
+ processor = PollingProcessor(config, requestor);
60
+
61
+ await promisify(processor.start)();
62
+
63
+ const flags = await promisifySingle(store.all)(dataKind.features);
64
+ expect(flags).toEqual(allData.flags);
65
+ const segments = await promisifySingle(store.all)(dataKind.segments);
66
+ expect(segments).toEqual(allData.segments);
67
+ processor.stop();
68
+ });
69
+
70
+ it('polls repeatedly', async() => {
71
+ const calls = new AsyncQueue();
72
+ const requestor = {
73
+ requestAllData: cb => {
74
+ calls.add();
75
+ cb(null, jsonData);
76
+ }
77
+ };
78
+ config.pollInterval = 0.05; // note, pollInterval is in seconds
79
+ processor = PollingProcessor(config, requestor);
80
+
81
+ processor.start(() => {});
82
+ const startTime = new Date().getTime();
83
+ for (let i = 0; i < 4; i++) {
84
+ await failOnTimeout(calls.take(), 500, 'timed out waiting for poll request #' + (i + 1));
85
+ }
86
+ expect(new Date().getTime() - startTime).toBeLessThanOrEqual(500);
87
+ });
88
+
89
+ async function testRecoverableError(err) {
90
+ const calls = new AsyncQueue();
91
+ let count = 0;
92
+ const requestor = {
93
+ // The first two calls will return the error; the third will succeed.
94
+ requestAllData: cb => {
95
+ calls.add();
96
+ count++;
97
+ if (count > 2) {
98
+ cb(null, jsonData);
99
+ } else {
100
+ cb(err);
101
+ }
102
+ }
103
+ };
104
+ config.pollInterval = 0.05;
105
+ processor = PollingProcessor(config, requestor);
106
+
107
+ let errReceived;
108
+ processor.start(e => { errReceived = e; });
109
+
110
+ for (let i = 0; i < 3; i++) {
111
+ await failOnTimeout(calls.take(), 500, 'timed out waiting for poll request #' + (i + 1));
112
+ }
113
+
114
+ expect(config.logger.error).not.toHaveBeenCalled();
115
+ expect(errReceived).toBeUndefined();
116
+ }
117
+
118
+ it.each([400, 408, 429, 500, 503])(
119
+ 'continues polling after error %d',
120
+ async (status) => {
121
+ const err = new Error('sorry');
122
+ err.status = status;
123
+ await testRecoverableError(err);
124
+ }
125
+ );
126
+
127
+ it('continues polling after I/O error', async () => await testRecoverableError(new Error('sorry')));
128
+
129
+ async function testUnrecoverableError(status) {
130
+ const err = new Error('sorry');
131
+ err.status = status;
132
+
133
+ const calls = new AsyncQueue();
134
+ const requestor = {
135
+ requestAllData: cb => {
136
+ calls.add();
137
+ cb(err);
138
+ }
139
+ };
140
+ config.pollInterval = 0.05;
141
+ processor = PollingProcessor(config, requestor);
142
+
143
+ const result = new AsyncQueue();
144
+ processor.start(e => result.add(e));
145
+
146
+ const errReceived = await failOnTimeout(result.take(), 1000, 'timed out waiting for initialization to complete');
147
+ expect(errReceived.message).toMatch(new RegExp('error ' + status + '.*giving up permanently'));
148
+
149
+ await failOnTimeout(calls.take(), 10, 'expected initial poll request but did not see one');
150
+ await failOnResolve(calls.take(), 100, 'received unexpected second poll request');
151
+ expect(config.logger.error).toHaveBeenCalledTimes(1);
152
+ }
153
+
154
+ it.each([401, 403])(
155
+ 'stops polling after error %d',
156
+ testUnrecoverableError
157
+ );
158
+ });
@@ -0,0 +1,60 @@
1
+ import Requestor from '../requestor';
2
+ import * as httpUtils from '../utils/httpUtils';
3
+ import * as dataKind from '../versioned_data_kind';
4
+
5
+ import { promisify, TestHttpHandlers, TestHttpServer, withCloseable } from 'launchdarkly-js-test-helpers';
6
+
7
+ describe('Requestor', () => {
8
+ const sdkKey = 'x';
9
+ const badUri = 'http://bad-uri';
10
+ const someData = { key: { version: 1 } };
11
+ const allData = { flags: someData, segments: someData };
12
+
13
+ describe('requestAllData', () => {
14
+ it('gets data', async () =>
15
+ await withCloseable(TestHttpServer.start, async server => {
16
+ server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson(allData));
17
+ const r = Requestor(sdkKey, { baseUri: server.url });
18
+ const result = await promisify(r.requestAllData)();
19
+ expect(JSON.parse(result)).toEqual(allData);
20
+ })
21
+ );
22
+
23
+ it('returns error result for HTTP error', async () =>
24
+ await withCloseable(TestHttpServer.start, async server => {
25
+ server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respond(401));
26
+ const r = Requestor(sdkKey, { baseUri: server.url });
27
+ const req = promisify(r.requestAllData)();
28
+ await expect(req).rejects.toThrow(/401/);
29
+ })
30
+ );
31
+
32
+ it('returns error result for network error', async () => {
33
+ const r = Requestor(sdkKey, { baseUri: badUri });
34
+ const req = promisify(r.requestAllData)();
35
+ await expect(req).rejects.toThrow(/bad-uri/);
36
+ });
37
+
38
+ it('stores and sends etag', async () => {
39
+ const etag = "abc123";
40
+ await withCloseable(TestHttpServer.start, async server => {
41
+ server.forMethodAndPath('get', '/sdk/latest-all', (req, res) => {
42
+ if (req.headers['if-none-match'] === etag) {
43
+ TestHttpHandlers.respond(304)(req, res);
44
+ } else {
45
+ TestHttpHandlers.respond(200, { 'content-type': 'application/json', etag }, JSON.stringify(allData))(req, res);
46
+ }
47
+ });
48
+ const r = Requestor(sdkKey, { baseUri: server.url });
49
+ const result1 = await promisify(r.requestAllData)();
50
+ expect(JSON.parse(result1)).toEqual(allData);
51
+ const result2 = await promisify(r.requestAllData)();
52
+ expect(result2).toEqual(null);
53
+ const req1 = await server.nextRequest();
54
+ const req2 = await server.nextRequest();
55
+ expect(req1.headers['if-none-match']).toBe(undefined);
56
+ expect(req2.headers['if-none-match']).toEqual(etag);
57
+ })
58
+ });
59
+ });
60
+ });
@@ -0,0 +1,61 @@
1
+ const { runBigSegmentStoreTests } = require('../sharedtest/store_tests');
2
+
3
+ // This verifies that the runBigSegmentStoreTests test suite behaves as expected as long as the
4
+ // BigSegmentStore implementation behaves as expected, so we can distinguish between flaws in the
5
+ // implementations and flaws in the test logic. Unfortunately, we can't verify that the test suite
6
+ // would *fail* in cases where they ought to fail (if the store does not behave as expected),
7
+ // because in Jest there is no way to say to express a conditional expectation of failure. But
8
+ // at least we can verify that they won't fail for no good reason.
9
+
10
+ const mockDatabase = {};
11
+
12
+ function createStore(prefix, logger) {
13
+ if (!mockDatabase[prefix]) {
14
+ mockDatabase[prefix] = {};
15
+ }
16
+ return {
17
+ getMetadata: async () => {
18
+ return mockDatabase[prefix]['$metadata'] || { lastUpToDate: undefined };
19
+ },
20
+ getUserMembership: async userHashKey => {
21
+ return mockDatabase[prefix][userHashKey];
22
+ },
23
+ close: () => {},
24
+ }
25
+ }
26
+
27
+ async function clearExistingData(prefix) {
28
+ mockDatabase[prefix] = {};
29
+ }
30
+
31
+ async function setMetadata(prefix, metadata) {
32
+ if (!mockDatabase[prefix]) {
33
+ mockDatabase[prefix] = {};
34
+ }
35
+ mockDatabase[prefix]['$metadata'] = metadata;
36
+ }
37
+
38
+ async function setSegments(prefix, userHashKey, included, excluded) {
39
+ if (!mockDatabase[prefix]) {
40
+ mockDatabase[prefix] = {};
41
+ }
42
+ const membership = {};
43
+ for (const ref of (excluded || [])) {
44
+ membership[ref] = false;
45
+ }
46
+ for (const ref of (included || [])) {
47
+ membership[ref] = true;
48
+ }
49
+ mockDatabase[prefix][userHashKey] = membership;
50
+ }
51
+
52
+ describe('runBigSegmentStoreTests', () => {
53
+ describe('tests pass with valid mock store', () => {
54
+ runBigSegmentStoreTests(
55
+ createStore,
56
+ clearExistingData,
57
+ setMetadata,
58
+ setSegments
59
+ );
60
+ });
61
+ });