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('文件已通过安全检查并恢复可用'); }); });