Skip to content
Open
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
28 changes: 28 additions & 0 deletions controlplane/test/feature-flag/feature-flag-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
assertExecutionConfigSubgraphNames,
assertFeatureFlagExecutionConfig,
assertNumberOfCompositions,
assertMapperContentIsCorrect,
createAndPublishSubgraph,
createFeatureFlag,
createFederatedGraph,
Expand Down Expand Up @@ -1508,6 +1509,8 @@ describe('Feature flag integration tests', () => {
const ffKey = blobStorage.keys().at(-1);
expect(ffKey).toContain(`${federatedGraphResponse.graph!.id}/manifest/feature-flags/${featureFlagName}.json`);

await assertMapperContentIsCorrect(blobStorage, 1);

// The base recomposition and the feature flag composition
await assertNumberOfCompositions(client, baseGraphName, 2);

Expand Down Expand Up @@ -1581,6 +1584,8 @@ describe('Feature flag integration tests', () => {
const ffKey = blobStorage.keys().at(-1);
expect(ffKey).toContain(`${federatedGraphResponse.graph!.id}/manifest/feature-flags/${featureFlagName}.json`);

await assertMapperContentIsCorrect(blobStorage, 1);

// The base recomposition and the feature flag composition
await assertNumberOfCompositions(client, baseGraphName, 2, namespace);
await assertFeatureFlagExecutionConfig(blobStorage, key, false);
Expand Down Expand Up @@ -1655,6 +1660,8 @@ describe('Feature flag integration tests', () => {
const ffKey = blobStorage.keys().at(-1);
expect(ffKey).toContain(`${federatedGraphResponse.graph!.id}/manifest/feature-flags/${featureFlagName}.json`);

await assertMapperContentIsCorrect(blobStorage, 1);

// The feature flag is disabled again and trigger a base recomposition
await toggleFeatureFlag(client, featureFlagName, false, namespace);
await assertNumberOfCompositions(client, baseGraphName, 2, namespace);
Expand Down Expand Up @@ -1750,6 +1757,8 @@ describe('Feature flag integration tests', () => {
]),
);

await assertMapperContentIsCorrect(blobStorage, 2);

// The base recomposition and the feature flag composition
await assertNumberOfCompositions(client, baseGraphName, 2, namespace);
await assertFeatureFlagExecutionConfig(blobStorage, baseGraphKey, false);
Expand Down Expand Up @@ -1845,6 +1854,8 @@ describe('Feature flag integration tests', () => {
]),
);

await assertMapperContentIsCorrect(blobStorage, 2);

// The base recomposition and the feature flag composition
await assertNumberOfCompositions(client, baseGraphName, 2, namespace);
await assertFeatureFlagExecutionConfig(blobStorage, baseGraphKey, false);
Expand Down Expand Up @@ -1967,6 +1978,8 @@ describe('Feature flag integration tests', () => {
const ffKey = blobStorage.keys().at(-1);
expect(ffKey).toContain(`${baseGraphResponse.graph!.id}/manifest/feature-flags/${featureFlagName}.json`);

await assertMapperContentIsCorrect(blobStorage, 1);

// There will be a failed base composition and one feature flag compositions
await assertNumberOfCompositions(client, baseGraphName, 3, namespace);
await createAndPublishSubgraph(
Expand Down Expand Up @@ -2068,6 +2081,8 @@ describe('Feature flag integration tests', () => {
]),
);

await assertMapperContentIsCorrect(blobStorage, 2);

// The base recomposition and the feature flag composition
await assertNumberOfCompositions(client, baseGraphName, 2, namespace);
await assertFeatureFlagExecutionConfig(blobStorage, baseGraphKey, false);
Expand Down Expand Up @@ -2187,6 +2202,8 @@ describe('Feature flag integration tests', () => {
]),
);

await assertMapperContentIsCorrect(blobStorage, 2);

// There should be a base recomposition, a feature flag composition, and an embedded feature flag config
for (const { name, key } of graphNamesAndKeys) {
await assertNumberOfCompositions(client, name, 2, namespace);
Expand Down Expand Up @@ -2349,6 +2366,8 @@ describe('Feature flag integration tests', () => {
]),
);

await assertMapperContentIsCorrect(blobStorage, 4);

/*
* Each federated graph should have produced two total compositions:
* 1. The original base composition
Expand Down Expand Up @@ -2398,6 +2417,8 @@ describe('Feature flag integration tests', () => {
]),
);

await assertMapperContentIsCorrect(blobStorage, 4);

const deleteFeatureSubgraphResponse = await client.deleteFederatedSubgraph({
subgraphName: 'products-feature',
namespace,
Expand Down Expand Up @@ -2503,6 +2524,8 @@ describe('Feature flag integration tests', () => {
const ffKey = blobStorage.keys()[2];
expect(ffKey).toContain(`${federatedGraphResponse.graph!.id}/manifest/feature-flags/${featureFlagName}.json`);

await assertMapperContentIsCorrect(blobStorage, 1);

// The base recomposition and the feature flag composition
await assertNumberOfCompositions(client, baseGraphName, 2);
await assertFeatureFlagExecutionConfig(blobStorage, key, false);
Expand Down Expand Up @@ -2625,6 +2648,8 @@ describe('Feature flag integration tests', () => {
const ffKey = blobStorage.keys().at(-1);
expect(ffKey).toContain(`${baseGraphResponse.graph!.id}/manifest/feature-flags/${featureFlagName}.json`);

await assertMapperContentIsCorrect(blobStorage, 1);

// The feature flag composition
await assertNumberOfCompositions(client, baseGraphName, 3, namespace);
await assertFeatureFlagExecutionConfig(blobStorage, baseGraphKey, false);
Expand Down Expand Up @@ -2912,6 +2937,9 @@ describe('Feature flag integration tests', () => {
),
]),
);

// Skip the number of feature flag check as we have two federated graphs with a different number of feature flags
await assertMapperContentIsCorrect(blobStorage, 2, -1);
},
);
});
Expand Down
56 changes: 55 additions & 1 deletion controlplane/test/test-util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { randomUUID, UUID } from 'node:crypto';
import { createHash, randomUUID, UUID } from 'node:crypto';
import { join, resolve } from 'node:path';
import fs from 'node:fs';
import { createPromiseClient, PromiseClient } from '@connectrpc/connect';
Expand Down Expand Up @@ -888,6 +888,60 @@ export async function assertExecutionConfigSubgraphNames(
expect(subgraphIds.has(subgraph.id)).toBe(true);
}
}
export async function assertMapperContentIsCorrect(
blobStorage: InMemoryBlobStorage,
expectedMapperKeysCount: number,
expectedNumberOfFeatureFlags = 1,
) {
const blobKeys = blobStorage.keys();
const existingMappers = blobKeys.filter((key) => key.endsWith('mapper.json'));
expect(existingMappers).toHaveLength(expectedMapperKeysCount);

let numberOfRouterConfigsInBlobStorage = 0;
for (const mapperKey of existingMappers) {
const mapperBlob = await blobStorage.getObject({ key: mapperKey });
const mapper = await mapperBlob.stream
.getReader()
.read()
.then((result) => JSON.parse(result.value.toString()));

const keyPrefix = mapperKey.split('/').slice(0, -1).join('/');
const mapperEntries = Object.entries(mapper);

/**
* The mapper should always contain the number of expected feature flags plus one (the federated graph.
*
* If the expected number of feature flags is `-1`, we are skipping this check.
*/
if (expectedNumberOfFeatureFlags !== -1) {
expect(mapperEntries).toHaveLength(expectedNumberOfFeatureFlags + 1);
}

for (const [featureFlagName, hash] of mapperEntries) {
const key =
featureFlagName === '' ? `${keyPrefix}/latest.json` : `${keyPrefix}/feature-flags/${featureFlagName}.json`;

expect(blobKeys).include(key);

const blob = await blobStorage.getObject({ key });
const blobContent = await blob.stream
.getReader()
.read()
.then((result) => result.value.toString());

const actualHash = createHash('sha256').update(blobContent).digest('hex');
expect(hash).toBe(actualHash);

numberOfRouterConfigsInBlobStorage++;
}
}

/**
* The number of elements in the blob storage should be the number of expected mappers plus the number
* of valid hashes in each mapper file
*/
expect(blobKeys).toHaveLength(numberOfRouterConfigsInBlobStorage + expectedMapperKeysCount);
}

export async function toggleFeatureFlag(
client: PromiseClient<typeof PlatformService>,
Expand Down
Loading