test: define safe and audit resource rename contract

This commit is contained in:
2026-07-27 14:03:45 +08:00
parent fcfe1b6a5b
commit 7a35bfe58c
4 changed files with 65 additions and 5 deletions

View File

@@ -0,0 +1,43 @@
# Task 1 Report: Safe and Audit Resource Rename Contracts
## Scope
Added only RED contract tests. No production source, database table, or data migration code was changed.
## Contract Coverage
- Backend resource catalogue: `safe_rule`, `safe_event`, `safe_inspection`, and `safe_event_disposal` under `safety`; `audit_operation_log`, `audit_export_log`, and `audit_approval` under `audit`.
- Backend routes: `/safety/safe_*`, the safe-event disposal action, `/audit/audit_*`, and the audit-approval action.
- Frontend resource definitions: required `safe_event` and `audit_approval` resource/path declarations.
## RED Evidence
### Backend
Command run from `backend/api`:
```powershell
$env:GIN_MODE='release'; go test ./internal/logic/platform ./internal/routers -run 'Test.*(Safe|Audit)' -v
```
Result: **failed as expected** (exit code 1).
- `TestSafeAndAuditResourceContracts` reports the missing `safety/safe_rule` contract; the current catalogue still defines `saf_rule` (and the other historical `saf_*`/`aud_*` names).
- `TestPlatformDeviceSafetyCommerceAndDeliveryRoutesFollowTheirContracts` reports unregistered `/heqi/platform/v1/safety/safe_*` routes and the `safe_event` disposal route.
- `TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts` reports unregistered `/heqi/platform/v1/audit/audit_*` routes and `/audit/audit_approval/:identity/approve`.
### Frontend
Command run from `frontend/platform_admin`:
```powershell
node --test scripts/audit-check.test.mjs
```
Result: **failed as expected** (exit code 1; 6 passing, 1 failing).
- The new `资源定义使用 safe 和 audit 前缀` test fails because `src/api/resources.ts` currently defines `saf_event` at `/safety/saf_event`; it therefore does not match the required `safe_event` declaration. The required `audit_approval` declaration remains absent as well.
## 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.

View File

@@ -25,12 +25,21 @@ import (
func TestExpectedResources(t *testing.T) {
assertContract(t, ExpectedResources(), "gas", "gas_basic", Writable, "list")
assertContract(t, ExpectedResources(), "safety", "saf_event", Writable, "list")
assertContract(t, ExpectedResources(), "ec", "ec_order_item", Writable, "list")
assertContract(t, ExpectedResources(), "wallet", "wallet_ledger", ReadOnly, "list")
assertContract(t, ExpectedResources(), "delivery", "delivery_track_point", ReadOnly, "list")
}
func TestSafeAndAuditResourceContracts(t *testing.T) {
assertContract(t, ExpectedResources(), "safety", "safe_rule", Writable, "list")
assertContract(t, ExpectedResources(), "safety", "safe_event", Writable, "list")
assertContract(t, ExpectedResources(), "safety", "safe_inspection", Writable, "list")
assertContract(t, ExpectedResources(), "safety", "safe_event_disposal", AppendOnly, "list")
assertContract(t, ExpectedResources(), "audit", "audit_operation_log", ReadOnly, "list")
assertContract(t, ExpectedResources(), "audit", "audit_export_log", ReadOnly, "list")
assertContract(t, ExpectedResources(), "audit", "audit_approval", ReadOnly, "list")
}
func TestResourceDefinitionAllowsOnlySupportedMethods(t *testing.T) {
if (ResourceDefinition{Mode: ReadOnly}).Allows(http.MethodPost) {
t.Fatal("readonly allows POST")

View File

@@ -101,7 +101,7 @@ func TestPlatformDeviceSafetyCommerceAndDeliveryRoutesFollowTheirContracts(t *te
for _, resource := range []string{
"/device/dev_smart_cylinder_valve", "/device/dev_device_binding",
"/safety/saf_rule", "/safety/saf_event", "/safety/saf_inspection",
"/safety/safe_rule", "/safety/safe_event", "/safety/safe_inspection",
"/ec/ec_category", "/ec/ec_product", "/ec/ec_product_attribute", "/ec/ec_product_image", "/ec/ec_cart", "/ec/ec_order", "/ec/ec_order_item", "/ec/ec_review",
"/delivery/delivery_task", "/delivery/delivery_track",
} {
@@ -126,7 +126,7 @@ func TestPlatformDeviceSafetyCommerceAndDeliveryRoutesFollowTheirContracts(t *te
}
}
disposal := "/heqi/platform/v1/safety/saf_event/:identity/disposals"
disposal := "/heqi/platform/v1/safety/safe_event/:identity/disposals"
assertRouteMethods(t, routes, disposal, http.MethodPost)
if routes[disposal][http.MethodDelete] {
t.Fatal("safety event disposals must be append-only")
@@ -157,7 +157,7 @@ func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T)
for _, resource := range []string{
"/wallet/wallet", "/wallet/wallet_ledger", "/wallet/wallet_recharge", "/wallet/wallet_withdrawal",
"/report/report", "/report/report_item", "/report/report_metric_snapshot",
"/audit/aud_operation_log", "/audit/aud_export_log", "/audit/aud_approval",
"/audit/audit_operation_log", "/audit/audit_export_log", "/audit/audit_approval",
} {
path := "/heqi/platform/v1" + resource
assertRouteMethods(t, routes, path, http.MethodGet)
@@ -169,7 +169,7 @@ func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T)
}
}
assertRouteMethods(t, routes, "/heqi/platform/v1/audit/aud_approval/:identity/approve", http.MethodPost)
assertRouteMethods(t, routes, "/heqi/platform/v1/audit/audit_approval/:identity/approve", http.MethodPost)
}
func assertRouteMethods(t *testing.T, routes map[string]map[string]bool, path string, methods ...string) {

View File

@@ -1,7 +1,15 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import { auditPlatform, scanInternalIdLeaks } from './audit-check.mjs';
const resourcesSource = readFileSync('src/api/resources.ts', 'utf8');
test('资源定义使用 safe 和 audit 前缀', () => {
assert.match(resourcesSource, /define\('safe_event', '\/safety\/safe_event'/);
assert.match(resourcesSource, /define\('audit_approval', '\/audit\/audit_approval'/);
});
test('只读页面将状态变更视为违规写操作', () => {
const failures = auditPlatform({
manifest: { resources: [], routes: [] },