import { describe, expect, it } from 'vitest';
|
|
import {
|
formatAuditDiffValue,
|
getAuditDiffDisplayValue,
|
getSafeAuditDiffFieldLabel,
|
isSensitiveAuditDiff,
|
parseAuditEventFieldDiffs,
|
parseAuditFieldDiffs,
|
} from '#/components/FormExtraPanel/auditDiff';
|
|
import { getAuditActionLabel, groupTimelineOperations, normalizeAuditPageQuery, normalizeOperation } from '../auditOperation';
|
|
describe('audit operation normalization', () => {
|
it('uses the BUSINESS row as the operation list row', () => {
|
const operation = normalizeOperation({
|
id: '100000000000000001',
|
operationId: 'op-1',
|
sourceLayer: 2,
|
diffCount: 0,
|
});
|
|
expect(operation.diffCount).toBe(0);
|
expect(operation.eventIds).toEqual(['100000000000000001']);
|
expect(operation.layers).toEqual([2]);
|
expect(operation.sourceSummary).toBe('业务动作');
|
expect(operation.subEvents).toEqual([]);
|
});
|
|
it('retains a layer 0 event without an operation id', () => {
|
const operation = normalizeOperation({
|
id: 7,
|
operationId: null,
|
clientEventId: 'visual-log-7',
|
sourceLayer: 0,
|
diffCount: 0,
|
});
|
|
expect(operation.correlationKey).toBe('visual-log-7');
|
expect(operation.sourceSummary).toBe('在线表单');
|
expect(operation.eventIds).toEqual([7]);
|
});
|
|
it('labels read events as queries', () => {
|
expect(getAuditActionLabel({ actionCode: 'READ', actionLabel: '查询列表' })).toBe('查询');
|
});
|
|
it('associates signature rows without adding their diffs to the business summary', () => {
|
const operation = normalizeOperation({
|
id: 8,
|
operationId: 'op-sign-used',
|
sourceLayer: 2,
|
diffCount: 2,
|
subEvents: [
|
{ id: 9, operationId: 'op-sign-used', sourceLayer: 3, eventType: 'E_SIGNATURE', diffCount: 5 },
|
],
|
});
|
|
expect(operation.diffCount).toBe(2);
|
expect(operation.eventIds).toEqual([8, 9]);
|
expect(operation.layers).toEqual([2, 3]);
|
expect(operation.sourceSummary).toBe('业务动作 + 电子签名 + 字段变更');
|
expect(operation.subEvents).toHaveLength(1);
|
});
|
|
it('labels a standalone signature event as electronic signature evidence', () => {
|
const operation = normalizeOperation({
|
id: 10,
|
sourceLayer: 3,
|
diffCount: 0,
|
actionCode: 'SIGN',
|
});
|
|
expect(operation.sourceSummary).toBe('电子签名');
|
});
|
|
it('keeps the id-based order supplied by the timeline endpoint', () => {
|
const operations = groupTimelineOperations([
|
{
|
id: 11,
|
operationId: 'op-2',
|
sourceLayer: 2,
|
eventTime: '2026-07-28 10:01:00',
|
diffCount: 2,
|
subEvents: [{ id: 12, operationId: 'op-2', sourceLayer: 3, eventType: 'E_SIGNATURE' }],
|
},
|
{ id: 21, operationId: null, clientEventId: 'client-21', sourceLayer: 0, eventTime: '2026-07-28 10:00:00', diffCount: 1 },
|
]);
|
|
expect(operations).toHaveLength(2);
|
const firstOperation = operations[0]!;
|
const secondOperation = operations[1]!;
|
expect(firstOperation.id).toBe(11);
|
expect(firstOperation.eventIds).toEqual([11, 12]);
|
expect(secondOperation.id).toBe(21);
|
});
|
|
it('keeps one BUSINESS row with primary and review signatures as switchable details', () => {
|
const operations = groupTimelineOperations([
|
{
|
id: 31,
|
operationId: 'BS-839875756384423365',
|
eventType: 'BIZ_ACTION',
|
sourceLayer: 2,
|
diffCount: 0,
|
subEvents: [
|
{
|
id: 33,
|
operationId: 'BS-839875756384423365',
|
eventType: 'E_SIGNATURE',
|
signatureRole: 'PRIMARY',
|
sourceLayer: 0,
|
},
|
{
|
id: 34,
|
operationId: 'BS-839875756384423365',
|
eventType: 'E_SIGNATURE',
|
signatureRole: 'REVIEW',
|
sourceLayer: 0,
|
},
|
],
|
},
|
]);
|
|
expect(operations).toHaveLength(1);
|
expect(operations[0]?.correlationKey).toBe('BS-839875756384423365');
|
expect(operations[0]?.eventIds).toEqual([31, 33, 34]);
|
expect(operations[0]?.subEvents.map((event) => event.signatureRole)).toEqual(['PRIMARY', 'REVIEW']);
|
});
|
|
it('trims optional filters without discarding pagination', () => {
|
expect(normalizeAuditPageQuery({ currentPage: 2, pageSize: 20, operatorName: ' 张三 ', bizCode: ' ' })).toEqual({
|
currentPage: 2,
|
pageSize: 20,
|
operatorName: '张三',
|
});
|
});
|
});
|
|
describe('audit field diff safety', () => {
|
it('keeps original values when nameModified is true', () => {
|
const result = parseAuditFieldDiffs('[{"field":"display_name","nameModified":true,"oldData":"old-name","newData":"new-name"}]');
|
|
expect(result.parseError).toBe(false);
|
const diff = result.diffs[0]!;
|
expect(diff.nameModified).toBe(true);
|
expect(diff.oldData).toBe('old-name');
|
expect(diff.newData).toBe('new-name');
|
});
|
|
it('prefers structured display semantics over legacy raw values', () => {
|
const result = parseAuditEventFieldDiffs(
|
[
|
{
|
componentType: 'select',
|
field: 'biz_status',
|
fieldName: '请验状态',
|
masked: false,
|
newData: 'confirmed',
|
newDisplay: '已提交',
|
oldData: 'created',
|
oldDisplay: '已创建',
|
technical: false,
|
valueType: 'enum',
|
},
|
],
|
'[{"field":"legacy_only","newData":"legacy"}]',
|
);
|
|
expect(result.parseError).toBe(false);
|
expect(result.diffs).toHaveLength(1);
|
const diff = result.diffs[0]!;
|
expect(getAuditDiffDisplayValue(diff, 'old')).toBe('已创建');
|
expect(getAuditDiffDisplayValue(diff, 'new')).toBe('已提交');
|
expect(diff.componentType).toBe('select');
|
expect(diff.valueType).toBe('enum');
|
expect(diff.technical).toBe(false);
|
});
|
|
it('falls back to raw values when display names are unavailable', () => {
|
const diff = {
|
newData: 'new-value',
|
newDisplay: ' 名称暂不可用 ',
|
oldData: 'old-value',
|
oldDisplay: '名称暂不可用',
|
};
|
|
expect(getAuditDiffDisplayValue(diff, 'old')).toBe('old-value');
|
expect(getAuditDiffDisplayValue(diff, 'new')).toBe('new-value');
|
});
|
|
it('treats an empty structured list as authoritative', () => {
|
expect(parseAuditEventFieldDiffs([], '[{"field":"legacy_only","newData":"legacy"}]')).toEqual({ diffs: [], parseError: false });
|
});
|
|
it('falls back to legacy fieldDiffs when the structured field is absent', () => {
|
const result = parseAuditEventFieldDiffs(undefined, '[{"field":"qingyan_beizhu","fieldName":"请验备注","newData":"已更新"}]');
|
|
expect(result.parseError).toBe(false);
|
expect(result.diffs[0]?.fieldName).toBe('请验备注');
|
});
|
|
it('keeps child-table rows and columns in the structured audit contract', () => {
|
const result = parseAuditEventFieldDiffs(
|
[
|
{
|
chidData: [{ jnpf_old_name: '旧项目', jnpf_type: 1, name: '新项目' }],
|
chidField: [{ jnpfKey: 'input', label: '项目', prop: 'name' }],
|
field: 'tableField1',
|
fieldName: '检测项目',
|
jnpfKey: 'table',
|
},
|
],
|
undefined,
|
);
|
|
expect(result.parseError).toBe(false);
|
expect(result.diffs[0]?.chidData).toHaveLength(1);
|
expect(result.diffs[0]?.chidField?.[0]?.label).toBe('项目');
|
});
|
|
it('drops raw and display values from the structured masked contract', () => {
|
const result = parseAuditEventFieldDiffs(
|
[
|
{
|
componentType: 'sign',
|
field: 'biz_sign',
|
fieldName: '电子签名',
|
masked: true,
|
newData: 'must-not-render',
|
newDisplay: 'must-not-render',
|
oldData: 'must-not-render',
|
oldDisplay: 'must-not-render',
|
valueType: 'masked',
|
},
|
],
|
undefined,
|
);
|
|
const diff = result.diffs[0]!;
|
expect(diff.masked).toBe(true);
|
expect(diff).not.toHaveProperty('oldData');
|
expect(diff).not.toHaveProperty('newData');
|
expect(diff).not.toHaveProperty('oldDisplay');
|
expect(diff).not.toHaveProperty('newDisplay');
|
expect(isSensitiveAuditDiff(diff)).toBe(true);
|
});
|
|
it('degrades malformed JSON without throwing', () => {
|
expect(parseAuditFieldDiffs('{bad json')).toEqual({ diffs: [], parseError: true });
|
});
|
|
it('keeps untrusted markup as plain text for Vue interpolation', () => {
|
expect(formatAuditDiffValue('<img src=x onerror=alert(1)>')).toBe('<img src=x onerror=alert(1)>');
|
expect(formatAuditDiffValue({ enabled: false, count: 0 })).toBe('{"enabled":false,"count":0}');
|
});
|
|
it('detects sensitive fields from technical and display names', () => {
|
expect(isSensitiveAuditDiff({ field: 'mobilePhone' })).toBe(true);
|
expect(isSensitiveAuditDiff({ field: 'bankCardNo' })).toBe(true);
|
expect(isSensitiveAuditDiff({ fieldName: '身份证号码' })).toBe(true);
|
expect(isSensitiveAuditDiff({ field: 'contact', fieldName: '联系电话' })).toBe(true);
|
expect(isSensitiveAuditDiff({ field: 'sampleName', fieldName: '样品名称' })).toBe(false);
|
expect(isSensitiveAuditDiff({ field: 'hotelName' })).toBe(false);
|
expect(isSensitiveAuditDiff({ field: 'biz_sign' })).toBe(true);
|
expect(isSensitiveAuditDiff({ fieldName: '签名图片', jnpfKey: 'sign' })).toBe(true);
|
expect(isSensitiveAuditDiff({ fieldName: '电子签章', jnpfKey: 'signature' })).toBe(true);
|
});
|
|
it('does not expose database identifiers as business field labels', () => {
|
expect(getSafeAuditDiffFieldLabel({ field: 'chanpin_mingcheng', fieldName: '产品名称' })).toBe('产品名称');
|
expect(getSafeAuditDiffFieldLabel({ field: 'biz_status', fieldName: 'biz_status' }, 1)).toBe('未配置字段名称 2');
|
expect(getSafeAuditDiffFieldLabel({ field: 'biz_sign', fieldName: 'biz_sign' })).toBe('电子签名');
|
});
|
});
|