Skip to content

Commit 75f7547

Browse files
OFREP web provider
Signed-off-by: Thomas Poignant <thomas.poignant@gofeatureflag.org>
1 parent 9ec4dca commit 75f7547

File tree

14 files changed

+345
-338
lines changed

14 files changed

+345
-338
lines changed

libs/providers/ofrep-web/jest.config.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
export default {
33
displayName: 'providers-ofrep-web',
44
preset: '../../../jest.preset.js',
5+
globals: {
6+
'ts-jest': {
7+
tsconfig: '<rootDir>/tsconfig.spec.json',
8+
},
9+
},
510
transform: {
6-
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
11+
'^.+\\.[tj]s$': 'ts-jest',
712
},
13+
testEnvironment: 'jsdom',
814
moduleFileExtensions: ['ts', 'js', 'html'],
9-
coverageDirectory: '../../../coverage/libs/providers/ofrep-web',
15+
setupFiles: ['./jest.polyfills.js'],
1016
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// jest.polyfills.js
2+
/**
3+
* @note The block below contains polyfills for Node.js globals
4+
* required for Jest to function when running JSDOM tests.
5+
* These HAVE to be require's and HAVE to be in this exact
6+
* order, since "undici" depends on the "TextEncoder" global API.
7+
*
8+
* Consider migrating to a more modern test runner if
9+
* you don't want to deal with this.
10+
*/
11+
12+
const { TextDecoder, TextEncoder } = require('node:util');
13+
14+
// eslint-disable-next-line no-undef
15+
Object.defineProperties(globalThis, {
16+
TextDecoder: { value: TextDecoder },
17+
TextEncoder: { value: TextEncoder },
18+
})
19+
20+
const { Blob, File } = require('node:buffer');
21+
const { fetch, Headers, FormData, Request, Response } = require('undici');
22+
23+
// eslint-disable-next-line no-undef
24+
Object.defineProperties(globalThis, {
25+
fetch: { value: fetch, writable: true },
26+
Blob: { value: Blob },
27+
File: { value: File },
28+
Headers: { value: Headers },
29+
FormData: { value: FormData },
30+
Request: { value: Request },
31+
Response: { value: Response },
32+
})

libs/providers/ofrep-web/src/lib/error/fetch-error.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

libs/providers/ofrep-web/src/lib/model/evaluate-response.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum EvaluationStatus {
2+
SUCCESS_NO_CHANGES = 'SUCCESS_NO_CHANGES',
3+
SUCCESS_WITH_CHANGES = 'SUCCESS_WITH_CHANGES',
4+
}

libs/providers/ofrep-web/src/lib/model/flag-changes-response.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

libs/providers/ofrep-web/src/lib/model/in-memory-cache-entry.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { FetchAPI } from '@openfeature/ofrep-core';
2+
export interface OfrepWebProviderOptions {
3+
baseUrl: string;
4+
pollInterval?: number; // in milliseconds
5+
disablePolling?: boolean;
6+
}

libs/providers/ofrep-web/src/lib/model/options.ts

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 60 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,74 @@
11
import { OfrepWebProvider } from './ofrep-web-provider';
2-
import { OpenFeature } from '@openfeature/web-sdk';
32
import TestLogger from './test-logger';
4-
import fetchMock from 'fetch-mock-jest';
5-
import { ChangePropagationStrategy } from './model/options';
3+
// eslint-disable-next-line @nx/enforce-module-boundaries
4+
import { server } from '../../../../shared/ofrep-core/src/test/mock-service-worker';
5+
import { ClientProviderEvents, ClientProviderStatus, OpenFeature } from '@openfeature/web-sdk';
66

77
describe('OFREPWebProvider', () => {
8-
beforeEach(async () => {
9-
fetchMock.mockClear();
10-
fetchMock.reset();
11-
});
8+
beforeAll(() => server.listen());
9+
afterEach(() => server.resetHandlers());
10+
afterAll(() => server.close());
11+
12+
const defaultContext = {
13+
targetingKey: '21640825-95e7-4335-b149-bd6881cf7875',
14+
errors: {
15+
// 403: true,
16+
},
17+
};
18+
1219
it('xxx', async () => {
13-
const provider = new OfrepWebProvider('http://localhost:1031/ofrep/', [], {
14-
logger: new TestLogger(),
15-
changePropagationStrategy: ChangePropagationStrategy.POLLING,
16-
pollingOptions: {
17-
interval: 500,
18-
},
20+
const providerName = expect.getState().currentTestName || 'test-provider';
21+
const provider = new OfrepWebProvider({ baseUrl: 'https://localhost:8080', pollInterval: 100 }, new TestLogger());
22+
23+
await OpenFeature.setContext(defaultContext);
24+
await OpenFeature.setProviderAndWait(providerName, provider);
25+
const client = OpenFeature.getClient(providerName);
26+
27+
client.addHandler(ClientProviderEvents.Ready, (xxx) => {
28+
console.log(`ready: ${JSON.stringify(xxx)}`);
1929
});
2030

21-
const evaluateEndpoint = 'http://localhost:1031/ofrep/v1/evaluate';
22-
const flagChangesEndpoint = 'http://localhost:1031/ofrep/v1/flag/changes';
23-
const fetchResult = [
24-
{
25-
key: 'flag1',
26-
metadata: {
27-
additionalProp1: 'xxx',
28-
},
29-
reason: 'TARGETING_MATCH',
30-
value: 'toto',
31-
variant: 'Variant1',
32-
ETag: '9f9fc0b4',
33-
},
34-
{
35-
key: 'flag2',
36-
metadata: {
37-
additionalProp1: 'xxx',
38-
},
39-
reason: 'SPLIT',
40-
value: 'titi',
41-
variant: 'Variant150',
42-
ETag: '6c23a4',
43-
},
44-
{
45-
key: 'flag3',
46-
metadata: {
47-
additionalProp1: 'xxx',
48-
},
49-
reason: 'SPLIT',
50-
value: 'titi',
51-
variant: 'Variant150',
52-
ETag: '6c23a4',
53-
},
54-
];
31+
client.addHandler(ClientProviderEvents.Error, (xxx) => {
32+
console.log(`error: ${JSON.stringify(xxx)}`);
33+
});
5534

56-
const fetchFlagChanges = [
57-
{
58-
key: 'flag1',
59-
ETag: '9f9fc0b1',
60-
},
61-
{
62-
key: 'flag2',
63-
ETag: '6c23a5',
64-
},
65-
{
66-
key: 'flag3',
67-
errorCode: 'FLAG_NOT_FOUND',
68-
errorDetails: 'Flag not found',
35+
client.addHandler(ClientProviderEvents.Stale, (xxx) => {
36+
console.log(`stale: ${JSON.stringify(xxx)}`);
37+
});
38+
39+
client.addHandler(ClientProviderEvents.Reconciling, (xxx) => {
40+
console.log(`Reconciling: ${JSON.stringify(xxx)}`);
41+
});
42+
43+
client.addHandler(ClientProviderEvents.ContextChanged, (xxx) => {
44+
console.log(`ContextChanged: ${JSON.stringify(xxx)}`);
45+
});
46+
47+
client.addHandler(ClientProviderEvents.ConfigurationChanged, (xxx) => {
48+
console.log(`ConfigurationChanged: ${JSON.stringify(xxx)}`);
49+
});
50+
51+
console.log(client.getBooleanDetails('bool-flag', true));
52+
53+
console.log('status:', client.providerStatus);
54+
55+
await OpenFeature.setContext({
56+
...defaultContext,
57+
errors: {
58+
403: true,
6959
},
70-
];
71-
72-
fetchMock.post(evaluateEndpoint, (url, options) => {
73-
const t = JSON.parse(options.body as string).flags as string[];
74-
if (t.length === 0) {
75-
return fetchResult;
76-
}
77-
return fetchResult.filter((f) => t.includes(f.key));
7860
});
79-
fetchMock.post(flagChangesEndpoint, fetchFlagChanges, { overwriteRoutes: true });
80-
await OpenFeature.setContext({ targetingKey: 'user1' });
81-
await OpenFeature.setProviderAndWait(provider);
82-
const cli = OpenFeature.getClient();
83-
console.log(cli.getStringDetails('flag1', 'default'));
84-
console.log(cli.getStringDetails('flag3', 'default'));
61+
62+
console.log('status:', client.providerStatus);
63+
64+
console.log(client.getBooleanDetails('bool-flag', false));
8565

8666
await new Promise((resolve) => setTimeout(resolve, 1000));
87-
console.log(cli.getStringDetails('flag3', 'default'));
67+
await OpenFeature.setContext({
68+
...defaultContext,
69+
email: 'john.doe@goff.org',
70+
});
71+
await OpenFeature.close();
72+
await new Promise((resolve) => setTimeout(resolve, 3000));
8873
});
8974
});

0 commit comments

Comments
 (0)