Skip to content

Commit a8b0960

Browse files
committed
test: cover contract validation and repo scanner
1 parent a258f9f commit a8b0960

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

tests/test_contracts.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Tests for SourceOS contract validation and repo scanning commands."""
2+
3+
import json
4+
import pathlib
5+
import sys
6+
import tempfile
7+
import unittest
8+
9+
_REPO_ROOT = pathlib.Path(__file__).parent.parent
10+
sys.path.insert(0, str(_REPO_ROOT))
11+
12+
from sourceosctl.commands import contracts
13+
14+
15+
VALID_MANIFEST = {
16+
"repo": "SourceOS-Linux/sourceos-devtools",
17+
"domain": "tooling",
18+
"specVersion": "0.1.0",
19+
"ownedSchemas": [],
20+
"syncEngines": [],
21+
"sourceChannels": [],
22+
"policyClasses": ["high"],
23+
"auditEvents": ["devtools.contract.validated"],
24+
"dangerousSurfaces": ["devtools.schema.validation_bypass"],
25+
}
26+
27+
28+
class TestContractValidation(unittest.TestCase):
29+
def test_validate_repo_manifest_accepts_valid_manifest(self):
30+
errors = contracts.validate_repo_manifest(dict(VALID_MANIFEST))
31+
self.assertEqual(errors, [])
32+
33+
def test_validate_repo_manifest_rejects_missing_required_field(self):
34+
payload = dict(VALID_MANIFEST)
35+
payload.pop("repo")
36+
errors = contracts.validate_repo_manifest(payload)
37+
self.assertIn("missing required field: repo", errors)
38+
39+
def test_validate_repo_manifest_rejects_invalid_domain(self):
40+
payload = dict(VALID_MANIFEST)
41+
payload["domain"] = "unknown"
42+
errors = contracts.validate_repo_manifest(payload)
43+
self.assertTrue(any("domain must be one of" in error for error in errors))
44+
45+
def test_validate_repo_manifest_rejects_invalid_policy_class(self):
46+
payload = dict(VALID_MANIFEST)
47+
payload["policyClasses"] = ["root"]
48+
errors = contracts.validate_repo_manifest(payload)
49+
self.assertIn("invalid policy class: root", errors)
50+
51+
def test_contract_validate_valid_file(self):
52+
with tempfile.TemporaryDirectory() as tmp:
53+
path = pathlib.Path(tmp) / "manifest.json"
54+
path.write_text(json.dumps(VALID_MANIFEST), encoding="utf-8")
55+
args = type("Args", (), {"path": str(path), "json": True})()
56+
self.assertEqual(contracts.contract_validate(args), 0)
57+
58+
def test_contract_validate_bad_json(self):
59+
with tempfile.TemporaryDirectory() as tmp:
60+
path = pathlib.Path(tmp) / "manifest.json"
61+
path.write_text("not json", encoding="utf-8")
62+
args = type("Args", (), {"path": str(path), "json": True})()
63+
self.assertEqual(contracts.contract_validate(args), 1)
64+
65+
66+
class TestRepoScan(unittest.TestCase):
67+
def test_repo_scan_valid_manifest(self):
68+
with tempfile.TemporaryDirectory() as tmp:
69+
root = pathlib.Path(tmp)
70+
manifest_dir = root / ".sourceos"
71+
manifest_dir.mkdir()
72+
(manifest_dir / "manifest.json").write_text(json.dumps(VALID_MANIFEST), encoding="utf-8")
73+
args = type("Args", (), {"path": str(root), "json": True})()
74+
self.assertEqual(contracts.repo_scan(args), 0)
75+
76+
def test_repo_scan_missing_manifest(self):
77+
with tempfile.TemporaryDirectory() as tmp:
78+
args = type("Args", (), {"path": tmp, "json": True})()
79+
self.assertEqual(contracts.repo_scan(args), 1)
80+
81+
def test_graph_and_sync_doctors_are_non_mutating(self):
82+
args = type("Args", (), {})()
83+
self.assertEqual(contracts.graph_doctor(args), 0)
84+
self.assertEqual(contracts.sync_doctor(args), 0)
85+
86+
87+
if __name__ == "__main__":
88+
unittest.main()

0 commit comments

Comments
 (0)