"""Tests for act.dag module.""" import time import pytest from act.dag import validate_dag from act.errors import ACTCapabilityError, ACTDAGError from act.ledger import ACTLedger from act.token import ACTRecord, Capability, TaskClaim def make_record(jti, pred=None, exec_act="do.thing", exec_ts=None, cap=None): """Helper to create a minimal ACTRecord.""" return ACTRecord( alg="EdDSA", kid="k", iss="a", sub="b", aud="b", iat=1772064000, exp=1772064900, jti=jti, task=TaskClaim(purpose="t"), cap=cap or [Capability(action="do.thing")], exec_act=exec_act, pred=pred or [], exec_ts=exec_ts or 1772064100, status="completed", ) class TestDAGValidation: def test_root_task(self): ledger = ACTLedger() r = make_record("root-1") validate_dag(r, ledger) def test_child_with_parent(self): ledger = ACTLedger() parent = make_record("parent-1", exec_ts=1772064050) ledger.append(parent) child = make_record("child-1", pred=["parent-1"], exec_ts=1772064100) validate_dag(child, ledger) def test_fan_in(self): ledger = ACTLedger() p1 = make_record("p1", exec_ts=1772064050) p2 = make_record("p2", exec_ts=1772064060) ledger.append(p1) ledger.append(p2) child = make_record("child", pred=["p1", "p2"], exec_ts=1772064100) validate_dag(child, ledger) def test_duplicate_jti(self): ledger = ACTLedger() r = make_record("dup-1") ledger.append(r) r2 = make_record("dup-1") with pytest.raises(ACTDAGError, match="Duplicate"): validate_dag(r2, ledger) def test_missing_parent(self): ledger = ACTLedger() r = make_record("orphan", pred=["nonexistent"]) with pytest.raises(ACTDAGError, match="not found"): validate_dag(r, ledger) def test_self_cycle(self): ledger = ACTLedger() r = make_record("cycle", pred=["cycle"]) with pytest.raises(ACTDAGError, match="cycle"): validate_dag(r, ledger) def test_indirect_cycle(self): ledger = ACTLedger() # a -> b -> a would be a cycle a = make_record("a", pred=["b"], exec_ts=1772064100) b = make_record("b", pred=["a"], exec_ts=1772064100) ledger.append(b) # When validating a, following pred leads to b, # which has pred=["a"] — cycle! with pytest.raises(ACTDAGError, match="cycle"): validate_dag(a, ledger) def test_temporal_ordering_violation(self): ledger = ACTLedger() parent = make_record("parent", exec_ts=1772064200) ledger.append(parent) # Child's exec_ts is way before parent child = make_record("child", pred=["parent"], exec_ts=1772064100) with pytest.raises(ACTDAGError, match="Temporal"): validate_dag(child, ledger) def test_temporal_within_tolerance(self): ledger = ACTLedger() parent = make_record("parent", exec_ts=1772064120) ledger.append(parent) # Child exec_ts is slightly before parent but within 30s tolerance child = make_record("child", pred=["parent"], exec_ts=1772064100) validate_dag(child, ledger) def test_bad_exec_act(self): ledger = ACTLedger() r = make_record("bad", exec_act="not.authorized", cap=[Capability(action="do.thing")]) with pytest.raises(ACTCapabilityError): validate_dag(r, ledger)