Files
clawX/tests/unit/session-label-fetch.test.ts
2026-05-13 15:29:44 +08:00

168 lines
5.1 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from 'vitest';
const invokeIpcMock = vi.fn();
vi.mock('@/lib/api-client', () => ({
invokeIpc: (...args: unknown[]) => invokeIpcMock(...args),
}));
vi.mock('@/stores/chat/helpers', () => ({
getCanonicalPrefixFromSessions: () => 'agent:main',
getMessageText: (content: unknown) => typeof content === 'string' ? content : '',
toMs: (v: unknown) => typeof v === 'number' ? v : 0,
}));
describe('session label fetch concurrency', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('skips sessions with existing frontend or backend labels', async () => {
invokeIpcMock.mockImplementation(async (_channel: string, method: string) => {
if (method === 'sessions.list') {
return {
success: true,
result: {
sessions: [
{ key: 'agent:main:session-a', label: 'Backend label', updatedAt: 1000 },
{ key: 'agent:main:session-b', displayName: 'Session B', updatedAt: 1001 },
{ key: 'agent:main:main', displayName: 'Main', updatedAt: 1002 },
],
},
};
}
if (method === 'chat.history') {
return {
success: true,
result: {
messages: [{ role: 'user', content: 'hello', timestamp: Date.now() }],
},
};
}
return { success: false };
});
vi.resetModules();
const { createSessionActions } = await import('@/stores/chat/session-actions');
const state = {
currentSessionKey: 'agent:main:main',
messages: [],
sessions: [],
sessionLabels: { 'agent:main:session-b': 'Already labeled' },
sessionLastActivity: {},
};
const set = vi.fn();
const get = vi.fn().mockReturnValue({
...state,
loadHistory: vi.fn(),
});
const actions = createSessionActions(set as never, get as never);
await actions.loadSessions();
await new Promise((resolve) => setTimeout(resolve, 10));
const chatHistoryCalls = invokeIpcMock.mock.calls.filter(([, method]) => method === 'chat.history');
expect(chatHistoryCalls).toHaveLength(0);
});
it('does not re-request unchanged sessions after an empty hydration result', async () => {
invokeIpcMock.mockImplementation(async (_channel: string, method: string) => {
if (method === 'sessions.list') {
return {
success: true,
result: {
sessions: [
{ key: 'agent:main:session-a', displayName: 'Session A', updatedAt: 1000 },
{ key: 'agent:main:main', displayName: 'Main', updatedAt: 1001 },
],
},
};
}
if (method === 'chat.history') {
return {
success: true,
result: {
messages: [],
},
};
}
return { success: false };
});
vi.resetModules();
const { createSessionActions } = await import('@/stores/chat/session-actions');
const state = {
currentSessionKey: 'agent:main:main',
messages: [],
sessions: [],
sessionLabels: {},
sessionLastActivity: {},
};
const set = vi.fn();
const get = vi.fn().mockReturnValue({
...state,
loadHistory: vi.fn(),
});
const actions = createSessionActions(set as never, get as never);
await actions.loadSessions();
await new Promise((resolve) => setTimeout(resolve, 10));
await actions.loadSessions();
await new Promise((resolve) => setTimeout(resolve, 10));
const chatHistoryCalls = invokeIpcMock.mock.calls.filter(([, method]) => method === 'chat.history');
expect(chatHistoryCalls).toHaveLength(1);
});
it('re-requests a session when updatedAt changes after an empty result', async () => {
let updatedAt = 1000;
invokeIpcMock.mockImplementation(async (_channel: string, method: string) => {
if (method === 'sessions.list') {
return {
success: true,
result: {
sessions: [
{ key: 'agent:main:session-a', displayName: 'Session A', updatedAt },
{ key: 'agent:main:main', displayName: 'Main', updatedAt: 1001 },
],
},
};
}
if (method === 'chat.history') {
return {
success: true,
result: {
messages: [],
},
};
}
return { success: false };
});
vi.resetModules();
const { createSessionActions } = await import('@/stores/chat/session-actions');
const state = {
currentSessionKey: 'agent:main:main',
messages: [],
sessions: [],
sessionLabels: {},
sessionLastActivity: {},
};
const set = vi.fn();
const get = vi.fn().mockReturnValue({
...state,
loadHistory: vi.fn(),
});
const actions = createSessionActions(set as never, get as never);
await actions.loadSessions();
await new Promise((resolve) => setTimeout(resolve, 10));
updatedAt = 2000;
await actions.loadSessions();
await new Promise((resolve) => setTimeout(resolve, 10));
const chatHistoryCalls = invokeIpcMock.mock.calls.filter(([, method]) => method === 'chat.history');
expect(chatHistoryCalls).toHaveLength(2);
});
});