Skip to content

Commit 4df7885

Browse files
committed
Merge branch 'main' into rlamb/SDK-1375/only-support-identify-with-result
2 parents 44f6e9d + 13ac90e commit 4df7885

File tree

12 files changed

+334
-13
lines changed

12 files changed

+334
-13
lines changed

.github/workflows/shopify-oxygen.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,15 @@ jobs:
2626
with:
2727
workspace_name: '@launchdarkly/shopify-oxygen-sdk'
2828
workspace_path: packages/sdk/shopify-oxygen
29+
- name: Install contract test service dependencies
30+
run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests install --no-immutable
31+
- name: Build the test service
32+
run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests build
33+
- name: Launch the test service in the background
34+
run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests start &> /dev/null &
35+
- uses: launchdarkly/gh-actions/actions/contract-tests@21174f3a7f3aa3e3121227ec91842e8a1ebeec6e
36+
with:
37+
test_service_port: 8000
38+
token: ${{ secrets.GITHUB_TOKEN }}
39+
# Based on run-test-harness.sh from the sdk package
40+
extra_params: '--url http://localhost:8000 --skip "streaming.*" --skip "evaluation.*" --skip "event.*" --skip "service.*" --stop-service-at-end'

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
"packages/telemetry/browser-telemetry",
4343
"contract-tests",
4444
"packages/sdk/combined-browser",
45-
"packages/sdk/shopify-oxygen"
45+
"packages/sdk/shopify-oxygen",
46+
"packages/sdk/shopify-oxygen/contract-tests"
4647
],
4748
"private": true,
4849
"scripts": {

packages/sdk/browser/__tests__/compat/LDClientCompatImpl.test.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe('given a LDClientCompatImpl client with mocked browser client that is i
7070
await expect(client.waitForInitialization()).resolves.toBeUndefined();
7171
expect(mockBrowserClient.identify).toHaveBeenCalledWith(
7272
{ kind: 'user', key: 'user-key' },
73-
{ bootstrap: undefined, hash: undefined, noTimeout: true },
73+
{ bootstrap: undefined, hash: undefined, noTimeout: true, sheddable: false },
7474
);
7575
});
7676
});
@@ -83,7 +83,7 @@ describe('given a LDClientCompatImpl client with mocked browser client that init
8383
mockBrowserClient.identify.mockImplementation(
8484
() =>
8585
new Promise((r) => {
86-
setTimeout(r, 100);
86+
setTimeout(() => r({ status: 'completed' }), 100);
8787
}),
8888
);
8989
client = new LDClientCompatImpl('env-key', { kind: 'user', key: 'user-key' });
@@ -99,7 +99,10 @@ describe('given a LDClientCompatImpl client with mocked browser client that init
9999

100100
const result = await client.identify(context);
101101

102-
expect(mockBrowserClient.identify).toHaveBeenCalledWith(context, { hash: undefined });
102+
expect(mockBrowserClient.identify).toHaveBeenCalledWith(context, {
103+
hash: undefined,
104+
sheddable: false,
105+
});
103106
expect(result).toEqual(mockFlags);
104107
});
105108

@@ -168,7 +171,7 @@ describe('given a LDClientCompatImpl client with mocked browser client that init
168171
await expect(client.waitForInitialization()).resolves.toBeUndefined();
169172
expect(mockBrowserClient.identify).toHaveBeenCalledWith(
170173
{ kind: 'user', key: 'user-key' },
171-
{ bootstrap: undefined, hash: undefined, noTimeout: true },
174+
{ bootstrap: undefined, hash: undefined, noTimeout: true, sheddable: false },
172175
);
173176
});
174177

@@ -180,7 +183,7 @@ describe('given a LDClientCompatImpl client with mocked browser client that init
180183
await expect(client.waitForInitialization()).resolves.toBeUndefined();
181184
expect(mockBrowserClient.identify).toHaveBeenCalledWith(
182185
{ kind: 'user', key: 'user-key' },
183-
{ bootstrap: undefined, hash: undefined, noTimeout: true },
186+
{ bootstrap: undefined, hash: undefined, noTimeout: true, sheddable: false },
184187
);
185188
});
186189

