Files

73 lines
5.0 KiB
JavaScript
Raw Permalink Normal View History

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { privacyDeletionProgress } from '../src/pages/privacy-detail.ts';
const webRoot = fileURLToPath(new URL('..', import.meta.url));
const requireDependency = createRequire(path.join(webRoot, 'package.json'));
const ts = requireDependency('typescript');
const React = requireDependency('react');
const { renderToStaticMarkup } = requireDependency('react-dom/server');
const source = fs.readFileSync(process.env.PRIVACY_CARD_SOURCE || path.join(webRoot, 'src/pages/SaaSWorkspace.tsx'), 'utf8');
// Exercise the production card without importing the entire workspace or its
// unrelated browser-only integrations. Its display helpers are small stubs.
const cardSource = source.slice(source.indexOf('function PrivacyRequestCard('), source.indexOf('function Info('));
assert.ok(cardSource, 'the privacy card must exist');
const compiled = ts.transpileModule(cardSource + '\nexports.PrivacyRequestCard = PrivacyRequestCard;', { compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX, target: ts.ScriptTarget.ES2022 } });
const module = { exports: {} };
new Function('require', 'exports', 'useState', 'useRef', 'privacyDeletionProgress', 'formatDate', 'privacyRequestTypeLabel', 'privacyRequestStatus', 'privacyStatusLabel', 'privacyCategoryLabel', 'privacyRequestSummary', 'privacySubsystemLabel', 'privacySubtaskStatusLabel', 'Info', compiled.outputText)(
requireDependency, module.exports, React.useState, React.useRef, privacyDeletionProgress,
value => value, () => '删除数据', item => item.status, value => value, value => value, () => '', value => value, value => value,
({ label, value }) => React.createElement('div', {}, label, value),
);
const tasks = ['COMPLETED', 'SKIPPED', 'COMPLETED', 'COMPLETED', 'FAILED', 'PENDING'].map((status, index) => ({ id: `private-task-${index}`, subsystem: ['redis', 'export_files', 'object_storage', 'pgvector', 'provider_index', 'postgresql'][index], status, attempts: status === 'FAILED' ? 8 : 0, last_error: status === 'FAILED' ? 'HTTP 429 synthetic-stack' : undefined, next_attempt_at: status === 'FAILED' ? '2026-10-04T14:48:45Z' : undefined }));
const request = { id: 'private-request-id', subject_id: 'private-subject-id', request_type: 'DELETE', status: 'EXECUTING', version: 2, created_at: '2026-10-04T10:00:00Z', updated_at: '2026-10-04T14:48:15Z', subtasks: tasks, result_summary: {} };
const render = overrides => renderToStaticMarkup(React.createElement(module.exports.PrivacyRequestCard, { request, subjectName: '测试长者', selected: false, loading: false, onRefresh() {}, ...overrides }));
const visibleSummary = markup => markup.slice(0, markup.indexOf('<details')).replace(/<[^>]*>/g, '');
test('normal view keeps evidence count and controls while technical data stays collapsed', () => {
const markup = render({ readAt: '2026-10-06T00:00:00Z' });
const summary = visibleSummary(markup);
assert.match(summary, /尚未全部清除/);
assert.match(summary, /已核对 4\/6/);
assert.match(summary, /最近核验:2026-10-06T00:00:00Z/);
assert.match(summary, /刷新/);
assert.match(summary, /复制请求编号/);
assert.doesNotMatch(summary, /private-|HTTP 429|provider_index|v2|已尝试/);
assert.match(markup, /<details class="cp-details"><summary>诊断详情/);
assert.doesNotMatch(markup, /<details[^>]* open/);
assert.match(markup, /HTTP 429 synthetic-stack/);
assert.match(markup, /2026-10-04T14:48:45Z/);
assert.match(markup, /不代表仍会自动重试/);
});
test('list-only state does not misrepresent an empty array as verified progress', () => {
const summary = visibleSummary(render({ request: { ...request, subtasks: [] } }));
assert.match(summary, /详情尚未读取或已更新/);
assert.match(summary, /最近核验:尚未读取详情/);
assert.doesNotMatch(summary, /0\/0|2026-10-04T14:48:15Z/);
});
test('failed refresh keeps its original verification time and hides the technical exception', () => {
const markup = render({ readAt: '2026-10-05T00:00:00Z', detailError: 'synthetic-detail-502' });
assert.match(visibleSummary(markup), /最近核验:2026-10-05T00:00:00Z(最新刷新失败,保留上次结果)/);
assert.doesNotMatch(visibleSummary(markup), /synthetic-detail-502/);
assert.match(markup, /最近读取异常:synthetic-detail-502/);
});
test('loading refresh disables the read action without offering deletion retry', () => {
const markup = render({ loading: true });
assert.match(markup, /disabled="">正在核验/);
assert.doesNotMatch(visibleSummary(markup), /重新删除|重试删除|重新入队/);
});
test('a completed parent with failed children stays incomplete in both badge and summary', () => {
const summary = visibleSummary(render({ request: { ...request, status: 'COMPLETED' } }));
assert.match(summary, /尚未全部清除/);
assert.match(summary, /核验信息不一致/);
assert.doesNotMatch(summary, /已核对完成/);
});