-
Notifications
You must be signed in to change notification settings - Fork 31
[sc-154361] #1 Implement target evaluation and 'OFF' evaluation. #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c695f15
30325bf
13cf885
3667ae8
a8ab5d9
a28b852
d0ed14c
1bfb4dd
0158058
8d08641
8941124
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,21 +67,21 @@ describe.each([ | |
| }); | ||
|
|
||
| it('should get the same values', () => { | ||
| expect(context?.valueForKind('user', new AttributeReference('cat'))).toEqual('calico'); | ||
| expect(context?.valueForKind('user', new AttributeReference('name'))).toEqual('context name'); | ||
| expect(context?.valueForKind(new AttributeReference('cat'), 'user')).toEqual('calico'); | ||
| expect(context?.valueForKind(new AttributeReference('name'), 'user')).toEqual('context name'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a big deal, but: I'm unclear on why you're using |
||
| expect(context?.kinds).toStrictEqual(['user']); | ||
| expect(context?.kindsAndKeys).toStrictEqual({ user: 'test' }); | ||
| // Canonical keys for 'user' contexts are just the key. | ||
| expect(context?.canonicalKey).toEqual('test'); | ||
| expect(context?.valueForKind('user', new AttributeReference('transient'))).toBeTruthy(); | ||
| expect(context?.valueForKind(new AttributeReference('transient'), 'user')).toBeTruthy(); | ||
| expect(context?.secondary('user')).toEqual('secondary'); | ||
| expect(context?.isMultiKind).toBeFalsy(); | ||
| expect(context?.privateAttributes('user')?.[0].redactionName) | ||
| .toEqual(new AttributeReference('/~1dog~0~0~1~1').redactionName); | ||
| }); | ||
|
|
||
| it('should not get values for a context kind that does not exist', () => { | ||
| expect(context?.valueForKind('org', new AttributeReference('cat'))).toBeUndefined(); | ||
| expect(context?.valueForKind(new AttributeReference('cat'), 'org')).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('should have the correct kinds', () => { | ||
|
|
@@ -108,12 +108,12 @@ describe('given a valid legacy user without custom attributes', () => { | |
| }); | ||
|
|
||
| it('should get expected values', () => { | ||
| expect(context?.valueForKind('user', new AttributeReference('name'))).toEqual('context name'); | ||
| expect(context?.valueForKind(new AttributeReference('name'), 'user')).toEqual('context name'); | ||
| expect(context?.kinds).toStrictEqual(['user']); | ||
| expect(context?.kindsAndKeys).toStrictEqual({ user: 'test' }); | ||
| // Canonical keys for 'user' contexts are just the key. | ||
| expect(context?.canonicalKey).toEqual('test'); | ||
| expect(context?.valueForKind('user', new AttributeReference('transient'))).toBeTruthy(); | ||
| expect(context?.valueForKind(new AttributeReference('transient'), 'user')).toBeTruthy(); | ||
| expect(context?.secondary('user')).toEqual('secondary'); | ||
| expect(context?.isMultiKind).toBeFalsy(); | ||
| expect(context?.privateAttributes('user')?.[0].redactionName) | ||
|
|
@@ -204,8 +204,8 @@ describe('given a multi-kind context', () => { | |
| }); | ||
|
|
||
| it('should get values from the correct context', () => { | ||
| expect(context?.valueForKind('org', new AttributeReference('value'))).toEqual('OrgValue'); | ||
| expect(context?.valueForKind('user', new AttributeReference('value'))).toEqual('UserValue'); | ||
| expect(context?.valueForKind(new AttributeReference('value'), 'org')).toEqual('OrgValue'); | ||
| expect(context?.valueForKind(new AttributeReference('value'), 'user')).toEqual('UserValue'); | ||
|
|
||
| expect(context?.secondary('org')).toEqual('value'); | ||
| expect(context?.secondary('user')).toBeUndefined(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| import { Context, LDContext } from '@launchdarkly/js-sdk-common'; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently there are very limited tests for just the couple things the evaluator supports. |
||
| import { BigSegmentStoreMembership } from '../../src/api/interfaces'; | ||
| import { Flag } from '../../src/evaluation/data/Flag'; | ||
| import { Segment } from '../../src/evaluation/data/Segment'; | ||
| import EvalResult from '../../src/evaluation/EvalResult'; | ||
| import Evaluator from '../../src/evaluation/Evaluator'; | ||
| import { Queries } from '../../src/evaluation/Queries'; | ||
| import Reasons from '../../src/evaluation/Reasons'; | ||
|
|
||
| const offBaseFlag = { | ||
| key: 'feature0', | ||
| version: 1, | ||
| on: false, | ||
| fallthrough: { variation: 1 }, | ||
| variations: [ | ||
| 'zero', | ||
| 'one', | ||
| 'two', | ||
| ], | ||
| }; | ||
|
|
||
| const noQueries: Queries = { | ||
| getFlag(): Promise<Flag | null> { | ||
| throw new Error('Function not implemented.'); | ||
| }, | ||
| getSegment(): Promise<Segment | null> { | ||
| throw new Error('Function not implemented.'); | ||
| }, | ||
| getBigSegmentsMembership(): Promise<BigSegmentStoreMembership | null> { | ||
| throw new Error('Function not implemented.'); | ||
| }, | ||
| }; | ||
|
|
||
| describe.each<[Flag, LDContext, EvalResult | undefined]>([ | ||
| [{ | ||
| ...offBaseFlag, | ||
| }, { key: 'user-key' }, EvalResult.ForSuccess(null, Reasons.Off, undefined)], | ||
| [{ | ||
| ...offBaseFlag, offVariation: 2, | ||
| }, { key: 'user-key' }, EvalResult.ForSuccess('two', Reasons.Off, 2)], | ||
| ])('Given off flags and an evaluator', (flag, context, expected) => { | ||
| const evaluator = new Evaluator(noQueries); | ||
|
|
||
| // @ts-ignore | ||
| it(`produces the expected evaluation result for context: ${context.key} ${context.kind} targets: ${flag.targets?.map((t) => `${t.values}, ${t.variation}`)} context targets: ${flag.contextTargets?.map((t) => `${t.contextKind}: ${t.values}, ${t.variation}`)}`, async () => { | ||
| const result = await evaluator.evaluate(flag, Context.FromLDContext(context)!); | ||
| expect(result?.isError).toEqual(expected?.isError); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to my earlier comment, I think I would prefer a more specific failure here if |
||
| expect(result?.detail).toStrictEqual(expected?.detail); | ||
| expect(result?.message).toEqual(expected?.message); | ||
| }); | ||
| }); | ||
|
|
||
| const targetBaseFlag = { | ||
| key: 'feature0', | ||
| version: 1, | ||
| on: true, | ||
| fallthrough: { variation: 1 }, | ||
| variations: [ | ||
| 'zero', | ||
| 'one', | ||
| 'two', | ||
| ], | ||
| }; | ||
|
|
||
| describe.each<[Flag, LDContext, EvalResult | undefined]>([ | ||
| [{ | ||
| ...targetBaseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }], | ||
| }, { key: 'user-key' }, EvalResult.ForSuccess('zero', Reasons.TargetMatch, 0)], | ||
| [{ | ||
| ...targetBaseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }, | ||
| { | ||
| values: ['user-key2'], | ||
| variation: 2, | ||
| }, | ||
| ], | ||
| }, { key: 'user-key2' }, EvalResult.ForSuccess('two', Reasons.TargetMatch, 2)], | ||
| [{ | ||
| ...targetBaseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }, | ||
| { | ||
| values: ['user-key2'], | ||
| variation: 2, | ||
| }, | ||
| ], | ||
| contextTargets: [{ | ||
| values: [], | ||
| variation: 2, | ||
| }], | ||
| }, { key: 'user-key2' }, EvalResult.ForSuccess('two', Reasons.TargetMatch, 2)], | ||
| [{ | ||
| ...targetBaseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }, | ||
| { | ||
| values: ['user-key2'], | ||
| variation: 2, | ||
| }, | ||
| ], | ||
| contextTargets: [{ | ||
| contextKind: 'org', | ||
| values: ['org-key'], | ||
| variation: 1, | ||
| }], | ||
| }, { kind: 'org', key: 'org-key' }, EvalResult.ForSuccess('one', Reasons.TargetMatch, 1)], | ||
| ])('given flag configurations with different targets that match', (flag, context, expected) => { | ||
| const evaluator = new Evaluator(noQueries); | ||
| // @ts-ignore | ||
| it(`produces the expected evaluation result for context: ${context.key} ${context.kind} targets: ${flag.targets?.map((t) => `${t.values}, ${t.variation}`)} context targets: ${flag.contextTargets?.map((t) => `${t.contextKind}: ${t.values}, ${t.variation}`)}`, async () => { | ||
| const result = await evaluator.evaluate(flag, Context.FromLDContext(context)!); | ||
| expect(result?.isError).toEqual(expected?.isError); | ||
| expect(result?.detail).toStrictEqual(expected?.detail); | ||
| expect(result?.message).toEqual(expected?.message); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import { Context, LDContext } from '@launchdarkly/js-sdk-common'; | ||
| import { Flag } from '../../src/evaluation/data/Flag'; | ||
| import EvalResult from '../../src/evaluation/EvalResult'; | ||
| import evalTargets from '../../src/evaluation/evalTargets'; | ||
| import Reasons from '../../src/evaluation/Reasons'; | ||
|
|
||
| const baseFlag = { | ||
| key: 'feature0', | ||
| version: 1, | ||
| on: true, | ||
| fallthrough: { variation: 1 }, | ||
| variations: [ | ||
| 'zero', | ||
| 'one', | ||
| 'two', | ||
| ], | ||
| }; | ||
|
|
||
| describe.each<[Flag, LDContext, EvalResult | undefined]>([ | ||
| [{ | ||
| ...baseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }], | ||
| }, { key: 'user-key' }, EvalResult.ForSuccess('zero', Reasons.TargetMatch, 0)], | ||
| [{ | ||
| ...baseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }], | ||
| }, { key: 'different-key' }, undefined], | ||
| [{ | ||
| ...baseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }, | ||
| { | ||
| values: ['user-key2'], | ||
| variation: 2, | ||
| }, | ||
| ], | ||
| }, { key: 'user-key2' }, EvalResult.ForSuccess('two', Reasons.TargetMatch, 2)], | ||
| [{ | ||
| ...baseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }], | ||
| }, { key: 'different-key' }, undefined], | ||
| [{ | ||
| ...baseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }, | ||
| { | ||
| values: ['user-key2'], | ||
| variation: 2, | ||
| }, | ||
| ], | ||
| contextTargets: [{ | ||
| values: [], | ||
| variation: 2, | ||
| }], | ||
| }, { key: 'user-key2' }, EvalResult.ForSuccess('two', Reasons.TargetMatch, 2)], | ||
| [{ | ||
| ...baseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }, | ||
| { | ||
| values: ['user-key2'], | ||
| variation: 2, | ||
| }, | ||
| ], | ||
| contextTargets: [{ | ||
| values: [], | ||
| variation: 2, | ||
| }], | ||
| }, { kind: 'org', key: 'user-key2' }, undefined], | ||
| [{ | ||
| ...baseFlag, | ||
| targets: [{ | ||
| values: ['user-key'], | ||
| variation: 0, | ||
| }, | ||
| { | ||
| values: ['user-key2'], | ||
| variation: 2, | ||
| }, | ||
| ], | ||
| contextTargets: [{ | ||
| contextKind: 'org', | ||
| values: ['org-key'], | ||
| variation: 1, | ||
| }], | ||
| }, { kind: 'org', key: 'org-key' }, EvalResult.ForSuccess('one', Reasons.TargetMatch, 1)], | ||
| [{ | ||
| ...baseFlag, | ||
| contextTargets: [{ | ||
| values: ['org-key'], | ||
| variation: 1, | ||
| }], | ||
| }, { key: 'user-key' }, undefined], | ||
| [{ | ||
| ...baseFlag, | ||
| }, { key: 'user-key' }, undefined], | ||
| ])('given flag configurations with different targets', (flag, context, expected) => { | ||
| // @ts-ignore | ||
| it(`produces the expected evaluation result for context: ${context.key} ${context.kind} targets: ${flag.targets?.map((t) => `${t.values}, ${t.variation}`)} context targets: ${flag.contextTargets?.map((t) => `${t.contextKind}: ${t.values}, ${t.variation}`)}`, () => { | ||
| const result = evalTargets(flag, Context.FromLDContext(context)!); | ||
| expect(result?.isError).toEqual(expected?.isError); | ||
| expect(result?.detail).toStrictEqual(expected?.detail); | ||
| expect(result?.message).toEqual(expected?.message); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the order of these params to allow for the kind to be optional and simplify evaluation logic.