Skip to content

Commit 8a423b2

Browse files
authored
fix: Ensure that test data user targets are handled correctly. (#223)
User targets require special logic to put the variation/values into `targets` instead of just in `contextTargets`. This was not being done, so user specific targets were not evaluating correctly. Github issue: launchdarkly/node-server-sdk#283
1 parent 63697e1 commit 8a423b2

File tree

3 files changed

+110
-14
lines changed

3 files changed

+110
-14
lines changed

packages/shared/sdk-server/__tests__/LDClient.evaluation.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,29 @@ describe('given an LDClient with test data', () => {
137137
reason: { kind: 'ERROR', errorKind: 'FLAG_NOT_FOUND' },
138138
});
139139
});
140+
141+
it('evaluates and updates user targets from test data', async () => {
142+
// This is a specific test case for a customer issue.
143+
// It tests the combination of evaluation and test data functionality.
144+
const userUuid = '1234';
145+
const userContextObject = {
146+
kind: 'user',
147+
key: userUuid,
148+
};
149+
150+
await td.update(
151+
td.flag('my-feature-flag-1').variationForUser(userUuid, false).fallthroughVariation(false),
152+
);
153+
const valueA = await client.variation('my-feature-flag-1', userContextObject, 'default');
154+
expect(valueA).toEqual(false);
155+
156+
await td.update(
157+
td.flag('my-feature-flag-1').variationForUser(userUuid, true).fallthroughVariation(false),
158+
);
159+
160+
const valueB = await client.variation('my-feature-flag-1', userContextObject, 'default');
161+
expect(valueB).toEqual(true);
162+
});
140163
});
141164

