mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: event search on new endpoint, first test (#7739)
Changed the url of event search to search/events to align with search/features. With that created a search controller to keep all searches under there. Added first test.
This commit is contained in:
		
							parent
							
								
									993d87516d
								
							
						
					
					
						commit
						57a8b9da79
					
				
							
								
								
									
										101
									
								
								src/lib/features/events/event-search-controller.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/lib/features/events/event-search-controller.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
			
		||||
import type { Response } from 'express';
 | 
			
		||||
import type { IUnleashConfig } from '../../types/option';
 | 
			
		||||
import type { IUnleashServices } from '../../types/services';
 | 
			
		||||
import type EventService from '../../features/events/event-service';
 | 
			
		||||
import { NONE } from '../../types/permissions';
 | 
			
		||||
import type { OpenApiService } from '../../services/openapi-service';
 | 
			
		||||
import { createResponseSchema } from '../../openapi/util/create-response-schema';
 | 
			
		||||
import { serializeDates } from '../../../lib/types/serialize-dates';
 | 
			
		||||
import type { IFlagResolver } from '../../types/experimental';
 | 
			
		||||
import {
 | 
			
		||||
    type EventSearchQueryParameters,
 | 
			
		||||
    eventSearchQueryParameters,
 | 
			
		||||
} from '../../openapi/spec/event-search-query-parameters';
 | 
			
		||||
import {
 | 
			
		||||
    type EventSearchResponseSchema,
 | 
			
		||||
    eventSearchResponseSchema,
 | 
			
		||||
} from '../../openapi';
 | 
			
		||||
import { normalizeQueryParams } from '../../features/feature-search/search-utils';
 | 
			
		||||
import Controller from '../../routes/controller';
 | 
			
		||||
import type { IAuthRequest } from '../../server-impl';
 | 
			
		||||
import type { IEvent } from '../../types';
 | 
			
		||||
import { anonymiseKeys } from '../../util';
 | 
			
		||||
 | 
			
		||||
const ANON_KEYS = ['email', 'username', 'createdBy'];
 | 
			
		||||
const version = 1 as const;
 | 
			
		||||
export default class EventSearchController extends Controller {
 | 
			
		||||
    private eventService: EventService;
 | 
			
		||||
 | 
			
		||||
    private flagResolver: IFlagResolver;
 | 
			
		||||
 | 
			
		||||
    private openApiService: OpenApiService;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        config: IUnleashConfig,
 | 
			
		||||
        {
 | 
			
		||||
            eventService,
 | 
			
		||||
            openApiService,
 | 
			
		||||
        }: Pick<IUnleashServices, 'eventService' | 'openApiService'>,
 | 
			
		||||
    ) {
 | 
			
		||||
        super(config);
 | 
			
		||||
        this.eventService = eventService;
 | 
			
		||||
        this.flagResolver = config.flagResolver;
 | 
			
		||||
        this.openApiService = openApiService;
 | 
			
		||||
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'get',
 | 
			
		||||
            path: '',
 | 
			
		||||
            handler: this.searchEvents,
 | 
			
		||||
            permission: NONE,
 | 
			
		||||
            middleware: [
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    operationId: 'searchEvents',
 | 
			
		||||
                    tags: ['Events'],
 | 
			
		||||
                    summary: 'Search for events',
 | 
			
		||||
                    description:
 | 
			
		||||
                        'Allows searching for events matching the search criteria in the request body. This operation is deprecated. You should perform a GET request to the same endpoint with your query encoded as query parameters instead.',
 | 
			
		||||
                    parameters: [...eventSearchQueryParameters],
 | 
			
		||||
                    responses: {
 | 
			
		||||
                        200: createResponseSchema('eventSearchResponseSchema'),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async searchEvents(
 | 
			
		||||
        req: IAuthRequest<any, any, any, EventSearchQueryParameters>,
 | 
			
		||||
        res: Response<EventSearchResponseSchema>,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const { normalizedLimit, normalizedOffset } = normalizeQueryParams(
 | 
			
		||||
            req.query,
 | 
			
		||||
            {
 | 
			
		||||
                limitDefault: 50,
 | 
			
		||||
                maxLimit: 1000,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const { events, totalEvents } = await this.eventService.searchEvents({
 | 
			
		||||
            ...req.query,
 | 
			
		||||
            offset: normalizedOffset,
 | 
			
		||||
            limit: normalizedLimit,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.openApiService.respondWithValidation(
 | 
			
		||||
            200,
 | 
			
		||||
            res,
 | 
			
		||||
            eventSearchResponseSchema.$id,
 | 
			
		||||
            serializeDates({
 | 
			
		||||
                events: serializeDates(this.maybeAnonymiseEvents(events)),
 | 
			
		||||
                total: totalEvents,
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maybeAnonymiseEvents(events: IEvent[]): IEvent[] {
 | 
			
		||||
        if (this.flagResolver.isEnabled('anonymiseEventLog')) {
 | 
			
		||||
            return anonymiseKeys(events, ANON_KEYS);
 | 
			
		||||
        }
 | 
			
		||||
        return events;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,14 +1,16 @@
 | 
			
		||||
import type { IUnleashConfig } from '../../types/option';
 | 
			
		||||
import type { IFeatureTagStore, IUnleashStores } from '../../types/stores';
 | 
			
		||||
import type { Logger } from '../../logger';
 | 
			
		||||
import type { IEventStore } from '../../types/stores/event-store';
 | 
			
		||||
import type {
 | 
			
		||||
    IEventSearchParams,
 | 
			
		||||
    IEventStore,
 | 
			
		||||
} from '../../types/stores/event-store';
 | 
			
		||||
import type { IBaseEvent, IEventList } from '../../types/events';
 | 
			
		||||
import type { DeprecatedSearchEventsSchema } from '../../openapi/spec/deprecated-search-events-schema';
 | 
			
		||||
import type EventEmitter from 'events';
 | 
			
		||||
import type { IApiUser, ITag, IUser } from '../../types';
 | 
			
		||||
import { ApiTokenType } from '../../types/models/api-token';
 | 
			
		||||
import { EVENTS_CREATED_BY_PROCESSED } from '../../metric-events';
 | 
			
		||||
import type { EventSearchQueryParameters } from '../../openapi/spec/event-search-query-parameters';
 | 
			
		||||
import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strategies-store-type';
 | 
			
		||||
import { parseSearchOperatorValue } from '../feature-search/search-utils';
 | 
			
		||||
 | 
			
		||||
@ -55,9 +57,7 @@ export default class EventService {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async searchEvents(
 | 
			
		||||
        search: EventSearchQueryParameters,
 | 
			
		||||
    ): Promise<IEventList> {
 | 
			
		||||
    async searchEvents(search: IEventSearchParams): Promise<IEventList> {
 | 
			
		||||
        const queryParams = this.convertToDbParams(search);
 | 
			
		||||
        const totalEvents = await this.eventStore.searchEventsCount(
 | 
			
		||||
            {
 | 
			
		||||
@ -143,7 +143,7 @@ export default class EventService {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    convertToDbParams = (params: EventSearchQueryParameters): IQueryParam[] => {
 | 
			
		||||
    convertToDbParams = (params: IEventSearchParams): IQueryParam[] => {
 | 
			
		||||
        const queryParams: IQueryParam[] = [];
 | 
			
		||||
 | 
			
		||||
        if (params.createdAtFrom) {
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,10 @@ import {
 | 
			
		||||
    SEGMENT_UPDATED,
 | 
			
		||||
} from '../../types/events';
 | 
			
		||||
import type { Logger, LogProvider } from '../../logger';
 | 
			
		||||
import type { IEventStore } from '../../types/stores/event-store';
 | 
			
		||||
import type {
 | 
			
		||||
    IEventSearchParams,
 | 
			
		||||
    IEventStore,
 | 
			
		||||
} from '../../types/stores/event-store';
 | 
			
		||||
import type { ITag } from '../../types/model';
 | 
			
		||||
import { sharedEventEmitter } from '../../util/anyEventEmitter';
 | 
			
		||||
import type { Db } from '../../db/db';
 | 
			
		||||
@ -87,12 +90,6 @@ export interface IEventTable {
 | 
			
		||||
    tags: ITag[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IEventSearchParams {
 | 
			
		||||
    query: string | undefined;
 | 
			
		||||
    offset: string | undefined;
 | 
			
		||||
    limit: string | undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TABLE = 'events';
 | 
			
		||||
 | 
			
		||||
class EventStore implements IEventStore {
 | 
			
		||||
 | 
			
		||||
@ -24,8 +24,6 @@ import {
 | 
			
		||||
import { normalizeQueryParams } from './search-utils';
 | 
			
		||||
import { anonymise } from '../../util';
 | 
			
		||||
 | 
			
		||||
const PATH = '/features';
 | 
			
		||||
 | 
			
		||||
type FeatureSearchServices = Pick<
 | 
			
		||||
    IUnleashServices,
 | 
			
		||||
    'openApiService' | 'featureSearchService'
 | 
			
		||||
@ -54,7 +52,7 @@ export default class FeatureSearchController extends Controller {
 | 
			
		||||
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'get',
 | 
			
		||||
            path: PATH,
 | 
			
		||||
            path: '',
 | 
			
		||||
            handler: this.searchFeatures,
 | 
			
		||||
            permission: NONE,
 | 
			
		||||
            middleware: [
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ export const eventSearchQueryParameters = [
 | 
			
		||||
            example: 'admin@example.com',
 | 
			
		||||
        },
 | 
			
		||||
        description:
 | 
			
		||||
            'Find events by a free-text search query. The query will be matched against the event type and the event data payload (if any).',
 | 
			
		||||
            'Find events by a free-text search query. The query will be matched against the event data payload (if any).',
 | 
			
		||||
        in: 'query',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -53,7 +53,7 @@ export const eventSearchQueryParameters = [
 | 
			
		||||
            pattern: '^(IS|IS_ANY_OF):(.*?)(,([a-zA-Z0-9_]+))*$',
 | 
			
		||||
        },
 | 
			
		||||
        description:
 | 
			
		||||
            'The ID of event creator to filter by. The creators can be specified with an operator. The supported operators are IS, IS_ANY_OF.',
 | 
			
		||||
            'Filter by the ID of the event creator, using supported operators: IS, IS_ANY_OF.',
 | 
			
		||||
        in: 'query',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -83,6 +83,7 @@ export const eventSearchQueryParameters = [
 | 
			
		||||
        schema: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            example: '50',
 | 
			
		||||
            default: '0',
 | 
			
		||||
        },
 | 
			
		||||
        description:
 | 
			
		||||
            'The number of features to skip when returning a page. By default it is set to 0.',
 | 
			
		||||
@ -93,9 +94,10 @@ export const eventSearchQueryParameters = [
 | 
			
		||||
        schema: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            example: '50',
 | 
			
		||||
            default: '50',
 | 
			
		||||
        },
 | 
			
		||||
        description:
 | 
			
		||||
            'The number of feature environments to return in a page. By default it is set to 50.',
 | 
			
		||||
            'The number of feature environments to return in a page. By default it is set to 50. The maximum is 1000.',
 | 
			
		||||
        in: 'query',
 | 
			
		||||
    },
 | 
			
		||||
] as const;
 | 
			
		||||
 | 
			
		||||
@ -21,16 +21,6 @@ import { getStandardResponses } from '../../../lib/openapi/util/standard-respons
 | 
			
		||||
import { createRequestSchema } from '../../openapi/util/create-request-schema';
 | 
			
		||||
import type { DeprecatedSearchEventsSchema } from '../../openapi/spec/deprecated-search-events-schema';
 | 
			
		||||
import type { IFlagResolver } from '../../types/experimental';
 | 
			
		||||
import {
 | 
			
		||||
    type EventSearchQueryParameters,
 | 
			
		||||
    eventSearchQueryParameters,
 | 
			
		||||
} from '../../openapi/spec/event-search-query-parameters';
 | 
			
		||||
import type { IAuthRequest } from '../unleash-types';
 | 
			
		||||
import {
 | 
			
		||||
    type EventSearchResponseSchema,
 | 
			
		||||
    eventSearchResponseSchema,
 | 
			
		||||
} from '../../openapi';
 | 
			
		||||
import { normalizeQueryParams } from '../../features/feature-search/search-utils';
 | 
			
		||||
 | 
			
		||||
const ANON_KEYS = ['email', 'username', 'createdBy'];
 | 
			
		||||
const version = 1 as const;
 | 
			
		||||
@ -125,26 +115,6 @@ export default class EventController extends Controller {
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.route({
 | 
			
		||||
            method: 'get',
 | 
			
		||||
            path: '/search',
 | 
			
		||||
            handler: this.searchEvents,
 | 
			
		||||
            permission: NONE,
 | 
			
		||||
            middleware: [
 | 
			
		||||
                openApiService.validPath({
 | 
			
		||||
                    operationId: 'searchEvents',
 | 
			
		||||
                    tags: ['Events'],
 | 
			
		||||
                    summary: 'Search for events',
 | 
			
		||||
                    description:
 | 
			
		||||
                        'Allows searching for events matching the search criteria in the request body. This operation is deprecated. You should perform a GET request to the same endpoint with your query encoded as query parameters instead.',
 | 
			
		||||
                    parameters: [...eventSearchQueryParameters],
 | 
			
		||||
                    responses: {
 | 
			
		||||
                        200: createResponseSchema('eventSearchResponseSchema'),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maybeAnonymiseEvents(events: IEvent[]): IEvent[] {
 | 
			
		||||
@ -227,33 +197,4 @@ export default class EventController extends Controller {
 | 
			
		||||
            response,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async searchEvents(
 | 
			
		||||
        req: IAuthRequest<any, any, any, EventSearchQueryParameters>,
 | 
			
		||||
        res: Response<EventSearchResponseSchema>,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const { normalizedLimit, normalizedOffset } = normalizeQueryParams(
 | 
			
		||||
            req.query,
 | 
			
		||||
            {
 | 
			
		||||
                limitDefault: 50,
 | 
			
		||||
                maxLimit: 1000,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const { events, totalEvents } = await this.eventService.searchEvents({
 | 
			
		||||
            ...req.body,
 | 
			
		||||
            offset: normalizedOffset,
 | 
			
		||||
            limit: normalizedLimit,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.openApiService.respondWithValidation(
 | 
			
		||||
            200,
 | 
			
		||||
            res,
 | 
			
		||||
            eventSearchResponseSchema.$id,
 | 
			
		||||
            serializeDates({
 | 
			
		||||
                events: serializeDates(this.maybeAnonymiseEvents(events)),
 | 
			
		||||
                total: totalEvents,
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -32,9 +32,9 @@ import { createKnexTransactionStarter } from '../../db/transaction';
 | 
			
		||||
import type { Db } from '../../db/db';
 | 
			
		||||
import ExportImportController from '../../features/export-import-toggles/export-import-controller';
 | 
			
		||||
import { SegmentsController } from '../../features/segment/segment-controller';
 | 
			
		||||
import FeatureSearchController from '../../features/feature-search/feature-search-controller';
 | 
			
		||||
import { InactiveUsersController } from '../../users/inactive/inactive-users-controller';
 | 
			
		||||
import { UiObservabilityController } from '../../features/ui-observability-controller/ui-observability-controller';
 | 
			
		||||
import { SearchApi } from './search';
 | 
			
		||||
 | 
			
		||||
export class AdminApi extends Controller {
 | 
			
		||||
    constructor(config: IUnleashConfig, services: IUnleashServices, db: Db) {
 | 
			
		||||
@ -158,10 +158,7 @@ export class AdminApi extends Controller {
 | 
			
		||||
            new TelemetryController(config, services).router,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        this.app.use(
 | 
			
		||||
            '/search',
 | 
			
		||||
            new FeatureSearchController(config, services).router,
 | 
			
		||||
        );
 | 
			
		||||
        this.app.use('/search', new SearchApi(config, services, db).router);
 | 
			
		||||
 | 
			
		||||
        this.app.use(
 | 
			
		||||
            '/record-ui-error',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								src/lib/routes/admin-api/search/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/lib/routes/admin-api/search/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
import EventSearchController from '../../../features/events/event-search-controller';
 | 
			
		||||
import FeatureSearchController from '../../../features/feature-search/feature-search-controller';
 | 
			
		||||
import type {
 | 
			
		||||
    Db,
 | 
			
		||||
    IUnleashConfig,
 | 
			
		||||
    IUnleashServices,
 | 
			
		||||
} from '../../../server-impl';
 | 
			
		||||
import Controller from '../../controller';
 | 
			
		||||
 | 
			
		||||
export class SearchApi extends Controller {
 | 
			
		||||
    constructor(config: IUnleashConfig, services: IUnleashServices, db: Db) {
 | 
			
		||||
        super(config);
 | 
			
		||||
 | 
			
		||||
        this.app.use(
 | 
			
		||||
            '/features',
 | 
			
		||||
            new FeatureSearchController(config, services).router,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        this.app.use(
 | 
			
		||||
            '/events',
 | 
			
		||||
            new EventSearchController(config, services).router,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -2,12 +2,20 @@ import type { IBaseEvent, IEvent } from '../events';
 | 
			
		||||
import type { Store } from './store';
 | 
			
		||||
import type { DeprecatedSearchEventsSchema } from '../../openapi';
 | 
			
		||||
import type EventEmitter from 'events';
 | 
			
		||||
import type {
 | 
			
		||||
    IEventSearchParams,
 | 
			
		||||
    IQueryOperations,
 | 
			
		||||
} from '../../features/events/event-store';
 | 
			
		||||
import type { IQueryOperations } from '../../features/events/event-store';
 | 
			
		||||
import type { IQueryParam } from '../../features/feature-toggle/types/feature-toggle-strategies-store-type';
 | 
			
		||||
 | 
			
		||||
export interface IEventSearchParams {
 | 
			
		||||
    project?: string;
 | 
			
		||||
    query?: string;
 | 
			
		||||
    createdAtFrom?: string;
 | 
			
		||||
    createdAtTo?: string;
 | 
			
		||||
    createdBy?: string;
 | 
			
		||||
    type?: string;
 | 
			
		||||
    offset: number;
 | 
			
		||||
    limit: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IEventStore
 | 
			
		||||
    extends Store<IEvent, number>,
 | 
			
		||||
        Pick<EventEmitter, 'on' | 'setMaxListeners' | 'emit' | 'off'> {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										89
									
								
								src/test/e2e/api/admin/event-search.e2e.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/test/e2e/api/admin/event-search.e2e.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
import type { EventSearchQueryParameters } from '../../../../lib/openapi/spec/event-search-query-parameters';
 | 
			
		||||
import dbInit, { type ITestDb } from '../../helpers/database-init';
 | 
			
		||||
 | 
			
		||||
import { FEATURE_CREATED } from '../../../../lib/types';
 | 
			
		||||
import { EventService } from '../../../../lib/services';
 | 
			
		||||
import EventEmitter from 'events';
 | 
			
		||||
import getLogger from '../../../fixtures/no-logger';
 | 
			
		||||
import {
 | 
			
		||||
    type IUnleashTest,
 | 
			
		||||
    setupAppWithCustomConfig,
 | 
			
		||||
} from '../../helpers/test-helper';
 | 
			
		||||
 | 
			
		||||
let app: IUnleashTest;
 | 
			
		||||
let db: ITestDb;
 | 
			
		||||
let eventService: EventService;
 | 
			
		||||
const TEST_USER_ID = -9999;
 | 
			
		||||
 | 
			
		||||
beforeAll(async () => {
 | 
			
		||||
    db = await dbInit('event_search', getLogger);
 | 
			
		||||
    app = await setupAppWithCustomConfig(db.stores, {
 | 
			
		||||
        experimental: {
 | 
			
		||||
            flags: {
 | 
			
		||||
                strictSchemaValidation: true,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    eventService = new EventService(db.stores, {
 | 
			
		||||
        getLogger,
 | 
			
		||||
        eventBus: new EventEmitter(),
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
afterAll(async () => {
 | 
			
		||||
    await app.destroy();
 | 
			
		||||
    await db.destroy();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
beforeEach(async () => {
 | 
			
		||||
    await db.stores.featureToggleStore.deleteAll();
 | 
			
		||||
    await db.stores.segmentStore.deleteAll();
 | 
			
		||||
    await db.stores.eventStore.deleteAll();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const searchEvents = async (
 | 
			
		||||
    queryParams: EventSearchQueryParameters,
 | 
			
		||||
    expectedCode = 200,
 | 
			
		||||
) => {
 | 
			
		||||
    const query = new URLSearchParams(queryParams as any).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,
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user