145 lines
4.1 KiB
JavaScript
145 lines
4.1 KiB
JavaScript
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');
|
|
});
|
|
});
|