142165
describe('given an offline client', () => {

packages/shared/sdk-server/__tests__/integrations/test_data/TestData.test.ts

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,8 @@ describe('given a TestData instance', () => {
207207
expect(builtFlag.fallthrough).toEqual({ variation: 1 });
208208
expect(builtFlag.offVariation).toEqual(0);
209209
expect(builtFlag.variations).toEqual([true, false]);
210-
expect(builtFlag.contextTargets).toEqual([
211-
{ contextKind: 'user', values: ['billy'], variation: 0 },
212-
]);
210+
expect(builtFlag.contextTargets).toEqual([{ contextKind: 'user', values: [], variation: 0 }]);
211+
expect(builtFlag.targets).toEqual([{ values: ['billy'], variation: 0 }]);
213212
expect(builtFlag.rules).toEqual(flagRules);
214213
});
215214

@@ -249,16 +248,32 @@ describe('given a TestData instance', () => {
249248
it('can set boolean values for a specific user target', () => {
250249
const flag = td.flag('test-flag').variationForContext('user', 'potato', false);
251250
const flag2 = td.flag('test-flag').variationForUser('potato', true);
252-
expect(flag.build(0).contextTargets).toEqual([
251+
const builtFlag1 = flag.build(0);
252+
const builtFlag2 = flag2.build(0);
253+
// User targets order by the context targets, but use values from
254+
// the legacy targets.
255+
expect(builtFlag1.contextTargets).toEqual([
253256
{
254257
contextKind: 'user',
258+
variation: 1,
259+
values: [],
260+
},
261+
]);
262+
expect(builtFlag1.targets).toEqual([
263+
{
255264
variation: 1,
256265
values: ['potato'],
257266
},
258267
]);
259-
expect(flag2.build(0).contextTargets).toEqual([
268+
expect(builtFlag2.contextTargets).toEqual([
260269
{
261270
contextKind: 'user',
271+
variation: 0,
272+
values: [],
273+
},
274+
]);
275+
expect(builtFlag2.targets).toEqual([
276+
{
262277
variation: 0,
263278
values: ['potato'],
264279
},
@@ -347,13 +362,32 @@ describe('given a TestData instance', () => {
347362
it('can move a targeted context from one variation to another', () => {
348363
const flag = td
349364
.flag('test-flag')
350-
.variationForContext('user', 'ben', false)
351-
.variationForContext('user', 'ben', true);
365+
.variationForContext('org', 'ben', false)
366+
.variationForContext('org', 'ben', true);
367+
// Because there was only one target in the first variation there will be only
368+
// a single variation after that target is removed.
369+
expect(flag.build(1).contextTargets).toEqual([
370+
{
371+
contextKind: 'org',
372+
variation: 0,
373+
values: ['ben'],
374+
},
375+
]);
376+
});
377+
378+
it('can move a targeted user from one variation to another', () => {
379+
const flag = td.flag('test-flag').variationForUser('ben', false).variationForUser('ben', true);
352380
// Because there was only one target in the first variation there will be only
353381
// a single variation after that target is removed.
354382
expect(flag.build(1).contextTargets).toEqual([
355383
{
356384
contextKind: 'user',
385+
variation: 0,
386+
values: [],
387+
},
388+
]);
389+
expect(flag.build(1).targets).toEqual([
390+
{
357391
variation: 0,
358392
values: ['ben'],
359393
},
@@ -363,19 +397,51 @@ describe('given a TestData instance', () => {
363397
it('if a targeted context is moved from one variation to another, then other targets remain for that variation', () => {
364398
const flag = td
365399
.flag('test-flag')
366-
.variationForContext('user', 'ben', false)
367-
.variationForContext('user', 'joe', false)
368-
.variationForContext('user', 'ben', true);
400+
.variationForUser('ben', false)
401+
.variationForUser('joe', false)
402+
.variationForUser('ben', true);
369403

370404
expect(flag.build(1).contextTargets).toEqual([
371405
{
372406
contextKind: 'user',
373407
variation: 0,
374-
values: ['ben'],
408+
values: [],
375409
},
376410
{
377411
contextKind: 'user',
378412
variation: 1,
413+
values: [],
414+
},
415+
]);
416+
417+
expect(flag.build(1).targets).toEqual([
418+
{
419+
variation: 0,
420+
values: ['ben'],
421+
},
422+
{
423+
variation: 1,
424+
values: ['joe'],
425+
},
426+
]);
427+
});
428+
429+
it('if a targeted user is moved from one variation to another, then other targets remain for that variation', () => {
430+
const flag = td
431+
.flag('test-flag')
432+
.variationForContext('org', 'ben', false)
433+
.variationForContext('org', 'joe', false)
434+
.variationForContext('org', 'ben', true);
435+
436+
expect(flag.build(1).contextTargets).toEqual([
437+
{
438+
contextKind: 'org',
439+
variation: 0,
440+
values: ['ben'],
441+
},
442+
{
443+
contextKind: 'org',
444+
variation: 1,
379445
values: ['joe'],
380446
},
381447
]);
@@ -393,13 +459,14 @@ describe('given a TestData instance', () => {
393459
{
394460
contextKind: 'user',
395461
variation: 1,
396-
values: ['ben'],
462+
values: [],
397463
},
398464
{
399465
contextKind: 'potato',
400466
variation: 1,
401467
values: ['russet', 'yukon'],
402468
},
403469
]);
470+
expect(flag.build(0).targets).toEqual([{ variation: 1, values: ['ben'] }]);
404471
});
405472
});

packages/shared/sdk-server/src/integrations/test_data/TestDataFlagBuilder.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,18 +394,24 @@ export default class TestDataFlagBuilder {
394394

395395
if (this.data.targetsByVariation) {
396396
const contextTargets: Target[] = [];
397+
const userTargets: Omit<Target, 'contextKind'>[] = [];
397398
Object.entries(this.data.targetsByVariation).forEach(
398399
([variation, contextTargetsForVariation]) => {
399400
Object.entries(contextTargetsForVariation).forEach(([contextKind, values]) => {
401+
const numberVariation = parseInt(variation, 10);
400402
contextTargets.push({
401403
contextKind,
402-
values,
404+
values: contextKind === 'user' ? [] : values,
403405
// Iterating the object it will be a string.
404-
variation: parseInt(variation, 10),
406+
variation: numberVariation,
405407
});
408+
if (contextKind === 'user') {
409+
userTargets.push({ values, variation: numberVariation });
410+
}
406411
});
407412
},
408413
);
414+
baseFlagObject.targets = userTargets;
409415
baseFlagObject.contextTargets = contextTargets;
410416
}
411417

0 commit comments

Comments
 (0)