@@ -191,7 +194,7 @@ describe('given a LDClientCompatImpl client with mocked browser client that init
191194
await expect(client.waitUntilReady()).resolves.toBeUndefined();
192195
expect(mockBrowserClient.identify).toHaveBeenCalledWith(
193196
{ kind: 'user', key: 'user-key' },
194-
{ bootstrap: undefined, hash: undefined, noTimeout: true },
197+
{ bootstrap: undefined, hash: undefined, noTimeout: true, sheddable: false },
195198
);
196199
});
197200

@@ -429,6 +432,7 @@ it('forwards bootstrap and hash to BrowserClient identify call', async () => {
429432
bootstrap: bootstrapData,
430433
hash: 'someHash',
431434
noTimeout: true,
435+
sheddable: false,
432436
});
433437
});
434438

@@ -453,7 +457,7 @@ describe('given a LDClientCompatImpl client with mocked browser client which fai
453457

454458
expect(mockBrowserClient.identify).toHaveBeenCalledWith(
455459
{ kind: 'user', key: 'user-key' },
456-
{ bootstrap: undefined, hash: undefined, noTimeout: true },
460+
{ bootstrap: undefined, hash: undefined, noTimeout: true, sheddable: false },
457461
);
458462
});
459463

@@ -466,7 +470,7 @@ describe('given a LDClientCompatImpl client with mocked browser client which fai
466470

467471
expect(mockBrowserClient.identify).toHaveBeenCalledWith(
468472
{ kind: 'user', key: 'user-key' },
469-
{ bootstrap: undefined, hash: undefined, noTimeout: true },
473+
{ bootstrap: undefined, hash: undefined, noTimeout: true, sheddable: false },
470474
);
471475
});
472476

@@ -477,7 +481,7 @@ describe('given a LDClientCompatImpl client with mocked browser client which fai
477481

478482
expect(mockBrowserClient.identify).toHaveBeenCalledWith(
479483
{ kind: 'user', key: 'user-key' },
480-
{ bootstrap: undefined, hash: undefined, noTimeout: true },
484+
{ bootstrap: undefined, hash: undefined, noTimeout: true, sheddable: false },
481485
);
482486
});
483487

@@ -489,7 +493,7 @@ describe('given a LDClientCompatImpl client with mocked browser client which fai
489493

490494
expect(mockBrowserClient.identify).toHaveBeenCalledWith(
491495
{ kind: 'user', key: 'user-key' },
492-
{ bootstrap: undefined, hash: undefined, noTimeout: true },
496+
{ bootstrap: undefined, hash: undefined, noTimeout: true, sheddable: false },
493497
);
494498
});
495499

