"""Unit: git-gate.signing manifest parsing + validation. PRD prd-new (signed commits & audit attribution): a bottle opts into per-activation commit signing with `git-gate.signing.enabled: true`. The block is bottle-only (rejected at the agent level, like git-gate.repos) and defaults to off. """ import unittest from bot_bottle.manifest import ManifestError, ManifestGitSigning, ManifestIndex def _bottle(git_gate: dict) -> dict: # type: ignore return { "bottles": {"dev": {"git-gate": git_gate}}, "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, } class TestSigningParsing(unittest.TestCase): def test_default_is_disabled(self): """A bottle with no git-gate block signs nothing.""" m = ManifestIndex.from_json_obj({ "bottles": {"dev": {}}, "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, }) self.assertEqual(ManifestGitSigning(), m.bottles["dev"].git_signing) self.assertFalse(m.bottles["dev"].git_signing.enabled) def test_git_gate_without_signing_is_disabled(self): """git-gate present (e.g. user only) but no signing → off.""" m = ManifestIndex.from_json_obj(_bottle({ "user": {"name": "claude", "email": "eric+claude@dideric.is"}, })) self.assertFalse(m.bottles["dev"].git_signing.enabled) def test_enabled_true(self): m = ManifestIndex.from_json_obj(_bottle({"signing": {"enabled": True}})) self.assertTrue(m.bottles["dev"].git_signing.enabled) def test_enabled_false(self): m = ManifestIndex.from_json_obj(_bottle({"signing": {"enabled": False}})) self.assertFalse(m.bottles["dev"].git_signing.enabled) def test_signing_coexists_with_user_and_repos(self): m = ManifestIndex.from_json_obj(_bottle({ "user": {"name": "claude", "email": "eric+claude@dideric.is"}, "signing": {"enabled": True}, "repos": { "bot-bottle": { "url": "ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git", "key": {"provider": "static", "path": "/dev/null"}, }, }, })) b = m.bottles["dev"] self.assertTrue(b.git_signing.enabled) self.assertEqual("claude", b.git_user.name) self.assertEqual(1, len(b.git)) class TestSigningValidation(unittest.TestCase): def test_unknown_key_under_signing_dies(self): with self.assertRaises(ManifestError) as cm: ManifestIndex.from_json_obj(_bottle({ "signing": {"enabled": True, "enforce": ["author"]}, })) msg = str(cm.exception) self.assertIn("git-gate.signing", msg) self.assertIn("enforce", msg) def test_non_bool_enabled_dies(self): with self.assertRaises(ManifestError) as cm: ManifestIndex.from_json_obj(_bottle({"signing": {"enabled": "yes"}})) self.assertIn("git-gate.signing.enabled must be a boolean", str(cm.exception)) def test_signing_not_a_mapping_dies(self): with self.assertRaises(ManifestError): ManifestIndex.from_json_obj(_bottle({"signing": ["enabled"]})) def test_unknown_git_gate_key_lists_signing(self): """The git-gate allowed-key error names signing as valid.""" with self.assertRaises(ManifestError) as cm: ManifestIndex.from_json_obj(_bottle({"bogus": {}})) self.assertIn("allowed: user, repos, signing", str(cm.exception)) class TestSigningIsBottleOnly(unittest.TestCase): def test_agent_level_signing_rejected(self): """git-gate.signing on an agent dies — it is bottle-only.""" with self.assertRaises(ManifestError) as cm: ManifestIndex.from_json_obj({ "bottles": {"dev": {}}, "agents": { "demo": { "skills": [], "prompt": "", "bottle": "dev", "git-gate": {"signing": {"enabled": True}}, }, }, }) msg = str(cm.exception) self.assertIn("git-gate.signing", msg) self.assertIn("not allowed at the agent level", msg) class TestSigningExtendsOverlay(unittest.TestCase): def test_child_inherits_parent_signing(self): """A child that omits signing inherits the parent's enabled flag.""" m = ManifestIndex.from_json_obj({ "bottles": { "base": {"git-gate": {"signing": {"enabled": True}}}, "dev": {"extends": "base"}, }, "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, }) self.assertTrue(m.bottles["dev"].git_signing.enabled) def test_child_enables_over_disabled_parent(self): m = ManifestIndex.from_json_obj({ "bottles": { "base": {}, "dev": {"extends": "base", "git-gate": {"signing": {"enabled": True}}}, }, "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, }) self.assertTrue(m.bottles["dev"].git_signing.enabled) if __name__ == "__main__": unittest.main()