1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-05 17:53:12 +02:00

Add unit-test for etag caching

This commit is contained in:
Christopher Kolstad 2022-12-20 15:57:45 +01:00 committed by Nuno Góis
parent 6e650289c8
commit 12ad1e92f1
3 changed files with 100 additions and 22 deletions

View File

@ -72,6 +72,7 @@ exports[`should create default config 1`] = `
"anonymiseEventLog": false, "anonymiseEventLog": false,
"batchMetrics": false, "batchMetrics": false,
"changeRequests": false, "changeRequests": false,
"clientFeaturesMemoizedEtags": false,
"embedProxy": true, "embedProxy": true,
"embedProxyFrontend": true, "embedProxyFrontend": true,
"favorites": false, "favorites": false,
@ -89,6 +90,7 @@ exports[`should create default config 1`] = `
"anonymiseEventLog": false, "anonymiseEventLog": false,
"batchMetrics": false, "batchMetrics": false,
"changeRequests": false, "changeRequests": false,
"clientFeaturesMemoizedEtags": false,
"embedProxy": true, "embedProxy": true,
"embedProxyFrontend": true, "embedProxyFrontend": true,
"favorites": false, "favorites": false,

View File

@ -7,6 +7,7 @@ import FeatureController from './feature';
import { createTestConfig } from '../../../test/config/test-config'; import { createTestConfig } from '../../../test/config/test-config';
import { secondsToMilliseconds } from 'date-fns'; import { secondsToMilliseconds } from 'date-fns';
import { ClientSpecService } from '../../services/client-spec-service'; import { ClientSpecService } from '../../services/client-spec-service';
import etag from 'etag';
async function getSetup() { async function getSetup() {
const base = `/random${Math.round(Math.random() * 1000)}`; const base = `/random${Math.round(Math.random() * 1000)}`;
@ -101,6 +102,74 @@ test('if caching is enabled should memoize', async () => {
expect(getClientFeatures).toHaveBeenCalledTimes(1); expect(getClientFeatures).toHaveBeenCalledTimes(1);
}); });
test('if caching and etags are enabled should add etag for query to list', async () => {
const getClientFeatures = jest.fn().mockReturnValue([]);
const getActive = jest.fn().mockReturnValue([]);
const respondWithValidation = jest.fn().mockReturnValue({});
const validPath = jest.fn().mockReturnValue(jest.fn());
const responseStatus = jest.fn();
const responseEnd = jest.fn();
const responseJson = jest.fn();
const clientSpecService = new ClientSpecService({ getLogger });
const openApiService = { respondWithValidation, validPath };
const featureToggleServiceV2 = { getClientFeatures };
const segmentService = { getActive };
const controller = new FeatureController(
{
clientSpecService,
// @ts-expect-error
openApiService,
// @ts-expect-error
featureToggleServiceV2,
// @ts-expect-error
segmentService,
},
{
getLogger,
clientFeatureCaching: {
enabled: true,
maxAge: secondsToMilliseconds(10),
},
flagResolver: {
isEnabled: () => true,
},
},
);
const response = {
json: responseJson,
status: responseStatus,
end: responseEnd,
};
await controller.getAllCached(
// @ts-expect-error
{ query: {}, header: () => undefined },
response,
);
await controller.getAllCached(
// @ts-expect-error
{
query: {},
header: (name: string) => {
if ('If-None-Match' === name) {
return etag(
JSON.stringify({
version: 2,
features: [],
query: { inlineSegmentConstraints: true },
segments: [],
}),
);
} else {
return undefined;
}
},
},
response,
);
expect(responseStatus).toHaveBeenCalledWith(304);
expect(responseEnd).toHaveBeenCalledTimes(1);
});
test('if caching is not enabled all calls goes to service', async () => { test('if caching is not enabled all calls goes to service', async () => {
const getClientFeatures = jest.fn().mockReturnValue([]); const getClientFeatures = jest.fn().mockReturnValue([]);
const getActive = jest.fn().mockReturnValue([]); const getActive = jest.fn().mockReturnValue([]);

View File

@ -120,7 +120,10 @@ export default class FeatureController extends Controller {
}, },
); );
this.logger.info('Cached features was enabled'); this.logger.info('Cached features was enabled');
if (flagResolver.isEnabled(FEATURE_TOGGLE_MEMOIZED_ETAGS)) { if (
flagResolver &&
flagResolver.isEnabled(FEATURE_TOGGLE_MEMOIZED_ETAGS)
) {
this.logger.info('Memoized etags was enabled'); this.logger.info('Memoized etags was enabled');
this.route({ this.route({
method: 'get', method: 'get',
@ -151,18 +154,20 @@ export default class FeatureController extends Controller {
this.featureToggleServiceV2.getClientFeatures(query), this.featureToggleServiceV2.getClientFeatures(query),
this.segmentService.getActive(), this.segmentService.getActive(),
]).then((data) => { ]).then((data) => {
if (flagResolver?.isEnabled(FEATURE_TOGGLE_MEMOIZED_ETAGS)) { if (
this.seenEtags.set( flagResolver &&
JSON.stringify(query), flagResolver.isEnabled(FEATURE_TOGGLE_MEMOIZED_ETAGS)
etag( ) {
JSON.stringify({ const key = JSON.stringify(query);
const value = {
version, version,
features: data[0], features: data[0],
query: { ...query }, query: { ...query },
segments: data[1], segments: data[1],
}), };
), const stringValue = JSON.stringify(value);
); const hash = etag(stringValue);
this.seenEtags.set(key, hash);
} }
return data; return data;
}); });
@ -241,16 +246,18 @@ export default class FeatureController extends Controller {
): Promise<void> { ): Promise<void> {
const query = await this.resolveQuery(req); const query = await this.resolveQuery(req);
const [features, segments] = await this.cachedFeatures(query); const [features, segments] = await this.cachedFeatures(query);
const modifiedSince = req.header('If-None-Match').substring(2); const modifiedSince = req.header('If-None-Match');
if (modifiedSince && modifiedSince.length > 0) {
this.logger.debug(`ETag header from Client: ${modifiedSince}`); this.logger.debug(`ETag header from Client: ${modifiedSince}`);
const cached = this.seenEtags.get(JSON.stringify(query)); const cached = this.seenEtags.get(JSON.stringify(query));
this.logger.debug(`ETag header from memoizee ${cached}`); this.logger.debug(`ETag header from memoizee ${cached}`);
if (modifiedSince !== undefined && modifiedSince === cached) { if (modifiedSince.replace('W/', '') === cached) {
// We have a match. Return 304 // We have a match. Return 304
res.status(304); res.status(304);
res.end(); res.end();
return; return;
} }
}
if (this.clientSpecService.requestSupportsSpec(req, 'segments')) { if (this.clientSpecService.requestSupportsSpec(req, 'segments')) {
this.openApiService.respondWithValidation( this.openApiService.respondWithValidation(
200, 200,