packages/sdk/browser/src/compat/LDClientCompatImpl.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,20 @@ export default class LDClientCompatImpl implements LDClient {
5454
hash?: string,
5555
): Promise<void> {
5656
try {
57-
await this._client.identify(context, { noTimeout: true, bootstrap, hash });
57+
const result = await this._client.identify(context, {
58+
noTimeout: true,
59+
bootstrap,
60+
hash,
61+
sheddable: false,
62+
});
63+
64+
if (result.status === 'error') {
65+
throw result.error;
66+
} else if (result.status === 'timeout') {
67+
throw new LDTimeoutError('Identify timed out');
68+
}
69+
// status === 'completed' ('shed' cannot happen with sheddable: false)
70+
5871
this._initState = 'success';
5972
this._initResolve?.();
6073
this._emitter.emit('initialized');
@@ -138,7 +151,16 @@ export default class LDClientCompatImpl implements LDClient {
138151
onDone?: (err: Error | null, flags: LDFlagSet | null) => void,
139152
): Promise<LDFlagSet> | undefined {
140153
return wrapPromiseCallback(
141-
this._client.identify(context, { hash }).then(() => this.allFlags()),
154+
this._client.identify(context, { hash, sheddable: false }).then((result) => {
155+
// Check if identification was successful
156+
if (result.status === 'error') {
157+
throw result.error;
158+
} else if (result.status === 'timeout') {
159+
throw new LDTimeoutError('Identify timed out');
160+
}
161+
// status === 'completed' ('shed' cannot happen with sheddable: false)
162+
return this.allFlags();
163+
}),
142164
onDone,
143165
) as Promise<LDFlagSet> | undefined;
144166
// The typing here is a little funny. The wrapPromiseCallback can technically return
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.log
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@launchdarkly/shopify-oxygen-contract-tests",
3+
"version": "0.0.0",
4+
"main": "dist/index.js",
5+
"scripts": {
6+
"start": "node --inspect dist/index.js",
7+
"build": "tsup",
8+
"dev": "tsc --watch"
9+
},
10+
"type": "module",
11+
"license": "Apache-2.0",
12+
"private": true,
13+
"dependencies": {
14+
"@launchdarkly/js-server-sdk-common": "workspace:^",
15+
"@launchdarkly/shopify-oxygen-sdk": "workspace:^",
16+
"express": "^5.0.1"
17+
},
18+
"devDependencies": {
19+
"@types/express": "^5.0.1",
20+
"@types/node": "^18.11.9",
21+
"tsup": "^8.5.0",
22+
"typescript": "^4.9.0"
23+
}
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
3+
# If sdk-test-harness is not in the path, then you will need to set
4+
# the SDK_TEST_HARNESS environment variable to the path to the sdk-test-harness binary.
5+
6+
# Default to the local sdk-test-harness binary if not provided
7+
if [ -z "${SDK_TEST_HARNESS}" ]; then
8+
SDK_TEST_HARNESS="sdk-test-harness"
9+
fi
10+
11+
# Uncomment this to start the test service in the background
12+
# yarn start &> test-service.log &
13+
14+
# skipping all tests that require streaming connections.
15+
${SDK_TEST_HARNESS} --url http://localhost:8000 \
16+
--skip "streaming.*" \
17+
--skip "evaluation.*" \
18+
--skip "event.*" \
19+
--skip "service.*" \
20+
--stop-service-at-end
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import express, { Request, Response } from 'express';
2+
import { Server } from 'http';
3+
4+
import { ClientPool } from './utils';
5+
6+
/* eslint-disable no-console */
7+
8+
// export DEBUG=true to enable debugging
9+
// unset DEBUG to disable debugging
10+
const debugging = process.env.DEBUG === 'true';
11+
12+
const app = express();
13+
let server: Server | null = null;
14+
15+
app.use(express.json());
16+
17+
const port = 8000;
18+
19+
const clientPool = new ClientPool();
20+
21+
if (debugging) {
22+
app.use((req: Request, res: Response, next: Function) => {
23+
console.debug('request', req.method, req.url);
24+
if (req.body) {
25+
console.debug('request', JSON.stringify(req.body, null, 2));
26+
}
27+
next();
28+
});
29+
} else {
30+
// NOOP global console.debug
31+
console.debug = () => {};
32+
}
33+
34+
app.get('/', (req: Request, res: Response) => {
35+
res.header('Content-Type', 'application/json');
36+
res.json({
37+
capabilities: ['server-side-polling', 'server-side'],
38+
});
39+
});
40+
41+
app.delete('/', (req: Request, res: Response) => {
42+
console.log('Test service has told us to exit');
43+
res.status(204);
44+
res.send();
45+
46+
if (server) {
47+
server.close(() => process.exit());
48+
}
49+
});
50+
51+
app.post('/', async (req: Request, res: Response) => {
52+
await clientPool.createClient(req.body, res);
53+
});
54+
55+
app.post('/clients/:id', async (req: Request, res: Response) => {
56+
await clientPool.runCommand(req.params.id, req.body, res);
57+
});
58+
59+
app.delete('/clients/:id', async (req: Request, res: Response) => {
60+
console.debug('DELETE request received /clients/:id');
61+
console.debug(req.params.id);
62+
await clientPool.deleteClient(req.params.id, res);
63+
});
64+
65+
server = app.listen(port, () => {
66+
// eslint-disable-next-line no-console
67+
console.log('Listening on port %d', port);
68+
});

0 commit comments

Comments
 (0)