BTerminal/v2/src/lib/adapters/bttask-bridge.test.ts
Hibryda e41d237745 test(btmsg): add regression tests for named column access and camelCase serialization
Covers the CRITICAL status vs system_prompt bug (positional index 7),
JOIN alias disambiguation, serde camelCase serialization, TypeScript
bridge IPC commands, and plantuml hex encoding algorithm.
49 new tests: 8 btmsg.rs + 7 bttask.rs + 8 sidecar + 17 btmsg-bridge.ts + 10 bttask-bridge.ts + 7 plantuml-encode.ts
2026-03-11 22:19:03 +01:00

150 lines
4.9 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
const { mockInvoke } = vi.hoisted(() => ({
mockInvoke: vi.fn(),
}));
vi.mock('@tauri-apps/api/core', () => ({
invoke: mockInvoke,
}));
import {
listTasks,
getTaskComments,
updateTaskStatus,
addTaskComment,
createTask,
deleteTask,
type Task,
type TaskComment,
} from './bttask-bridge';
beforeEach(() => {
vi.clearAllMocks();
});
describe('bttask-bridge', () => {
// ---- REGRESSION: camelCase field names ----
describe('Task camelCase fields', () => {
it('receives camelCase fields from Rust backend', async () => {
const task: Task = {
id: 't1',
title: 'Fix bug',
description: 'Critical fix',
status: 'progress',
priority: 'high',
assignedTo: 'a1', // was: assigned_to
createdBy: 'admin', // was: created_by
groupId: 'g1', // was: group_id
parentTaskId: null, // was: parent_task_id
sortOrder: 1, // was: sort_order
createdAt: '2026-01-01', // was: created_at
updatedAt: '2026-01-01', // was: updated_at
};
mockInvoke.mockResolvedValue([task]);
const result = await listTasks('g1');
expect(result).toHaveLength(1);
expect(result[0].assignedTo).toBe('a1');
expect(result[0].createdBy).toBe('admin');
expect(result[0].groupId).toBe('g1');
expect(result[0].parentTaskId).toBeNull();
expect(result[0].sortOrder).toBe(1);
// Verify no snake_case leaks
expect((result[0] as Record<string, unknown>)['assigned_to']).toBeUndefined();
expect((result[0] as Record<string, unknown>)['created_by']).toBeUndefined();
expect((result[0] as Record<string, unknown>)['group_id']).toBeUndefined();
});
});
describe('TaskComment camelCase fields', () => {
it('receives camelCase fields from Rust backend', async () => {
const comment: TaskComment = {
id: 'c1',
taskId: 't1', // was: task_id
agentId: 'a1', // was: agent_id
content: 'Working on it',
createdAt: '2026-01-01',
};
mockInvoke.mockResolvedValue([comment]);
const result = await getTaskComments('t1');
expect(result[0].taskId).toBe('t1');
expect(result[0].agentId).toBe('a1');
expect((result[0] as Record<string, unknown>)['task_id']).toBeUndefined();
expect((result[0] as Record<string, unknown>)['agent_id']).toBeUndefined();
});
});
// ---- IPC command name tests ----
describe('IPC commands', () => {
it('listTasks invokes bttask_list', async () => {
mockInvoke.mockResolvedValue([]);
await listTasks('g1');
expect(mockInvoke).toHaveBeenCalledWith('bttask_list', { groupId: 'g1' });
});
it('getTaskComments invokes bttask_comments', async () => {
mockInvoke.mockResolvedValue([]);
await getTaskComments('t1');
expect(mockInvoke).toHaveBeenCalledWith('bttask_comments', { taskId: 't1' });
});
it('updateTaskStatus invokes bttask_update_status', async () => {
mockInvoke.mockResolvedValue(undefined);
await updateTaskStatus('t1', 'done');
expect(mockInvoke).toHaveBeenCalledWith('bttask_update_status', { taskId: 't1', status: 'done' });
});
it('addTaskComment invokes bttask_add_comment', async () => {
mockInvoke.mockResolvedValue('c-id');
const result = await addTaskComment('t1', 'a1', 'Done!');
expect(result).toBe('c-id');
expect(mockInvoke).toHaveBeenCalledWith('bttask_add_comment', { taskId: 't1', agentId: 'a1', content: 'Done!' });
});
it('createTask invokes bttask_create with all fields', async () => {
mockInvoke.mockResolvedValue('t-id');
const result = await createTask('Fix bug', 'desc', 'high', 'g1', 'admin', 'a1');
expect(result).toBe('t-id');
expect(mockInvoke).toHaveBeenCalledWith('bttask_create', {
title: 'Fix bug',
description: 'desc',
priority: 'high',
groupId: 'g1',
createdBy: 'admin',
assignedTo: 'a1',
});
});
it('createTask invokes bttask_create without assignedTo', async () => {
mockInvoke.mockResolvedValue('t-id');
await createTask('Add tests', '', 'medium', 'g1', 'a1');
expect(mockInvoke).toHaveBeenCalledWith('bttask_create', {
title: 'Add tests',
description: '',
priority: 'medium',
groupId: 'g1',
createdBy: 'a1',
assignedTo: undefined,
});
});
it('deleteTask invokes bttask_delete', async () => {
mockInvoke.mockResolvedValue(undefined);
await deleteTask('t1');
expect(mockInvoke).toHaveBeenCalledWith('bttask_delete', { taskId: 't1' });
});
});
describe('error propagation', () => {
it('propagates invoke errors', async () => {
mockInvoke.mockRejectedValue(new Error('btmsg database not found'));
await expect(listTasks('g1')).rejects.toThrow('btmsg database not found');
});
});
});