Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
对话链路: 4 缝测试 (Tauri→Kernel / Kernel→LLM / LLM→UI / 流式生命周期) Hands链路: 3 缝测试 (工具路由 / 执行回调 / 通用工具) 记忆链路: 3 缝测试 (FTS5存储 / 模式检索 / 去重) 冒烟测试: 3 Rust + 8 TypeScript 全量 PASS - Kernel::boot_with_driver() 测试辅助方法 - 全量 cargo test 0 回归 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
144 lines
4.6 KiB
TypeScript
144 lines
4.6 KiB
TypeScript
/**
|
|
* Chat seam tests — verify request/response type contracts
|
|
*
|
|
* Tests that the TypeScript types match the Rust serde-serialized format.
|
|
* These are pure type contract tests — no Tauri dependency needed.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Rust side: StreamChatRequest (camelCase via serde rename_all)
|
|
// ---------------------------------------------------------------------------
|
|
interface StreamChatRequest {
|
|
agentId: string;
|
|
sessionId: string;
|
|
message: string;
|
|
thinkingEnabled?: boolean;
|
|
reasoningEffort?: string;
|
|
planMode?: boolean;
|
|
subagentEnabled?: boolean;
|
|
model?: string;
|
|
}
|
|
|
|
interface ChatRequest {
|
|
agentId: string;
|
|
message: string;
|
|
thinkingEnabled?: boolean;
|
|
reasoningEffort?: string;
|
|
planMode?: boolean;
|
|
subagentEnabled?: boolean;
|
|
model?: string;
|
|
}
|
|
|
|
interface ChatResponse {
|
|
content: string;
|
|
inputTokens: number;
|
|
outputTokens: number;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Rust side: StreamChatEvent (tagged union, tag = "type")
|
|
// ---------------------------------------------------------------------------
|
|
type StreamChatEvent =
|
|
| { type: 'delta'; delta: string }
|
|
| { type: 'thinkingDelta'; delta: string }
|
|
| { type: 'toolStart'; name: string; input: unknown }
|
|
| { type: 'toolEnd'; name: string; output: unknown }
|
|
| { type: 'subtaskStatus'; taskId: string; description: string; status: string; detail?: string }
|
|
| { type: 'iterationStart'; iteration: number; maxIterations: number }
|
|
| { type: 'handStart'; name: string; params: unknown }
|
|
| { type: 'handEnd'; name: string; result: unknown }
|
|
| { type: 'complete'; inputTokens: number; outputTokens: number }
|
|
| { type: 'error'; message: string };
|
|
|
|
describe('Chat Seam: request format contract', () => {
|
|
it('StreamChatRequest has required camelCase fields', () => {
|
|
const req: StreamChatRequest = {
|
|
agentId: 'test-agent',
|
|
sessionId: 'session-123',
|
|
message: 'Hello',
|
|
};
|
|
expect(req.agentId).toBe('test-agent');
|
|
expect(req.sessionId).toBe('session-123');
|
|
expect(req.message).toBe('Hello');
|
|
});
|
|
|
|
it('StreamChatRequest optional fields are camelCase', () => {
|
|
const req: StreamChatRequest = {
|
|
agentId: 'a',
|
|
sessionId: 's',
|
|
message: 'm',
|
|
thinkingEnabled: true,
|
|
reasoningEffort: 'high',
|
|
planMode: false,
|
|
subagentEnabled: true,
|
|
model: 'gpt-4o',
|
|
};
|
|
expect(req.thinkingEnabled).toBe(true);
|
|
expect(req.reasoningEffort).toBe('high');
|
|
expect(req.planMode).toBe(false);
|
|
expect(req.subagentEnabled).toBe(true);
|
|
expect(req.model).toBe('gpt-4o');
|
|
});
|
|
|
|
it('ChatRequest format for non-streaming', () => {
|
|
const req: ChatRequest = {
|
|
agentId: 'test-agent',
|
|
message: 'Hello',
|
|
model: 'gpt-4o',
|
|
};
|
|
expect(req.agentId).toBe('test-agent');
|
|
expect(req.message).toBe('Hello');
|
|
});
|
|
|
|
it('ChatResponse has expected fields', () => {
|
|
const resp: ChatResponse = {
|
|
content: 'Hello back!',
|
|
inputTokens: 10,
|
|
outputTokens: 5,
|
|
};
|
|
expect(resp.content).toBe('Hello back!');
|
|
expect(resp.inputTokens).toBe(10);
|
|
expect(resp.outputTokens).toBe(5);
|
|
});
|
|
});
|
|
|
|
describe('Chat Seam: StreamChatEvent format contract', () => {
|
|
it('delta event matches Rust StreamChatEvent::Delta', () => {
|
|
const event: StreamChatEvent = { type: 'delta', delta: 'Hello' };
|
|
expect(event.type).toBe('delta');
|
|
if (event.type === 'delta') {
|
|
expect(typeof event.delta).toBe('string');
|
|
}
|
|
});
|
|
|
|
it('complete event has token counts', () => {
|
|
const event: StreamChatEvent = { type: 'complete', inputTokens: 10, outputTokens: 5 };
|
|
if (event.type === 'complete') {
|
|
expect(event.inputTokens).toBeGreaterThanOrEqual(0);
|
|
expect(event.outputTokens).toBeGreaterThanOrEqual(0);
|
|
}
|
|
});
|
|
|
|
it('handStart/handEnd events have correct structure', () => {
|
|
const start: StreamChatEvent = { type: 'handStart', name: 'hand_quiz', params: { topic: 'math' } };
|
|
const end: StreamChatEvent = { type: 'handEnd', name: 'hand_quiz', result: { questions: [] } };
|
|
|
|
if (start.type === 'handStart') {
|
|
expect(start.name).toMatch(/^hand_/);
|
|
expect(start.params).toBeDefined();
|
|
}
|
|
if (end.type === 'handEnd') {
|
|
expect(end.name).toMatch(/^hand_/);
|
|
expect(end.result).toBeDefined();
|
|
}
|
|
});
|
|
|
|
it('error event has message field', () => {
|
|
const event: StreamChatEvent = { type: 'error', message: '已取消' };
|
|
if (event.type === 'error') {
|
|
expect(event.message).toBeTruthy();
|
|
}
|
|
});
|
|
});
|