@splitsoftware/openfeature-js-split-provider 1.0.6 → 1.1.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.
@@ -1,188 +1,230 @@
1
- const OpenFeature = require('@openfeature/js-sdk').OpenFeature;
2
- const SplitFactory = require('@splitsoftware/splitio').SplitFactory;
3
- import { OpenFeatureSplitProvider } from '../..';
4
-
5
- export default async function(assert) {
1
+ /* eslint-disable jest/no-conditional-expect */
2
+ import { OpenFeatureSplitProvider } from '../../lib/js-split-provider';
3
+ import { getLocalHostSplitClient, getSplitFactory } from '../testUtils';
4
+
5
+ import { OpenFeature } from '@openfeature/server-sdk';
6
+
7
+ const cases = [
8
+ [
9
+ 'openfeature client tests mode: splitClient',
10
+ () => ({ splitClient: getLocalHostSplitClient()}),
11
+
12
+ ],
13
+ [
14
+ 'openfeature client tests mode: splitFactory',
15
+ getSplitFactory
16
+ ],
17
+ ];
18
+
19
+ describe.each(cases)('%s', (label, getOptions) => {
20
+
21
+ let client;
22
+ let provider;
23
+ let options;
24
+
25
+ beforeEach(() => {
26
+
27
+ options = getOptions();
28
+ provider = new OpenFeatureSplitProvider(options);
29
+ OpenFeature.setProvider(provider);
30
+
31
+ client = OpenFeature.getClient('test');
32
+ let evaluationContext = {
33
+ targetingKey: 'key'
34
+ };
35
+ client.setContext(evaluationContext);
36
+ });
37
+ afterEach(async () => {
38
+ await OpenFeature.close();
39
+ });
6
40
 
7
- const useDefaultTest = async (client) => {
41
+ test('use default test', async () => {
8
42
  let flagName = 'random-non-existent-feature';
9
43
 
10
44
  let result = await client.getBooleanValue(flagName, false);
11
- assert.equal(result, false);
45
+ expect(result).toBe(false);
12
46
 
13
47
  let result2 = await client.getBooleanValue(flagName, true);
14
- assert.equal(result2, true);
48
+ expect(result2).toBe(true);
15
49
 
16
50
  let defaultString = 'blah';
17
51
  let resultString = await client.getStringValue(flagName, defaultString);
18
- assert.equals(resultString, defaultString);
52
+ expect(resultString).toBe(defaultString);
19
53
 
20
54
  let defaultInt = 100;
21
55
  let resultInt = await client.getNumberValue(flagName, defaultInt);
22
- assert.equals(resultInt, defaultInt);
56
+ expect(resultInt).toBe(defaultInt);
23
57
 
24
58
  let defaultStructure = {
25
59
  foo: 'bar'
26
60
  };
27
61
  let resultStructure = await client.getObjectValue(flagName, defaultStructure);
28
- assert.equals(resultStructure, defaultStructure);
29
- };
62
+ expect(resultStructure).toEqual(defaultStructure);
63
+ });
30
64
 
31
- const missingTargetingKeyTest = async (client) => {
65
+ test('missing targetingKey test', async () => {
32
66
  let details = await client.getBooleanDetails('non-existent-feature', false, { targetingKey: undefined });
33
- assert.equals(details.value, false);
34
- assert.equals(details.errorCode, 'TARGETING_KEY_MISSING');
35
- };
67
+ expect(details.value).toBe(false);
68
+ expect(details.errorCode).toBe('TARGETING_KEY_MISSING');
69
+ });
36
70
 
37
- const getControlVariantNonExistentSplit = async (client) => {
71
+ test('evaluate Boolean control test', async () => {
38
72
  let details = await client.getBooleanDetails('non-existent-feature', false);
39
- assert.equals(details.value, false);
40
- assert.equals(details.errorCode, 'FLAG_NOT_FOUND');
41
- assert.equals(details.reason, 'ERROR');
42
- };
73
+ expect(details.value).toBe(false);
74
+ expect(details.errorCode).toBe('FLAG_NOT_FOUND');
75
+ expect(details.reason).toBe('ERROR');
76
+ });
43
77
 
44
- const getBooleanSplitTest = async (client) => {
78
+ test('evaluate Boolean test', async () => {
45
79
  let result = await client.getBooleanValue('some_other_feature', true);
46
- assert.equals(result, false);
47
- };
80
+ expect(result).toBe(false);
81
+ });
48
82
 
49
- const getBooleanSplitWithKeyTest = async (client) => {
50
- let result = await client.getBooleanValue('my_feature', false);
51
- assert.equals(result, true);
83
+ test('evaluate Boolean details test', async () => {
84
+ let result = await client.getBooleanDetails('my_feature', false);
85
+ expect(result.value).toBe(true);
86
+ expect(result.flagMetadata).toEqual({ config: '{"desc" : "this applies only to ON treatment"}' });
52
87
 
53
- result = await client.getBooleanValue('my_feature', true, { targetingKey: 'randomKey' });
54
- assert.equals(result, false);
55
- };
88
+ result = await client.getBooleanDetails('my_feature', true, { targetingKey: 'randomKey' });
89
+ expect(result.value).toBe(false);
90
+ expect(result.flagMetadata).toEqual({ config: '' });
91
+ });
56
92
 
57
- const getStringSplitTest = async (client) => {
93
+ test('evaluate String test', async () => {
58
94
  let result = await client.getStringValue('some_other_feature', 'on');
59
- assert.equals(result, 'off');
60
- };
95
+ expect(result).toBe('off');
96
+ });
97
+
98
+ test('evaluate String details test', async () => {
99
+ let result = await client.getStringDetails('my_feature', 'off');
100
+ expect(result.value).toBe('on');
101
+ expect(result.flagMetadata).toEqual({ config: '{"desc" : "this applies only to ON treatment"}' });
102
+
103
+ result = await client.getStringDetails('my_feature', 'on', { targetingKey: 'randomKey' });
104
+ expect(result.value).toBe('off');
105
+ expect(result.flagMetadata).toEqual({ config: '' });
106
+ });
61
107
 
62
- const getNumberSplitTest = async (client) => {
108
+ test('evaluate Number test', async () => {
63
109
  let result = await client.getNumberValue('int_feature', 0);
64
- assert.equals(result, 32);
65
- };
110
+ expect(result).toBe(32);
111
+ });
66
112
 
67
- const getObjectSplitTest = async (client) => {
113
+ test('evaluate Object test', async () => {
68
114
  let result = await client.getObjectValue('obj_feature', {});
69
- assert.looseEquals(result, { 'key': 'value' });
70
- };
115
+ expect(result).toEqual({ key: 'value' });
116
+ });
71
117
 
72
- const getMetadataNameTest = async (client) => {
73
- assert.equals(client.metadata.name, 'test');
74
- };
118
+ test('evaluate Metadata name test', async () => {
119
+ expect(client.metadata.name).toBe('test');
120
+ });
75
121
 
76
- const getBooleanDetailsTest = async (client) => {
122
+ test('evaluate Boolean without details test', async () => {
77
123
  let details = await client.getBooleanDetails('some_other_feature', true);
78
- assert.equals(details.flagKey, 'some_other_feature');
79
- assert.equals(details.reason, 'TARGETING_MATCH');
80
- assert.equals(details.value, false);
81
- assert.equals(details.variant, 'off');
82
- assert.equals(details.errorCode, undefined);
83
- };
84
-
85
- const getNumberDetailsTest = async (client) => {
124
+ expect(details.flagKey).toBe('some_other_feature');
125
+ expect(details.reason).toBe('TARGETING_MATCH');
126
+ expect(details.value).toBe(false);
127
+ expect(details.variant).toBe('off');
128
+ expect(details.errorCode).toBeUndefined();
129
+ });
130
+
131
+ test('evaluate Number details test', async () => {
86
132
  let details = await client.getNumberDetails('int_feature', 0);
87
- assert.equals(details.flagKey, 'int_feature');
88
- assert.equals(details.reason, 'TARGETING_MATCH');
89
- assert.equals(details.value, 32);
90
- assert.equals(details.variant, '32');
91
- assert.equals(details.errorCode, undefined);
92
- };
93
-
94
- const getStringDetailsTest = async (client) => {
133
+ expect(details.flagKey).toBe('int_feature');
134
+ expect(details.reason).toBe('TARGETING_MATCH');
135
+ expect(details.value).toBe(32);
136
+ expect(details.variant).toBe('32');
137
+ expect(details.errorCode).toBeUndefined();
138
+ });
139
+
140
+ test('evaluate String without details test', async () => {
95
141
  let details = await client.getStringDetails('some_other_feature', 'blah');
96
- assert.equals(details.flagKey, 'some_other_feature');
97
- assert.equals(details.reason, 'TARGETING_MATCH');
98
- assert.equals(details.value, 'off');
99
- assert.equals(details.variant, 'off');
100
- assert.equals(details.errorCode, undefined);
101
- };
102
-
103
- const getObjectDetailsTest = async (client) => {
142
+ expect(details.flagKey).toBe('some_other_feature');
143
+ expect(details.reason).toBe('TARGETING_MATCH');
144
+ expect(details.value).toBe('off');
145
+ expect(details.variant).toBe('off');
146
+ expect(details.errorCode).toBeUndefined();
147
+ });
148
+
149
+ test('evaluate Object details test', async () => {
104
150
  let details = await client.getObjectDetails('obj_feature', {});
105
- assert.equals(details.flagKey, 'obj_feature');
106
- assert.equals(details.reason, 'TARGETING_MATCH');
107
- assert.looseEquals(details.value, { key: 'value' });
108
- assert.equals(details.variant, '{"key": "value"}');
109
- assert.equals(details.errorCode, undefined);
110
- };
111
-
112
- const getBooleanFailTest = async (client) => {
151
+ expect(details.flagKey).toBe('obj_feature');
152
+ expect(details.reason).toBe('TARGETING_MATCH');
153
+ expect(details.value).toEqual({ key: 'value' });
154
+ expect(details.variant).toBe('{"key": "value"}');
155
+ expect(details.errorCode).toBeUndefined();
156
+ });
157
+
158
+ test('evaluate Boolean fail test', async () => {
113
159
  let value = await client.getBooleanValue('obj_feature', false);
114
- assert.equals(value, false);
160
+ expect(value).toBe(false);
115
161
 
116
162
  let details = await client.getBooleanDetails('obj_feature', false);
117
- assert.equals(details.value, false);
118
- assert.equals(details.errorCode, 'PARSE_ERROR');
119
- assert.equals(details.reason, 'ERROR');
120
- assert.equals(details.variant, undefined);
121
- };
163
+ expect(details.value).toBe(false);
164
+ expect(details.errorCode).toBe('PARSE_ERROR');
165
+ expect(details.reason).toBe('ERROR');
166
+ expect(details.variant).toBeUndefined();
167
+ });
122
168
 
123
- const getNumberFailTest = async (client) => {
169
+ test('evaluate Number fail test', async () => {
124
170
  let value = await client.getNumberValue('obj_feature', 10);
125
- assert.equals(value, 10);
171
+ expect(value).toBe(10);
126
172
 
127
173
  let details = await client.getNumberDetails('obj_feature', 10);
128
- assert.equals(details.value, 10);
129
- assert.equals(details.errorCode, 'PARSE_ERROR');
130
- assert.equals(details.reason, 'ERROR');
131
- assert.equals(details.variant, undefined);
132
- };
174
+ expect(details.value).toBe(10);
175
+ expect(details.errorCode).toBe('PARSE_ERROR');
176
+ expect(details.reason).toBe('ERROR');
177
+ expect(details.variant).toBeUndefined();
178
+ });
133
179
 
134
- const getObjectFailTest = async (client) => {
180
+ test('evaluate Object fail test', async () => {
135
181
  let defaultObject = { foo: 'bar' };
136
182
  let value = await client.getObjectValue('int_feature', defaultObject);
137
- assert.equals(value, defaultObject);
183
+ expect(value).toEqual(defaultObject);
138
184
 
139
185
  let details = await client.getObjectDetails('int_feature', defaultObject);
140
- assert.equals(details.value, defaultObject);
141
- assert.equals(details.errorCode, 'PARSE_ERROR');
142
- assert.equals(details.reason, 'ERROR');
143
- assert.equals(details.variant, undefined);
144
- };
145
-
146
- let splitClient = SplitFactory({
147
- core: {
148
- authorizationKey: 'localhost'
149
- },
150
- features: './split.yaml',
151
- debug: 'DEBUG'
152
- }).client();
153
-
154
- let provider = new OpenFeatureSplitProvider({splitClient});
155
- OpenFeature.setProvider(provider);
156
-
157
- let client = OpenFeature.getClient('test');
158
- let evaluationContext = {
159
- targetingKey: 'key'
160
- };
161
- client.setContext(evaluationContext);
162
-
163
- await useDefaultTest(client);
164
- await missingTargetingKeyTest(client);
165
- await getControlVariantNonExistentSplit(client);
166
-
167
- await getBooleanSplitTest(client);
168
- await getBooleanSplitWithKeyTest(client);
169
-
170
- await getStringSplitTest(client);
171
- await getNumberSplitTest(client);
172
- await getObjectSplitTest(client);
173
-
174
- await getMetadataNameTest(client);
175
-
176
- await getBooleanDetailsTest(client);
177
- await getNumberDetailsTest(client);
178
- await getStringDetailsTest(client);
179
- await getObjectDetailsTest(client);
180
-
181
- await getBooleanFailTest(client);
182
- await getNumberFailTest(client);
183
- await getObjectFailTest(client);
184
-
185
- splitClient.destroy(); // Shut down open handles
186
-
187
- assert.end();
188
- }
186
+ expect(details.value).toEqual(defaultObject);
187
+ expect(details.errorCode).toBe('PARSE_ERROR');
188
+ expect(details.reason).toBe('ERROR');
189
+ expect(details.variant).toBeUndefined();
190
+ });
191
+
192
+ test('track: throws when missing eventName', async () => {
193
+ try {
194
+ await client.track('', { targetingKey: 'u1', trafficType: 'user' }, {});
195
+ } catch (e) {
196
+ expect(e.message).toBe('Missing eventName, required to track');
197
+ expect(e.code).toBe('PARSE_ERROR');
198
+ }
199
+ });
200
+
201
+ test('track: throws when missing targetingKey', async () => {
202
+ try {
203
+ await client.track('my-event', { trafficType: 'user' }, {});
204
+ } catch (e) {
205
+ expect(e.message).toBe('Missing targetingKey, required to track');
206
+ expect(e.code).toBe('PARSE_ERROR');
207
+ }
208
+ });
209
+
210
+ test('track: throws when missing trafficType', async () => {
211
+ try {
212
+ await client.track('my-event', { targetingKey: 'u1' }, {});
213
+ } catch (e) {
214
+ expect(e.message).toBe('Missing trafficType variable, required to track');
215
+ expect(e.code).toBe('INVALID_CONTEXT');
216
+ }
217
+ });
218
+
219
+ test('track: without value', async () => {
220
+ const trackSpy = jest.spyOn(options.splitClient ? options.splitClient : options.client(), 'track');
221
+ await client.track('my-event', { targetingKey: 'u1', trafficType: 'user' }, { properties: { prop1: 'value1' } });
222
+ expect(trackSpy).toHaveBeenCalledWith('u1', 'user', 'my-event', undefined, { prop1: 'value1' });
223
+ });
224
+
225
+ test('track: with value', async () => {
226
+ const trackSpy = jest.spyOn(options.splitClient ? options.splitClient : options.client(), 'track');
227
+ await client.track('my-event', { targetingKey: 'u1', trafficType: 'user' }, { value: 9.99, properties: { prop1: 'value1' } });
228
+ expect(trackSpy).toHaveBeenCalledWith('u1', 'user', 'my-event', 9.99, { prop1: 'value1' });
229
+ });
230
+ });
@@ -0,0 +1,115 @@
1
+ import RedisServer from 'redis-server';
2
+ import { exec } from 'child_process';
3
+ import { OpenFeature } from '@openfeature/server-sdk';
4
+
5
+ import { getRedisSplitClient } from '../testUtils';
6
+ import { OpenFeatureSplitProvider } from '../../lib/js-split-provider';
7
+
8
+ const redisPort = '6385';
9
+
10
+ /**
11
+ * Initialize redis server and run a cli bash command to load redis with data to do the proper tests
12
+ */
13
+ const startRedis = () => {
14
+ // Simply pass the port that you want a Redis server to listen on.
15
+ const server = new RedisServer(redisPort);
16
+
17
+ const promise = new Promise((resolve, reject) => {
18
+ server
19
+ .open()
20
+ .then(() => {
21
+ exec(`cat ./src/__tests__/mocks/redis-commands.txt | redis-cli -p ${redisPort}`, err => {
22
+ if (err) {
23
+ reject(server);
24
+ // Node.js couldn't execute the command
25
+ return;
26
+ }
27
+ resolve(server);
28
+ });
29
+ });
30
+ });
31
+
32
+ return promise;
33
+ };
34
+
35
+ let redisServer
36
+ let splitClient
37
+
38
+ beforeAll(async () => {
39
+ redisServer = await startRedis();
40
+ }, 30000);
41
+
42
+ afterAll(async () => {
43
+ await redisServer.close();
44
+ await splitClient.destroy();
45
+ });
46
+
47
+ describe('Regular usage - DEBUG strategy', () => {
48
+ splitClient = getRedisSplitClient(redisPort);
49
+ const provider = new OpenFeatureSplitProvider({ splitClient });
50
+
51
+ OpenFeature.setProviderAndWait(provider);
52
+ const client = OpenFeature.getClient();
53
+
54
+ test('Evaluate always on flag', async () => {
55
+ await client.getBooleanValue('always-on', false, {targetingKey: 'emma-ss'}).then(result => {
56
+ expect(result).toBe(true);
57
+ });
58
+ });
59
+
60
+ test('Evaluate user in segment', async () => {
61
+ await client.getBooleanValue('UT_IN_SEGMENT', false, {targetingKey: 'UT_Segment_member', properties: { /* empty properties are ignored */ }}).then(result => {
62
+ expect(result).toBe(true);
63
+ });
64
+
65
+ await client.getBooleanValue('UT_IN_SEGMENT', false, {targetingKey: 'UT_Segment_member', properties: { some: 'value1' } }).then(result => {
66
+ expect(result).toBe(true);
67
+ });
68
+
69
+ await client.getBooleanValue('UT_IN_SEGMENT', false, {targetingKey: 'other' }).then(result => {
70
+ expect(result).toBe(false);
71
+ });
72
+
73
+ await client.getBooleanValue('UT_NOT_IN_SEGMENT', true, {targetingKey: 'UT_Segment_member' }).then(result => {
74
+ expect(result).toBe(false);
75
+ });
76
+
77
+ await client.getBooleanValue('UT_NOT_IN_SEGMENT', true, {targetingKey: 'other' }).then(result => {
78
+ expect(result).toBe(true);
79
+ });
80
+
81
+ await client.getBooleanValue('UT_NOT_IN_SEGMENT', true, {targetingKey: 'other' }).then(result => {
82
+ expect(result).toBe(true);
83
+ });
84
+ });
85
+
86
+ test('Evaluate with attributes set matcher', async () => {
87
+ await client.getBooleanValue('UT_SET_MATCHER', false, {targetingKey: 'UT_Segment_member', permissions: ['admin'] }).then(result => {
88
+ expect(result).toBe(true);
89
+ });
90
+
91
+ await client.getBooleanValue('UT_SET_MATCHER', false, {targetingKey: 'UT_Segment_member', permissions: ['not_matching'] }).then(result => {
92
+ expect(result).toBe(false);
93
+ });
94
+
95
+ await client.getBooleanValue('UT_NOT_SET_MATCHER', true, {targetingKey: 'UT_Segment_member', permissions: ['create'] }).then(result => {
96
+ expect(result).toBe(false);
97
+ });
98
+
99
+ await client.getBooleanValue('UT_NOT_SET_MATCHER', true, {targetingKey: 'UT_Segment_member', permissions: ['not_matching'] }).then(result => {
100
+ expect(result).toBe(true);
101
+ });
102
+ })
103
+
104
+ test('Evaluate with dynamic config', async () => {
105
+ await client.getBooleanDetails('UT_NOT_SET_MATCHER', true, {targetingKey: 'UT_Segment_member', permissions: ['not_matching'] }).then(result => {
106
+ expect(result.value).toBe(true);
107
+ expect(result.flagMetadata).toEqual({'config': ''});
108
+ });
109
+
110
+ await client.getStringDetails('always-o.n-with-config', 'control', {targetingKey: 'other'}).then(result => {
111
+ expect(result.value).toBe('o.n');
112
+ expect(result.flagMetadata).toEqual({config: '{"color":"brown"}'});
113
+ });
114
+ })
115
+ });