diff --git a/src/lib/db/feature-type-store.ts b/src/lib/db/feature-type-store.ts index 3f4fb32511..52d925da82 100644 --- a/src/lib/db/feature-type-store.ts +++ b/src/lib/db/feature-type-store.ts @@ -69,12 +69,12 @@ class FeatureTypeStore implements IFeatureTypeStore { } async updateLifetime( - name: string, + id: string, newLifetimeDays: number | null, ): Promise { const [updatedType] = await this.db(TABLE) .update({ lifetime_days: newLifetimeDays }) - .where({ name }) + .where({ id }) .returning(['*']); if (updatedType) { diff --git a/src/lib/routes/admin-api/feature-type.ts b/src/lib/routes/admin-api/feature-type.ts index 9ab4f19695..d4aff7d94f 100644 --- a/src/lib/routes/admin-api/feature-type.ts +++ b/src/lib/routes/admin-api/feature-type.ts @@ -13,13 +13,13 @@ import { createResponseSchema } from '../../openapi/util/create-response-schema' import Controller from '../controller'; import { createRequestSchema, + featureTypeSchema, FeatureTypeSchema, getStandardResponses, UpdateFeatureTypeLifetimeSchema, } from '../../openapi'; import { IAuthRequest } from '../unleash-types'; import { IFlagResolver } from '../../types'; -import NotImplementedError from '../../error/not-implemented-error'; const version = 1; @@ -114,8 +114,16 @@ When a feature toggle type's expected lifetime is changed, this will also cause res: Response, ): Promise { if (this.flagResolver.isEnabled('configurableFeatureTypeLifetimes')) { - throw new NotImplementedError( - "This operation isn't implemented yet", + const result = await this.featureTypeService.updateLifetime( + req.params.id.toLowerCase(), + req.body.lifetimeDays, + ); + + this.openApiService.respondWithValidation( + 200, + res, + featureTypeSchema.$id, + result, ); } else { res.status(409).end(); diff --git a/src/lib/services/feature-type-service.ts b/src/lib/services/feature-type-service.ts index 39ac51d852..c31d222514 100644 --- a/src/lib/services/feature-type-service.ts +++ b/src/lib/services/feature-type-service.ts @@ -5,6 +5,7 @@ import { IFeatureType, IFeatureTypeStore, } from '../types/stores/feature-type-store'; +import NotFoundError from '../error/notfound-error'; export default class FeatureTypeService { private featureTypeStore: IFeatureTypeStore; @@ -22,6 +23,29 @@ export default class FeatureTypeService { async getAll(): Promise { return this.featureTypeStore.getAll(); } + + async updateLifetime( + id: string, + newLifetimeDays: number | null, + ): Promise { + // 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; diff --git a/src/test/e2e/api/admin/feature-type.test.ts b/src/test/e2e/api/admin/feature-type.test.ts index 4ceb529a9a..d1c78d9334 100644 --- a/src/test/e2e/api/admin/feature-type.test.ts +++ b/src/test/e2e/api/admin/feature-type.test.ts @@ -1,6 +1,6 @@ import dbInit from '../../helpers/database-init'; 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 { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema'; @@ -9,7 +9,14 @@ let db; beforeAll(async () => { 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 () => { @@ -32,3 +39,44 @@ test('Should get all defined feature types', async () => { ).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); + }); +}); diff --git a/src/test/e2e/stores/feature-type-store.e2e.test.ts b/src/test/e2e/stores/feature-type-store.e2e.test.ts index bf90fcc6c8..8fe917ea6c 100644 --- a/src/test/e2e/stores/feature-type-store.e2e.test.ts +++ b/src/test/e2e/stores/feature-type-store.e2e.test.ts @@ -46,15 +46,13 @@ describe('update lifetimes', () => { for (const type of featureTypes) { const updated = await featureTypeStore.updateLifetime( - type.name, + type.id, newLifetime, ); expect(updated?.lifetimeDays).toBe(newLifetime); - expect(updated).toMatchObject( - await featureTypeStore.getByName(type.name), - ); + expect(updated).toMatchObject(await featureTypeStore.get(type.id)); } });