From 290ef6ca40ee27fe5094b8b07107d17293c27525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Tue, 27 May 2025 09:12:36 +0100 Subject: [PATCH] chore!: remove deprecated POST events search endpoint (#10030) https://linear.app/unleash/issue/2-3368/remove-post-apiadmineventssearch-deprecated-in-610 Removes POST `/api/admin/events/search` which was deprecated in v6.1. Also cleans up related code. --- .../features/tag-type/tag-types.e2e.test.ts | 19 ++++- src/lib/routes/admin-api/event.ts | 45 ---------- .../e2e/api/admin/event-search.e2e.test.ts | 75 ++++------------- src/test/e2e/api/admin/event.e2e.test.ts | 69 --------------- src/test/e2e/api/admin/feature-type.test.ts | 19 ++++- src/test/e2e/helpers/test-helper.ts | 84 +++++++++++++++++-- 6 files changed, 129 insertions(+), 182 deletions(-) diff --git a/src/lib/features/tag-type/tag-types.e2e.test.ts b/src/lib/features/tag-type/tag-types.e2e.test.ts index b053ed8e39..a9129fe85c 100644 --- a/src/lib/features/tag-type/tag-types.e2e.test.ts +++ b/src/lib/features/tag-type/tag-types.e2e.test.ts @@ -2,17 +2,21 @@ import dbInit, { type ITestDb, } from '../../../test/e2e/helpers/database-init.js'; import { + createUserWithRootRole, type IUnleashTest, - setupAppWithCustomConfig, + setupAppWithAuth, } from '../../../test/e2e/helpers/test-helper.js'; import getLogger from '../../../test/fixtures/no-logger.js'; +import { RoleName } from '../../types/index.js'; let app: IUnleashTest; let db: ITestDb; +const adminEmail = 'admin-user@getunleash.io'; + beforeAll(async () => { db = await dbInit('tag_types_api_serial', getLogger); - app = await setupAppWithCustomConfig( + app = await setupAppWithAuth( db.stores, { experimental: { @@ -23,6 +27,17 @@ beforeAll(async () => { }, db.rawDatabase, ); + + await createUserWithRootRole({ + app, + stores: db.stores, + email: adminEmail, + roleName: RoleName.ADMIN, + }); +}); + +beforeEach(async () => { + await app.login({ email: adminEmail }); }); afterAll(async () => { diff --git a/src/lib/routes/admin-api/event.ts b/src/lib/routes/admin-api/event.ts index 8a389c54a9..c6def62596 100644 --- a/src/lib/routes/admin-api/event.ts +++ b/src/lib/routes/admin-api/event.ts @@ -18,8 +18,6 @@ import { type FeatureEventsSchema, } from '../../../lib/openapi/spec/feature-events-schema.js'; import { getStandardResponses } from '../../../lib/openapi/util/standard-responses.js'; -import { createRequestSchema } from '../../openapi/util/create-request-schema.js'; -import type { DeprecatedSearchEventsSchema } from '../../openapi/spec/deprecated-search-events-schema.js'; import type { IFlagResolver } from '../../types/experimental.js'; import type { IAuthRequest } from '../unleash-types.js'; import { @@ -100,27 +98,6 @@ export default class EventController extends Controller { ], }); - this.route({ - method: 'post', - path: '/events/search', - handler: this.deprecatedSearchEvents, - permission: NONE, - middleware: [ - openApiService.validPath({ - operationId: 'deprecatedSearchEvents', - tags: ['Events'], - deprecated: true, - summary: 'Search for events (deprecated)', - description: - 'Allows searching for events matching the search criteria in the request body', - requestBody: createRequestSchema( - 'deprecatedSearchEventsSchema', - ), - responses: { 200: createResponseSchema('eventsSchema') }, - }), - ], - }); - this.route({ method: 'get', path: '/event-creators', @@ -201,28 +178,6 @@ export default class EventController extends Controller { ); } - async deprecatedSearchEvents( - req: Request, - res: Response, - ): Promise { - const eventList = await this.eventService.deprecatedSearchEvents( - req.body, - ); - - const response = { - version, - events: serializeDates(this.maybeAnonymiseEvents(eventList.events)), - totalEvents: eventList.totalEvents, - }; - - this.openApiService.respondWithValidation( - 200, - res, - featureEventsSchema.$id, - response, - ); - } - async getEventCreators( req: IAuthRequest, res: Response, diff --git a/src/test/e2e/api/admin/event-search.e2e.test.ts b/src/test/e2e/api/admin/event-search.e2e.test.ts index 55cbea955f..8cfae7f7b8 100644 --- a/src/test/e2e/api/admin/event-search.e2e.test.ts +++ b/src/test/e2e/api/admin/event-search.e2e.test.ts @@ -6,66 +6,26 @@ import { type IUnleashStores, RoleName, } from '../../../../lib/types/index.js'; -import type { - AccessService, - EventService, -} from '../../../../lib/services/index.js'; +import type { EventService } from '../../../../lib/services/index.js'; import getLogger from '../../../fixtures/no-logger.js'; import { + createUserWithRootRole, type IUnleashTest, setupAppWithAuth, } from '../../helpers/test-helper.js'; import { createEventsService } from '../../../../lib/features/index.js'; import { createTestConfig } from '../../../config/test-config.js'; -import type { IRole } from '../../../../lib/types/stores/access-store.js'; import { FEATURE_CREATED, USER_CREATED } from '../../../../lib/events/index.js'; let app: IUnleashTest; let db: ITestDb; let eventService: EventService; const TEST_USER_ID = -9999; -const regularUserName = 'import-user'; -const adminUserName = 'admin-user'; +const regularEmail = 'import-user@getunleash.io'; +const adminEmail = 'admin-user@getunleash.io'; const config: IUnleashConfig = createTestConfig(); -let adminRole: IRole; let stores: IUnleashStores; -let accessService: AccessService; - -const loginRegularUser = () => - app.request - .post(`/auth/demo/login`) - .send({ - email: `${regularUserName}@getunleash.io`, - }) - .expect(200); - -const loginAdminUser = () => - app.request - .post(`/auth/demo/login`) - .send({ - email: `${adminUserName}@getunleash.io`, - }) - .expect(200); - -const createUserEditorAccess = async (name, email) => { - const { userStore } = stores; - const user = await userStore.insert({ - name, - email, - }); - return user; -}; - -const createUserAdminAccess = async (name, email) => { - const { userStore } = stores; - const user = await userStore.insert({ - name, - email, - }); - await accessService.addUserToRole(user.id, adminRole.id, 'default'); - return user; -}; beforeAll(async () => { db = await dbInit('event_search', getLogger); @@ -84,19 +44,18 @@ beforeAll(async () => { eventService = createEventsService(db.rawDatabase, config); - accessService = app.services.accessService; + await createUserWithRootRole({ + app, + stores, + email: regularEmail, + }); - const roles = await accessService.getRootRoles(); - adminRole = roles.find((role) => role.name === RoleName.ADMIN)!; - - await createUserEditorAccess( - regularUserName, - `${regularUserName}@getunleash.io`, - ); - await createUserAdminAccess( - adminUserName, - `${adminUserName}@getunleash.io`, - ); + await createUserWithRootRole({ + app, + stores, + email: adminEmail, + roleName: RoleName.ADMIN, + }); }); afterAll(async () => { @@ -105,7 +64,7 @@ afterAll(async () => { }); beforeEach(async () => { - await loginAdminUser(); + await app.login({ email: adminEmail }); await db.stores.featureToggleStore.deleteAll(); await db.stores.segmentStore.deleteAll(); await db.stores.eventStore.deleteAll(); @@ -522,7 +481,7 @@ test('should filter events by project using IS_ANY_OF', async () => { }); test('should not show user creation events for non-admins', async () => { - await loginRegularUser(); + await app.login({ email: regularEmail }); await eventService.storeEvent({ type: USER_CREATED, createdBy: 'test-user', diff --git a/src/test/e2e/api/admin/event.e2e.test.ts b/src/test/e2e/api/admin/event.e2e.test.ts index dfe5ac219c..7c0a0ca736 100644 --- a/src/test/e2e/api/admin/event.e2e.test.ts +++ b/src/test/e2e/api/admin/event.e2e.test.ts @@ -90,75 +90,6 @@ test('Can filter by project', async () => { }); }); -test('can search for events', async () => { - const events: IBaseEvent[] = [ - { - type: FEATURE_CREATED, - project: randomId(), - data: { id: randomId() }, - tags: [], - createdBy: randomId(), - createdByUserId: TEST_USER_ID, - ip: '127.0.0.1', - }, - { - type: FEATURE_CREATED, - project: randomId(), - data: { id: randomId() }, - preData: { id: randomId() }, - tags: [{ type: 'simple', value: randomId() }], - createdBy: randomId(), - createdByUserId: TEST_USER_ID, - ip: '127.0.0.1', - }, - ]; - - await Promise.all( - events.map((event) => { - return eventService.storeEvent(event); - }), - ); - - await app.request - .post('/api/admin/events/search') - .send({}) - .expect(200) - .expect((res) => { - expect(res.body.events).toHaveLength(2); - }); - await app.request - .post('/api/admin/events/search') - .send({ limit: 1, offset: 1 }) - .expect(200) - .expect((res) => { - expect(res.body.events).toHaveLength(1); - }); - await app.request - .post('/api/admin/events/search') - .send({ query: events[1].data.id }) - .expect(200) - .expect((res) => { - expect(res.body.events).toHaveLength(1); - expect(res.body.events[0].data.id).toEqual(events[1].data.id); - }); - await app.request - .post('/api/admin/events/search') - .send({ query: events[1].preData.id }) - .expect(200) - .expect((res) => { - expect(res.body.events).toHaveLength(1); - expect(res.body.events[0].preData.id).toEqual(events[1].preData.id); - }); - await app.request - .post('/api/admin/events/search') - .send({ query: events[1].tags![0].value }) - .expect(200) - .expect((res) => { - expect(res.body.events).toHaveLength(1); - expect(res.body.events[0].data.id).toEqual(events[1].data.id); - }); -}); - test('event creators - if system user, return system name, else should return name from database if user exists, else from events table', async () => { const user = await db.stores.userStore.insert({ name: 'database-user' }); const events: IBaseEvent[] = [ diff --git a/src/test/e2e/api/admin/feature-type.test.ts b/src/test/e2e/api/admin/feature-type.test.ts index 15d592988a..1fb2e43fc9 100644 --- a/src/test/e2e/api/admin/feature-type.test.ts +++ b/src/test/e2e/api/admin/feature-type.test.ts @@ -1,18 +1,22 @@ import dbInit, { type ITestDb } from '../../helpers/database-init.js'; import getLogger from '../../../fixtures/no-logger.js'; import { + createUserWithRootRole, type IUnleashTest, - setupAppWithCustomConfig, + setupAppWithAuth, } from '../../helpers/test-helper.js'; import { validateSchema } from '../../../../lib/openapi/validate.js'; import { featureTypesSchema } from '../../../../lib/openapi/spec/feature-types-schema.js'; +import { RoleName } from '../../../../lib/types/index.js'; let app: IUnleashTest; let db: ITestDb; +const adminEmail = 'admin-user@getunleash.io'; + beforeAll(async () => { db = await dbInit('feature_type_api_serial', getLogger); - app = await setupAppWithCustomConfig( + app = await setupAppWithAuth( db.stores, { experimental: { @@ -23,6 +27,17 @@ beforeAll(async () => { }, db.rawDatabase, ); + + await createUserWithRootRole({ + app, + stores: db.stores, + email: adminEmail, + roleName: RoleName.ADMIN, + }); +}); + +beforeEach(async () => { + await app.login({ email: adminEmail }); }); afterAll(async () => { diff --git a/src/test/e2e/helpers/test-helper.ts b/src/test/e2e/helpers/test-helper.ts index bd8b251403..5cd4fc0106 100644 --- a/src/test/e2e/helpers/test-helper.ts +++ b/src/test/e2e/helpers/test-helper.ts @@ -24,9 +24,23 @@ import type { Knex } from 'knex'; import type TestAgent from 'supertest/lib/agent.d.ts'; import type Test from 'supertest/lib/test.d.ts'; import type { Server } from 'node:http'; -import { initialServiceSetup } from '../../../lib/server-impl.js'; +import { + initialServiceSetup, + type IUser, + type RoleName, +} from '../../../lib/server-impl.js'; +import type { EventSearchQueryParameters } from '../../../lib/openapi/spec/event-search-query-parameters.js'; process.env.NODE_ENV = 'test'; +type DemoLoginArgs = { + email: string; +}; + +type SimpleLoginArgs = { + username: string; + password: string; +}; + export interface IUnleashTest extends IUnleashHttpAPI { request: TestAgent; destroy: () => Promise; @@ -115,7 +129,10 @@ export interface IUnleashHttpAPI { expectedResponseCode?: number, ): supertest.Test; - getRecordedEvents(): supertest.Test; + getRecordedEvents( + queryParams?: EventSearchQueryParameters, + expectedResponseCode?: number, + ): supertest.Test; createSegment(postData: object, expectStatusCode?: number): supertest.Test; deleteSegment( @@ -127,6 +144,8 @@ export interface IUnleashHttpAPI { postData: object, expectStatusCode?: number, ): supertest.Test; + + login(args: DemoLoginArgs | SimpleLoginArgs): supertest.Test; } function httpApis( @@ -318,15 +337,32 @@ function httpApis( .expect(expectStatusCode); }, getRecordedEvents( - project: string | null = null, + queryParams: EventSearchQueryParameters = {}, expectedResponseCode: number = 200, ): supertest.Test { + const query = new URLSearchParams(queryParams as any).toString(); return request - .post('/api/admin/events/search') - .send({ project, query: '', limit: 50, offset: 0 }) - .set('Content-Type', 'application/json') + .get(`/api/admin/search/events${query ? `?${query}` : ''}`) .expect(expectedResponseCode); }, + login(args: DemoLoginArgs | SimpleLoginArgs): supertest.Test { + if ('email' in args) { + const { email } = args; + return request + .post(`${base}/auth/demo/login`) + .send({ email }) + .expect(200); + } + + const { username, password } = args; + return request + .post(`${base}/auth/simple/login`) + .send({ + username, + password, + } as SimpleLoginArgs) + .expect(200); + }, }; } @@ -509,3 +545,39 @@ export const insertFeatureEnvironmentsLastSeen = async ( return date; }; + +export const createUserWithRootRole = async ({ + app, + stores, + email, + name = email, + roleName, +}: { + app: IUnleashTest; + stores: IUnleashStores; + name?: string; + email: string; + roleName?: RoleName; +}): Promise => { + const createdUser = await stores.userStore.insert({ + name, + email, + }); + + if (roleName) { + const roles = await app.services.accessService.getRootRoles(); + const role = roles.find((role) => role.name === roleName); + + if (!role) { + throw new Error(`Role ${roleName} not found`); + } + + await app.services.accessService.addUserToRole( + createdUser.id, + role.id, + 'default', + ); + } + + return createdUser; +};