Skip to content

Commit 8cc6cc3

Browse files
committed
feat(contract-tests): add flag change listener support for Node server SDK
Implement contract test support for flag change listeners in the Node server SDK test service: - Add 'flag-change-listeners' capability to the service - Handle registerFlagChangeListener, registerFlagValueChangeListener, and unregisterListener commands - Use SDK 'update' and 'update:${key}' events for config and value changes - Post listener callbacks to the test harness via HTTP - Unregister listeners on client close
1 parent 8ead8ad commit 8cc6cc3

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

packages/sdk/server-node/contract-tests/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ app.get('/', (req: Request, res: Response) => {
4242
'client-prereq-events',
4343
'event-gzip',
4444
'optional-event-gzip',
45+
'flag-change-listeners',
4546
],
4647
});
4748
});

packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,21 @@ interface CommandParams {
140140
newEndpoint: string;
141141
oldEndpoint: string;
142142
};
143+
registerFlagChangeListener?: {
144+
listenerId: string;
145+
flagKey: string;
146+
callbackUri: string;
147+
};
148+
registerFlagValueChangeListener?: {
149+
listenerId: string;
150+
flagKey: string;
151+
context: LDContext;
152+
defaultValue: LDFlagValue;
153+
callbackUri: string;
154+
};
155+
unregisterListener?: {
156+
listenerId: string;
157+
};
143158
}
144159

145160
export function makeSdkConfig(options: SdkConfigOptions, tag: string): LDOptions {
@@ -316,9 +331,15 @@ export interface SdkClientEntity {
316331
doCommand: (params: CommandParams) => Promise<any>;
317332
}
318333

334+
interface ListenerEntry {
335+
eventName: string;
336+
handler: (...args: any[]) => void;
337+
}
338+
319339
export async function newSdkClientEntity(options: any): Promise<SdkClientEntity> {
320340
const c: any = {};
321341
const log = Log(options.tag);
342+
const listeners = new Map<string, ListenerEntry>();
322343

323344
log.info(`Creating client with configuration: ${JSON.stringify(options.configuration)}`);
324345
const timeout =
@@ -341,6 +362,10 @@ export async function newSdkClientEntity(options: any): Promise<SdkClientEntity>
341362
}
342363

343364
c.close = () => {
365+
listeners.forEach((entry) => {
366+
client.off(entry.eventName, entry.handler);
367+
});
368+
listeners.clear();
344369
client.close();
345370
log.info('Test ended');
346371
};
@@ -514,6 +539,65 @@ export async function newSdkClientEntity(options: any): Promise<SdkClientEntity>
514539
}
515540
}
516541

542+
case 'registerFlagChangeListener': {
543+
const p = params.registerFlagChangeListener!;
544+
const eventName = p.flagKey ? `update:${p.flagKey}` : 'update';
545+
546+
const handler = (eventParams: { key: string }) => {
547+
got
548+
.post(p.callbackUri, {
549+
json: {
550+
listenerId: p.listenerId,
551+
flagKey: eventParams.key,
552+
},
553+
})
554+
.catch(() => {});
555+
};
556+
557+
listeners.set(p.listenerId, { eventName, handler });
558+
client.on(eventName, handler);
559+
return undefined;
560+
}
561+
562+
case 'registerFlagValueChangeListener': {
563+
const p = params.registerFlagValueChangeListener!;
564+
const eventName = `update:${p.flagKey}`;
565+
566+
let oldValue = await client.variation(p.flagKey, p.context, p.defaultValue);
567+
568+
const handler = async () => {
569+
const newValue = await client.variation(p.flagKey, p.context, p.defaultValue);
570+
if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
571+
const previousValue = oldValue;
572+
oldValue = newValue;
573+
got
574+
.post(p.callbackUri, {
575+
json: {
576+
listenerId: p.listenerId,
577+
flagKey: p.flagKey,
578+
oldValue: previousValue,
579+
newValue,
580+
},
581+
})
582+
.catch(() => {});
583+
}
584+
};
585+
586+
listeners.set(p.listenerId, { eventName, handler });
587+
client.on(eventName, handler);
588+
return undefined;
589+
}
590+
591+
case 'unregisterListener': {
592+
const p = params.unregisterListener!;
593+
const entry = listeners.get(p.listenerId);
594+
if (entry) {
595+
client.off(entry.eventName, entry.handler);
596+
listeners.delete(p.listenerId);
597+
}
598+
return undefined;
599+
}
600+
517601
default:
518602
throw badCommandError;
519603
}

0 commit comments

Comments
 (0)