import { describe, it, expect, vi, beforeEach } from 'vitest'; const express = require('express'); // Mock dependencies of backend/routes/aiInsights.js before loading it vi.mock('../models/AiInsight.js', () => { return { getByCycleAndKandang: vi.fn(), upsert: vi.fn(), cleanupExpired: vi.fn(), }; }); vi.mock('../services/dashboardInsightService.js', () => { return { generateDashboardInsight: vi.fn(), INSIGHT_VERSION: 'v1', }; }); // Mock database module so it doesn't attempt real connection during test vi.mock('../database/db', () => ({})); // Import express router const router = require('./aiInsights.js'); describe('AI Insights Proxy Route', () => { let app; beforeEach(() => { vi.resetAllMocks(); app = express(); app.use(express.json()); app.use('/api/ai-insights', router); }); it('should accept contextData and send it to LM Studio proxy with CP707 chunks', async () => { // 1. Mock fetch globally const mockResponseText = JSON.stringify({ choices: [ { message: { content: JSON.stringify({ headline: 'Analisis Test FCR', status: 'ok', summary: ['FCR normal'], risks: [], actions: ['Pertahankan'], }), }, finish_reason: 'stop', }, ], }); const mockFetch = vi.fn().mockImplementation((url, options) => { // Mock /api/v1/models (checking loaded models) if (url.includes('/api/v1/models')) { return Promise.resolve({ ok: true, json: () => Promise.resolve({ data: [{ id: 'mock-model' }] }), }); } // Mock RAG Service on port 5002 if (url.includes(':5002/query')) { return Promise.resolve({ ok: true, json: () => Promise.resolve({ chunks: ['Ini adalah chunk standar FCR broiler CP 707 dari buku.'], sources: ['page 1'], }), }); } // Mock LM Studio chat completions if (url.includes('/v1/chat/completions')) { const body = JSON.parse(options.body); // Assert that body.messages contains system prompt with RAG chunks expect(body.messages[0].role).toBe('system'); expect(body.messages[0].content).toContain('REFERENSI BUKU: Manajemen Broiler CP 707'); expect(body.messages[0].content).toContain('Ini adalah chunk standar FCR broiler CP 707 dari buku.'); // Assert that body.messages contains user message with contextData expect(body.messages[1].role).toBe('user'); expect(body.messages[1].content).toContain('"FCR_aktual": 1.25'); return Promise.resolve({ ok: true, json: () => Promise.resolve(JSON.parse(mockResponseText)), }); } return Promise.reject(new Error(`Unhandled fetch url: ${url}`)); }); global.fetch = mockFetch; // Simulate calling the endpoint using standard express request-response handling const req = { method: 'POST', url: '/api/ai-insights/proxy', headers: { 'content-type': 'application/json' }, body: { topic: 'fcr', context_query: 'FCR standar broiler CP707', input: 'Data: FCR_aktual 1.25', contextData: { FCR_aktual: 1.25 }, system_prompt: 'Anda adalah analis CP707', }, }; // We can run a small integration check by calling the router directly let resObj; await new Promise((resolve) => { const res = { statusCode: 200, headers: {}, setHeader(name, val) { this.headers[name.toLowerCase()] = val; }, status(code) { this.statusCode = code; return this; }, json(data) { this.body = data; resObj = this; resolve(); }, }; app(req, res, (err) => { if (err) console.error('Router error:', err); resObj = res; resolve(); }); }); expect(resObj.statusCode).toBe(200); expect(resObj.body.success).toBe(true); expect(JSON.parse(resObj.body.response).headline).toBe('Analisis Test FCR'); }); });