66 lines
2.8 KiB
TypeScript
66 lines
2.8 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
ARCHIVE_API_BASE,
|
|
archiveErrorText,
|
|
normalizeArchiveFileDetail,
|
|
normalizeArchivePackage,
|
|
} from '../src/api/archive';
|
|
|
|
describe('archive R4 frontend contracts', () => {
|
|
it('uses the documented plural archive API prefix', () => {
|
|
expect(ARCHIVE_API_BASE).toBe('/archives');
|
|
});
|
|
|
|
it('normalizes nullable archive response collections and action allowlists', () => {
|
|
const packageView = normalizeArchivePackage({
|
|
allowedActions: null,
|
|
items: [{ allowedActions: null }],
|
|
objectSnapshots: null,
|
|
actions: undefined,
|
|
} as any);
|
|
const detail = normalizeArchiveFileDetail({
|
|
file: { allowedActions: null },
|
|
versions: null,
|
|
borrows: [{ allowedActions: undefined }],
|
|
} as any);
|
|
|
|
expect(packageView).toMatchObject({
|
|
allowedActions: [],
|
|
items: [{ allowedActions: [] }],
|
|
objectSnapshots: [],
|
|
actions: [],
|
|
});
|
|
expect(detail).toMatchObject({ file: { allowedActions: [] }, versions: [], borrows: [{ allowedActions: [] }] });
|
|
});
|
|
|
|
it('renders actionable error classes without dropping server field errors', () => {
|
|
expect(archiveErrorText({ status: 401 }, '失败')).toContain('登录会话已失效');
|
|
expect(archiveErrorText({ status: 403 }, '失败')).toContain('没有访问');
|
|
expect(archiveErrorText({ status: 404 }, '失败')).toContain('不存在');
|
|
expect(archiveErrorText({ status: 409 }, '失败')).toContain('数据已发生变化');
|
|
expect(archiveErrorText({ status: 422, fieldErrors: { dueAt: '日期无效' } }, '失败')).toContain('日期无效');
|
|
expect(archiveErrorText({ status: 503 }, '失败')).toContain('服务处理失败');
|
|
});
|
|
|
|
it('keeps downloaded blob URLs alive until the browser consumes them', () => {
|
|
const page = readFileSync(new URL('../src/pages/archives/ArchiveFilesPage.vue', import.meta.url), 'utf8');
|
|
expect(page).toContain('window.setTimeout(() => URL.revokeObjectURL(url), 60_000)');
|
|
});
|
|
|
|
it('exposes a recoverable workflow for quarantined archive files', () => {
|
|
const api = readFileSync(new URL('../src/api/archive.ts', import.meta.url), 'utf8');
|
|
const page = readFileSync(new URL('../src/pages/archives/ArchiveFilesPage.vue', import.meta.url), 'utf8');
|
|
const rescanPath = '`$' + '{ARCHIVE_API_BASE}/files/$' + '{publicId}/rescan`';
|
|
expect(api).toContain('export function rescanArchiveFile');
|
|
expect(api).toContain(rescanPath);
|
|
expect(api).toContain("commandHeaders('archive-file-rescan'");
|
|
expect(page).toContain("row.allowedActions.includes('RESCAN')");
|
|
expect(page).toContain('@click="rescanFile(row)"');
|
|
expect(page).toContain("fileFilters.scanStatus = 'QUARANTINED'");
|
|
expect(page).toContain('文件已通过安全检查并恢复可用');
|
|
});
|
|
});
|