flagsmith-nodejs 5.1.1 → 6.0.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/build/cjs/sdk/analytics.d.ts +1 -1
- package/build/cjs/sdk/analytics.js +1 -1
- package/build/cjs/sdk/index.d.ts +44 -42
- package/build/cjs/sdk/index.js +150 -152
- package/build/cjs/sdk/polling_manager.d.ts +3 -1
- package/build/cjs/sdk/polling_manager.js +9 -2
- package/build/cjs/sdk/types.d.ts +73 -5
- package/build/cjs/sdk/utils.d.ts +24 -1
- package/build/cjs/sdk/utils.js +50 -18
- package/build/esm/sdk/analytics.d.ts +1 -1
- package/build/esm/sdk/analytics.js +1 -1
- package/build/esm/sdk/index.d.ts +44 -42
- package/build/esm/sdk/index.js +151 -153
- package/build/esm/sdk/polling_manager.d.ts +3 -1
- package/build/esm/sdk/polling_manager.js +9 -2
- package/build/esm/sdk/types.d.ts +73 -5
- package/build/esm/sdk/utils.d.ts +24 -1
- package/build/esm/sdk/utils.js +48 -17
- package/package.json +1 -1
- package/sdk/analytics.ts +5 -5
- package/sdk/index.ts +173 -176
- package/sdk/polling_manager.ts +9 -2
- package/sdk/types.ts +74 -3
- package/sdk/utils.ts +50 -16
- package/tests/sdk/analytics.test.ts +0 -4
- package/tests/sdk/flagsmith-cache.test.ts +5 -17
- package/tests/sdk/flagsmith-environment-flags.test.ts +1 -29
- package/tests/sdk/flagsmith-identity-flags.test.ts +20 -15
- package/tests/sdk/flagsmith.test.ts +88 -83
- package/tests/sdk/polling.test.ts +0 -4
- package/tests/sdk/utils.ts +28 -6
- package/vitest.config.ts +1 -0
- /package/{.prettierrc.js → .prettierrc.cjs} +0 -0
package/sdk/types.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { EnvironmentModel } from '../flagsmith-engine/index.js';
|
|
|
3
3
|
import { Dispatcher } from 'undici-types';
|
|
4
4
|
import { Logger } from 'pino';
|
|
5
5
|
import { BaseOfflineHandler } from './offline_handlers.js';
|
|
6
|
+
import { Flagsmith } from './index.js'
|
|
6
7
|
|
|
7
8
|
export type IFlagsmithValue<T = string | number | boolean | null> = T;
|
|
8
9
|
|
|
@@ -27,22 +28,92 @@ export interface FlagsmithCache {
|
|
|
27
28
|
|
|
28
29
|
export type Fetch = typeof fetch
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* The configuration options for a {@link Flagsmith} client.
|
|
33
|
+
*/
|
|
30
34
|
export interface FlagsmithConfig {
|
|
35
|
+
/**
|
|
36
|
+
* The environment's client-side or server-side key.
|
|
37
|
+
*/
|
|
31
38
|
environmentKey?: string;
|
|
39
|
+
/**
|
|
40
|
+
* The Flagsmith API URL. Set this if you are not using Flagsmith's public service, i.e. https://app.flagsmith.com.
|
|
41
|
+
*
|
|
42
|
+
* @default https://edge.api.flagsmith.com/api/v1/
|
|
43
|
+
*/
|
|
32
44
|
apiUrl?: string;
|
|
45
|
+
/**
|
|
46
|
+
* A custom {@link Dispatcher} to use when making HTTP requests.
|
|
47
|
+
*/
|
|
33
48
|
agent?: Dispatcher;
|
|
49
|
+
/**
|
|
50
|
+
* A custom {@link fetch} implementation to use when making HTTP requests.
|
|
51
|
+
*/
|
|
34
52
|
fetch?: Fetch;
|
|
35
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Custom headers to use in all HTTP requests.
|
|
55
|
+
*/
|
|
56
|
+
customHeaders?: HeadersInit
|
|
57
|
+
/**
|
|
58
|
+
* The network request timeout duration, in seconds.
|
|
59
|
+
*
|
|
60
|
+
* @default 10
|
|
61
|
+
*/
|
|
36
62
|
requestTimeoutSeconds?: number;
|
|
63
|
+
/**
|
|
64
|
+
* The amount of time, in milliseconds, to wait before retrying failed network requests.
|
|
65
|
+
*/
|
|
66
|
+
requestRetryDelayMilliseconds?: number;
|
|
67
|
+
/**
|
|
68
|
+
* If enabled, flags are evaluated locally using the environment state cached in memory.
|
|
69
|
+
*
|
|
70
|
+
* The client will lazily fetch the environment from the Flagsmith API, and poll it every {@link environmentRefreshIntervalSeconds}.
|
|
71
|
+
*/
|
|
37
72
|
enableLocalEvaluation?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The time, in seconds, to wait before refreshing the cached environment state.
|
|
75
|
+
* @default 60
|
|
76
|
+
*/
|
|
38
77
|
environmentRefreshIntervalSeconds?: number;
|
|
78
|
+
/**
|
|
79
|
+
* How many times to retry any failed network request before giving up.
|
|
80
|
+
* @default 3
|
|
81
|
+
*/
|
|
39
82
|
retries?: number;
|
|
83
|
+
/**
|
|
84
|
+
* If enabled, the client will keep track of any flags evaluated using {@link Flags.isFeatureEnabled},
|
|
85
|
+
* {@link Flags.getFeatureValue} or {@link Flags.getFlag}, and periodically flush this data to the Flagsmith API.
|
|
86
|
+
*/
|
|
40
87
|
enableAnalytics?: boolean;
|
|
41
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Used to return fallback values for flags when evaluation fails for any reason. If not provided and flag
|
|
90
|
+
* evaluation fails, an error will be thrown intsead.
|
|
91
|
+
*
|
|
92
|
+
* @param flagKey The key of the flag that failed to evaluate.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // All flags disabled and with no value by default
|
|
96
|
+
* const defaultHandler = () => new DefaultFlag(undefined, false)
|
|
97
|
+
*
|
|
98
|
+
* // Enable only VIP flags by default
|
|
99
|
+
* const vipDefaultHandler = (key: string) => new Default(undefined, key.startsWith('vip_'))
|
|
100
|
+
*/
|
|
101
|
+
defaultFlagHandler?: (flagKey: string) => DefaultFlag;
|
|
42
102
|
cache?: FlagsmithCache;
|
|
43
|
-
|
|
103
|
+
/**
|
|
104
|
+
* A callback function to invoke whenever the cached environment is updated.
|
|
105
|
+
* @param error The error that occurred when the environment state failed to update, if any.
|
|
106
|
+
* @param result The updated environment state, if no error was thrown.
|
|
107
|
+
*/
|
|
108
|
+
onEnvironmentChange?: (error: Error | null, result?: EnvironmentModel) => void;
|
|
44
109
|
logger?: Logger;
|
|
110
|
+
/**
|
|
111
|
+
* If enabled, the client will work offline and not make any network requests. Requires {@link offlineHandler}.
|
|
112
|
+
*/
|
|
45
113
|
offlineMode?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* If {@link offlineMode} is enabled, this handler is used to calculate the values of all flags.
|
|
116
|
+
*/
|
|
46
117
|
offlineHandler?: BaseOfflineHandler;
|
|
47
118
|
}
|
|
48
119
|
|
package/sdk/utils.ts
CHANGED
|
@@ -46,25 +46,59 @@ export const retryFetch = (
|
|
|
46
46
|
fetchOptions: RequestInit & { dispatcher?: Dispatcher },
|
|
47
47
|
retries: number = 3,
|
|
48
48
|
timeoutMs: number = 10, // set an overall timeout for this function
|
|
49
|
+
retryDelayMs: number = 1000,
|
|
49
50
|
customFetch: Fetch,
|
|
50
51
|
): Promise<Response> => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
customFetch(url, {
|
|
52
|
+
const retryWrapper = async (n: number): Promise<Response> => {
|
|
53
|
+
try {
|
|
54
|
+
return await customFetch(url, {
|
|
54
55
|
...fetchOptions,
|
|
55
56
|
signal: AbortSignal.timeout(timeoutMs)
|
|
56
|
-
})
|
|
57
|
-
.then(res => resolve(res))
|
|
58
|
-
.catch(async err => {
|
|
59
|
-
if (n > 0) {
|
|
60
|
-
await delay(1000);
|
|
61
|
-
retryWrapper(--n);
|
|
62
|
-
} else {
|
|
63
|
-
reject(err);
|
|
64
|
-
}
|
|
65
57
|
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
} catch (e) {
|
|
59
|
+
if (n > 0) {
|
|
60
|
+
await delay(retryDelayMs);
|
|
61
|
+
return await retryWrapper(n - 1);
|
|
62
|
+
} else {
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
return retryWrapper(retries);
|
|
70
68
|
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A deferred promise can be resolved or rejected outside its creation scope.
|
|
72
|
+
*
|
|
73
|
+
* @template T The type of the value that the deferred promise will resolve to.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const deferred = new Deferred<string>()
|
|
77
|
+
*
|
|
78
|
+
* // Pass the promise somewhere
|
|
79
|
+
* performAsyncOperation(deferred.promise)
|
|
80
|
+
*
|
|
81
|
+
* // Resolve it when ready from anywhere
|
|
82
|
+
* deferred.resolve("Operation completed")
|
|
83
|
+
* deferred.failed("Error")
|
|
84
|
+
*/
|
|
85
|
+
export class Deferred<T> {
|
|
86
|
+
public readonly promise: Promise<T>;
|
|
87
|
+
private resolvePromise!: (value: T | PromiseLike<T>) => void;
|
|
88
|
+
private rejectPromise!: (reason?: unknown) => void;
|
|
89
|
+
|
|
90
|
+
constructor(initial?: T) {
|
|
91
|
+
this.promise = new Promise<T>((resolve, reject) => {
|
|
92
|
+
this.resolvePromise = resolve;
|
|
93
|
+
this.rejectPromise = reject;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public resolve(value: T | PromiseLike<T>): void {
|
|
98
|
+
this.resolvePromise(value);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public reject(reason?: unknown): void {
|
|
102
|
+
this.rejectPromise(reason);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { fetch, environmentJSON, environmentModel, flagsJSON, flagsmith, identitiesJSON, TestCache } from './utils.js';
|
|
2
2
|
|
|
3
|
-
beforeEach(() => {
|
|
4
|
-
vi.clearAllMocks();
|
|
5
|
-
});
|
|
6
|
-
|
|
7
3
|
test('test_empty_cache_not_read_but_populated', async () => {
|
|
8
|
-
fetch.mockResolvedValue(new Response(flagsJSON));
|
|
9
|
-
|
|
10
4
|
const cache = new TestCache();
|
|
11
5
|
const set = vi.spyOn(cache, 'set');
|
|
12
6
|
|
|
@@ -23,8 +17,6 @@ test('test_empty_cache_not_read_but_populated', async () => {
|
|
|
23
17
|
});
|
|
24
18
|
|
|
25
19
|
test('test_api_not_called_when_cache_present', async () => {
|
|
26
|
-
fetch.mockResolvedValue(new Response(flagsJSON));
|
|
27
|
-
|
|
28
20
|
const cache = new TestCache();
|
|
29
21
|
const set = vi.spyOn(cache, 'set');
|
|
30
22
|
|
|
@@ -56,27 +48,25 @@ test('test_api_called_twice_when_no_cache', async () => {
|
|
|
56
48
|
});
|
|
57
49
|
|
|
58
50
|
test('test_get_environment_flags_uses_local_environment_when_available', async () => {
|
|
59
|
-
fetch.mockResolvedValue(new Response(flagsJSON));
|
|
60
|
-
|
|
61
51
|
const cache = new TestCache();
|
|
62
52
|
const set = vi.spyOn(cache, 'set');
|
|
63
53
|
|
|
64
|
-
const flg = flagsmith({ cache });
|
|
54
|
+
const flg = flagsmith({ cache, environmentKey: 'ser.key', enableLocalEvaluation: true });
|
|
65
55
|
const model = environmentModel(JSON.parse(environmentJSON));
|
|
66
|
-
|
|
56
|
+
const getEnvironment = vi.spyOn(flg, 'getEnvironment')
|
|
57
|
+
getEnvironment.mockResolvedValue(model)
|
|
67
58
|
|
|
68
|
-
const allFlags =
|
|
59
|
+
const allFlags = (await flg.getEnvironmentFlags()).allFlags();
|
|
69
60
|
|
|
70
61
|
expect(set).toBeCalled();
|
|
71
62
|
expect(fetch).toBeCalledTimes(0);
|
|
63
|
+
expect(getEnvironment).toBeCalledTimes(1);
|
|
72
64
|
expect(allFlags[0].enabled).toBe(model.featureStates[0].enabled);
|
|
73
65
|
expect(allFlags[0].value).toBe(model.featureStates[0].getValue());
|
|
74
66
|
expect(allFlags[0].featureName).toBe(model.featureStates[0].feature.name);
|
|
75
67
|
});
|
|
76
68
|
|
|
77
69
|
test('test_cache_used_for_identity_flags', async () => {
|
|
78
|
-
fetch.mockResolvedValue(new Response(identitiesJSON));
|
|
79
|
-
|
|
80
70
|
const cache = new TestCache();
|
|
81
71
|
const set = vi.spyOn(cache, 'set');
|
|
82
72
|
|
|
@@ -98,8 +88,6 @@ test('test_cache_used_for_identity_flags', async () => {
|
|
|
98
88
|
});
|
|
99
89
|
|
|
100
90
|
test('test_cache_used_for_identity_flags_local_evaluation', async () => {
|
|
101
|
-
fetch.mockResolvedValue(new Response(environmentJSON));
|
|
102
|
-
|
|
103
91
|
const cache = new TestCache();
|
|
104
92
|
const set = vi.spyOn(cache, 'set');
|
|
105
93
|
|
|
@@ -4,13 +4,7 @@ import { DefaultFlag } from '../../sdk/models.js';
|
|
|
4
4
|
|
|
5
5
|
vi.mock('../../sdk/polling_manager');
|
|
6
6
|
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
vi.clearAllMocks();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
7
|
test('test_get_environment_flags_calls_api_when_no_local_environment', async () => {
|
|
12
|
-
fetch.mockResolvedValue(new Response(flagsJSON));
|
|
13
|
-
|
|
14
8
|
const flg = flagsmith();
|
|
15
9
|
const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
|
|
16
10
|
|
|
@@ -20,20 +14,6 @@ test('test_get_environment_flags_calls_api_when_no_local_environment', async ()
|
|
|
20
14
|
expect(allFlags[0].featureName).toBe('some_feature');
|
|
21
15
|
});
|
|
22
16
|
|
|
23
|
-
test('test_get_environment_flags_uses_local_environment_when_available', async () => {
|
|
24
|
-
fetch.mockResolvedValue(new Response(flagsJSON));
|
|
25
|
-
|
|
26
|
-
const flg = flagsmith();
|
|
27
|
-
const model = environmentModel(JSON.parse(environmentJSON));
|
|
28
|
-
flg.environment = model;
|
|
29
|
-
|
|
30
|
-
const allFlags = await (await flg.getEnvironmentFlags()).allFlags();
|
|
31
|
-
expect(fetch).toBeCalledTimes(0);
|
|
32
|
-
expect(allFlags[0].enabled).toBe(model.featureStates[0].enabled);
|
|
33
|
-
expect(allFlags[0].value).toBe(model.featureStates[0].getValue());
|
|
34
|
-
expect(allFlags[0].featureName).toBe(model.featureStates[0].feature.name);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
17
|
test('test_default_flag_is_used_when_no_environment_flags_returned', async () => {
|
|
38
18
|
fetch.mockResolvedValue(new Response(JSON.stringify([])));
|
|
39
19
|
|
|
@@ -57,8 +37,6 @@ test('test_default_flag_is_used_when_no_environment_flags_returned', async () =>
|
|
|
57
37
|
});
|
|
58
38
|
|
|
59
39
|
test('test_analytics_processor_tracks_flags', async () => {
|
|
60
|
-
fetch.mockResolvedValue(new Response(flagsJSON));
|
|
61
|
-
|
|
62
40
|
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
63
41
|
|
|
64
42
|
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
@@ -78,8 +56,6 @@ test('test_analytics_processor_tracks_flags', async () => {
|
|
|
78
56
|
});
|
|
79
57
|
|
|
80
58
|
test('test_getFeatureValue', async () => {
|
|
81
|
-
fetch.mockResolvedValue(new Response(flagsJSON));
|
|
82
|
-
|
|
83
59
|
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
84
60
|
|
|
85
61
|
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
@@ -106,7 +82,7 @@ test('test_throws_when_no_default_flag_handler_after_multiple_API_errors', async
|
|
|
106
82
|
await expect(async () => {
|
|
107
83
|
const flags = await flg.getEnvironmentFlags();
|
|
108
84
|
const flag = flags.getFlag('some_feature');
|
|
109
|
-
}).rejects.toThrow('
|
|
85
|
+
}).rejects.toThrow('getEnvironmentFlags failed and no default flag handler was provided');
|
|
110
86
|
});
|
|
111
87
|
|
|
112
88
|
test('test_non_200_response_raises_flagsmith_api_error', async () => {
|
|
@@ -122,8 +98,6 @@ test('test_non_200_response_raises_flagsmith_api_error', async () => {
|
|
|
122
98
|
await expect(flg.getEnvironmentFlags()).rejects.toThrow();
|
|
123
99
|
});
|
|
124
100
|
test('test_default_flag_is_not_used_when_environment_flags_returned', async () => {
|
|
125
|
-
fetch.mockResolvedValue(new Response(flagsJSON));
|
|
126
|
-
|
|
127
101
|
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
128
102
|
|
|
129
103
|
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
@@ -161,8 +135,6 @@ test('test_default_flag_is_used_when_bad_api_response_happens', async () => {
|
|
|
161
135
|
});
|
|
162
136
|
|
|
163
137
|
test('test_local_evaluation', async () => {
|
|
164
|
-
fetch.mockResolvedValue(new Response(environmentJSON));
|
|
165
|
-
|
|
166
138
|
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
167
139
|
|
|
168
140
|
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import Flagsmith from '../../sdk/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
fetch,
|
|
4
|
+
environmentJSON,
|
|
5
|
+
flagsmith,
|
|
6
|
+
identitiesJSON,
|
|
7
|
+
identityWithTransientTraitsJSON,
|
|
8
|
+
transientIdentityJSON,
|
|
9
|
+
badFetch
|
|
10
|
+
} from './utils.js';
|
|
3
11
|
import { DefaultFlag } from '../../sdk/models.js';
|
|
4
12
|
|
|
5
13
|
vi.mock('../../sdk/polling_manager');
|
|
6
14
|
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
vi.clearAllMocks();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
|
|
12
15
|
test('test_get_identity_flags_calls_api_when_no_local_environment_no_traits', async () => {
|
|
13
|
-
fetch.mockResolvedValue(new Response(identitiesJSON));
|
|
14
16
|
const identifier = 'identifier';
|
|
15
17
|
|
|
16
18
|
const flg = flagsmith();
|
|
@@ -23,7 +25,6 @@ test('test_get_identity_flags_calls_api_when_no_local_environment_no_traits', as
|
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
test('test_get_identity_flags_uses_environment_when_local_environment_no_traits', async () => {
|
|
26
|
-
fetch.mockResolvedValue(new Response(environmentJSON))
|
|
27
28
|
const identifier = 'identifier';
|
|
28
29
|
|
|
29
30
|
const flg = flagsmith({
|
|
@@ -40,7 +41,6 @@ test('test_get_identity_flags_uses_environment_when_local_environment_no_traits'
|
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
test('test_get_identity_flags_calls_api_when_no_local_environment_with_traits', async () => {
|
|
43
|
-
fetch.mockResolvedValue(new Response(identitiesJSON))
|
|
44
44
|
const identifier = 'identifier';
|
|
45
45
|
const traits = { some_trait: 'some_value' };
|
|
46
46
|
const flg = flagsmith();
|
|
@@ -53,8 +53,6 @@ test('test_get_identity_flags_calls_api_when_no_local_environment_with_traits',
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
test('test_default_flag_is_not_used_when_identity_flags_returned', async () => {
|
|
56
|
-
fetch.mockResolvedValue(new Response(identitiesJSON))
|
|
57
|
-
|
|
58
56
|
const defaultFlag = new DefaultFlag('some-default-value', true);
|
|
59
57
|
|
|
60
58
|
const defaultFlagHandler = (featureName: string) => defaultFlag;
|
|
@@ -125,7 +123,6 @@ test('test_default_flag_is_used_when_no_identity_flags_returned_and_no_custom_de
|
|
|
125
123
|
expect(flag.enabled).toBe(false);
|
|
126
124
|
});
|
|
127
125
|
|
|
128
|
-
|
|
129
126
|
test('test_get_identity_flags_multivariate_value_with_local_evaluation_enabled', async () => {
|
|
130
127
|
fetch.mockResolvedValue(new Response(environmentJSON));
|
|
131
128
|
const identifier = 'identifier';
|
|
@@ -133,7 +130,6 @@ test('test_get_identity_flags_multivariate_value_with_local_evaluation_enabled',
|
|
|
133
130
|
const flg = flagsmith({
|
|
134
131
|
environmentKey: 'ser.key',
|
|
135
132
|
enableLocalEvaluation: true,
|
|
136
|
-
|
|
137
133
|
});
|
|
138
134
|
|
|
139
135
|
const identityFlags = (await flg.getIdentityFlags(identifier))
|
|
@@ -170,10 +166,10 @@ test('test_transient_identity', async () => {
|
|
|
170
166
|
test('test_identity_with_transient_traits', async () => {
|
|
171
167
|
fetch.mockResolvedValue(new Response(identityWithTransientTraitsJSON));
|
|
172
168
|
const identifier = 'transient_trait_identifier';
|
|
173
|
-
const traits = {
|
|
169
|
+
const traits = {
|
|
174
170
|
some_trait: 'some_value',
|
|
175
171
|
another_trait: {value: 'another_value', transient: true},
|
|
176
|
-
explicitly_non_transient_trait: {value: 'non_transient_value', transient: false}
|
|
172
|
+
explicitly_non_transient_trait: {value: 'non_transient_value', transient: false}
|
|
177
173
|
}
|
|
178
174
|
const traitsInRequest = [
|
|
179
175
|
{
|
|
@@ -206,3 +202,12 @@ test('test_identity_with_transient_traits', async () => {
|
|
|
206
202
|
expect(identityFlags[0].value).toBe('some-identity-with-transient-trait-value');
|
|
207
203
|
expect(identityFlags[0].featureName).toBe('some_feature');
|
|
208
204
|
});
|
|
205
|
+
|
|
206
|
+
test('getIdentityFlags fails if API call failed and no default flag handler was provided', async () => {
|
|
207
|
+
const flg = flagsmith({
|
|
208
|
+
fetch: badFetch,
|
|
209
|
+
})
|
|
210
|
+
await expect(flg.getIdentityFlags('user'))
|
|
211
|
+
.rejects
|
|
212
|
+
.toThrow('getIdentityFlags failed and no default flag handler was provided')
|
|
213
|
+
})
|