CI / headless-tests (push) Canceled after 49s
- Godot 4.3 project scaffold with first-person guard, procedural world, interaction HUD, access inspection, radio forms, debug overlay, shift review - Simulation core: ShiftCoordinator, VenueScenario, EventScheduler, NPCController/FSM, AccessVerifier, EscalationManager, RadioSystem, IncidentRecorder, ReputationEvaluator, CrowdSystem, EventBus - Three authored scenarios (Hollywood Blvd building, The Joke Shop, TV studio) - Custom headless test runner (25 tests) replacing editor-dependent GUT, plus GitHub Actions CI - Documentation workflow live: README, PLAN (tech stack), TODO/DONE, MEMORY
121 lines
5.7 KiB
GDScript
121 lines
5.7 KiB
GDScript
extends RefCounted
|
|
## Headless tests for AccessVerifier (ADR-0003, Access Policy).
|
|
|
|
const Rules := preload("res://src/core/rules.gd")
|
|
const AccessVerifier := preload("res://src/core/access_verifier.gd")
|
|
const AccessPolicy := preload("res://src/core/access_policy.gd")
|
|
const AccessArtifact := preload("res://src/core/access_artifact.gd")
|
|
|
|
var verifier := AccessVerifier.new()
|
|
|
|
func test_wristband_valid_grants() -> bool:
|
|
var policy := AccessPolicy.new()
|
|
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
|
|
var artifacts: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.VALID, {"color": "red", "date": "2026-02-13", "event": "gala"}),
|
|
]
|
|
var result := verifier.verify(artifacts, policy)
|
|
return TestUtils.check_eq(result["granted"], true, "valid wristband should grant access")
|
|
|
|
func test_wristband_expired_denies() -> bool:
|
|
var policy := AccessPolicy.new()
|
|
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
|
|
var artifacts: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.EXPIRED, {}),
|
|
]
|
|
var result := verifier.verify(artifacts, policy)
|
|
return TestUtils.check_eq(result["granted"], false, "expired wristband must deny")
|
|
|
|
func test_wristband_counterfeit_denies() -> bool:
|
|
var policy := AccessPolicy.new()
|
|
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
|
|
var artifacts: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.COUNTERFEIT, {}),
|
|
]
|
|
var result := verifier.verify(artifacts, policy)
|
|
return TestUtils.check_eq(result["granted"], false, "counterfeit wristband must deny")
|
|
|
|
func test_missing_wristband_denies() -> bool:
|
|
var policy := AccessPolicy.new()
|
|
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
|
|
var result := verifier.verify([], policy)
|
|
return TestUtils.check_eq(result["granted"], false, "missing wristband must deny")
|
|
|
|
func test_recognition_grants_without_wristband() -> bool:
|
|
## PLAN.md: "The guard already knows and recognizes them."
|
|
var policy := AccessPolicy.new()
|
|
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
|
|
var artifacts: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.RECOGNITION, Rules.ArtifactStatus.VALID, {}),
|
|
]
|
|
var result := verifier.verify(artifacts, policy)
|
|
return TestUtils.check_eq(result["granted"], true, "recognition should grant access")
|
|
|
|
func test_recognition_expired_does_not_grant() -> bool:
|
|
var policy := AccessPolicy.new()
|
|
policy.required_artifacts = { Rules.AccessKind.WRISTBAND: true }
|
|
var artifacts: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.RECOGNITION, Rules.ArtifactStatus.MISSING, {}),
|
|
]
|
|
var result := verifier.verify(artifacts, policy)
|
|
return TestUtils.check_eq(result["granted"], false, "missing recognition should not grant under band policy")
|
|
|
|
func test_joke_shop_id_and_ticket() -> bool:
|
|
var policy := AccessPolicy.new()
|
|
policy.required_artifacts = { Rules.AccessKind.ID: true, Rules.AccessKind.TICKET: true }
|
|
policy.minimum_age = 21
|
|
var ok: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.ID, Rules.ArtifactStatus.VALID, {"age": 24}),
|
|
AccessArtifact.new(Rules.AccessKind.TICKET, Rules.ArtifactStatus.VALID, {}),
|
|
]
|
|
var denied_underage: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.ID, Rules.ArtifactStatus.VALID, {"age": 19}),
|
|
AccessArtifact.new(Rules.AccessKind.TICKET, Rules.ArtifactStatus.VALID, {}),
|
|
]
|
|
var denied_no_ticket: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.ID, Rules.ArtifactStatus.VALID, {"age": 24}),
|
|
]
|
|
var ok_result := verifier.verify(ok, policy)
|
|
var denied_underage_result := verifier.verify(denied_underage, policy)
|
|
var denied_no_ticket_result := verifier.verify(denied_no_ticket, policy)
|
|
var a := TestUtils.check_eq(ok_result["granted"], true, "valid ID + ticket grants")
|
|
var b := TestUtils.check_eq(denied_underage_result["granted"], false, "underage denied")
|
|
var c := TestUtils.check_eq(denied_no_ticket_result["granted"], false, "no ticket denied")
|
|
return a and b and c
|
|
|
|
func test_joke_shop_requires_wand_screen() -> bool:
|
|
var policy := AccessPolicy.new()
|
|
policy.required_artifacts = { Rules.AccessKind.ID: true, Rules.AccessKind.TICKET: true }
|
|
policy.wand_screening_required = true
|
|
var artifacts: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.ID, Rules.ArtifactStatus.VALID, {"age": 25}),
|
|
AccessArtifact.new(Rules.AccessKind.TICKET, Rules.ArtifactStatus.VALID, {}),
|
|
]
|
|
var result := verifier.verify(artifacts, policy)
|
|
return TestUtils.check_eq(result["granted"], false, "unscreened guest denied when screening required")
|
|
|
|
func test_tv_studio_any_valid_grants() -> bool:
|
|
## OR-admission: any single valid artifact among several kinds grants entry.
|
|
var policy := AccessPolicy.new()
|
|
policy.required_artifacts = {
|
|
Rules.AccessKind.CREDENTIAL: true,
|
|
Rules.AccessKind.WRISTBAND: true,
|
|
Rules.AccessKind.TICKET: true,
|
|
Rules.AccessKind.GUEST_LIST: true,
|
|
}
|
|
policy.grant_if_any_required = true
|
|
var with_wristband: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.VALID, {}),
|
|
]
|
|
var with_guest_list: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.GUEST_LIST, Rules.ArtifactStatus.VALID, {}),
|
|
]
|
|
var all_fake: Array[AccessArtifact] = [
|
|
AccessArtifact.new(Rules.AccessKind.WRISTBAND, Rules.ArtifactStatus.COUNTERFEIT, {}),
|
|
AccessArtifact.new(Rules.AccessKind.TICKET, Rules.ArtifactStatus.EXPIRED, {}),
|
|
]
|
|
var a := TestUtils.check_eq(verifier.verify(with_wristband, policy)["granted"], true, "wristband grants")
|
|
var b := TestUtils.check_eq(verifier.verify(with_guest_list, policy)["granted"], true, "guest list grants")
|
|
var c := TestUtils.check_eq(verifier.verify(all_fake, policy)["granted"], false, "all invalid denied")
|
|
return a and b and c
|