fix(tests): stabilize contract digests across line endings

This commit is contained in:
rookiestar28
2026-07-11 17:25:34 +08:00
parent a28316ed38
commit c1aca449c5
13 changed files with 147 additions and 26 deletions
+10
View File
@@ -0,0 +1,10 @@
/** Portable digest helpers for governed UTF-8 text contracts. */
import crypto from "node:crypto";
import fs from "node:fs";
export function stableTextDigest(filePath) {
// IMPORTANT: normalize text newlines; raw hashing breaks frozen contracts after Windows checkout.
const normalized = fs.readFileSync(filePath, "utf8").replace(/\r\n?/g, "\n");
return crypto.createHash("sha256").update(normalized, "utf8").digest("hex");
}
+23
View File
@@ -0,0 +1,23 @@
"""Portable digest and write helpers for governed text contracts."""
from __future__ import annotations
import hashlib
from pathlib import Path
def normalize_text_newlines(payload: bytes) -> bytes:
"""Return text bytes with CRLF and lone CR represented as LF."""
# IMPORTANT: normalize text newlines; raw hashing breaks frozen contracts after Windows checkout.
return payload.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
def stable_text_digest(path: Path) -> str:
"""Hash governed text independently of checkout newline representation."""
return hashlib.sha256(normalize_text_newlines(path.read_bytes())).hexdigest()
def write_text_lf(path: Path, text: str) -> None:
"""Write UTF-8 contract text with explicit LF newlines on every platform."""
with path.open("w", encoding="utf-8", newline="\n") as handle:
handle.write(text)
+5 -5
View File
@@ -14,6 +14,8 @@ ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from scripts.contract_digest import stable_text_digest, write_text_lf # noqa: E402
CONTRACT_PATH = ROOT / "tests" / "api_config_contract_r221.json"
@@ -126,10 +128,8 @@ def build_contract() -> dict[str, Any]:
"tests.test_r219_exception_boundary_phase2",
],
},
"r220_route_contract_sha256": hashlib.sha256(
route_contract.read_bytes()
).hexdigest(),
"openapi_sha256": hashlib.sha256(openapi.read_bytes()).hexdigest(),
"r220_route_contract_sha256": stable_text_digest(route_contract),
"openapi_sha256": stable_text_digest(openapi),
}
@@ -139,7 +139,7 @@ def main() -> int:
args = parser.parse_args()
actual = build_contract()
if args.write_baseline:
CONTRACT_PATH.write_text(_canonical_json(actual), encoding="utf-8")
write_text_lf(CONTRACT_PATH, _canonical_json(actual))
print(f"API-CONFIG-CONTRACT-WRITTEN: {CONTRACT_PATH}")
return 0
expected = json.loads(CONTRACT_PATH.read_text(encoding="utf-8"))
+4 -4
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
import argparse
import hashlib
import inspect
import json
import sys
@@ -15,6 +14,8 @@ ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from scripts.contract_digest import stable_text_digest, write_text_lf # noqa: E402
CONTRACT_PATH = ROOT / "tests" / "api_route_contract_r220.json"
@@ -122,7 +123,6 @@ def build_contract() -> dict[str, Any]:
"trace_handler",
)
}
openapi_bytes = (ROOT / "docs" / "openapi.yaml").read_bytes()
return {
"schema_version": 1,
"registration_order": [
@@ -149,7 +149,7 @@ def build_contract() -> dict[str, Any]:
"families": families,
"facade_signatures": facade,
"facade_metadata": metadata,
"openapi_sha256": hashlib.sha256(openapi_bytes).hexdigest(),
"openapi_sha256": stable_text_digest(ROOT / "docs" / "openapi.yaml"),
}
@@ -163,7 +163,7 @@ def main() -> int:
args = parser.parse_args()
actual = build_contract()
if args.write_baseline:
CONTRACT_PATH.write_text(_canonical_json(actual), encoding="utf-8")
write_text_lf(CONTRACT_PATH, _canonical_json(actual))
print(f"API-ROUTE-CONTRACT-WRITTEN: {CONTRACT_PATH}")
return 0
expected = json.loads(CONTRACT_PATH.read_text(encoding="utf-8"))
+5 -3
View File
@@ -4,7 +4,6 @@ from __future__ import annotations
import argparse
import ast
import hashlib
import inspect
import json
import sys
@@ -15,6 +14,9 @@ from typing import Any
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from scripts.contract_digest import stable_text_digest, write_text_lf # noqa: E402
CONTRACT_PATH = ROOT / "tests" / "connector_router_contract_r222.json"
@@ -75,7 +77,7 @@ def build_contract() -> dict[str, Any]:
"api_route_contract_r220.json",
):
path = ROOT / "tests" / filename
digests[filename] = hashlib.sha256(path.read_bytes()).hexdigest()
digests[filename] = stable_text_digest(path)
return {
"schema_version": 1,
"constructor_signature": str(inspect.signature(CommandRouter)),
@@ -116,7 +118,7 @@ def main() -> int:
args = parser.parse_args()
actual = build_contract()
if args.write_baseline:
CONTRACT_PATH.write_text(_canonical_json(actual), encoding="utf-8")
write_text_lf(CONTRACT_PATH, _canonical_json(actual))
print(f"CONNECTOR-ROUTER-CONTRACT-WRITTEN: {CONTRACT_PATH}")
return 0
expected = json.loads(CONTRACT_PATH.read_text(encoding="utf-8"))
@@ -1,9 +1,9 @@
/** Verify the frozen R224 Settings/API frontend contract. */
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { stableTextDigest } from "./contract_digest.mjs";
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const CONTRACT_PATH = path.join(ROOT, "web", "tests", "fixtures", "frontend_decomposition_contract_r224.json");
@@ -42,7 +42,7 @@ function methodSignatures(source) {
}
function digest(relativePath) {
return crypto.createHash("sha256").update(fs.readFileSync(path.join(ROOT, relativePath))).digest("hex");
return stableTextDigest(path.join(ROOT, relativePath));
}
export function buildContract() {
+5 -5
View File
@@ -4,7 +4,6 @@ from __future__ import annotations
import argparse
import ast
import hashlib
import inspect
import json
import sys
@@ -15,6 +14,9 @@ from typing import Any
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from scripts.contract_digest import stable_text_digest, write_text_lf # noqa: E402
CONTRACT_PATH = ROOT / "tests" / "platform_adapter_contract_r223.json"
@@ -131,9 +133,7 @@ def build_contract() -> dict[str, Any]:
"tests.test_f74_reply_visibility_policy",
"tests.security.test_s80_connector_ingress",
],
"router_contract_digest": hashlib.sha256(
router_contract.read_bytes()
).hexdigest(),
"router_contract_digest": stable_text_digest(router_contract),
}
@@ -143,7 +143,7 @@ def main() -> int:
args = parser.parse_args()
actual = build_contract()
if args.write_baseline:
CONTRACT_PATH.write_text(_canonical_json(actual), encoding="utf-8")
write_text_lf(CONTRACT_PATH, _canonical_json(actual))
print(f"PLATFORM-ADAPTER-CONTRACT-WRITTEN: {CONTRACT_PATH}")
return 0
expected = json.loads(CONTRACT_PATH.read_text(encoding="utf-8"))