Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as fs from 'fs';
import * as path from 'path';

import {
fdv2FullPayload,
makeFDv2Client,
makeFDv2Platform,
singleValueFlag,
} from './overrides/overridesTestSupport';
import TestOverrideSource from './overrides/TestOverrideSource';

// These vectors come from the OVERRIDE specification. Each vector sets up LaunchDarkly data, an
// override layer, and an initialization state. The test evaluates one flag through the full
// client stack and checks the value, the variation index, and the reason.
const vectorsPath = path.join(__dirname, 'overrides', 'override-vectors', 'vectors.json');

// The vectors' semantics are versioned. A schema change means this runner needs review.
const supportedVectorSchema = '0.4.0';

interface OverrideVector {
description: string;
group: string;
launchDarklyData: {
initialized: boolean;
flags: Record<string, any>;
segments: Record<string, any>;
};
overrides: {
flags?: Record<string, any>;
flagValues?: Record<string, any>;
segments?: Record<string, any>;
};
evaluate: {
flagKey: string;
context: any;
defaultValue: any;
};
expect: {
value: any;
variationIndex: number | null;
reason: Record<string, any>;
summaryOverrideAffected?: boolean;
};
}

const file: { schemaVersion: string; vectors: OverrideVector[] } = JSON.parse(
fs.readFileSync(vectorsPath, 'utf8'),
);

it('uses the supported vector schema', () => {
expect(file.schemaVersion).toEqual(supportedVectorSchema);
expect(file.vectors.length).toBeGreaterThan(0);
});

describe.each(
file.vectors.map((vector) => [`${vector.group}: ${vector.description}`, vector] as const),
)('%s', (_name, vector) => {
it('evaluates as the vector expects', async () => {
// The override layer is the same document shape the file-based source accepts. A flag value
// entry expands into a full flag that is on and serves the value by fallthrough.
const source = new TestOverrideSource({
flags: [
...Object.values(vector.overrides.flags ?? {}),
...Object.entries(vector.overrides.flagValues ?? {}).map(([key, value]) =>
singleValueFlag(key, value),
),
],
segments: Object.values(vector.overrides.segments ?? {}),
});
const platform = vector.launchDarklyData.initialized
? makeFDv2Platform(
fdv2FullPayload(vector.launchDarklyData.flags, vector.launchDarklyData.segments),
)
: makeFDv2Platform();
const client = makeFDv2Client(platform, { dataSystem: { overrides: source } });
try {
if (vector.launchDarklyData.initialized) {
await client.waitForInitialization({ timeout: 5 });
}

const detail = await client.variationDetail(
vector.evaluate.flagKey,
vector.evaluate.context,
vector.evaluate.defaultValue,
);

expect(detail.value).toEqual(vector.expect.value);
if (vector.expect.variationIndex === null) {
expect(detail.variationIndex ?? null).toBeNull();
} else {
expect(detail.variationIndex).toEqual(vector.expect.variationIndex);
}

// The reason is compared only on the fields the vector lists. The override indicator
// collapses tri-state: an expected reason that omits it requires the actual reason to
// report false or to omit it.
const actualReason = detail.reason as Record<string, any>;
Object.entries(vector.expect.reason).forEach(([field, expected]) => {
expect(actualReason[field]).toEqual(expected);
});
if (!('overrideAffected' in vector.expect.reason)) {
expect(actualReason.overrideAffected ?? false).toBe(false);
}
} finally {
client.close();
}
});
});
Loading
Loading