UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

696 lines • 22.1 kB
import dbInit from '../../helpers/database-init.js'; import { RoleName, } from '../../../../lib/types/index.js'; import getLogger from '../../../fixtures/no-logger.js'; import { createUserWithRootRole, setupAppWithAuth, } from '../../helpers/test-helper.js'; import { createEventsService } from '../../../../lib/features/index.js'; import { createTestConfig } from '../../../config/test-config.js'; import { FEATURE_CREATED, USER_CREATED } from '../../../../lib/events/index.js'; import { withTransactional } from '../../../../lib/db/transaction.js'; import { EventStore } from '../../../../lib/features/events/event-store.js'; let app; let db; let eventService; const TEST_USER_ID = -9999; const regularEmail = 'import-user@getunleash.io'; const adminEmail = 'admin-user@getunleash.io'; const config = createTestConfig(); let stores; beforeAll(async () => { db = await dbInit('event_search', getLogger); stores = db.stores; app = await setupAppWithAuth(db.stores, { experimental: { flags: { strictSchemaValidation: true, }, }, }, db.rawDatabase); eventService = createEventsService(db.rawDatabase, config); await createUserWithRootRole({ app, stores, email: regularEmail, }); await createUserWithRootRole({ app, stores, email: adminEmail, roleName: RoleName.ADMIN, }); }); afterAll(async () => { await app.destroy(); await db.destroy(); }); beforeEach(async () => { await app.login({ email: adminEmail }); await db.stores.featureToggleStore.deleteAll(); await db.stores.segmentStore.deleteAll(); await db.stores.eventStore.deleteAll(); }); const searchEvents = async (queryParams, expectedCode = 200) => { const query = new URLSearchParams(queryParams).toString(); return app.request .get(`/api/admin/search/events?${query}`) .expect(expectedCode); }; test('should search events by query', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'something-else', data: { id: 'some-other-feature' }, tags: [], createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'something-else', data: { id: 'my-other-feature' }, tags: [], createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ query: 'some-other-feature' }); expect(body).toMatchObject({ events: [ { type: 'feature-created', data: { id: 'some-other-feature', }, }, ], total: 1, }); }); test('should filter events by feature', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', featureName: 'my_feature_a', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', featureName: 'my_feature_b', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ feature: 'IS:my_feature_b' }); expect(body).toMatchObject({ events: [ { type: 'feature-created', featureName: 'my_feature_b', }, ], total: 1, }); }); test('should filter events by project', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'another_project', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ project: 'IS:another_project' }); expect(body).toMatchObject({ events: [ { type: 'feature-created', project: 'another_project', }, ], total: 1, }); }); test('should filter events by type', async () => { await eventService.storeEvent({ type: 'change-added', project: 'default', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: 'feature-created', project: 'default', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ type: 'IS:change-added' }); expect(body).toMatchObject({ events: [ { type: 'change-added', }, ], total: 1, }); }); test('should filter events by created by', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', createdBy: 'admin1@example.com', createdByUserId: TEST_USER_ID + 1, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', createdBy: 'admin2@example.com', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ createdBy: `IS:${TEST_USER_ID}` }); expect(body).toMatchObject({ events: [ { createdBy: 'admin2@example.com', }, ], total: 1, }); }); test('should filter events by created date range', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { featureName: 'my_feature_a' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { featureName: 'my_feature_b' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const today = new Date(); const { body } = await searchEvents({ from: `IS:${today.toISOString().split('T')[0]}`, }); expect(body).toMatchObject({ events: [ { type: FEATURE_CREATED, data: { featureName: 'my_feature_b' }, }, { type: FEATURE_CREATED, data: { featureName: 'my_feature_a' }, }, ], total: 2, }); }); test('should include dates created on the `to` date', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { featureName: 'my_feature_b' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const today = new Date(); const { body } = await searchEvents({ to: `IS:${today.toISOString().split('T')[0]}`, }); expect(body).toMatchObject({ events: [ { type: FEATURE_CREATED, data: { featureName: 'my_feature_b' }, }, ], total: 1, }); }); test('should not include events before `from` or after `to`', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { featureName: 'early-event' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { featureName: 'late-event' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { featureName: 'goldilocks' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { events } = await eventService.getEvents(); const earlyEvent = events.find((e) => e.data.featureName === 'early-event'); await db.rawDatabase.raw(`UPDATE events SET created_at = created_at - interval '1 day' where id = ?`, [earlyEvent?.id]); const lateEvent = events.find((e) => e.data.featureName === 'late-event'); await db.rawDatabase.raw(`UPDATE events SET created_at = created_at + interval '1 day' where id = ?`, [lateEvent?.id]); const today = new Date(); const todayString = today.toISOString().split('T')[0]; const { body } = await searchEvents({ from: `IS:${todayString}`, to: `IS:${todayString}`, }); expect(body).toMatchObject({ events: [ { type: FEATURE_CREATED, data: { featureName: 'goldilocks' }, }, ], total: 1, }); }); test('should paginate with offset and limit', async () => { for (let i = 0; i < 5; i++) { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { featureName: `my_feature_${i}` }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); } const { body: secondPage } = await searchEvents({ offset: '2', limit: '2', }); expect(secondPage.events).toMatchObject([ { data: { featureName: `my_feature_2` }, }, { data: { featureName: `my_feature_1` }, }, ]); }); test('should filter events by feature using IS_ANY_OF', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', featureName: 'my_feature_a', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: USER_CREATED, featureName: 'my_feature_b', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', featureName: 'my_feature_c', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ feature: 'IS_ANY_OF:my_feature_a,my_feature_b', }); expect(body).toMatchObject({ events: [ { type: 'user-created', featureName: 'my_feature_b', }, { type: 'feature-created', featureName: 'my_feature_a', }, ], total: 2, }); }); test('should filter events by project using IS_ANY_OF', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'project_a', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'project_b', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'project_c', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ project: 'IS_ANY_OF:project_a,project_b', }); expect(body).toMatchObject({ events: [ { type: 'feature-created', project: 'project_b', }, { type: 'feature-created', project: 'project_a', }, ], total: 2, }); }); test('should not show user creation events for non-admins', async () => { await app.login({ email: regularEmail }); await eventService.storeEvent({ type: USER_CREATED, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({}); expect(body).toMatchObject({ events: [ { type: FEATURE_CREATED, }, ], total: 1, }); }); test('should show user creation events for admins', async () => { await eventService.storeEvent({ type: USER_CREATED, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({}); expect(body).toMatchObject({ events: [ { type: FEATURE_CREATED, }, { type: USER_CREATED, }, ], total: 2, }); }); test('should filter events by environment', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', environment: 'production', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', environment: 'staging', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ environment: 'IS:production' }); expect(body).toMatchObject({ events: [ { type: 'feature-created', environment: 'production', }, ], total: 1, }); }); test('should filter events by environment using IS_ANY_OF', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', environment: 'production', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', environment: 'staging', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', environment: 'development', createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ environment: 'IS_ANY_OF:production,staging', }); expect(body).toMatchObject({ events: [ { type: 'feature-created', environment: 'staging', }, { type: 'feature-created', environment: 'production', }, ], total: 2, }); }); test('should filter events by ID', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { name: 'feature1' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { name: 'feature2' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { name: 'feature3' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body: allEventsResponse } = await searchEvents({}); const targetEvent = allEventsResponse.events.find((e) => e.data.name === 'feature2'); const { body } = await searchEvents({ id: `IS:${targetEvent.id}` }); expect(body).toMatchObject({ events: [ { id: targetEvent.id, type: 'feature-created', data: { name: 'feature2' }, }, ], total: 1, }); }); test('should filter events by multiple IDs using IS_ANY_OF', async () => { await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { name: 'feature1' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { name: 'feature2' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { name: 'feature3' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body: allEventsResponse } = await searchEvents({}); const targetEvent1 = allEventsResponse.events.find((e) => e.data.name === 'feature1'); const targetEvent3 = allEventsResponse.events.find((e) => e.data.name === 'feature3'); const { body } = await searchEvents({ id: `IS_ANY_OF:${targetEvent1.id},${targetEvent3.id}`, }); expect(body.total).toBe(2); expect(body.events).toHaveLength(2); const returnedIds = body.events.map((e) => e.id); expect(returnedIds).toContain(targetEvent1.id); expect(returnedIds).toContain(targetEvent3.id); const feature2Event = allEventsResponse.events.find((e) => e.data.name === 'feature2'); expect(returnedIds).not.toContain(feature2Event.id); }); test('should filter events by group ID', async () => { const eventStoreService = withTransactional((db) => new EventStore(db, getLogger), db.rawDatabase); const events = [ { type: FEATURE_CREATED, project: 'default', data: { name: 'feature1' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }, { type: FEATURE_CREATED, project: 'default', data: { name: 'feature2' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }, ]; await eventStoreService.transactional(async (transactionalEventStore) => { await transactionalEventStore.batchStore(events); }, { type: 'transaction', id: '1' }); await eventService.storeEvent({ type: FEATURE_CREATED, project: 'default', data: { name: 'feature3' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); const { body } = await searchEvents({ groupId: 'IS:1' }); expect(body.total).toBe(2); expect(body.events).toHaveLength(2); expect(body.events.every((e) => e.groupId === '1')).toBe(true); }); test('should filter events by multiple group IDs using IS_ANY_OF', async () => { const eventStoreService = withTransactional((db) => new EventStore(db, getLogger), db.rawDatabase); const event1 = { type: FEATURE_CREATED, project: 'default', data: { name: 'feature1' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }; const event2 = { type: FEATURE_CREATED, project: 'default', data: { name: 'feature2' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }; const event3 = { type: FEATURE_CREATED, project: 'default', data: { name: 'feature3' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }; await eventStoreService.transactional(async (transactionalEventStore) => { await transactionalEventStore.store(event1); }, { type: 'transaction', id: '10' }); await eventStoreService.transactional(async (transactionalEventStore) => { await transactionalEventStore.store(event2); }, { type: 'transaction', id: '20' }); await eventStoreService.transactional(async (transactionalEventStore) => { await transactionalEventStore.store(event3); }, { type: 'transaction', id: '30' }); const { body } = await searchEvents({ groupId: 'IS_ANY_OF:10,30' }); expect(body.total).toBe(2); expect(body.events).toHaveLength(2); const returnedGroupIds = body.events.map((e) => e.groupId); expect(returnedGroupIds).toContain('10'); expect(returnedGroupIds).toContain('30'); expect(returnedGroupIds).not.toContain('20'); }); test('Should return empty result when filtering by non-existent group ID', async () => { const eventStoreService = withTransactional((db) => new EventStore(db, getLogger), db.rawDatabase); await eventStoreService.transactional(async (transactionalEventStore) => { await transactionalEventStore.store({ type: FEATURE_CREATED, project: 'default', data: { name: 'feature1' }, createdBy: 'test-user', createdByUserId: TEST_USER_ID, ip: '127.0.0.1', }); }, { type: 'transaction', id: '1' }); const { body } = await searchEvents({ groupId: 'IS:999' }); expect(body.total).toBe(0); expect(body.events).toHaveLength(0); }); //# sourceMappingURL=event-search.e2e.test.js.map