"""Unit: Forge abstraction + ScopedForge (PRD forge-native-integration).""" from __future__ import annotations import unittest from bot_bottle.contrib.forge.base import ( Comment, Forge, ForgeScopeError, Issue, PullRequest, ScopedForge, ) class _RecordingForge(Forge): """In-memory fake that records writes.""" def __init__(self) -> None: self.comments: list[tuple[int, str]] = [] self.descriptions: list[tuple[int, str]] = [] def read_issue(self, number: int) -> Issue: return Issue(number=number, title="t", body="b", state="open") def read_pr(self, number: int) -> PullRequest: return PullRequest( number=number, title="pr", body="b", state="open", merged=False ) def read_comments(self, number: int) -> list[Comment]: return [Comment(id=1, user="alice", body="hi")] def post_comment(self, number: int, body: str) -> None: self.comments.append((number, body)) def update_description(self, number: int, body: str) -> None: self.descriptions.append((number, body)) def is_org_member(self, org: str, username: str) -> bool: return username == "member" def get_pr_for_issue(self, number: int) -> int | None: return 99 if number == 17 else None def is_pr_open(self, number: int) -> bool: return True class TestScopedForgeReads(unittest.TestCase): def setUp(self) -> None: self.inner = _RecordingForge() self.scoped = ScopedForge(self.inner, assigned_issue=17, assigned_prs=[42]) def test_reads_pass_through_to_any_number(self): # A number well outside the writable scope still reads fine. self.assertEqual(123, self.scoped.read_issue(123).number) self.assertEqual("alice", self.scoped.read_comments(500)[0].user) def test_read_pr_passes_through(self): pr = self.scoped.read_pr(999) self.assertIsInstance(pr, PullRequest) self.assertEqual(999, pr.number) self.assertFalse(pr.merged) def test_membership_and_pr_lookups_delegate(self): self.assertTrue(self.scoped.is_org_member("bot-bottle", "member")) self.assertFalse(self.scoped.is_org_member("bot-bottle", "stranger")) self.assertEqual(99, self.scoped.get_pr_for_issue(17)) self.assertTrue(self.scoped.is_pr_open(8000)) class TestScopedForgeWrites(unittest.TestCase): def setUp(self) -> None: self.inner = _RecordingForge() self.scoped = ScopedForge(self.inner, assigned_issue=17, assigned_prs=[42]) def test_writable_set_is_issue_plus_prs(self): self.assertEqual(frozenset({17, 42}), self.scoped.writable) def test_write_to_assigned_issue_allowed(self): self.scoped.post_comment(17, "done") self.assertEqual([(17, "done")], self.inner.comments) def test_write_to_assigned_pr_allowed(self): self.scoped.update_description(42, "new body") self.assertEqual([(42, "new body")], self.inner.descriptions) def test_comment_outside_scope_rejected(self): with self.assertRaises(ForgeScopeError) as ctx: self.scoped.post_comment(500, "spam") self.assertIn("500", str(ctx.exception)) self.assertEqual([], self.inner.comments) def test_description_outside_scope_rejected(self): with self.assertRaises(ForgeScopeError): self.scoped.update_description(500, "tamper") self.assertEqual([], self.inner.descriptions) def test_scope_error_is_permission_error(self): # Sidecars can catch the stdlib base type. self.assertIn(PermissionError, ForgeScopeError.__mro__) if __name__ == "__main__": unittest.main()