- BFF 薄适配层(FastAPI,8090):QA 代理/SSE 中继/Neo4j 直连/抽取代理/会话持久化 - Vue3 前端:五相位检索流水线、文献来源与证据链、知识图谱、提取任务四 tab - 四档响应式(桌面/笔记本/平板/手机)、深浅主题 - 81 个 FR 全部实现,测试追溯矩阵 81/81,后端 121 测/前端 156 测 - 真实凭据已脱敏,运行时配置在 backend/.env(不入 git) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
109 lines
4.4 KiB
JavaScript
109 lines
4.4 KiB
JavaScript
// extraction.pane.test.js —— B3 单测:ExtractionPane(progress_pct 派生展示、failed 错误态、轮询条件)
|
||
import { describe, it, expect, vi } from 'vitest';
|
||
import { mount } from '@vue/test-utils';
|
||
import { createPinia, setActivePinia } from 'pinia';
|
||
import { deriveProgressPct, shouldPoll } from '../../../../frontend/src/components/bench/kgPaneCompat.js';
|
||
import { useBenchStore } from '../../../../frontend/src/stores/bench.js';
|
||
import ProgressItem from '../../../../frontend/src/components/bench/ProgressItem.vue';
|
||
|
||
vi.mock('../../../../frontend/src/api/extraction.js', () => ({
|
||
listJobs: vi.fn().mockResolvedValue({ jobs: [], count: 0 }),
|
||
getJob: vi.fn(),
|
||
uploadPaper: vi.fn(),
|
||
getRunLog: vi.fn().mockResolvedValue({ text: '' }),
|
||
getProcessingStatus: vi.fn(),
|
||
getSummary: vi.fn(),
|
||
}));
|
||
|
||
describe('ExtractionPane progress_pct 派生(FR-EXTRACT-001)', () => {
|
||
it('按 stage 派生:queued=5 / mineru=35 / llm=75 / completed=100 / failed=null', () => {
|
||
expect(deriveProgressPct({ stage: 'queued' })).toBe(5);
|
||
expect(deriveProgressPct({ stage: 'mineru' })).toBe(35);
|
||
expect(deriveProgressPct({ stage: 'llm' })).toBe(75);
|
||
expect(deriveProgressPct({ stage: 'completed' })).toBe(100);
|
||
expect(deriveProgressPct({ stage: 'failed' })).toBeNull();
|
||
expect(deriveProgressPct({ stage: 'unknown' })).toBeNull();
|
||
expect(deriveProgressPct(null)).toBeNull();
|
||
});
|
||
|
||
it('BFF 已给 progress_pct 时优先使用', () => {
|
||
expect(deriveProgressPct({ stage: 'llm', progress_pct: 77 })).toBe(77);
|
||
});
|
||
|
||
it('ProgressItem:failed 显示错误态且不渲染进度条', () => {
|
||
const wrapper = mount(ProgressItem, {
|
||
props: {
|
||
job: {
|
||
job_id: 'j3',
|
||
title: 'Rice_NAC_2020.pdf',
|
||
status: 'failed',
|
||
stage: 'failed',
|
||
progress_pct: null,
|
||
message: 'MinerU 解析失败:/home/xia-lab/miniconda3/envs/minerupy311/bin/mineru 不存在',
|
||
},
|
||
open: false,
|
||
},
|
||
});
|
||
expect(wrapper.find('.pi-pct.err').text()).toBe('失败');
|
||
expect(wrapper.find('.prog-track').exists()).toBe(false); // 不显示进度条
|
||
expect(wrapper.find('.pi-errmsg').exists()).toBe(true);
|
||
expect(wrapper.find('.pi-errmsg').text()).toContain('MinerU 解析失败');
|
||
});
|
||
|
||
it('ProgressItem:completed 显示 100% success 停扫光', () => {
|
||
const wrapper = mount(ProgressItem, {
|
||
props: {
|
||
job: { job_id: 'j1', title: 'wang2019.pdf', status: 'completed', stage: 'completed', progress_pct: 100 },
|
||
open: false,
|
||
},
|
||
});
|
||
expect(wrapper.find('.pi-pct.ok').text()).toBe('100%');
|
||
expect(wrapper.find('.prog-fill.full').exists()).toBe(true);
|
||
});
|
||
|
||
it('ProgressItem:running 显示派生的 75% 渐变进度条', () => {
|
||
const wrapper = mount(ProgressItem, {
|
||
props: {
|
||
job: { job_id: 'j2', title: 'Pecan_SPL_2021.pdf', status: 'running', stage: 'llm', progress_pct: 75 },
|
||
open: false,
|
||
},
|
||
});
|
||
expect(wrapper.find('.pi-pct').text()).toBe('75%');
|
||
expect(wrapper.find('.prog-fill').exists()).toBe(true);
|
||
expect(wrapper.find('.prog-fill.full').exists()).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('ExtractionPane 轮询条件(FR-EXTRACT-005)', () => {
|
||
it('存在 running/queued 才轮询;全终态停止', () => {
|
||
expect(shouldPoll([{ status: 'completed' }, { status: 'failed' }])).toBe(false);
|
||
expect(shouldPoll([{ status: 'completed' }, { status: 'running' }])).toBe(true);
|
||
expect(shouldPoll([{ status: 'queued' }])).toBe(true);
|
||
expect(shouldPoll([])).toBe(false);
|
||
});
|
||
|
||
it('store.hasActiveJobs 与 shouldPoll 一致', () => {
|
||
setActivePinia(createPinia());
|
||
const store = useBenchStore();
|
||
store.jobs = [
|
||
{ job_id: 'a', status: 'completed' },
|
||
{ job_id: 'b', status: 'failed' },
|
||
];
|
||
expect(store.hasActiveJobs).toBe(false);
|
||
store.jobs.push({ job_id: 'c', status: 'running' });
|
||
expect(store.hasActiveJobs).toBe(true);
|
||
});
|
||
|
||
it('tab 切走即暂停:setActiveTab 离开 extract 调用 pausePolling', () => {
|
||
setActivePinia(createPinia());
|
||
const store = useBenchStore();
|
||
store.pollPaused = false;
|
||
store.setActiveTab('extract');
|
||
store.pollPaused = true; // 轮询曾启动
|
||
store.setActiveTab('retrieval');
|
||
expect(store.pollPaused).toBe(true); // 保持暂停
|
||
store.setActiveTab('extract');
|
||
expect(store.pollPaused).toBe(false); // 回到 extract 解除暂停
|
||
});
|
||
});
|