Files
TREK/server/tests/unit/nest/booking-import/booking-import.service.test.ts
T
2026-06-24 18:45:52 +02:00

80 lines
4.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { HttpException } from '@nestjs/common';
// Mock the heavy side-effect imports so the service module loads cleanly; the
// preview() path under test only touches the extractor + llmParse deps.
vi.mock('../../../../src/db/database', () => ({ db: { prepare: vi.fn() }, closeDb: () => {}, reinitialize: () => {} }));
vi.mock('../../../../src/websocket', () => ({ broadcast: vi.fn() }));
vi.mock('../../../../src/services/permissions', () => ({ checkPermission: vi.fn(() => true) }));
vi.mock('../../../../src/services/tripAccess', () => ({ verifyTripAccess: vi.fn() }));
vi.mock('../../../../src/services/reservationService', () => ({ createReservation: vi.fn() }));
vi.mock('../../../../src/services/placeService', () => ({ createPlace: vi.fn() }));
vi.mock('../../../../src/services/mapsService', () => ({ searchNominatim: vi.fn() }));
import { BookingImportService } from '../../../../src/nest/booking-import/booking-import.service';
const HOTEL_KI = { '@type': 'LodgingReservation', reservationNumber: 'ABC', reservationFor: { name: 'Hotel X' }, checkinTime: '2026-06-11T15:00', checkoutTime: '2026-06-12T11:00' };
const file = (name = 'a.pdf') => ({ buffer: Buffer.from('x'), originalname: name } as any);
function make(opts: { kit?: boolean; ai?: boolean; extract?: any; parse?: any }) {
const extractor = { isAvailable: () => opts.kit ?? false, extract: vi.fn(opts.extract ?? (async () => [])) };
const llmParse = { isAvailable: () => opts.ai ?? false, parse: vi.fn(opts.parse ?? (async () => ({ kiItems: [], warnings: [] }))) };
return { svc: new BookingImportService(extractor as any, llmParse as any), extractor, llmParse };
}
beforeEach(() => vi.clearAllMocks());
describe('BookingImportService.preview', () => {
it('no-ai: maps kitinerary items, does not force needs_review, reports aiUsed:false', async () => {
const { svc, llmParse } = make({ kit: true, ai: false, extract: async () => [HOTEL_KI] });
const res = await svc.preview([file()], 'no-ai', 1);
expect(res.items).toHaveLength(1);
expect(res.items[0].needs_review).toBeFalsy();
expect(res.files).toEqual([{ fileName: 'a.pdf', aiAvailable: false, aiUsed: false }]);
expect(llmParse.parse).not.toHaveBeenCalled();
});
it('throws 503 when neither parser is available', async () => {
const { svc } = make({ kit: false, ai: false });
try {
await svc.preview([file()], 'no-ai', 1);
throw new Error('expected throw');
} catch (err) {
expect(err).toBeInstanceOf(HttpException);
expect((err as HttpException).getStatus()).toBe(503);
}
});
it('fallback-on-empty: runs the LLM when kitinerary finds nothing and flags needs_review', async () => {
const { svc, extractor, llmParse } = make({
kit: true, ai: true,
extract: async () => [],
parse: async () => ({ kiItems: [HOTEL_KI], warnings: [] }),
});
const res = await svc.preview([file()], 'fallback-on-empty', 1);
expect(extractor.extract).toHaveBeenCalled();
expect(llmParse.parse).toHaveBeenCalled();
expect(res.items).toHaveLength(1);
expect(res.items[0].needs_review).toBe(true);
expect(res.files![0]).toEqual({ fileName: 'a.pdf', aiAvailable: true, aiUsed: true });
});
it('fallback-on-empty: skips the LLM when kitinerary already found items', async () => {
const { svc, llmParse } = make({ kit: true, ai: true, extract: async () => [HOTEL_KI] });
const res = await svc.preview([file()], 'fallback-on-empty', 1);
expect(llmParse.parse).not.toHaveBeenCalled();
expect(res.files![0].aiUsed).toBe(false);
});
it('force-ai: skips kitinerary entirely and uses the LLM', async () => {
const { svc, extractor, llmParse } = make({
kit: true, ai: true,
parse: async () => ({ kiItems: [HOTEL_KI], warnings: [] }),
});
const res = await svc.preview([file()], 'force-ai', 1);
expect(extractor.extract).not.toHaveBeenCalled();
expect(llmParse.parse).toHaveBeenCalled();
expect(res.items[0].needs_review).toBe(true);
});
});