1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-19 17:52:45 +02:00

Add etagByEnv feature and corresponding tests

This commit is contained in:
Gastón Fournier 2025-08-22 09:29:19 +02:00
parent a575a792b3
commit 0da567dd9b
No known key found for this signature in database
GPG Key ID: AF45428626E17A8E
3 changed files with 220 additions and 122 deletions

View File

@ -357,9 +357,10 @@ export default class FeatureController extends Controller {
} }
async calculateMeta(query: IFeatureToggleQuery): Promise<IMeta> { async calculateMeta(query: IFeatureToggleQuery): Promise<IMeta> {
const etagByEnvEnabled = this.flagResolver.isEnabled('etagByEnv');
const revisionId = const revisionId =
await this.configurationRevisionService.getMaxRevisionId( await this.configurationRevisionService.getMaxRevisionId(
query.environment, etagByEnvEnabled ? query.environment : undefined,
); );
const queryHash = hashSum(query); const queryHash = hashSum(query);

View File

@ -67,7 +67,8 @@ export type IFlagKey =
| 'lifecycleGraphs' | 'lifecycleGraphs'
| 'addConfiguration' | 'addConfiguration'
| 'filterFlagsToArchive' | 'filterFlagsToArchive'
| 'projectListViewToggle'; | 'projectListViewToggle'
| 'etagByEnv';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;

View File

@ -39,11 +39,11 @@ const prodTokenSecret = validTokens[1].secret;
const allEnvsTokenSecret = validTokens[2].secret; const allEnvsTokenSecret = validTokens[2].secret;
async function setup({ async function setup({
etagVariantName, etagVariant,
enabled, etagByEnvEnabled,
}: { }: {
etagVariantName: string; etagVariant: string | undefined;
enabled: boolean; etagByEnvEnabled: boolean;
}): Promise<{ app: IUnleashTest; db: ITestDb }> { }): Promise<{ app: IUnleashTest; db: ITestDb }> {
const db = await dbInit(`ignored`, getLogger); const db = await dbInit(`ignored`, getLogger);
@ -59,10 +59,11 @@ async function setup({
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
etagVariant: { etagVariant: {
name: etagVariantName, name: etagVariant,
enabled, enabled: etagVariant !== undefined,
feature_enabled: enabled, feature_enabled: etagVariant !== undefined,
}, },
etagByEnv: etagByEnvEnabled,
}, },
}, },
}, },
@ -176,139 +177,234 @@ async function validateInitialState({
describe.each([ describe.each([
{ {
name: 'disabled', etagVariant: undefined,
enabled: false, etagByEnvEnabled: false,
}, },
{ {
name: 'v2', etagVariant: 'v2',
enabled: true, etagByEnvEnabled: false,
}, },
])('feature 304 api client (etag variant = $name)', ({ name, enabled }) => { {
let app: IUnleashTest; etagVariant: 'v2',
let db: ITestDb; etagByEnvEnabled: true,
beforeAll(async () => { },
({ app, db } = await setup({ ])(
etagVariantName: name, 'feature 304 api client (etag variant = $etagVariant)',
enabled, ({ etagVariant, etagByEnvEnabled }) => {
})); let app: IUnleashTest;
await initialize({ app, db }); let db: ITestDb;
await validateInitialState({ app, db }); const etagVariantEnabled = etagVariant !== undefined;
}); const etagVariantName = etagVariant ?? 'disabled';
const expectedDevEventId = etagByEnvEnabled ? 13 : 15;
beforeAll(async () => {
({ app, db } = await setup({
etagVariant,
etagByEnvEnabled,
}));
await initialize({ app, db });
await validateInitialState({ app, db });
});
afterAll(async () => { afterAll(async () => {
await app.destroy(); await app.destroy();
await db.destroy(); await db.destroy();
}); });
test('returns calculated hash without if-none-match header (dev env token)', async () => { test('returns calculated hash without if-none-match header (dev env token)', async () => {
const res = await app.request const res = await app.request
.get('/api/client/features') .get('/api/client/features')
.set('Authorization', devTokenSecret) .set('Authorization', devTokenSecret)
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200); .expect(200);
if (enabled) { if (etagVariantEnabled) {
expect(res.headers.etag).toBe(`"76d8bb0e:13:${name}"`); expect(res.headers.etag).toBe(
expect(res.body.meta.etag).toBe(`"76d8bb0e:13:${name}"`); `"76d8bb0e:${expectedDevEventId}:${etagVariantName}"`,
} else { );
expect(res.headers.etag).toBe('"76d8bb0e:13"'); expect(res.body.meta.etag).toBe(
expect(res.body.meta.etag).toBe('"76d8bb0e:13"'); `"76d8bb0e:${expectedDevEventId}:${etagVariantName}"`,
} );
}); } else {
expect(res.headers.etag).toBe(
`"76d8bb0e:${expectedDevEventId}"`,
);
expect(res.body.meta.etag).toBe(
`"76d8bb0e:${expectedDevEventId}"`,
);
}
});
test(`returns ${enabled ? 200 : 304} for pre-calculated hash${enabled ? ' because hash changed' : ''} (dev env token)`, async () => { test(`returns ${etagVariantEnabled ? 200 : 304} for pre-calculated hash${etagVariantEnabled ? ' because hash changed' : ''} (dev env token)`, async () => {
const res = await app.request const res = await app.request
.get('/api/client/features') .get('/api/client/features')
.set('Authorization', devTokenSecret) .set('Authorization', devTokenSecret)
.set('if-none-match', '"76d8bb0e:13"') .set('if-none-match', `"76d8bb0e:${expectedDevEventId}"`)
.expect(enabled ? 200 : 304); .expect(etagVariantEnabled ? 200 : 304);
if (enabled) { if (etagVariantEnabled) {
expect(res.headers.etag).toBe(`"76d8bb0e:13:${name}"`); expect(res.headers.etag).toBe(
expect(res.body.meta.etag).toBe(`"76d8bb0e:13:${name}"`); `"76d8bb0e:${expectedDevEventId}:${etagVariantName}"`,
} );
}); expect(res.body.meta.etag).toBe(
`"76d8bb0e:${expectedDevEventId}:${etagVariantName}"`,
);
}
});
test('creating a new feature does not modify etag', async () => { test('creating a new feature does not modify etag', async () => {
await app.createFeature('new'); await app.createFeature('new');
await app.services.configurationRevisionService.updateMaxRevisionId(); await app.services.configurationRevisionService.updateMaxRevisionId();
await app.request await app.request
.get('/api/client/features') .get('/api/client/features')
.set('Authorization', devTokenSecret) .set('Authorization', devTokenSecret)
.set('if-none-match', `"76d8bb0e:13${enabled ? `:${name}` : ''}"`) .set(
.expect(304); 'if-none-match',
}); `"76d8bb0e:${expectedDevEventId}${etagVariantEnabled ? `:${etagVariantName}` : ''}"`,
)
.expect(304);
});
test('a token with all envs should get the max id regardless of the environment', async () => { test('a token with all envs should get the max id regardless of the environment', async () => {
const currentProdEtag = `"67e24428:15${enabled ? `:${name}` : ''}"`; const currentProdEtag = `"67e24428:15${etagVariantEnabled ? `:${etagVariantName}` : ''}"`;
const { headers } = await app.request const { headers } = await app.request
.get('/api/client/features') .get('/api/client/features')
.set('if-none-match', currentProdEtag) .set('if-none-match', currentProdEtag)
.set('Authorization', allEnvsTokenSecret) .set('Authorization', allEnvsTokenSecret)
.expect(200); .expect(200);
// it's a different hash than prod, but gets the max id // it's a different hash than prod, but gets the max id
expect(headers.etag).toEqual( expect(headers.etag).toEqual(
`"ae443048:15${enabled ? `:${name}` : ''}"`, `"ae443048:15${etagVariantEnabled ? `:${etagVariantName}` : ''}"`,
); );
}); });
test('production environment gets a different etag than development', async () => { test.runIf(!etagByEnvEnabled)(
const { headers: prodHeaders } = await app.request 'production environment gets same event id in etag than development',
.get('/api/client/features?bla=1') async () => {
.set('Authorization', prodTokenSecret) const { headers: prodHeaders } = await app.request
.expect(200); .get('/api/client/features?bla=1')
.set('Authorization', prodTokenSecret)
.expect(200);
expect(prodHeaders.etag).toEqual( expect(prodHeaders.etag).toEqual(
`"67e24428:15${enabled ? `:${name}` : ''}"`, `"67e24428:15${etagVariantEnabled ? `:${etagVariantName}` : ''}"`,
);
const { headers: devHeaders } = await app.request
.get('/api/client/features')
.set('Authorization', devTokenSecret)
.expect(200);
expect(devHeaders.etag).toEqual(
`"76d8bb0e:15${etagVariantEnabled ? `:${etagVariantName}` : ''}"`,
);
},
); );
const { headers: devHeaders } = await app.request test.runIf(!etagByEnvEnabled)(
.get('/api/client/features') 'modifying dev environment also invalidates prod tokens',
.set('Authorization', devTokenSecret) async () => {
.expect(200); const currentDevEtag = `"76d8bb0e:${expectedDevEventId}${etagVariantEnabled ? `:${etagVariantName}` : ''}"`;
const currentProdEtag = `"67e24428:15${etagVariantEnabled ? `:${etagVariantName}` : ''}"`;
await app.request
.get('/api/client/features')
.set('if-none-match', currentProdEtag)
.set('Authorization', prodTokenSecret)
.expect(304);
expect(devHeaders.etag).toEqual( await app.request
`"76d8bb0e:13${enabled ? `:${name}` : ''}"`, .get('/api/client/features')
.set('Authorization', devTokenSecret)
.set('if-none-match', currentDevEtag)
.expect(304);
await app.enableFeature('X', DEFAULT_ENV);
await app.services.configurationRevisionService.updateMaxRevisionId();
await app.request
.get('/api/client/features')
.set('Authorization', prodTokenSecret)
.set('if-none-match', currentProdEtag)
.expect(200);
const { headers: devHeaders } = await app.request
.get('/api/client/features')
.set('Authorization', devTokenSecret)
.set('if-none-match', currentDevEtag)
.expect(200);
// Note: this test yields a different result if run in isolation
// this is because the id 19 depends on a previous test adding a feature
// otherwise the id will be 18
expect(devHeaders.etag).toEqual(
`"76d8bb0e:19${etagVariantEnabled ? `:${etagVariantName}` : ''}"`,
);
},
); );
});
test('modifying dev environment should only invalidate dev tokens', async () => { test.runIf(etagByEnvEnabled)(
const currentDevEtag = `"76d8bb0e:13${enabled ? `:${name}` : ''}"`; 'production environment gets a different etag than development',
const currentProdEtag = `"67e24428:15${enabled ? `:${name}` : ''}"`; async () => {
await app.request const { headers: prodHeaders } = await app.request
.get('/api/client/features') .get('/api/client/features?bla=1')
.set('if-none-match', currentProdEtag) .set('Authorization', prodTokenSecret)
.set('Authorization', prodTokenSecret) .expect(200);
.expect(304);
await app.request expect(prodHeaders.etag).toEqual(
.get('/api/client/features') `"67e24428:15${etagVariantEnabled ? `:${etagVariantName}` : ''}"`,
.set('Authorization', devTokenSecret) );
.set('if-none-match', currentDevEtag)
.expect(304);
await app.enableFeature('X', DEFAULT_ENV); const { headers: devHeaders } = await app.request
await app.services.configurationRevisionService.updateMaxRevisionId(); .get('/api/client/features')
.set('Authorization', devTokenSecret)
.expect(200);
await app.request expect(devHeaders.etag).toEqual(
.get('/api/client/features') `"76d8bb0e:13${etagVariantEnabled ? `:${etagVariantName}` : ''}"`,
.set('Authorization', prodTokenSecret) );
.set('if-none-match', currentProdEtag) },
.expect(304);
const { headers: devHeaders } = await app.request
.get('/api/client/features')
.set('Authorization', devTokenSecret)
.set('if-none-match', currentDevEtag)
.expect(200);
// Note: this test yields a different result if run in isolation
// this is because the id 19 depends on a previous test adding a feature
// otherwise the id will be 18
expect(devHeaders.etag).toEqual(
`"76d8bb0e:19${enabled ? `:${name}` : ''}"`,
); );
});
}); test.runIf(etagByEnvEnabled)(
'modifying dev environment should only invalidate dev tokens',
async () => {
const currentDevEtag = `"76d8bb0e:13${etagVariantEnabled ? `:${etagVariantName}` : ''}"`;
const currentProdEtag = `"67e24428:15${etagVariantEnabled ? `:${etagVariantName}` : ''}"`;
await app.request
.get('/api/client/features')
.set('if-none-match', currentProdEtag)
.set('Authorization', prodTokenSecret)
.expect(304);
await app.request
.get('/api/client/features')
.set('Authorization', devTokenSecret)
.set('if-none-match', currentDevEtag)
.expect(304);
await app.enableFeature('X', DEFAULT_ENV);
await app.services.configurationRevisionService.updateMaxRevisionId();
await app.request
.get('/api/client/features')
.set('Authorization', prodTokenSecret)
.set('if-none-match', currentProdEtag)
.expect(304);
const { headers: devHeaders } = await app.request
.get('/api/client/features')
.set('Authorization', devTokenSecret)
.set('if-none-match', currentDevEtag)
.expect(200);
// Note: this test yields a different result if run in isolation
// this is because the id 19 depends on a previous test adding a feature
// otherwise the id will be 18
expect(devHeaders.etag).toEqual(
`"76d8bb0e:19${etagVariantEnabled ? `:${etagVariantName}` : ''}"`,
);
},
);
},
);