152 lines
5.2 KiB
TypeScript
152 lines
5.2 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
OPERATION_TIMING_SCHEMA_VERSION,
|
|
sanitizeOperationTiming
|
|
} from '../src/operation-timing.js';
|
|
|
|
test('control plane keeps only the operation timing allowlisted shape', () => {
|
|
const sanitized = sanitizeOperationTiming({
|
|
schema_version: OPERATION_TIMING_SCHEMA_VERSION,
|
|
status: 'completed',
|
|
total_ms: 12_345.6,
|
|
stage_count: 2,
|
|
started_at: '2026-08-25T00:00:00.000Z',
|
|
customer: '测试客户',
|
|
stages: [{
|
|
stage: 'preflight',
|
|
status: 'completed',
|
|
duration_ms: 2_345.4,
|
|
url: 'https://example.invalid/private',
|
|
details: [{
|
|
step: 'network_wait.preflight_raw_instruction',
|
|
status: 'completed',
|
|
duration_ms: 1_800,
|
|
response_text: 'private',
|
|
counters: {
|
|
request_count: 4,
|
|
customer_name: 1,
|
|
invalid_negative: -1,
|
|
invalid_text: 'secret'
|
|
}
|
|
}]
|
|
}, {
|
|
stage: 'john_smith',
|
|
status: 'completed',
|
|
duration_ms: 100
|
|
}]
|
|
});
|
|
|
|
assert.deepEqual(sanitized, {
|
|
schema_version: OPERATION_TIMING_SCHEMA_VERSION,
|
|
status: 'completed',
|
|
total_ms: 12_346,
|
|
stage_count: 2,
|
|
stages: [{
|
|
stage: 'preflight',
|
|
status: 'completed',
|
|
duration_ms: 2_345,
|
|
details: [{
|
|
step: 'network_wait.preflight_raw_instruction',
|
|
status: 'completed',
|
|
duration_ms: 1_800,
|
|
counters: { request_count: 4 }
|
|
}]
|
|
}]
|
|
});
|
|
assert.equal(JSON.stringify(sanitized).includes('测试客户'), false);
|
|
assert.equal(JSON.stringify(sanitized).includes('example.invalid'), false);
|
|
assert.equal(JSON.stringify(sanitized).includes('response_text'), false);
|
|
});
|
|
|
|
test('control plane rejects unknown timing contracts', () => {
|
|
assert.equal(sanitizeOperationTiming(null), null);
|
|
assert.equal(sanitizeOperationTiming({ schema_version: 'other', stages: [] }), null);
|
|
assert.equal(sanitizeOperationTiming({ stages: [] }), null);
|
|
});
|
|
|
|
test('control plane accepts every plugin page-action timing code', () => {
|
|
const actions = [
|
|
'cleanup_completed_lifecycle_dialog',
|
|
'create_split_child_live',
|
|
'create_split_parent_live',
|
|
'export_confirmation_sources',
|
|
'inspect_operation_context',
|
|
'live_submit_approved',
|
|
'live_submit_lifecycle_operation',
|
|
'live_submit_team_batch_approved',
|
|
'open_order_form',
|
|
'open_team_batch_form',
|
|
'ping_order_frame',
|
|
'ping_team_batch_frame',
|
|
'preflight_lifecycle_operation',
|
|
'preflight_raw_instruction',
|
|
'preflight_team_batch_native',
|
|
'prepare_lifecycle_operation',
|
|
'resolve_lifecycle_operation',
|
|
'return_to_order_list',
|
|
'unknown_action',
|
|
'verify_lifecycle_operation',
|
|
'verify_order_marker',
|
|
'verify_team_batch_receipts'
|
|
];
|
|
const sanitized = sanitizeOperationTiming({
|
|
schema_version: OPERATION_TIMING_SCHEMA_VERSION,
|
|
status: 'completed',
|
|
total_ms: 1,
|
|
stages: [{
|
|
stage: 'browser_execution',
|
|
status: 'completed',
|
|
duration_ms: 1,
|
|
details: actions.map((action) => ({
|
|
step: `action_roundtrip.${action}`,
|
|
status: 'completed',
|
|
duration_ms: 1,
|
|
counters: { call_count: 1 }
|
|
}))
|
|
}]
|
|
});
|
|
|
|
const details = sanitized?.stages as Array<{ details?: Array<{ step: string }> }>;
|
|
assert.deepEqual(details[0]?.details?.map((detail) => detail.step), actions.map((action) => `action_roundtrip.${action}`));
|
|
});
|
|
|
|
test('control plane keeps the privacy-safe orchestration breakdown used to locate unattributed time', () => {
|
|
const safeDetails = [
|
|
{ step: 'storage_prune.local_history', counters: { removed_result_count: 8, protected_root_count: 2 } },
|
|
{ step: 'storage_read.result_state', counters: { result_count: 12, execution_count: 12, timing_count: 1 } },
|
|
{ step: 'storage_write.result_state', counters: { call_count: 1 } },
|
|
{ step: 'tab_lookup.cache_hit', counters: { cache_hit_count: 1 } },
|
|
{ step: 'preflight_lookup.parallel_wall', counters: { request_count: 4 } },
|
|
{ step: 'preflight_product_linkage', counters: { sample_count: 5, stable_sample_count: 4 } },
|
|
{ step: 'preflight_submit_intercept', counters: { submit_count: 1, quiet_wait_ms: 150 } }
|
|
];
|
|
const sanitized = sanitizeOperationTiming({
|
|
schema_version: OPERATION_TIMING_SCHEMA_VERSION,
|
|
status: 'completed',
|
|
total_ms: 10,
|
|
stages: [{
|
|
stage: 'preflight',
|
|
status: 'completed',
|
|
duration_ms: 10,
|
|
details: safeDetails.map((detail) => ({ ...detail, status: 'completed', duration_ms: 1 }))
|
|
}]
|
|
});
|
|
const stages = sanitized?.stages as Array<{ details?: Array<{ step: string }> }>;
|
|
assert.deepEqual(stages[0]?.details?.map((detail) => detail.step), safeDetails.map((detail) => detail.step));
|
|
|
|
const rejected = sanitizeOperationTiming({
|
|
schema_version: OPERATION_TIMING_SCHEMA_VERSION,
|
|
status: 'completed',
|
|
total_ms: 1,
|
|
stages: [{
|
|
stage: 'preflight',
|
|
status: 'completed',
|
|
duration_ms: 1,
|
|
details: [{ step: 'storage_read.customer_value', duration_ms: 1, counters: { customer_name: 1 } }]
|
|
}]
|
|
});
|
|
const rejectedStages = rejected?.stages as Array<{ details?: Array<unknown> }>;
|
|
assert.equal(rejectedStages[0]?.details, undefined);
|
|
});
|