145 lines
4.7 KiB
TypeScript
145 lines
4.7 KiB
TypeScript
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import os from 'node:os';
|
|
import { MindDB } from '@waggle/core';
|
|
import { buildLocalServer } from '../../src/local/index.js';
|
|
import type { FastifyInstance } from 'fastify';
|
|
|
|
describe('Health Endpoint', () => {
|
|
let server: FastifyInstance;
|
|
let tmpDir: string;
|
|
|
|
beforeAll(async () => {
|
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-health-test-'));
|
|
const personalPath = path.join(tmpDir, 'personal.mind');
|
|
const mind = new MindDB(personalPath);
|
|
mind.close();
|
|
|
|
server = await buildLocalServer({
|
|
dataDir: tmpDir,
|
|
port: 38123,
|
|
instanceId: 'desktop-instance-test',
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await server.close();
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it('returns structured health with llm and database status', async () => {
|
|
const res = await server.inject({ method: 'GET', url: '/health' });
|
|
expect(res.statusCode).toBe(200);
|
|
|
|
const body = JSON.parse(res.payload);
|
|
expect(body.mode).toBe('local');
|
|
expect(body.timestamp).toBeTruthy();
|
|
|
|
// LLM section
|
|
expect(body.llm).toBeDefined();
|
|
expect(body.llm.provider).toMatch(/^(litellm|anthropic-proxy|ollama)$/);
|
|
expect(body.llm.health).toMatch(/^(healthy|degraded|unavailable)$/);
|
|
expect(body.llm.detail).toBeTruthy();
|
|
expect(body.llm.checkedAt).toBeTruthy();
|
|
|
|
// Database section
|
|
expect(body.database).toBeDefined();
|
|
expect(body.database.healthy).toBe(true);
|
|
});
|
|
|
|
it('reports the desktop launch identity used for sidecar ownership checks', async () => {
|
|
vi.stubEnv('WAGGLE_INSTANCE_ID', 'mutated-after-launch');
|
|
try {
|
|
const res = await server.inject({ method: 'GET', url: '/health' });
|
|
expect(res.statusCode).toBe(200);
|
|
expect(JSON.parse(res.payload)).toMatchObject({
|
|
instanceId: 'desktop-instance-test',
|
|
port: 38123,
|
|
});
|
|
} finally {
|
|
vi.unstubAllEnvs();
|
|
}
|
|
});
|
|
|
|
it('overall status reflects LLM health', async () => {
|
|
// Default: llmProvider was set to unavailable (no init via startService)
|
|
const res = await server.inject({ method: 'GET', url: '/health' });
|
|
const body = JSON.parse(res.payload);
|
|
|
|
// Since we didn't go through startService, llmProvider defaults to unavailable
|
|
// Overall status should be 'unavailable' or 'degraded', not 'ok'
|
|
expect(body.status).not.toBe('ok');
|
|
});
|
|
|
|
it('reports healthy when LLM provider is marked healthy', async () => {
|
|
// Simulate a healthy provider
|
|
server.agentState.llmProvider = {
|
|
provider: 'litellm',
|
|
health: 'healthy',
|
|
detail: 'LiteLLM on port 4000',
|
|
checkedAt: new Date().toISOString(),
|
|
};
|
|
|
|
const res = await server.inject({ method: 'GET', url: '/health' });
|
|
const body = JSON.parse(res.payload);
|
|
|
|
expect(body.status).toBe('ok');
|
|
expect(body.llm.provider).toBe('litellm');
|
|
expect(body.llm.health).toBe('healthy');
|
|
expect(body.llm.reachable).toBe(true);
|
|
});
|
|
|
|
it('reports a healthy resolved provider as reachable even when the offline probe is stale', async () => {
|
|
const offlineProbe = server.offlineManager as unknown as { _offline: boolean };
|
|
const previousOffline = offlineProbe._offline;
|
|
offlineProbe._offline = true;
|
|
server.agentState.llmProvider = {
|
|
provider: 'ollama',
|
|
health: 'healthy',
|
|
detail: 'Local Ollama model',
|
|
checkedAt: new Date().toISOString(),
|
|
};
|
|
|
|
try {
|
|
const res = await server.inject({ method: 'GET', url: '/health' });
|
|
const body = JSON.parse(res.payload);
|
|
|
|
expect(body.status).toBe('ok');
|
|
expect(body.llm.health).toBe('healthy');
|
|
expect(body.llm.reachable).toBe(true);
|
|
} finally {
|
|
offlineProbe._offline = previousOffline;
|
|
}
|
|
});
|
|
|
|
it('reports degraded when LLM provider is configured but not verified', async () => {
|
|
server.agentState.llmProvider = {
|
|
provider: 'anthropic-proxy',
|
|
health: 'degraded',
|
|
detail: 'Built-in Anthropic proxy (no API key)',
|
|
checkedAt: new Date().toISOString(),
|
|
};
|
|
|
|
const res = await server.inject({ method: 'GET', url: '/health' });
|
|
const body = JSON.parse(res.payload);
|
|
|
|
expect(body.status).toBe('degraded');
|
|
expect(body.llm.health).toBe('degraded');
|
|
});
|
|
|
|
it('reports unavailable when no LLM path works', async () => {
|
|
server.agentState.llmProvider = {
|
|
provider: 'anthropic-proxy',
|
|
health: 'unavailable',
|
|
detail: 'No working LLM path',
|
|
checkedAt: new Date().toISOString(),
|
|
};
|
|
|
|
const res = await server.inject({ method: 'GET', url: '/health' });
|
|
const body = JSON.parse(res.payload);
|
|
|
|
expect(body.status).toBe('unavailable');
|
|
});
|
|
});
|