mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
test(multiplayer): add release promotion gate
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from verify_release_gate import validate_release_report
|
||||
|
||||
|
||||
def valid_report():
|
||||
return {
|
||||
"release_id": "release-2026-09-01-001",
|
||||
"from_stage": "development",
|
||||
"to_stage": "internal",
|
||||
"slo_passed": True,
|
||||
"security_passed": True,
|
||||
"cost_passed": True,
|
||||
"rollback_rehearsed": True,
|
||||
"playtests": {"eu_passed": True, "na_passed": True},
|
||||
"legacy": {"phase6_passed": True, "enet_passed": True},
|
||||
}
|
||||
|
||||
|
||||
class ReleaseGateTest(unittest.TestCase):
|
||||
def test_accepts_one_complete_promotion(self):
|
||||
self.assertEqual(validate_release_report(valid_report()), ("development", "internal"))
|
||||
|
||||
def test_rejects_skipped_stage(self):
|
||||
report = valid_report()
|
||||
report["to_stage"] = "casual"
|
||||
with self.assertRaises(ValueError):
|
||||
validate_release_report(report)
|
||||
|
||||
def test_rejects_missing_or_false_gate(self):
|
||||
report = valid_report()
|
||||
del report["playtests"]["na_passed"]
|
||||
with self.assertRaises(ValueError):
|
||||
validate_release_report(report)
|
||||
report = valid_report()
|
||||
report["cost_passed"] = 1
|
||||
with self.assertRaises(ValueError):
|
||||
validate_release_report(report)
|
||||
|
||||
def test_rejects_unknown_stage_and_blank_release(self):
|
||||
report = valid_report()
|
||||
report["release_id"] = " "
|
||||
with self.assertRaises(ValueError):
|
||||
validate_release_report(report)
|
||||
report = valid_report()
|
||||
report["from_stage"] = "experimental"
|
||||
with self.assertRaises(ValueError):
|
||||
validate_release_report(report)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail-closed validation for a multiplayer promotion evidence report."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
|
||||
STAGES = ("development", "internal", "casual-canary", "casual", "provisional-ranked", "ranked")
|
||||
|
||||
|
||||
def _required_true(report: dict[str, Any], key: str) -> None:
|
||||
value: Any = report
|
||||
for part in key.split("."):
|
||||
if not isinstance(value, dict) or part not in value:
|
||||
raise ValueError(f"missing gate: {key}")
|
||||
value = value[part]
|
||||
if value is not True:
|
||||
raise ValueError(f"gate did not pass: {key}")
|
||||
|
||||
|
||||
def validate_release_report(report: dict[str, Any]) -> tuple[str, str]:
|
||||
if not isinstance(report, dict):
|
||||
raise ValueError("release report must be an object")
|
||||
source = report.get("from_stage")
|
||||
target = report.get("to_stage")
|
||||
if source not in STAGES or target not in STAGES:
|
||||
raise ValueError("from_stage and to_stage must be known release stages")
|
||||
if STAGES.index(target) != STAGES.index(source) + 1:
|
||||
raise ValueError(f"promotion must advance exactly one stage: {source!r} -> {target!r}")
|
||||
if not isinstance(report.get("release_id"), str) or not report["release_id"].strip():
|
||||
raise ValueError("release_id is required")
|
||||
for gate in (
|
||||
"slo_passed", "security_passed", "cost_passed", "rollback_rehearsed",
|
||||
"playtests.eu_passed", "playtests.na_passed", "legacy.phase6_passed",
|
||||
"legacy.enet_passed",
|
||||
):
|
||||
_required_true(report, gate)
|
||||
return source, target
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 2:
|
||||
print(f"usage: {sys.argv[0]} report.json", file=sys.stderr)
|
||||
return 2
|
||||
try:
|
||||
with open(sys.argv[1], encoding="utf-8") as handle:
|
||||
source, target = validate_release_report(json.load(handle))
|
||||
except (OSError, ValueError, json.JSONDecodeError) as error:
|
||||
print(f"release gate failed: {error}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"release gate passed: {source} -> {target}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user