1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

feat(#4205): implement configurable lifetimes (#4263)

This PR updates the feature type service by adding a new
`updateLifetime` method. This method handles the connection between the
API (#4256) and the store (#4252).

I've also added some new e2e tests to ensure that the API behaves as
expected.
This commit is contained in:
Thomas Heartman 2023-07-18 11:26:32 +02:00 committed by GitHub
parent 276261c913
commit b990c6dfe0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 89 additions and 11 deletions

View File

@ -69,12 +69,12 @@ class FeatureTypeStore implements IFeatureTypeStore {
} }
async updateLifetime( async updateLifetime(
name: string, id: string,
newLifetimeDays: number | null, newLifetimeDays: number | null,
): Promise<IFeatureType | undefined> { ): Promise<IFeatureType | undefined> {
const [updatedType] = await this.db(TABLE) const [updatedType] = await this.db(TABLE)
.update({ lifetime_days: newLifetimeDays }) .update({ lifetime_days: newLifetimeDays })
.where({ name }) .where({ id })
.returning(['*']); .returning(['*']);
if (updatedType) { if (updatedType) {

View File

@ -13,13 +13,13 @@ import { createResponseSchema } from '../../openapi/util/create-response-schema'
import Controller from '../controller'; import Controller from '../controller';
import { import {
createRequestSchema, createRequestSchema,
featureTypeSchema,
FeatureTypeSchema, FeatureTypeSchema,
getStandardResponses, getStandardResponses,
UpdateFeatureTypeLifetimeSchema, UpdateFeatureTypeLifetimeSchema,
} from '../../openapi'; } from '../../openapi';
import { IAuthRequest } from '../unleash-types'; import { IAuthRequest } from '../unleash-types';
import { IFlagResolver } from '../../types'; import { IFlagResolver } from '../../types';
import NotImplementedError from '../../error/not-implemented-error';
const version = 1; const version = 1;
@ -114,8 +114,16 @@ When a feature toggle type's expected lifetime is changed, this will also cause
res: Response<FeatureTypeSchema>, res: Response<FeatureTypeSchema>,
): Promise<void> { ): Promise<void> {
if (this.flagResolver.isEnabled('configurableFeatureTypeLifetimes')) { if (this.flagResolver.isEnabled('configurableFeatureTypeLifetimes')) {
throw new NotImplementedError( const result = await this.featureTypeService.updateLifetime(
"This operation isn't implemented yet", req.params.id.toLowerCase(),
req.body.lifetimeDays,
);
this.openApiService.respondWithValidation(
200,
res,
featureTypeSchema.$id,
result,
); );
} else { } else {
res.status(409).end(); res.status(409).end();

View File

@ -5,6 +5,7 @@ import {
IFeatureType, IFeatureType,
IFeatureTypeStore, IFeatureTypeStore,
} from '../types/stores/feature-type-store'; } from '../types/stores/feature-type-store';
import NotFoundError from '../error/notfound-error';
export default class FeatureTypeService { export default class FeatureTypeService {
private featureTypeStore: IFeatureTypeStore; private featureTypeStore: IFeatureTypeStore;
@ -22,6 +23,29 @@ export default class FeatureTypeService {
async getAll(): Promise<IFeatureType[]> { async getAll(): Promise<IFeatureType[]> {
return this.featureTypeStore.getAll(); return this.featureTypeStore.getAll();
} }
async updateLifetime(
id: string,
newLifetimeDays: number | null,
): Promise<IFeatureType> {
// because our OpenAPI library does type coercion, any `null` values you
// pass in get converted to `0`.
const translatedLifetime =
newLifetimeDays === 0 ? null : newLifetimeDays;
const result = await this.featureTypeStore.updateLifetime(
id,
translatedLifetime,
);
if (!result) {
throw new NotFoundError(
`The feature type you tried to update ("${id}") does not exist.`,
);
}
return result;
}
} }
module.exports = FeatureTypeService; module.exports = FeatureTypeService;

View File

@ -1,6 +1,6 @@
import dbInit from '../../helpers/database-init'; import dbInit from '../../helpers/database-init';
import getLogger from '../../../fixtures/no-logger'; import getLogger from '../../../fixtures/no-logger';
import { setupApp } from '../../helpers/test-helper'; import { setupAppWithCustomConfig } from '../../helpers/test-helper';
import { validateSchema } from '../../../../lib/openapi/validate'; import { validateSchema } from '../../../../lib/openapi/validate';
import { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema'; import { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema';
@ -9,7 +9,14 @@ let db;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('feature_type_api_serial', getLogger); db = await dbInit('feature_type_api_serial', getLogger);
app = await setupApp(db.stores); app = await setupAppWithCustomConfig(db.stores, {
experimental: {
flags: {
configurableFeatureTypeLifetimes: true,
strictSchemaValidation: true,
},
},
});
}); });
afterAll(async () => { afterAll(async () => {
@ -32,3 +39,44 @@ test('Should get all defined feature types', async () => {
).toBeUndefined(); ).toBeUndefined();
}); });
}); });
describe('updating lifetimes', () => {
test.each([null, 5])(
'it updates to the lifetime correctly: `%s`',
async (lifetimeDays) => {
const { body } = await app.request
.put(`/api/admin/feature-types/release/lifetime`)
.send({ lifetimeDays })
.expect(200);
expect(body.lifetimeDays).toEqual(lifetimeDays);
},
);
test("if the feature type doesn't exist, you get a 404", async () => {
await app.request
.put(`/api/admin/feature-types/bogus-feature-type/lifetime`)
.send({ lifetimeDays: 45 })
.expect(404);
});
test('Setting lifetime to `null` is the same as setting it to `0`', async () => {
const setLifetime = async (lifetimeDays) => {
const { body } = await app.request
.put('/api/admin/feature-types/release/lifetime')
.send({ lifetimeDays })
.expect(200);
return body;
};
expect(await setLifetime(0)).toMatchObject(await setLifetime(null));
});
test('the :id parameter is not case sensitive', async () => {
const lifetimeDays = 45;
const { body } = await app.request
.put(`/api/admin/feature-types/kIlL-SwItCh/lifetime`)
.send({ lifetimeDays })
.expect(200);
expect(body.lifetimeDays).toEqual(lifetimeDays);
});
});

View File

@ -46,15 +46,13 @@ describe('update lifetimes', () => {
for (const type of featureTypes) { for (const type of featureTypes) {
const updated = await featureTypeStore.updateLifetime( const updated = await featureTypeStore.updateLifetime(
type.name, type.id,
newLifetime, newLifetime,
); );
expect(updated?.lifetimeDays).toBe(newLifetime); expect(updated?.lifetimeDays).toBe(newLifetime);
expect(updated).toMatchObject( expect(updated).toMatchObject(await featureTypeStore.get(type.id));
await featureTypeStore.getByName(type.name),
);
} }
}); });