Files
dashboard/backend/routes/aiInsights.test.js
T
Alberto-Audrix 32a36cceff
CI / lint-and-test (push) Canceled after 0s
first commit
2026-07-28 08:55:05 +07:00

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');
});
});