import { describe, expect, it, vi } from 'vitest'; import Fastify from 'fastify'; import { RecommendationsController } from '../../../src/app/controllers/RecommendationsController'; import type { AppConfig } from '../../../src/app/config/AppConfig'; import type { RecommendationPipeline } from '../../../src/app/pipelines/RecommendationPipeline'; function makeApp(config: Partial, pipeline: Partial) { const app = Fastify(); new RecommendationsController(config as AppConfig, pipeline as RecommendationPipeline).register(app); return app; } describe('RecommendationsController', () => { it('GET /recommendations runs the pipeline with the configured count and LLM flag', async () => { const runAsync = vi.fn().mockResolvedValue([{ title: 'Dune', year: 2021, tmdbId: 1, reason: 'r' }]); const app = makeApp({ RECOMMEND_COUNT: 10, LLM_ENABLED: true }, { runAsync } as unknown as RecommendationPipeline); const res = await app.inject({ method: 'GET', url: '/recommendations' }); expect(res.statusCode).toBe(200); expect(runAsync).toHaveBeenCalledWith({ count: 10, useLlm: true }); }); it('GET /recommendations?count= and force_llm/force_tmdb override config', async () => { const runAsync = vi.fn().mockResolvedValue([]); const app = makeApp({ RECOMMEND_COUNT: 10, LLM_ENABLED: false }, { runAsync } as unknown as RecommendationPipeline); await app.inject({ method: 'GET', url: '/recommendations?count=3&force_llm=true' }); expect(runAsync).toHaveBeenCalledWith({ count: 3, useLlm: true }); }); it('falls back to the configured count for a malformed or non-positive count query param', async () => { const runAsync = vi.fn().mockResolvedValue([]); const app = makeApp({ RECOMMEND_COUNT: 10, LLM_ENABLED: true }, { runAsync } as unknown as RecommendationPipeline); await app.inject({ method: 'GET', url: '/recommendations?count=abc' }); expect(runAsync).toHaveBeenCalledWith({ count: 10, useLlm: true }); await app.inject({ method: 'GET', url: '/recommendations?count=-5' }); expect(runAsync).toHaveBeenCalledWith({ count: 10, useLlm: true }); await app.inject({ method: 'GET', url: '/recommendations?count=0' }); expect(runAsync).toHaveBeenCalledWith({ count: 10, useLlm: true }); }); it('POST /recommendations/trigger auto-requests via Radarr when enabled', async () => { const recs = [{ title: 'Dune', year: 2021, tmdbId: 1, reason: 'r' }]; const runAsync = vi.fn().mockResolvedValue(recs); const autoRequest = vi.fn().mockResolvedValue(undefined); const app = makeApp( { RECOMMEND_COUNT: 10, LLM_ENABLED: true, RADARR_ENABLED: true }, { runAsync, autoRequest } as unknown as RecommendationPipeline ); const res = await app.inject({ method: 'POST', url: '/recommendations/trigger' }); expect(res.statusCode).toBe(200); expect(autoRequest).toHaveBeenCalledWith(recs); }); it('POST /recommendations/search requires LLM to be enabled and a query body', async () => { const app = makeApp({ LLM_ENABLED: false }, {} as RecommendationPipeline); const res = await app.inject({ method: 'POST', url: '/recommendations/search', payload: { query: 'x' } }); expect(res.statusCode).toBe(400); }); it('POST /recommendations/search runs pipeline.searchAsync and returns results', async () => { const searchAsync = vi.fn().mockResolvedValue([{ title: 'Dune', year: 2021, tmdbId: 1, reason: 'r' }]); const app = makeApp( { LLM_ENABLED: true, RECOMMEND_COUNT: 10 }, { searchAsync } as unknown as RecommendationPipeline ); const res = await app.inject({ method: 'POST', url: '/recommendations/search', payload: { query: 'sci-fi like Blade Runner' }, }); expect(res.statusCode).toBe(200); expect(searchAsync).toHaveBeenCalledWith('sci-fi like Blade Runner', 10); }); });