Files
PlantAI-rag-new/tests/frontend/unit/bioflow/archive.reuse.test.js
T

200 lines
8.6 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
// 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();
});
});