200 lines
8.6 KiB
JavaScript
200 lines
8.6 KiB
JavaScript
// @vitest-environment jsdom
|
||
// tests/frontend/unit/bioflow/archive.reuse.test.js —— P4 存档与复用单测(L4)
|
||
// 覆盖:存档提示卡全流程(已有 cards.test.js 覆盖 UI 动作矩阵,此处补 P4 专属):
|
||
// 复用预填表单(FR-BIO-023:input=存档值 + 来源标注=存档名 + 确认创建新任务)、
|
||
// 存档状态回复消息更新(FR-BIO-021 三要素)、存档放弃不重复提示(FR-BIO-020)。
|
||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||
import { mount, flushPromises } from '@vue/test-utils';
|
||
import { createPinia, setActivePinia } from 'pinia';
|
||
|
||
vi.mock('../../../../ui/src/api/bioflow.js', () => ({
|
||
runBioflowArchive: vi.fn(async () => ({
|
||
ok: true,
|
||
data: { id: 'job-9', title: 'SPL 水稻鉴定(复用)', status: 'created', stages: [] },
|
||
})),
|
||
getBioflowArchive: vi.fn(async () => ({
|
||
ok: true,
|
||
data: {
|
||
id: 'arc-1', name: 'SPL 水稻鉴定',
|
||
stage_chain: ['identify', 'align'],
|
||
params: { identify: { prefix: 'Ft.SPL', cpu: 16 } },
|
||
},
|
||
})),
|
||
createBioflowArchive: vi.fn(async () => ({
|
||
ok: true,
|
||
data: { id: 'arc-1', name: 'SPL 水稻鉴定', disk_path: '/d/arc-1', size_bytes: 2048 },
|
||
})),
|
||
listBioflowArchives: vi.fn(async () => ({
|
||
ok: true,
|
||
data: {
|
||
items: [{ id: 'arc-1', name: 'SPL 水稻鉴定', size_bytes: 2048, reused_count: 0 }],
|
||
total: 1,
|
||
},
|
||
})),
|
||
deleteBioflowArchive: vi.fn(async () => {}),
|
||
bioflowHealth: vi.fn(async () => ({ ok: true })),
|
||
listBioflowJobs: vi.fn(async () => ({ ok: true, data: { items: [], total: 0 } })),
|
||
getBioflowJob: vi.fn(async () => ({ ok: true, data: {} })),
|
||
deleteBioflowJob: vi.fn(async () => {}),
|
||
createBioflowJob: vi.fn(async () => ({ ok: true, data: { id: 'j1', stages: [] } })),
|
||
confirmBioflowStage: vi.fn(async () => ({ ok: true, data: {} })),
|
||
rerunBioflowStage: vi.fn(async () => ({ ok: true, data: {} })),
|
||
skipBioflowStage: vi.fn(async () => ({ ok: true, data: {} })),
|
||
pauseBioflowJob: vi.fn(async () => ({ ok: true, data: {} })),
|
||
resumeBioflowJob: vi.fn(async () => ({ ok: true, data: {} })),
|
||
getBioflowStageManifest: vi.fn(async () => ({ ok: true, data: {} })),
|
||
bioflowCatalog: vi.fn(async () => ({ ok: true, data: { stages: [] } })),
|
||
uploadBioflowFile: vi.fn(async () => ({ ok: true, data: {} })),
|
||
bioflowChatStream: vi.fn(async () => { throw new Error('unused'); }),
|
||
bioflowJobEvents: vi.fn(async () => { throw new Error('unused'); }),
|
||
bioflowAsk: vi.fn(async () => ({ ok: true, answer: { citations: [] } })),
|
||
reviseBioflowReport: vi.fn(async () => ({ ok: true, data: {} })),
|
||
registerBioflowReport: vi.fn(async () => ({ ok: true, data: {} })),
|
||
runBioflowArchiveByName: vi.fn(async () => ({ ok: true, data: {} })),
|
||
}));
|
||
|
||
vi.mock('../../../../ui/src/api/sessions.js', () => ({
|
||
createMessage: vi.fn(async () => ({ id: 'm1' })),
|
||
updateMessage: vi.fn(async () => ({ id: 'm1' })),
|
||
createSession: vi.fn(async () => ({ id: 'sess-1', title: 'x', status: 'idle', created_at: 'x', updated_at: 'x', message_count: 0, meta: {} })),
|
||
updateSession: vi.fn(async () => ({})),
|
||
deleteSession: vi.fn(async () => {}),
|
||
listSessions: vi.fn(async () => []),
|
||
getSession: vi.fn(),
|
||
getSessionSources: vi.fn(async () => ({ total: 0, sources: [] })),
|
||
}));
|
||
|
||
vi.mock('../../../../ui/src/api/qa.js', () => ({
|
||
chatStream: vi.fn(async () => { throw new Error('unused'); }),
|
||
search: vi.fn(async () => { throw new Error('unused'); }),
|
||
}));
|
||
|
||
import FlowFormCard from '../../../../ui/src/components/chat/FlowFormCard.vue';
|
||
import FlowArchiveCard from '../../../../ui/src/components/chat/FlowArchiveCard.vue';
|
||
import { useBioflowStore } from '../../../../ui/src/stores/bioflow.js';
|
||
import * as bioflowApi from '../../../../ui/src/api/bioflow.js';
|
||
|
||
function setup() {
|
||
setActivePinia(createPinia());
|
||
const bioflow = useBioflowStore();
|
||
bioflow.currentJob.jobId = 'job-1';
|
||
return bioflow;
|
||
}
|
||
|
||
describe('P4 复用预填表单(FR-BIO-023;TC-BIO-PANE-009/010)', () => {
|
||
beforeEach(() => {
|
||
setup();
|
||
vi.clearAllMocks();
|
||
});
|
||
|
||
it('预填表单显示存档值 + 来源标注=存档名(TC-BIO-PANE-009)', async () => {
|
||
const store = useBioflowStore();
|
||
await store.loadReusePrefill('arc-1');
|
||
const block = {
|
||
type: 'bioflow_form',
|
||
stage: 'identify',
|
||
mode: 'reuse',
|
||
archiveId: 'arc-1',
|
||
archiveName: 'SPL 水稻鉴定',
|
||
sessionId: 'sess-1',
|
||
target: '玉米 bZIP',
|
||
fields: [
|
||
{ name: 'prefix', type: 'string', default: '', source: 'contract' },
|
||
{ name: 'cpu', type: 'int', default: 4, source: 'contract' },
|
||
],
|
||
prefill: {
|
||
prefix: { value: 'Ft.SPL', source: 'archive', archiveName: 'SPL 水稻鉴定' },
|
||
cpu: { value: 16, source: 'archive', archiveName: 'SPL 水稻鉴定' },
|
||
},
|
||
};
|
||
const w = mount(FlowFormCard, { props: { block } });
|
||
const inputs = w.findAll('.ffc-input');
|
||
expect(inputs[0].element.value).toBe('Ft.SPL');
|
||
expect(inputs[1].element.value).toBe('16');
|
||
const sources = w.findAll('.ffc-source');
|
||
expect(sources[0].text()).toContain('存档 SPL 水稻鉴定');
|
||
expect(w.find('.ffc-title').text()).toContain('SPL 水稻鉴定');
|
||
});
|
||
|
||
it('确认创建:发出 archives/{id}/run,新任务入 store(TC-BIO-PANE-010)', async () => {
|
||
const block = {
|
||
type: 'bioflow_form',
|
||
stage: 'identify',
|
||
mode: 'reuse',
|
||
archiveId: 'arc-1',
|
||
archiveName: 'SPL 水稻鉴定',
|
||
sessionId: 'sess-1',
|
||
target: '玉米 bZIP',
|
||
fields: [{ name: 'cpu', type: 'int', default: 4, source: 'contract' }],
|
||
prefill: { cpu: { value: 16, source: 'archive', archiveName: 'SPL 水稻鉴定' } },
|
||
};
|
||
const w = mount(FlowFormCard, { props: { block } });
|
||
const emitted = [];
|
||
await w.find('.ffc-continue').trigger('click');
|
||
await flushPromises();
|
||
expect(bioflowApi.runBioflowArchive).toHaveBeenCalledWith('arc-1', expect.objectContaining({
|
||
session_id: 'sess-1', target: '玉米 bZIP', params: expect.objectContaining({ cpu: 16 }),
|
||
}));
|
||
expect(block.jobId).toBe('job-9');
|
||
expect(block.submitted).toBe(true);
|
||
const submitEvents = w.emitted('submitted');
|
||
expect(submitEvents).toBeTruthy();
|
||
expect(submitEvents[0][0].reuse).toBe(true);
|
||
expect(submitEvents[0][0].jobId).toBe('job-9');
|
||
});
|
||
|
||
it('store.runArchive 直接复用发起(TC-BIO-PANE-008 面板路径共用)', async () => {
|
||
const store = useBioflowStore();
|
||
const detail = await store.runArchive('arc-1', { sessionId: 'sess-1', target: '玉米 bZIP', params: {} });
|
||
expect(detail.id).toBe('job-9');
|
||
expect(bioflowApi.runBioflowArchive).toHaveBeenCalledWith('arc-1', {
|
||
session_id: 'sess-1', target: '玉米 bZIP', params: {},
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('P4 存档提示与状态回复(FR-BIO-020/021)', () => {
|
||
beforeEach(() => {
|
||
setup();
|
||
vi.clearAllMocks();
|
||
});
|
||
|
||
it('存档成功:消息块写回名称/位置/大小三要素(FR-BIO-021)', async () => {
|
||
const block = {
|
||
type: 'bioflow_archive',
|
||
jobId: 'job-1',
|
||
status: 'pending',
|
||
name: '', description: '', result: null, error: null,
|
||
};
|
||
const w = mount(FlowArchiveCard, { props: { block } });
|
||
await w.findAll('.fac-input')[0].setValue('SPL 水稻鉴定');
|
||
await w.find('.fac-archive').trigger('click');
|
||
await flushPromises();
|
||
expect(block.status).toBe('done');
|
||
expect(block.result.name).toBe('SPL 水稻鉴定');
|
||
expect(block.result.disk_path).toBeTruthy();
|
||
expect(block.result.size_bytes).toBe(2048);
|
||
expect(w.find('.fac-meta').text()).toContain('位置');
|
||
expect(w.find('.fac-meta').text()).toContain('大小');
|
||
});
|
||
|
||
it('放弃后同一任务不再提示(FR-BIO-020;store 状态 dismissed)', async () => {
|
||
const store = useBioflowStore();
|
||
store.archiveSuggestion['job-1'] = { jobId: 'job-1', status: 'pending', name: '', description: '' };
|
||
const block = store.archiveSuggestion['job-1'];
|
||
const w = mount(FlowArchiveCard, { props: { block } });
|
||
await w.find('.pane-btn').trigger('click');
|
||
expect(block.status).toBe('dismissed');
|
||
expect(store.archiveSuggestion['job-1'].status).toBe('dismissed');
|
||
expect(bioflowApi.createBioflowArchive).not.toHaveBeenCalled();
|
||
// 放弃后卡片不再渲染(v-if dismissed)
|
||
expect(w.find('.flow-archive-card').exists()).toBe(false);
|
||
});
|
||
|
||
it('存档名称缺省:按钮禁用不发空 name 请求(TC-BIO-ARCH-008)', () => {
|
||
const block = { type: 'bioflow_archive', jobId: 'job-1', status: 'pending', name: '', description: '', result: null, error: null };
|
||
const w = mount(FlowArchiveCard, { props: { block } });
|
||
expect(w.find('.fac-archive').attributes('disabled')).toBeDefined();
|
||
});
|
||
});
|