Skip to content

Commit affbb5f

Browse files
roncohenCopilot
andauthored
feat(node-sdk): Improve feature overrides (#437)
- include a testing section - rewrote many tests for Client because it was difficult to work with because of mocking and many asserts. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 474e922 commit affbb5f

File tree

4 files changed

+472
-645
lines changed

4 files changed

+472
-645
lines changed

packages/node-sdk/README.md

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,43 @@ bucketClient.initialize().then(() => {
403403

404404
![Config type check failed](docs/type-check-payload-failed.png "Remote config type check failed")
405405

406+
## Testing
407+
408+
When writing tests that cover code with feature flags, you can toggle features on/off programmatically to test the different behavior.
409+
410+
`bucket.ts`:
411+
412+
```typescript
413+
import { BucketClient } from "@bucketco/node-sdk";
414+
415+
export const bucket = new BucketClient();
416+
```
417+
418+
`app.test.ts`:
419+
420+
```typescript
421+
import { bucket } from "./bucket.ts";
422+
423+
beforeAll(async () => await bucket.initialize());
424+
afterEach(() => {
425+
bucket.clearFeatureOverrides();
426+
});
427+
428+
describe("API Tests", () => {
429+
it("should return 200 for the root endpoint", async () => {
430+
bucket.featureOverrides = {
431+
"show-todo": true,
432+
};
433+
434+
const response = await request(app).get("/");
435+
expect(response.status).toBe(200);
436+
expect(response.body).toEqual({ message: "Ready to manage some TODOs!" });
437+
});
438+
});
439+
```
440+
441+
See more on feature overrides in the section below.
442+
406443
## Feature Overrides
407444

408445
Feature overrides allow you to override feature flags and their configurations locally. This is particularly useful for development and testing. You can specify overrides in three ways:
@@ -414,7 +451,7 @@ BUCKET_FEATURES_ENABLED=feature1,feature2
414451
BUCKET_FEATURES_DISABLED=feature3,feature4
415452
```
416453

417-
1. Through `bucketConfig.json`:
454+
2. Through `bucketConfig.json`:
418455

419456
```json
420457
{
@@ -433,7 +470,21 @@ BUCKET_FEATURES_DISABLED=feature3,feature4
433470
}
434471
```
435472

436-
1. Programmatically through the client options:
473+
3. Programmatically through the client options:
474+
475+
You can use a simple `Record<string, boolean>` and pass it either in the constructor or by setting `client.featureOverrides`:
476+
477+
```typescript
478+
// pass directly in the constructor
479+
const client = new BucketClient({ featureOverrides: { myFeature: true } });
480+
// or set on the client at a later time
481+
client.featureOverrides = { myFeature: false };
482+
483+
// clear feature overrides. Same as setting to {}.
484+
client.clearFeatureOverrides();
485+
```
486+
487+
To get dynamic overrides, use a function which takes a context and returns a boolean or an object with the shape of `{isEnabled, config}`:
437488

438489
```typescript
439490
import { BucketClient, Context } from "@bucketco/node-sdk";

packages/node-sdk/example/app.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import bucket from "./bucket";
66

77
beforeAll(async () => await bucket.initialize());
88
beforeEach(() => {
9-
bucket.featureOverrides = () => ({
9+
bucket.featureOverrides = {
1010
"show-todos": true,
11-
});
11+
};
1212
});
1313

1414
describe("API Tests", () => {

packages/node-sdk/src/client.ts

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
EvaluatedFeaturesAPIResponse,
2222
FeatureAPIResponse,
2323
FeatureDefinition,
24+
FeatureOverrides,
2425
FeatureOverridesFn,
2526
IdType,
2627
RawFeature,
@@ -338,9 +339,38 @@ export class BucketClient {
338339
* @remarks
339340
* The feature overrides are used to override the feature definitions.
340341
* This is useful for testing or development.
342+
*
343+
* @example
344+
* ```ts
345+
* client.featureOverrides = {
346+
* "feature-1": true,
347+
* "feature-2": false,
348+
* };
349+
* ```
350+
**/
351+
set featureOverrides(overrides: FeatureOverridesFn | FeatureOverrides) {
352+
if (typeof overrides === "object") {
353+
this._config.featureOverrides = () => overrides;
354+
} else {
355+
this._config.featureOverrides = overrides;
356+
}
357+
}
358+
359+
/**
360+
* Clears the feature overrides.
361+
*
362+
* @remarks
363+
* This is useful for testing or development.
364+
*
365+
* @example
366+
* ```ts
367+
* afterAll(() => {
368+
* client.clearFeatureOverrides();
369+
* });
370+
* ```
341371
**/
342-
set featureOverrides(overrides: FeatureOverridesFn) {
343-
this._config.featureOverrides = overrides;
372+
clearFeatureOverrides() {
373+
this._config.featureOverrides = () => ({});
344374
}
345375

346376
/**
@@ -1107,11 +1137,17 @@ export class BucketClient {
11071137
this._config.featureOverrides(context),
11081138
).map(([key, override]) => [
11091139
key,
1110-
{
1111-
key,
1112-
isEnabled: isObject(override) ? override.isEnabled : !!override,
1113-
config: isObject(override) ? override.config : undefined,
1114-
},
1140+
isObject(override)
1141+
? {
1142+
key,
1143+
isEnabled: override.isEnabled,
1144+
config: override.config,
1145+
}
1146+
: {
1147+
key,
1148+
isEnabled: !!override,
1149+
config: undefined,
1150+
},
11151151
]);
11161152

11171153
if (overrides.length > 0) {

0 commit comments

Comments
 (0)