1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: delta api e2e test (#9003)

WIP PR for delta-api tests.

---------

Co-authored-by: sjaanus <sellinjaanus@gmail.com>
This commit is contained in:
Fredrik Strand Oseberg 2024-12-30 13:16:41 +01:00 committed by GitHub
parent 563352909a
commit 71eb6b1511
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 189 additions and 65 deletions

View File

@ -73,6 +73,47 @@ export default class ClientFeatureToggleDeltaController extends Controller {
});
}
async getDelta(
req: IAuthRequest,
res: Response<RevisionDeltaEntry>,
): Promise<void> {
if (!this.flagResolver.isEnabled('deltaApi')) {
throw new NotFoundError();
}
const query = await this.resolveQuery(req);
const etag = req.headers['if-none-match'];
const currentSdkRevisionId = etag ? Number.parseInt(etag) : undefined;
const changedFeatures =
await this.clientFeatureToggleService.getClientDelta(
currentSdkRevisionId,
query,
);
if (!changedFeatures) {
res.status(304);
res.getHeaderNames().forEach((header) => res.removeHeader(header));
res.end();
return;
}
if (changedFeatures.revisionId === currentSdkRevisionId) {
res.status(304);
res.getHeaderNames().forEach((header) => res.removeHeader(header));
res.end();
return;
}
res.setHeader('ETag', changedFeatures.revisionId.toString());
this.openApiService.respondWithValidation(
200,
res,
clientFeaturesDeltaSchema.$id,
changedFeatures,
);
}
private async resolveQuery(
req: IAuthRequest,
): Promise<IFeatureToggleQuery> {
@ -139,45 +180,4 @@ export default class ClientFeatureToggleDeltaController extends Controller {
return query;
}
async getDelta(
req: IAuthRequest,
res: Response<RevisionDeltaEntry>,
): Promise<void> {
if (!this.flagResolver.isEnabled('deltaApi')) {
throw new NotFoundError();
}
const query = await this.resolveQuery(req);
const etag = req.headers['if-none-match'];
const currentSdkRevisionId = etag ? Number.parseInt(etag) : undefined;
const changedFeatures =
await this.clientFeatureToggleService.getClientDelta(
currentSdkRevisionId,
query,
);
if (!changedFeatures) {
res.status(304);
res.getHeaderNames().forEach((header) => res.removeHeader(header));
res.end();
return;
}
if (changedFeatures.revisionId === currentSdkRevisionId) {
res.status(304);
res.getHeaderNames().forEach((header) => res.removeHeader(header));
res.end();
return;
}
res.setHeader('ETag', changedFeatures.revisionId.toString());
this.openApiService.respondWithValidation(
200,
res,
clientFeaturesDeltaSchema.$id,
changedFeatures,
);
}
}

View File

@ -169,28 +169,6 @@ export class ClientFeatureToggleDelta {
await this.updateSegments();
}
// TODO: 19.12 this logic seems to be not logical, when no revisionId is coming, it should not go to db, but take latest from cache
// Should get the latest state if revision does not exist or if sdkRevision is not present
// We should be able to do this without going to the database by merging revisions from the delta with
// the base case
const firstTimeCalling = !sdkRevisionId;
if (
firstTimeCalling ||
(sdkRevisionId &&
sdkRevisionId !== this.currentRevisionId &&
!this.delta[environment].hasRevision(sdkRevisionId))
) {
//TODO: populate delta based on this?
return {
revisionId: this.currentRevisionId,
// @ts-ignore
updated: await this.getClientFeatures({ environment }),
segments: this.segments,
removed: [],
};
}
if (requiredRevisionId >= this.currentRevisionId) {
return undefined;
}
@ -211,7 +189,7 @@ export class ClientFeatureToggleDelta {
return Promise.resolve(revisionResponse);
}
private async onUpdateRevisionEvent() {
public async onUpdateRevisionEvent() {
if (this.flagResolver.isEnabled('deltaApi')) {
await this.updateFeaturesDelta();
await this.updateSegments();
@ -219,7 +197,11 @@ export class ClientFeatureToggleDelta {
}
}
public async updateFeaturesDelta() {
public resetDelta() {
this.delta = {};
}
private async updateFeaturesDelta() {
const keys = Object.keys(this.delta);
if (keys.length === 0) return;

View File

@ -0,0 +1,142 @@
import dbInit, {
type ITestDb,
} from '../../../../test/e2e/helpers/database-init';
import {
type IUnleashTest,
setupAppWithCustomConfig,
} from '../../../../test/e2e/helpers/test-helper';
import getLogger from '../../../../test/fixtures/no-logger';
import { DEFAULT_ENV } from '../../../util/constants';
let app: IUnleashTest;
let db: ITestDb;
const setupFeatures = async (
db: ITestDb,
app: IUnleashTest,
project = 'default',
) => {
await app.createFeature('test1', project);
await app.createFeature('test2', project);
await app.addStrategyToFeatureEnv(
{
name: 'flexibleRollout',
constraints: [],
parameters: {
rollout: '100',
stickiness: 'default',
groupId: 'test1',
},
},
DEFAULT_ENV,
'test1',
project,
);
await app.addStrategyToFeatureEnv(
{
name: 'default',
constraints: [
{ contextName: 'userId', operator: 'IN', values: ['123'] },
],
parameters: {},
},
DEFAULT_ENV,
'test2',
project,
);
};
beforeAll(async () => {
db = await dbInit('client_feature_toggles_delta', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{
experimental: {
flags: {
strictSchemaValidation: true,
deltaApi: true,
},
},
},
db.rawDatabase,
);
});
beforeEach(async () => {
await db.stores.eventStore.deleteAll();
await db.stores.featureToggleStore.deleteAll();
// @ts-ignore
app.services.clientFeatureToggleService.clientFeatureToggleDelta.resetDelta();
});
afterAll(async () => {
await app.destroy();
await db.destroy();
});
test('should match with /api/client/delta', async () => {
await setupFeatures(db, app);
const { body } = await app.request
.get('/api/client/features')
.expect('Content-Type', /json/)
.expect(200);
const { body: deltaBody } = await app.request
.get('/api/client/delta')
.expect('Content-Type', /json/)
.expect(200);
expect(body.features).toMatchObject(deltaBody.updated);
});
test('should get 304 if asked for latest revision', async () => {
await setupFeatures(db, app);
const { body } = await app.request.get('/api/client/delta').expect(200);
const currentRevisionId = body.revisionId;
await app.request
.set('If-None-Match', currentRevisionId)
.get('/api/client/delta')
.expect(304);
});
test('should return correct delta after feature created', async () => {
await app.createFeature('base_feature');
await syncRevisions();
const { body } = await app.request.get('/api/client/delta').expect(200);
const currentRevisionId = body.revisionId;
expect(body).toMatchObject({
updated: [
{
name: 'base_feature',
},
],
});
await app.createFeature('new_feature');
await syncRevisions();
const { body: deltaBody } = await app.request
.get('/api/client/delta')
.set('If-None-Match', currentRevisionId)
.expect(200);
expect(deltaBody).toMatchObject({
updated: [
{
name: 'new_feature',
},
],
});
});
const syncRevisions = async () => {
await app.services.configurationRevisionService.updateMaxRevisionId();
// @ts-ignore
await app.services.clientFeatureToggleService.clientFeatureToggleDelta.onUpdateRevisionEvent();
};