- 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>
85 lines
3.3 KiB
JavaScript
85 lines
3.3 KiB
JavaScript
// @vitest-environment jsdom
|
||
// chatpanel.health.test.js —— BUG-R1-04 回归:QA 降级时作曲器禁用 + 降级提示(FR-HEALTH-002)
|
||
// 契约:health store(B1)暴露 qaOk getter;ChatPanel 读 health.data != null && !qaOk → 禁用。
|
||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||
import { mount, flushPromises } from '@vue/test-utils';
|
||
import { setActivePinia, createPinia } from 'pinia';
|
||
|
||
// mock health store(B1 归属,不实改其文件):qaOk 由 data.upstreams.qa.ok 派生
|
||
vi.mock('../../../../ui/src/stores/health.js', () => ({
|
||
useHealthStore: vi.fn(() => healthState),
|
||
}));
|
||
|
||
// mock bench store(B3):ChatPanel 对 bench 的引用须容错(本测试不关心工作台)
|
||
vi.mock('../../../../ui/src/stores/bench.js', () => ({
|
||
useBenchStore: vi.fn(() => ({ setActiveTab: vi.fn(), setSourceScope: vi.fn() })),
|
||
}));
|
||
|
||
// sessions/chat store 用真实现(chat.store 内部 mock api 层,见下)
|
||
vi.mock('../../../../ui/src/api/sessions.js', () => ({
|
||
listSessions: vi.fn(async () => []),
|
||
createSession: vi.fn(async () => ({ id: 's1', title: '新会话', status: 'idle', created_at: 'x', updated_at: 'x', message_count: 0, meta: {} })),
|
||
getSession: vi.fn(async () => ({ id: 's1', title: 't', status: 'done', meta: {}, messages: [] })),
|
||
updateSession: vi.fn(async (id, b) => ({ id, ...b })),
|
||
deleteSession: vi.fn(async () => {}),
|
||
createMessage: vi.fn(),
|
||
updateMessage: vi.fn(),
|
||
getSessionSources: vi.fn(),
|
||
}));
|
||
vi.mock('../../../../ui/src/api/qa.js', () => ({
|
||
chatStream: vi.fn(),
|
||
chat: vi.fn(),
|
||
search: vi.fn(),
|
||
info: vi.fn(),
|
||
}));
|
||
|
||
import ChatPanel from '../../../../ui/src/components/chat/ChatPanel.vue';
|
||
|
||
/** health 状态(响应式替换:mock 工厂每次返回同一对象) */
|
||
let healthState;
|
||
|
||
function makeHealthState(data) {
|
||
return {
|
||
data,
|
||
qaOk: !!(data && data.upstreams && data.upstreams.qa && data.upstreams.qa.ok === true),
|
||
};
|
||
}
|
||
|
||
async function mountPanel() {
|
||
const w = mount(ChatPanel, { global: { stubs: { teleport: true } } });
|
||
await flushPromises();
|
||
return w;
|
||
}
|
||
|
||
describe('ChatPanel × health(BUG-R1-04)', () => {
|
||
beforeEach(() => {
|
||
setActivePinia(createPinia());
|
||
healthState = makeHealthState(null); // 未知态:首轮探测未返回
|
||
});
|
||
|
||
it('未知态(data=null):作曲器不禁用(避免首屏闪烁)', async () => {
|
||
const w = await mountPanel();
|
||
const ta = w.find('.composer textarea');
|
||
expect(ta.attributes('disabled')).toBeUndefined();
|
||
expect(w.find('.hint').text()).toContain('Enter');
|
||
});
|
||
|
||
it('qaOk=false:作曲器禁用 + 降级提示(labels.qaDown 文案)', async () => {
|
||
healthState = makeHealthState({
|
||
status: 'degraded',
|
||
upstreams: { qa: { ok: false }, rag: { ok: true }, ragAlt: { ok: true }, neo4j: { ok: true } },
|
||
});
|
||
const w = await mountPanel();
|
||
const ta = w.find('.composer textarea');
|
||
expect(ta.attributes('disabled')).toBeDefined();
|
||
expect(w.find('.hint').text()).toBe('知识问答服务当前不可用,请稍后重试');
|
||
});
|
||
|
||
it('qaOk=true:作曲器可用,提示为快捷键说明', async () => {
|
||
healthState = makeHealthState({ status: 'ok', upstreams: { qa: { ok: true } } });
|
||
const w = await mountPanel();
|
||
expect(w.find('.composer textarea').attributes('disabled')).toBeUndefined();
|
||
expect(w.find('.hint').text()).toContain('Enter');
|
||
});
|
||
});
|