Files
PlantAI-rag-new/tests/frontend/unit/b2/sessions.store.test.js
T
jerryandClaude Fable 5 0875bb036c P0/M1: 四部分重构——frontend→ui、backend→sdk 改名 + 引用修复 + 骨架
- git mv 保留历史:ui/(Vue3+Vite)、sdk/(BFF 中间层)
- 路径语义修正:db_path/figures_cache 改相对 sdk/(BFF cwd),
  历史库 sdk/backend/data 迁移至 sdk/data/(会话/图片缓存无损)
- 引用修复 ~15 处:config.py、deploy/*.sh、vitest/playwright 配置、
  单测导入路径、docs、README、gitignore、.env.example
- 骨架:agent/{qa,methodology,legacy-rag,vendor}、knowledge/manifest.yaml、
  tooling/{compile,kb,crypto,assemble,audit,tools,scripts} + README
- 门禁验证:sdk pytest 125 通过、ui vitest 158 通过、
  生产 BFF 脚本重启四上游全绿 + 静态托管 + 会话库无损

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 12:56:47 +08:00

163 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @vitest-environment jsdom
// sessions.store.test.js —— sessions store 单测
// 覆盖:分组(今天/昨天/更早)、搜索过滤、CRUD、标题自动生成(FR-SESSION-001/005/008)。
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
const updateCalls = [];
vi.mock('../../../../ui/src/api/sessions.js', () => ({
listSessions: vi.fn(async () => []),
createSession: vi.fn(async () => ({ id: 'new-1', title: '新会话', status: 'idle', created_at: '2026-08-25T00:00:00Z', updated_at: '2026-08-25T00:00:00Z', message_count: 0, meta: {} })),
getSession: vi.fn(async () => ({})),
updateSession: vi.fn(async (id, body) => { updateCalls.push({ id, body }); return { id, ...body }; }),
deleteSession: vi.fn(async () => {}),
createMessage: vi.fn(),
updateMessage: vi.fn(),
getSessionSources: vi.fn(),
}));
import { useSessionsStore, groupKeyOf, deriveTitle } from '../../../../ui/src/stores/sessions.js';
const iso = (s) => new Date(s).toISOString();
describe('groupKeyOf', () => {
const now = new Date('2026-08-25T15:00:00');
it('今天/昨天/更早分界(本地日界)', () => {
expect(groupKeyOf(iso('2026-08-25T00:01:00'), now)).toBe('today');
expect(groupKeyOf(iso('2026-08-24T23:59:00'), now)).toBe('yesterday');
expect(groupKeyOf(iso('2026-08-24T00:00:00'), now)).toBe('yesterday');
expect(groupKeyOf(iso('2026-08-23T23:59:00'), now)).toBe('older');
expect(groupKeyOf(iso('2026-08-20T09:00:00'), now)).toBe('older');
});
it('无效/缺失时间归更早', () => {
expect(groupKeyOf(null, now)).toBe('older');
expect(groupKeyOf('不是时间', now)).toBe('older');
});
it('未来时间归今天', () => {
expect(groupKeyOf(iso('2026-08-26T00:00:00'), now)).toBe('today');
});
});
describe('deriveTitle(FR-SESSION-008)', () => {
it('前 30 字符 + 省略号', () => {
const q = '水稻SPL基因家族的功能研究有哪些关键进展?这是超过三十个字符的问题内容继续延长';
expect(deriveTitle(q).length).toBeLessThanOrEqual(30);
expect(deriveTitle(q)).toBe(q.slice(0, 30));
});
it('空白回退「新建会话」(labels.T.newTask)', () => {
expect(deriveTitle(' ')).toBe('新建会话');
});
});
describe('sessions store', () => {
beforeEach(() => {
setActivePinia(createPinia());
updateCalls.length = 0;
vi.clearAllMocks();
});
it('分组:按 updated_at 倒序 + 三组', async () => {
const { listSessions } = await import('../../../../ui/src/api/sessions.js');
// 2026-08-26 修复:夹具日期相对「真实今天」动态生成——硬编码日期会随时间
// 腐烂(store 分组用真实 new Date()),此前每天翻车。
const d = (daysAgo) => {
const t = new Date();
t.setDate(t.getDate() - daysAgo);
t.setHours(10, 0, 0, 0);
return t.toISOString();
};
listSessions.mockResolvedValueOnce([
{ id: 'a', title: '今天会话', status: 'done', created_at: 'x', updated_at: d(0), message_count: 2, meta: {} },
{ id: 'b', title: '昨天会话', status: 'done', created_at: 'x', updated_at: d(1), message_count: 4, meta: {} },
{ id: 'c', title: '更早会话', status: 'error', created_at: 'x', updated_at: d(5), message_count: 1, meta: {} },
]);
const store = useSessionsStore();
await store.load();
expect(store.groups.map((g) => g.label)).toEqual(['今天', '昨天', '更早']);
expect(store.groups[0].items.map((s) => s.id)).toEqual(['a']);
expect(store.groups[1].items.map((s) => s.id)).toEqual(['b']);
expect(store.groups[2].items.map((s) => s.id)).toEqual(['c']);
});
it('搜索过滤:title 子串不区分大小写 + 无匹配(FR-SESSION-005)', async () => {
const { listSessions } = await import('../../../../ui/src/api/sessions.js');
listSessions.mockResolvedValueOnce([
{ id: 'a', title: '水稻SPL基因家族', status: 'done', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 2, meta: {} },
{ id: 'b', title: 'NAC 转录因子研究综述', status: 'done', created_at: 'x', updated_at: iso('2026-08-24T10:00:00'), message_count: 2, meta: {} },
]);
const store = useSessionsStore();
await store.load();
store.searchQuery = 'spl';
expect(store.matchedCount).toBe(1);
expect(store.filtered[0].id).toBe('a');
store.searchQuery = '不存在的词';
expect(store.noMatch).toBe(true);
expect(store.matchedCount).toBe(0);
store.searchQuery = '';
expect(store.matchedCount).toBe(2);
expect(store.noMatch).toBe(false);
});
it('新建会话:切为活动会话 + 连点两次产生两个独立会话(FR-SESSION-003)', async () => {
const { createSession } = await import('../../../../ui/src/api/sessions.js');
let n = 0;
createSession.mockImplementation(async () => ({ id: `new-${++n}`, title: '新会话', status: 'idle', created_at: 'x', updated_at: 'x', message_count: 0, meta: {} }));
const store = useSessionsStore();
const s1 = await store.create();
const s2 = await store.create();
expect(s1.id).not.toBe(s2.id);
expect(store.activeId).toBe(s2.id);
expect(store.items).toHaveLength(2);
});
it('删除:删除当前会话 → 切最新一个;列表空 → activeId=null(FR-SESSION-007)', async () => {
const { listSessions, deleteSession } = await import('../../../../ui/src/api/sessions.js');
listSessions.mockResolvedValueOnce([
{ id: 'a', title: 'A', status: 'done', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 2, meta: {} },
{ id: 'b', title: 'B', status: 'done', created_at: 'x', updated_at: iso('2026-08-24T10:00:00'), message_count: 2, meta: {} },
]);
const store = useSessionsStore();
await store.load();
store.activeId = 'a';
await store.remove('a');
expect(store.activeId).toBe('b');
expect(store.items.map((s) => s.id)).toEqual(['b']);
await store.remove('b');
expect(store.activeId).toBeNull();
expect(store.empty).toBe(true);
expect(deleteSession).toHaveBeenCalledTimes(2);
});
it('标题自动生成:首问 PATCH 前 30 字符;title_manual 不再覆盖(FR-SESSION-008)', async () => {
const { listSessions } = await import('../../../../ui/src/api/sessions.js');
const q = '水稻SPL基因家族的功能研究有哪些关键进展?';
listSessions.mockResolvedValueOnce([
{ id: 'a', title: '新会话', status: 'running', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 1, meta: { title_manual: false } },
{ id: 'b', title: '手动改名', status: 'running', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 1, meta: { title_manual: true } },
]);
const store = useSessionsStore();
await store.load();
await store.ensureTitle('a', q);
expect(store.items.find((s) => s.id === 'a').title).toBe(q.slice(0, 30));
expect(updateCalls.some((c) => c.id === 'a' && c.body.title === q.slice(0, 30))).toBe(true);
await store.ensureTitle('b', q);
expect(store.items.find((s) => s.id === 'b').title).toBe('手动改名');
expect(updateCalls.some((c) => c.id === 'b')).toBe(false);
});
it('重命名:PATCH 后本地同步(FR-SESSION-006)', async () => {
const { listSessions } = await import('../../../../ui/src/api/sessions.js');
listSessions.mockResolvedValueOnce([
{ id: 'a', title: '旧标题', status: 'done', created_at: 'x', updated_at: iso('2026-08-25T10:00:00'), message_count: 2, meta: { title_manual: false } },
]);
const store = useSessionsStore();
await store.load();
await store.rename('a', '新标题');
expect(store.items[0].title).toBe('新标题');
expect(store.items[0].meta.title_manual).toBe(true);
});
});