Files
TREK/server/tests/unit/plugins/trip-warnings.controller.test.ts
T

81 lines
4.2 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
const { canAccessTrip, pluginsEnabled } = vi.hoisted(() => ({
canAccessTrip: vi.fn(async (tripId: number, userId: number) => (tripId === 1 && userId === 5 ? { id: 1 } : undefined)),
pluginsEnabled: vi.fn(() => true),
}));
vi.mock('../../../src/db/database', () => ({ db: {}, canAccessTrip }));
vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { TripWarningsController } from '../../../src/nest/plugins/contributions/trip-warnings.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
function controller(over: Partial<PluginHooks> = {}) {
const runtime = {
providersOf: vi.fn(() => ['p1', 'p2']),
tripWarnings: vi.fn(async (id: string) =>
id === 'p2' ? [{ level: 'error', message: 'Day 3 is overpacked', dayId: 3 }] : [{ level: 'warning', message: 'Museum closed Mon', placeId: 7 }]),
...over,
} as unknown as PluginHooks;
return { c: new TripWarningsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
}
describe('TripWarningsController', () => {
beforeEach(() => { pluginsEnabled.mockReturnValue(true); canAccessTrip.mockResolvedValue({ id: 1 } as never); });
it('returns [] when disabled / no user / no access', async () => {
pluginsEnabled.mockReturnValue(false);
expect(await controller().c.get('1', req(5))).toEqual({ warnings: [] });
pluginsEnabled.mockReturnValue(true);
expect(await controller().c.get('1', req(undefined))).toEqual({ warnings: [] });
canAccessTrip.mockResolvedValue(undefined as never);
expect(await controller().c.get('1', req(5))).toEqual({ warnings: [] });
});
it('merges provider warnings, tags each with the plugin id, defaults an odd level to warning', async () => {
const { c } = controller({
tripWarnings: vi.fn(async (id: string) =>
id === 'p1' ? [{ level: 'bogus', message: 'Check timings' }, { message: '' }] : [{ level: 'info', message: 'Rainy week' }]) as unknown as PluginHooks['tripWarnings'],
});
const res = await c.get('1', req(5));
expect(res.warnings).toEqual([
{ pluginId: 'p1', level: 'warning', message: 'Check timings', dayId: undefined, placeId: undefined }, // unknown level → warning; empty-message row dropped
{ pluginId: 'p2', level: 'info', message: 'Rainy week', dayId: undefined, placeId: undefined },
]);
});
it('skips a provider that throws (graceful) and calls the hook with the trip + acting user', async () => {
const { c, runtime } = controller({
tripWarnings: vi.fn(async (id: string) => { if (id === 'p2') throw new Error('slow'); return [{ level: 'warning', message: 'ok' }]; }) as unknown as PluginHooks['tripWarnings'],
});
const res = await c.get('1', req(5));
expect(res.warnings).toEqual([{ pluginId: 'p1', level: 'warning', message: 'ok', dayId: undefined, placeId: undefined }]);
expect(runtime.tripWarnings).toHaveBeenCalledWith('p1', 1, 5);
});
it('strips emojis from the warning message (rendered natively in TREK chrome)', async () => {
const { c } = controller({
providersOf: vi.fn(() => ['p1']),
tripWarnings: vi.fn(async () => [{ level: 'error', message: '🔥 Overbooked!' }]) as unknown as PluginHooks['tripWarnings'],
});
const res = await c.get('1', req(5));
expect(res.warnings[0].message).toBe('Overbooked!');
});
it('caps a flooding provider at 20 warnings and truncates an oversized message', async () => {
const { c } = controller({
providersOf: vi.fn(() => ['flood']),
tripWarnings: vi.fn(async () => [
{ level: 'warning', message: 'z'.repeat(1000) },
...Array.from({ length: 50 }, (_v, i) => ({ level: 'info', message: `w${i}` })),
]) as unknown as PluginHooks['tripWarnings'],
});
const res = await c.get('1', req(5));
expect(res.warnings).toHaveLength(20); // per-provider count cap
expect(res.warnings[0].message).toHaveLength(300); // message length cap
});
});