98 lines
3.4 KiB
TypeScript
Executable File
98 lines
3.4 KiB
TypeScript
Executable File
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { buildServer } from '../src/server';
|
|
import { FastifyInstance } from 'fastify';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { config } from '../src/config';
|
|
|
|
describe('Proxy Integration Test', () => {
|
|
let server: FastifyInstance;
|
|
|
|
beforeEach(() => {
|
|
config.proxyMode = 'ollama';
|
|
server = buildServer();
|
|
// In vitest we can mock the global fetch
|
|
global.fetch = vi.fn();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await server.close();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('proxies request and rewrites XML response to tool_calls', async () => {
|
|
// Read fixtures
|
|
const requestFixturePath = path.join(__dirname, 'fixtures', 'openclaw-like-request.json');
|
|
const responseFixturePath = path.join(__dirname, 'fixtures', 'ollama-xml-response.json');
|
|
|
|
const requestJson = JSON.parse(fs.readFileSync(requestFixturePath, 'utf8'));
|
|
const responseJson = JSON.parse(fs.readFileSync(responseFixturePath, 'utf8'));
|
|
|
|
// Mock fetch to return the ollama-xml-response.json
|
|
(global.fetch as any).mockResolvedValue({
|
|
ok: true,
|
|
text: async () => JSON.stringify(responseJson),
|
|
json: async () => responseJson
|
|
});
|
|
|
|
const response = await server.inject({
|
|
method: 'POST',
|
|
url: '/api/chat',
|
|
payload: requestJson
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
const body = JSON.parse(response.payload);
|
|
|
|
// Verify proxy forwarded it
|
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
const fetchArgs = (global.fetch as any).mock.calls[0];
|
|
expect(fetchArgs[0]).toContain('/api/chat');
|
|
|
|
const upstreamBody = JSON.parse(fetchArgs[1].body);
|
|
expect(upstreamBody.model).toBe('hotwa/qwen35-9b-agent:latest');
|
|
|
|
// Verify response was rewritten
|
|
expect(body.message.content).toBe("");
|
|
expect(body.message.tool_calls).toBeDefined();
|
|
expect(body.message.tool_calls).toHaveLength(1);
|
|
expect(body.message.tool_calls[0].function.name).toBe('read');
|
|
expect(body.message.tool_calls[0].function.arguments).toEqual({
|
|
path: "/tmp/test.txt"
|
|
});
|
|
});
|
|
|
|
it('spoofs streaming responses for stream=true requests', async () => {
|
|
const requestFixturePath = path.join(__dirname, 'fixtures', 'openclaw-like-request.json');
|
|
const responseFixturePath = path.join(__dirname, 'fixtures', 'ollama-xml-response.json');
|
|
const requestJson = JSON.parse(fs.readFileSync(requestFixturePath, 'utf8'));
|
|
const responseJson = JSON.parse(fs.readFileSync(responseFixturePath, 'utf8'));
|
|
requestJson.stream = true;
|
|
|
|
(global.fetch as any).mockResolvedValue({
|
|
ok: true,
|
|
text: async () => JSON.stringify(responseJson),
|
|
json: async () => responseJson
|
|
});
|
|
|
|
const response = await server.inject({
|
|
method: 'POST',
|
|
url: '/api/chat',
|
|
payload: requestJson
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
expect(response.headers['content-type']).toContain('text/event-stream');
|
|
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
|
|
const fetchArgs = (global.fetch as any).mock.calls[0];
|
|
const upstreamBody = JSON.parse(fetchArgs[1].body);
|
|
expect(upstreamBody.stream).toBe(false);
|
|
|
|
expect(response.payload).toContain('"role":"assistant"');
|
|
expect(response.payload).toContain('"tool_calls"');
|
|
expect(response.payload).toContain('"finish_reason":"tool_calls"');
|
|
expect(response.payload).toContain('data: [DONE]');
|
|
});
|
|
});
|