314dc03b0d
Moves the orchestrator into bot_bottle/orchestrator/ so one install gets everything. Entry point is now `python -m bot_bottle.orchestrator run`. - Add bot_bottle/orchestrator/ with all 14 modules (verbatim move; internal imports were already relative, so no changes inside orchestrator modules) - Rewrite bootstrap.py: remove the lazy bot_bottle import guard, use direct relative imports from ..contrib.* - Add bot_bottle/contrib/forge/base.py: ScopedForge (read-anywhere / write-scoped) - Add bot_bottle/contrib/gitea/client.py: GiteaClient + GiteaForge (urllib.request only) - Add bot_bottle/contrib/gitea/forge_state.py: ForgeState + SqliteForgeStateStore - Add tests/unit/orchestrator/ (82 tests: 63 migrated + 19 new for contrib modules) Closes #321
76 lines
2.5 KiB
Python
76 lines
2.5 KiB
Python
"""Unit: ScopedForge — read-anywhere / write-scoped access control."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from bot_bottle.contrib.forge.base import ScopedForge
|
|
|
|
from ._fakes import FakeForge
|
|
|
|
|
|
class ScopedForgeTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.inner = FakeForge()
|
|
self.scoped = ScopedForge(
|
|
self.inner, assigned_issue=10, assigned_prs=[20, 30]
|
|
)
|
|
|
|
# --- reads always pass through -----------------------------------------
|
|
|
|
def test_read_issue_allowed_anywhere(self):
|
|
for number in (10, 20, 99):
|
|
result = self.scoped.read_issue(number)
|
|
self.assertEqual(number, result["number"])
|
|
|
|
def test_read_pr_allowed_anywhere(self):
|
|
for number in (10, 20, 99):
|
|
result = self.scoped.read_pr(number)
|
|
self.assertEqual(number, result["number"])
|
|
|
|
def test_read_comments_allowed_anywhere(self):
|
|
comments = self.scoped.read_comments(99)
|
|
self.assertTrue(len(comments) > 0)
|
|
|
|
def test_is_org_member_passes_through(self):
|
|
inner = FakeForge(members=("alice",))
|
|
scoped = ScopedForge(inner, assigned_issue=1, assigned_prs=[])
|
|
self.assertTrue(scoped.is_org_member("org", "alice"))
|
|
self.assertFalse(scoped.is_org_member("org", "bob"))
|
|
|
|
# --- writes: assigned numbers allowed ----------------------------------
|
|
|
|
def test_post_comment_on_assigned_issue(self):
|
|
self.scoped.post_comment(10, "hi")
|
|
self.assertIn((10, "hi"), self.inner.comments)
|
|
|
|
def test_post_comment_on_assigned_pr(self):
|
|
self.scoped.post_comment(20, "lgtm")
|
|
self.assertIn((20, "lgtm"), self.inner.comments)
|
|
|
|
def test_update_description_on_assigned(self):
|
|
self.scoped.update_description(30, "updated")
|
|
self.assertIn((30, "updated"), self.inner.descriptions)
|
|
|
|
# --- writes: unassigned numbers denied ---------------------------------
|
|
|
|
def test_post_comment_denied_for_unassigned(self):
|
|
with self.assertRaises(PermissionError):
|
|
self.scoped.post_comment(99, "nope")
|
|
self.assertEqual([], self.inner.comments)
|
|
|
|
def test_update_description_denied_for_unassigned(self):
|
|
with self.assertRaises(PermissionError):
|
|
self.scoped.update_description(99, "nope")
|
|
self.assertEqual([], self.inner.descriptions)
|
|
|
|
def test_error_message_names_number(self):
|
|
try:
|
|
self.scoped.post_comment(99, "nope")
|
|
except PermissionError as exc:
|
|
self.assertIn("99", str(exc))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|