251 lines
7.5 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// Mock dependencies first
vi.mock('./log.js', () => ({
default: {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn()
}
}));
vi.mock('./sync_mutex.js', () => ({
default: {
doExclusively: vi.fn().mockImplementation((fn) => fn())
}
}));
vi.mock('./sql.js', () => ({
getManyRows: vi.fn(),
getValue: vi.fn(),
getRow: vi.fn()
}));
vi.mock('../becca/becca.js', () => ({
default: {
getAttribute: vi.fn(),
getBranch: vi.fn(),
getNote: vi.fn(),
getOption: vi.fn()
}
}));
vi.mock('./protected_session.js', () => ({
default: {
decryptString: vi.fn((str) => str)
}
}));
vi.mock('./cls.js', () => ({
getAndClearEntityChangeIds: vi.fn().mockReturnValue([])
}));
// Mock the entire ws module instead of trying to set up the server
vi.mock('ws', () => ({
Server: vi.fn(),
WebSocket: {
OPEN: 1,
CLOSED: 3,
CONNECTING: 0,
CLOSING: 2
}
}));
describe('WebSocket Service', () => {
let wsService: any;
let log: any;
beforeEach(async () => {
vi.clearAllMocks();
// Get mocked log
log = (await import('./log.js')).default;
// Import service after mocks are set up
wsService = (await import('./ws.js')).default;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('Message Broadcasting', () => {
it('should handle sendMessageToAllClients method exists', () => {
expect(wsService.sendMessageToAllClients).toBeDefined();
expect(typeof wsService.sendMessageToAllClients).toBe('function');
});
it('should handle LLM stream messages', () => {
const message = {
type: 'llm-stream' as const,
chatNoteId: 'test-chat-123',
content: 'Hello world',
done: false
};
// Since WebSocket server is not initialized in test environment,
// this should not throw an error
expect(() => {
wsService.sendMessageToAllClients(message);
}).not.toThrow();
});
it('should handle regular messages', () => {
const message = {
type: 'frontend-update' as const,
data: { test: 'data' }
};
expect(() => {
wsService.sendMessageToAllClients(message);
}).not.toThrow();
});
it('should handle sync-failed messages', () => {
const message = {
type: 'sync-failed' as const,
lastSyncedPush: 123
};
expect(() => {
wsService.sendMessageToAllClients(message);
}).not.toThrow();
});
it('should handle api-log-messages', () => {
const message = {
type: 'api-log-messages' as const,
messages: ['test message']
};
expect(() => {
wsService.sendMessageToAllClients(message);
}).not.toThrow();
});
});
describe('Service Methods', () => {
it('should have all required methods', () => {
expect(wsService.init).toBeDefined();
expect(wsService.sendMessageToAllClients).toBeDefined();
expect(wsService.syncPushInProgress).toBeDefined();
expect(wsService.syncPullInProgress).toBeDefined();
expect(wsService.syncFinished).toBeDefined();
expect(wsService.syncFailed).toBeDefined();
expect(wsService.sendTransactionEntityChangesToAllClients).toBeDefined();
expect(wsService.setLastSyncedPush).toBeDefined();
expect(wsService.reloadFrontend).toBeDefined();
});
it('should handle sync methods without errors', () => {
expect(() => wsService.syncPushInProgress()).not.toThrow();
expect(() => wsService.syncPullInProgress()).not.toThrow();
expect(() => wsService.syncFinished()).not.toThrow();
expect(() => wsService.syncFailed()).not.toThrow();
});
it('should handle reload frontend', () => {
expect(() => wsService.reloadFrontend('test reason')).not.toThrow();
});
it('should handle transaction entity changes', () => {
expect(() => wsService.sendTransactionEntityChangesToAllClients()).not.toThrow();
});
it('should handle setLastSyncedPush', () => {
expect(() => wsService.setLastSyncedPush(123)).not.toThrow();
});
});
describe('LLM Stream Message Handling', () => {
it('should handle streaming with content', () => {
const message = {
type: 'llm-stream' as const,
chatNoteId: 'chat-456',
content: 'Streaming content here',
done: false
};
expect(() => wsService.sendMessageToAllClients(message)).not.toThrow();
});
it('should handle streaming with thinking', () => {
const message = {
type: 'llm-stream' as const,
chatNoteId: 'chat-789',
thinking: 'AI is thinking...',
done: false
};
expect(() => wsService.sendMessageToAllClients(message)).not.toThrow();
});
it('should handle streaming with tool execution', () => {
const message = {
type: 'llm-stream' as const,
chatNoteId: 'chat-012',
toolExecution: {
action: 'executing',
tool: 'test-tool',
toolCallId: 'tc-123'
},
done: false
};
expect(() => wsService.sendMessageToAllClients(message)).not.toThrow();
});
it('should handle streaming completion', () => {
const message = {
type: 'llm-stream' as const,
chatNoteId: 'chat-345',
done: true
};
expect(() => wsService.sendMessageToAllClients(message)).not.toThrow();
});
it('should handle streaming with error', () => {
const message = {
type: 'llm-stream' as const,
chatNoteId: 'chat-678',
error: 'Something went wrong',
done: true
};
expect(() => wsService.sendMessageToAllClients(message)).not.toThrow();
});
});
describe('Non-LLM Message Types', () => {
it('should handle frontend-update messages', () => {
const message = {
type: 'frontend-update' as const,
data: {
lastSyncedPush: 100,
entityChanges: []
}
};
expect(() => wsService.sendMessageToAllClients(message)).not.toThrow();
});
it('should handle ping messages', () => {
const message = {
type: 'ping' as const
};
expect(() => wsService.sendMessageToAllClients(message)).not.toThrow();
});
it('should handle task progress messages', () => {
const message = {
type: 'task-progress' as const,
taskId: 'task-123',
progressCount: 50
};
expect(() => wsService.sendMessageToAllClients(message)).not.toThrow();
});
});
});