← Files Graph ModeARCHIVED FILE

skills/graph/scripts/test_validate_graph_state.py

4.58 KB · Oct 2, 2026 · 00:29 UTC

↓ Download file

#!/usr/bin/env python3
"""Unit tests for validate_graph_state.py."""

from __future__ import annotations

import copy
import unittest

from validate_graph_state import validate_state


def node(
    node_id: str,
    *,
    status: str = "PASSED",
    depends_on: list[str] | None = None,
    access: str = "read-only",
    approval: str = "not-required",
    evidence: list[str] | None = None,
    attempt: int | None = None,
    max_attempts: int = 1,
    status_history: list[str] | None = None,
) -> dict:
    if status_history is None:
        status_history = {
            "PENDING": ["PENDING"],
            "READY": ["PENDING", "READY"],
            "RUNNING": ["PENDING", "READY", "RUNNING"],
            "PASSED": ["PENDING", "READY", "RUNNING", "PASSED"],
            "FAILED": ["PENDING", "READY", "RUNNING", "FAILED"],
            "BLOCKED": ["PENDING", "BLOCKED"],
            "NEEDS_APPROVAL": ["PENDING", "NEEDS_APPROVAL"],
            "SKIPPED": ["PENDING", "SKIPPED"],
        }[status]
    if attempt is None:
        attempt = status_history.count("RUNNING")
    return {
        "id": node_id,
        "title": node_id.replace("-", " ").title(),
        "kind": "verify",
        "role": "tool",
        "access": access,
        "status": status,
        "status_history": status_history,
        "depends_on": depends_on or [],
        "expected_output": "Verified result",
        "evidence_required": True,
        "evidence": ["test output"] if evidence is None and status == "PASSED" else (evidence or []),
        "attempt": attempt,
        "max_attempts": max_attempts,
        "approval": approval,
    }


def state(*nodes: dict, status: str = "COMPLETE") -> dict:
    return {
        "schema_version": 1,
        "run_id": "graph-test",
        "task": "Test the validator",
        "status": status,
        "nodes": list(nodes),
    }


class GraphStateValidatorTests(unittest.TestCase):
    def assert_has_error(self, payload: dict, fragment: str) -> None:
        errors = validate_state(payload)
        self.assertTrue(any(fragment in error for error in errors), errors)

    def test_valid_complete_graph(self) -> None:
        payload = state(node("scope"), node("verify", depends_on=["scope"]))
        self.assertEqual(validate_state(payload), [])

    def test_duplicate_ids(self) -> None:
        self.assert_has_error(state(node("same"), node("same")), "duplicate node id")

    def test_missing_dependency(self) -> None:
        self.assert_has_error(state(node("verify", depends_on=["missing"])), "missing dependency")

    def test_dependency_cycle(self) -> None:
        payload = state(node("a", depends_on=["b"]), node("b", depends_on=["a"]))
        self.assert_has_error(payload, "dependency cycle detected")

    def test_attempt_exceeds_limit(self) -> None:
        retry_history = ["PENDING", "READY", "RUNNING", "FAILED", "READY", "RUNNING", "PASSED"]
        payload = state(node("verify", status_history=retry_history, attempt=2, max_attempts=1))
        self.assert_has_error(payload, "attempt exceeds max_attempts")

    def test_ready_before_dependency_resolves(self) -> None:
        payload = state(
            node("scope", status="RUNNING"),
            node("verify", status="READY", depends_on=["scope"]),
            status="RUNNING",
        )
        self.assert_has_error(payload, "active before dependencies resolved")

    def test_passed_evidence_is_required(self) -> None:
        payload = state(node("verify", evidence=[]))
        self.assert_has_error(payload, "passed node requires evidence")

    def test_external_write_requires_approval(self) -> None:
        payload = state(node("publish", access="external-write", approval="required"))
        self.assert_has_error(payload, "gated access requires granted approval")

    def test_external_write_with_approval_is_valid(self) -> None:
        payload = state(node("publish", access="external-write", approval="granted"))
        self.assertEqual(validate_state(payload), [])

    def test_illegal_transition(self) -> None:
        payload = state(node("verify", status_history=["PENDING", "PASSED"], attempt=0))
        self.assert_has_error(payload, "illegal transition PENDING -> PASSED")

    def test_terminal_run_consistency(self) -> None:
        payload = state(node("verify", status="RUNNING"), status="COMPLETE")
        self.assert_has_error(payload, "COMPLETE requires every node")

    def test_input_is_not_mutated(self) -> None:
        payload = state(node("scope"))
        original = copy.deepcopy(payload)
        validate_state(payload)
        self.assertEqual(payload, original)


if __name__ == "__main__":
    unittest.main()

SHA-256: 8b16d31a58305d25711e7093cab9ea274a5025350206b9552cd01b6a6cd72103