test: prohibit legacy safe and audit aliases

This commit is contained in:
2026-07-27 14:16:39 +08:00
parent 7a35bfe58c
commit 068b4e0ed9
4 changed files with 42 additions and 1 deletions

View File

@@ -41,3 +41,12 @@ Result: **failed as expected** (exit code 1; 6 passing, 1 failing).
## Handoff ## Handoff
The red baseline is intentional. The next task should rename production resource contracts, backend routes, and frontend definitions without preserving the historical public names. The red baseline is intentional. The next task should rename production resource contracts, backend routes, and frontend definitions without preserving the historical public names.
## Review Follow-up
The RED tests now also reject legacy `saf_*` and `aud_*` resource contracts, routes, and frontend definitions. This prevents a dual-registration implementation from satisfying only the new-name assertions. Frontend static coverage now checks all seven renamed resources, and the filtered route suite requires both `GET` and `POST` for the append-only safe-event disposal history endpoint.
Focused verification was rerun after these additions:
- Backend: the filtered suite remains RED (exit code 1), reporting both absent `safe_*`/`audit_*` routes and currently registered legacy `saf_*`/`aud_*` routes.
- Frontend: the focused suite remains RED (exit code 1; 6 passing, 1 failing), first reporting the missing `define('safe_rule', '/safety/safe_rule'...)` declaration. Once the new declarations exist, the anti-alias assertions will also reject any retained legacy definitions.

View File

@@ -38,6 +38,13 @@ func TestSafeAndAuditResourceContracts(t *testing.T) {
assertContract(t, ExpectedResources(), "audit", "audit_operation_log", ReadOnly, "list") assertContract(t, ExpectedResources(), "audit", "audit_operation_log", ReadOnly, "list")
assertContract(t, ExpectedResources(), "audit", "audit_export_log", ReadOnly, "list") assertContract(t, ExpectedResources(), "audit", "audit_export_log", ReadOnly, "list")
assertContract(t, ExpectedResources(), "audit", "audit_approval", ReadOnly, "list") assertContract(t, ExpectedResources(), "audit", "audit_approval", ReadOnly, "list")
for _, contract := range ExpectedResources() {
if (contract.Domain == "safety" && strings.HasPrefix(contract.Name, "saf_")) ||
(contract.Domain == "audit" && strings.HasPrefix(contract.Name, "aud_")) {
t.Fatalf("legacy resource contract %s/%s must not be registered", contract.Domain, contract.Name)
}
}
} }
func TestResourceDefinitionAllowsOnlySupportedMethods(t *testing.T) { func TestResourceDefinitionAllowsOnlySupportedMethods(t *testing.T) {

View File

@@ -127,10 +127,18 @@ func TestPlatformDeviceSafetyCommerceAndDeliveryRoutesFollowTheirContracts(t *te
} }
disposal := "/heqi/platform/v1/safety/safe_event/:identity/disposals" disposal := "/heqi/platform/v1/safety/safe_event/:identity/disposals"
assertRouteMethods(t, routes, disposal, http.MethodPost) assertRouteMethods(t, routes, disposal, http.MethodGet, http.MethodPost)
if routes[disposal][http.MethodDelete] { if routes[disposal][http.MethodDelete] {
t.Fatal("safety event disposals must be append-only") t.Fatal("safety event disposals must be append-only")
} }
for _, resource := range []string{"/safety/saf_rule", "/safety/saf_event", "/safety/saf_inspection"} {
path := "/heqi/platform/v1" + resource
assertNoRouteMethods(t, routes, path, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
assertNoRouteMethods(t, routes, path+"/:identity/status", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
}
assertNoRouteMethods(t, routes, "/heqi/platform/v1/safety/saf_event/:identity/disposals", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
} }
func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T) { func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T) {
@@ -170,6 +178,13 @@ func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T)
} }
assertRouteMethods(t, routes, "/heqi/platform/v1/audit/audit_approval/:identity/approve", http.MethodPost) assertRouteMethods(t, routes, "/heqi/platform/v1/audit/audit_approval/:identity/approve", http.MethodPost)
for _, resource := range []string{"/audit/aud_operation_log", "/audit/aud_export_log", "/audit/aud_approval"} {
path := "/heqi/platform/v1" + resource
assertNoRouteMethods(t, routes, path, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
assertNoRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
}
assertNoRouteMethods(t, routes, "/heqi/platform/v1/audit/aud_approval/:identity/approve", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
} }
func assertRouteMethods(t *testing.T, routes map[string]map[string]bool, path string, methods ...string) { func assertRouteMethods(t *testing.T, routes map[string]map[string]bool, path string, methods ...string) {

View File

@@ -6,8 +6,18 @@ import { auditPlatform, scanInternalIdLeaks } from './audit-check.mjs';
const resourcesSource = readFileSync('src/api/resources.ts', 'utf8'); const resourcesSource = readFileSync('src/api/resources.ts', 'utf8');
test('资源定义使用 safe 和 audit 前缀', () => { test('资源定义使用 safe 和 audit 前缀', () => {
assert.match(resourcesSource, /define\('safe_rule', '\/safety\/safe_rule'/);
assert.match(resourcesSource, /define\('safe_event', '\/safety\/safe_event'/); assert.match(resourcesSource, /define\('safe_event', '\/safety\/safe_event'/);
assert.match(resourcesSource, /define\('safe_inspection', '\/safety\/safe_inspection'/);
assert.match(resourcesSource, /define\('safe_event_disposal', '\/safety\/safe_event\/:identity\/disposals'/);
assert.match(resourcesSource, /define\('audit_operation_log', '\/audit\/audit_operation_log'/);
assert.match(resourcesSource, /define\('audit_export_log', '\/audit\/audit_export_log'/);
assert.match(resourcesSource, /define\('audit_approval', '\/audit\/audit_approval'/); assert.match(resourcesSource, /define\('audit_approval', '\/audit\/audit_approval'/);
assert.doesNotMatch(resourcesSource, /define\('saf_(?:rule|event|inspection|event_disposal)',/);
assert.doesNotMatch(resourcesSource, /action\('saf_event_disposal',/);
assert.doesNotMatch(resourcesSource, /define\('aud_(?:operation_log|export_log|approval)',/);
assert.doesNotMatch(resourcesSource, /\/audit\/aud_approval\/:identity\/approve/);
}); });
test('只读页面将状态变更视为违规写操作', () => { test('只读页面将状态变更视为违规写操作', () => {