-
quality_score: 0.86
-
建议补充“日志留存周期”的引用来源,并将私有化部署边界写得更明确。
+
+
测试结果摘要
+
{{ latestTestSummary }}
-
- {{ version.version }}
- {{ version.status }}
- {{ version.note }} · {{ version.updatedAt }}
+
+ v{{ version.versionNo }}
+ {{ normalizeVersionStatus(version.publishStatus) }}
+ {{ version.remark || '无备注' }} · {{ version.publishedTime || '未发布' }}
diff --git a/frontend/src/pages/studio/__tests__/McpImportPage.spec.ts b/frontend/src/pages/studio/__tests__/McpImportPage.spec.ts
new file mode 100644
index 0000000..36a3c2f
--- /dev/null
+++ b/frontend/src/pages/studio/__tests__/McpImportPage.spec.ts
@@ -0,0 +1,94 @@
+import { flushPromises, mount } from '@vue/test-utils';
+import ElementPlus from 'element-plus';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+import McpImportPage from '../McpImportPage.vue';
+import { importMcpServer, listMcpCapabilitiesByServerCode, listMcpServers } from '@/api/mcp';
+
+vi.mock('@/api/mcp', () => ({
+ listMcpServers: vi.fn(() =>
+ Promise.resolve({
+ resultcode: '0',
+ message: null,
+ data: [
+ {
+ id: '1',
+ serverCode: 'jira_server',
+ serverName: 'Jira 服务',
+ importType: 'URL',
+ healthStatus: 'HEALTHY',
+ manifestJson: '{"server":"jira"}',
+ enabled: true,
+ },
+ ],
+ }),
+ ),
+ listMcpCapabilitiesByServerCode: vi.fn(() =>
+ Promise.resolve({
+ resultcode: '0',
+ message: null,
+ data: [
+ {
+ id: '11',
+ serverId: '1',
+ capabilityCode: 'jira_issue_search',
+ capabilityName: '问题检索',
+ capabilityType: 'TOOL',
+ schemaJson: '{"type":"object"}',
+ enabled: true,
+ },
+ ],
+ }),
+ ),
+ importMcpServer: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: true })),
+ saveMcpCapability: vi.fn(),
+ getMcpWorkspace: vi.fn(),
+ listMcpCapabilitiesByServerId: vi.fn(),
+}));
+
+describe('McpImportPage', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('loads server tabs and capability preview from backend api', async () => {
+ const wrapper = mount(McpImportPage, {
+ global: {
+ plugins: [ElementPlus],
+ },
+ });
+
+ await flushPromises();
+
+ expect(listMcpServers).toHaveBeenCalled();
+ expect(listMcpCapabilitiesByServerCode).toHaveBeenCalledWith('jira_server');
+ expect(wrapper.text()).toContain('Jira 服务');
+ expect(wrapper.text()).toContain('问题检索');
+ expect(wrapper.find('[data-test="mcp-selected-server"]').text()).toContain('HEALTHY');
+ });
+
+ it('submits import form through backend api', async () => {
+ const wrapper = mount(McpImportPage, {
+ global: {
+ plugins: [ElementPlus],
+ },
+ });
+
+ await flushPromises();
+ await wrapper.get('[data-test="mcp-server-code"]').setValue('deploy_server');
+ await wrapper.get('[data-test="mcp-server-name"]').setValue('部署服务');
+ await wrapper.get('[data-test="mcp-import-type-NPM_PACKAGE"]').trigger('click');
+ await wrapper.get('[data-test="mcp-package-name"]').setValue('@acme/mcp-deploy');
+ await wrapper.get('[data-test="mcp-import-submit"]').trigger('click');
+ await flushPromises();
+
+ expect(importMcpServer).toHaveBeenCalledWith(
+ expect.objectContaining({
+ serverCode: 'deploy_server',
+ serverName: '部署服务',
+ importType: 'NPM_PACKAGE',
+ packageName: '@acme/mcp-deploy',
+ }),
+ );
+ });
+});
diff --git a/frontend/src/pages/studio/__tests__/ObservabilityPage.spec.ts b/frontend/src/pages/studio/__tests__/ObservabilityPage.spec.ts
new file mode 100644
index 0000000..0a5b042
--- /dev/null
+++ b/frontend/src/pages/studio/__tests__/ObservabilityPage.spec.ts
@@ -0,0 +1,97 @@
+import { flushPromises, mount } from '@vue/test-utils';
+import ElementPlus from 'element-plus';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+import ObservabilityPage from '../ObservabilityPage.vue';
+import { exportObservabilityTrace, getObservabilityTrace, listObservabilityRuns } from '@/api/observability';
+
+vi.mock('@/api/observability', () => ({
+ listObservabilityRuns: vi.fn(() =>
+ Promise.resolve({
+ resultcode: '0',
+ message: null,
+ data: [
+ {
+ runId: '101',
+ workflowId: '2001',
+ requestId: 'req-1001',
+ status: 'SUCCESS',
+ durationMs: 1420,
+ estimatedCost: 0.018,
+ },
+ ],
+ }),
+ ),
+ getObservabilityTrace: vi.fn(() =>
+ Promise.resolve({
+ resultcode: '0',
+ message: null,
+ data: {
+ requestId: 'req-1001',
+ workflowStatus: 'SUCCESS',
+ sessionStatus: 'ACTIVE',
+ workflowStepCount: 2,
+ messageCount: 2,
+ modelCallCount: 1,
+ totalDurationMs: 1420,
+ estimatedCost: 0.018,
+ stepLogs: [
+ {
+ nodeName: 'Knowledge Retrieval',
+ status: 'SUCCESS',
+ durationMs: 218,
+ outputSummary: '召回 6 个切片',
+ },
+ ],
+ modelCalls: [],
+ },
+ }),
+ ),
+ listObservabilityModelCalls: vi.fn(),
+ exportObservabilityTrace: vi.fn(() =>
+ Promise.resolve({
+ resultcode: '0',
+ message: null,
+ data: {
+ requestId: 'req-1001',
+ exportSummary: '仅导出脱敏摘要,不包含密钥和完整请求体',
+ },
+ }),
+ ),
+}));
+
+describe('ObservabilityPage', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('loads run list and trace detail from backend api', async () => {
+ const wrapper = mount(ObservabilityPage, {
+ global: {
+ plugins: [ElementPlus],
+ },
+ });
+
+ await flushPromises();
+
+ expect(listObservabilityRuns).toHaveBeenCalled();
+ expect(getObservabilityTrace).toHaveBeenCalledWith('req-1001');
+ expect(wrapper.text()).toContain('req-1001');
+ expect(wrapper.text()).toContain('Knowledge Retrieval');
+ });
+
+ it('exports masked trace through backend api', async () => {
+ const wrapper = mount(ObservabilityPage, {
+ global: {
+ plugins: [ElementPlus],
+ },
+ });
+
+ await flushPromises();
+ await wrapper.get('[data-test="observability-export"]').trigger('click');
+ await flushPromises();
+
+ expect(exportObservabilityTrace).toHaveBeenCalledWith('req-1001');
+ expect(wrapper.find('[data-test="observability-export-summary"]').text()).toContain('脱敏摘要');
+ });
+});
diff --git a/frontend/src/pages/studio/__tests__/SkillWorkspacePage.spec.ts b/frontend/src/pages/studio/__tests__/SkillWorkspacePage.spec.ts
new file mode 100644
index 0000000..55c080f
--- /dev/null
+++ b/frontend/src/pages/studio/__tests__/SkillWorkspacePage.spec.ts
@@ -0,0 +1,101 @@
+import { flushPromises, mount } from '@vue/test-utils';
+import ElementPlus from 'element-plus';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+import SkillWorkspacePage from '../SkillWorkspacePage.vue';
+import { getSkillWorkspace, publishSkillDraft, saveSkillDraft, testSkillDraft } from '@/api/skill';
+
+vi.mock('@/api/skill', () => ({
+ getSkillWorkspace: vi.fn(() =>
+ Promise.resolve({
+ resultcode: '0',
+ message: null,
+ data: {
+ skillId: '1001',
+ skillCode: 'skill-citation',
+ skillName: '引用审校 Skill',
+ skillType: 'MIXED',
+ description: '检查答案与引用是否一致',
+ status: 'PUBLISHED',
+ publishedVersionNo: 4,
+ latestTestResultJson: '{"quality_score":0.86}',
+ skills: [],
+ versions: [
+ {
+ id: '2001',
+ skillId: '1001',
+ versionNo: 4,
+ promptText: '你是回答审校器',
+ configJson: '{"timeout":3000}',
+ variableSchemaJson: '{"type":"object"}',
+ testResultJson: '{"quality_score":0.86}',
+ publishStatus: 'PUBLISHED',
+ publishedTime: '2026-06-01 10:00:00',
+ remark: '生产版本',
+ },
+ ],
+ },
+ }),
+ ),
+ saveSkillDraft: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: true })),
+ testSkillDraft: vi.fn(() =>
+ Promise.resolve({
+ resultcode: '0',
+ message: null,
+ data: {
+ versionNo: 5,
+ testResultJson: '{"quality_score":0.92,"summary":"建议补充日志留存周期引用"}',
+ publishStatus: 'DRAFT',
+ },
+ }),
+ ),
+ publishSkillDraft: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: true })),
+ archiveSkillVersion: vi.fn(),
+}));
+
+describe('SkillWorkspacePage', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('loads workspace detail and renders version list from backend api', async () => {
+ const wrapper = mount(SkillWorkspacePage, {
+ global: {
+ plugins: [ElementPlus],
+ },
+ });
+
+ await flushPromises();
+
+ expect(getSkillWorkspace).toHaveBeenCalledWith('skill-citation');
+ expect(wrapper.text()).toContain('引用审校 Skill');
+ expect(wrapper.text()).toContain('v4');
+ expect(wrapper.find('[data-test="skill-test-result"]').text()).toContain('quality_score');
+ });
+
+ it('saves draft, runs test and publishes through backend api', async () => {
+ const wrapper = mount(SkillWorkspacePage, {
+ global: {
+ plugins: [ElementPlus],
+ },
+ });
+
+ await flushPromises();
+ await wrapper.get('[data-test="skill-prompt-editor"]').setValue('新的审校提示词');
+ await wrapper.get('[data-test="skill-save-draft"]').trigger('click');
+ await flushPromises();
+ await wrapper.get('[data-test="skill-run-test"]').trigger('click');
+ await flushPromises();
+ await wrapper.get('[data-test="skill-publish"]').trigger('click');
+ await flushPromises();
+
+ expect(saveSkillDraft).toHaveBeenCalledWith(
+ 'skill-citation',
+ expect.objectContaining({
+ promptText: '新的审校提示词',
+ }),
+ );
+ expect(testSkillDraft).toHaveBeenCalledWith('skill-citation', expect.any(Object));
+ expect(publishSkillDraft).toHaveBeenCalledWith('skill-citation', expect.any(Object));
+ });
+});