M9.2-M9.5: Add improvement pipeline, community workflow, transitive integrity, and supply chain attestation
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Tests for M9.4 transitive integrity verification."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from cmdforge.integrity import (
|
||||
IntegrityNode,
|
||||
IntegrityChain,
|
||||
compute_tool_hash,
|
||||
build_integrity_chain,
|
||||
verify_integrity,
|
||||
)
|
||||
from cmdforge.tool import Tool, PromptStep, ToolStep
|
||||
|
||||
|
||||
class TestComputeToolHash:
|
||||
def test_same_tool_same_hash(self):
|
||||
tool = Tool(name="test", version="1.0.0")
|
||||
assert compute_tool_hash(tool) == compute_tool_hash(tool)
|
||||
|
||||
def test_different_tools_different_hash(self):
|
||||
a = Tool(name="a", version="1.0.0")
|
||||
b = Tool(name="b", version="1.0.0")
|
||||
assert compute_tool_hash(a) != compute_tool_hash(b)
|
||||
|
||||
def test_version_change_changes_hash(self):
|
||||
v1 = Tool(name="test", version="1.0.0")
|
||||
v2 = Tool(name="test", version="2.0.0")
|
||||
assert compute_tool_hash(v1) != compute_tool_hash(v2)
|
||||
|
||||
|
||||
class TestIntegrityChain:
|
||||
def test_single_tool_chain(self):
|
||||
tool = Tool(name="solo", version="1.0.0")
|
||||
chain = build_integrity_chain(tool)
|
||||
assert chain.root.name == "solo"
|
||||
assert chain.is_valid
|
||||
|
||||
def test_chain_with_unresolved_dep(self):
|
||||
tool = Tool(
|
||||
name="parent",
|
||||
version="1.0.0",
|
||||
steps=[ToolStep(tool="missing-dep", output_var="x")],
|
||||
output="{x}",
|
||||
)
|
||||
chain = build_integrity_chain(tool)
|
||||
assert "missing-dep" not in chain.nodes
|
||||
# Chain is still valid because the dependency hash is "unresolved"
|
||||
# and there's no node to compare against
|
||||
assert chain.is_valid
|
||||
|
||||
def test_chain_valid_with_resolved_dep(self, tmp_path):
|
||||
with patch("cmdforge.tool.TOOLS_DIR", tmp_path / ".cmdforge"):
|
||||
from cmdforge.tool import save_tool
|
||||
|
||||
child = Tool(name="child", version="1.0.0")
|
||||
save_tool(child)
|
||||
|
||||
parent = Tool(
|
||||
name="parent",
|
||||
version="1.0.0",
|
||||
steps=[ToolStep(tool="child", output_var="x")],
|
||||
output="{x}",
|
||||
)
|
||||
save_tool(parent)
|
||||
|
||||
chain = build_integrity_chain(parent)
|
||||
assert "child" in chain.nodes
|
||||
assert chain.is_valid
|
||||
|
||||
|
||||
class TestVerifyIntegrity:
|
||||
def test_valid_chain(self):
|
||||
chain = IntegrityChain(
|
||||
root=IntegrityNode(name="root", content_hash="abc"),
|
||||
nodes={"root": IntegrityNode(name="root", content_hash="abc")},
|
||||
)
|
||||
assert verify_integrity(chain)
|
||||
|
||||
def test_tampered_chain(self):
|
||||
chain = IntegrityChain(
|
||||
root=IntegrityNode(
|
||||
name="root",
|
||||
content_hash="abc",
|
||||
dependencies=["dep"],
|
||||
dependency_hashes={"dep": "expected_hash"},
|
||||
),
|
||||
nodes={
|
||||
"root": IntegrityNode(
|
||||
name="root",
|
||||
content_hash="abc",
|
||||
dependencies=["dep"],
|
||||
dependency_hashes={"dep": "expected_hash"},
|
||||
),
|
||||
"dep": IntegrityNode(name="dep", content_hash="different_hash"),
|
||||
},
|
||||
)
|
||||
assert not chain.is_valid
|
||||
Reference in New Issue
Block a user