diff --git a/server/tests/unit/nest/admin.controller.test.ts b/server/tests/unit/nest/admin.controller.test.ts index 9ff9d8d09..c1fbc3030 100644 --- a/server/tests/unit/nest/admin.controller.test.ts +++ b/server/tests/unit/nest/admin.controller.test.ts @@ -10,6 +10,7 @@ import { AdminController } from '../../../src/nest/admin/admin.controller'; import type { TokenService } from '../../../src/nest/tokens/token.service'; import type { RegistrationInvitesService } from '../../../src/nest/auth/registration-invites.service'; import type { OauthService } from '../../../src/nest/oauth/oauth.service'; +import { KitineraryExtractorService } from '../../../src/nest/booking-import/kitinerary-extractor.service'; import type { AdminService } from '../../../src/nest/admin/admin.service'; import type { PluginRuntimeService } from '../../../src/nest/plugins/plugin-runtime.service'; import type { AuditService } from '../../../src/nest/audit/audit.service'; @@ -50,8 +51,18 @@ const addonsStub = () => ({ // The MCP-token routes read TokenService now, not AdminService. Stubbed via a // fourth, optional argument so every existing call site stays as it was. -const adminCtl = (s: AdminService, rt?: PluginRuntimeService, addons: AddonsService = addonsStub(), tokens: Partial = {}, invites: Partial = {}, oauth: Partial = {}) => - new AdminController(s, addons, rt as unknown as PluginRuntimeService, audit, notifications, tokens as TokenService, invites as RegistrationInvitesService, oauth as OauthService); +const adminCtl = (s: AdminService, rt?: PluginRuntimeService, addons: AddonsService = addonsStub(), tokens: Partial = {}, invites: Partial = {}, oauth: Partial = {}, kitinerary: Partial = {}) => + new AdminController(s, addons, rt as unknown as PluginRuntimeService, audit, notifications, tokens as TokenService, invites as RegistrationInvitesService, oauth as OauthService, kitinerary as KitineraryExtractorService); +// #2261 — the extractor's version is what tells a stale binary from a provider +// nobody wrote a script for; both come back empty otherwise. +describe('AdminController system-info', () => { + it('ADM-SYSINFO-001: hands the extractor description through unchanged', () => { + const describeFn = vi.fn(() => ({ available: true, path: '/usr/local/bin/kitinerary-extractor', version: '6.3.3', configuredPath: null })); + const res = adminCtl({} as AdminService, undefined, addonsStub(), {}, {}, {}, { describe: describeFn }).systemInfo(); + expect(res).toEqual({ kitinerary: { available: true, path: '/usr/local/bin/kitinerary-extractor', version: '6.3.3', configuredPath: null } }); + }); +}); + function thrown(fn: () => unknown): { status: number; body: unknown } { try { fn(); } catch (err) { if (err instanceof NotFoundException) return { status: 404, body: err.getResponse() }; diff --git a/server/tests/unit/nest/booking-import/kitinerary-extractor.service.test.ts b/server/tests/unit/nest/booking-import/kitinerary-extractor.service.test.ts index 8dfdf4448..45e569f06 100644 --- a/server/tests/unit/nest/booking-import/kitinerary-extractor.service.test.ts +++ b/server/tests/unit/nest/booking-import/kitinerary-extractor.service.test.ts @@ -136,8 +136,7 @@ describe('KitineraryExtractorService binary probe', () => { describe('KitineraryExtractorService diagnostics', () => { it('KIT-EXT-010: reports the version the binary prints', () => { existsSync.mockImplementation((p: string) => p === '/opt/ki'); - execFileSync.mockReturnValue(Buffer.from('kitinerary-extractor 6.3.3 -')); + execFileSync.mockReturnValue(Buffer.from('kitinerary-extractor 6.3.3\n')); expect(boot({ kitineraryExtractorPath: '/opt/ki' }).describe()).toEqual({ available: true, path: '/opt/ki', version: '6.3.3', configuredPath: '/opt/ki', @@ -190,9 +189,7 @@ describe('KitineraryExtractorService stderr handling', () => { it('KIT-EXT-015: passes every raw line to the debug log, script errors included', async () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); - await extractWith('JS ERROR: lufthansa.js failed -Invalid result type from script -'); + await extractWith('JS ERROR: lufthansa.js failed\nInvalid result type from script\n'); const logged = logDebug.mock.calls.map(c => String(c[0])); expect(logged.some(l => l.includes('JS ERROR: lufthansa.js failed'))).toBe(true); @@ -204,17 +201,14 @@ Invalid result type from script it('KIT-EXT-016: an unexpected line still reaches console.warn', async () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); - await extractWith('JS ERROR: noise -something genuinely odd -'); + await extractWith('JS ERROR: noise\nsomething genuinely odd\n'); expect(warn).toHaveBeenCalledWith(expect.stringContaining('booking.eml'), 'something genuinely odd'); warn.mockRestore(); }); it('KIT-EXT-017: the debug dump is capped so one document cannot flood the log', async () => { - await extractWith(Array.from({ length: 500 }, (_, i) => `line ${i}`).join(' -')); + await extractWith(Array.from({ length: 500 }, (_, i) => `line ${i}`).join('\n')); expect(logDebug.mock.calls.length).toBe(200); }); });