fix(client): resolve a bare repo name against what the forge grants
Found by running the real flow on a fresh clone against production, not by a test: `granthi-sync get Ai-Assistant` failed with "Repository not found". `get <name>` meant `<your-login>/<name>` and nothing else, but of 172 repos granted to this estate's own admin, 157 are owned by an ORG -- so the bare name failed for 91% of what a user can actually see, with an error that reads like a permissions problem rather than a naming one. A bare name is now matched against the granted list, which is the forge's own answer about what this account may have. An owner-qualified name is taken as given. An ambiguous bare name is REFUSED with its candidates rather than guessed -- picking one of two repos called `notes` owned by different teams is not a guess worth making for someone. Best effort by design: if the listing is unreachable, a fully-qualified name still clones and a bare name degrades to the caller's own namespace, so a network blip cannot block a clone. The clone that follows reports the real problem precisely. cmd_bootstrap carried the same assumption and is fixed with it. 203 tests (was 198). Verified live afterwards: the failing command now clones Nirlabinc/Ai-Assistant, and `get shreai` lists both candidates instead of guessing.
This commit is contained in:
@@ -1229,5 +1229,60 @@ class TestWorkspaceBootstrap(GitScenarioBase):
|
||||
client.cmd_bootstrap(self.ns())
|
||||
self.assertIn("no workspace.json", str(e.exception))
|
||||
|
||||
|
||||
class TestResolveGranted(unittest.TestCase):
|
||||
"""`get <name>` has to find the repo the forge actually grants.
|
||||
|
||||
Found by running the real flow against production: of 172 repos granted
|
||||
to this estate's own admin, 157 are owned by an ORG, so a bare name failed
|
||||
for 91% of what the user could see -- and the error read "Repository not
|
||||
found", which sounds like a permissions problem rather than a naming one.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.cfg = {"login": "alice", "gitea_base": "http://forge.example",
|
||||
"token": "t"}
|
||||
self.repos = [{"full_name": "Nirlabinc/Ai-Assistant"},
|
||||
{"full_name": "alice/notes"},
|
||||
{"full_name": "Shreai/notes"}]
|
||||
|
||||
def test_a_bare_name_finds_an_org_owned_repo(self):
|
||||
self.assertEqual(
|
||||
client.resolve_granted(self.cfg, "Ai-Assistant", self.repos),
|
||||
"Nirlabinc/Ai-Assistant")
|
||||
|
||||
def test_an_owner_qualified_name_is_taken_as_given(self):
|
||||
self.assertEqual(
|
||||
client.resolve_granted(self.cfg, "Nirlabinc/Ai-Assistant",
|
||||
self.repos),
|
||||
"Nirlabinc/Ai-Assistant")
|
||||
|
||||
def test_an_ambiguous_bare_name_is_refused_with_the_candidates(self):
|
||||
"""Two teams with a repo called `notes` is not a guess worth making
|
||||
on someone's behalf."""
|
||||
with self.assertRaises(SystemExit) as e:
|
||||
client.resolve_granted(self.cfg, "notes", self.repos)
|
||||
msg = str(e.exception)
|
||||
self.assertIn("ambiguous", msg)
|
||||
self.assertIn("Shreai/notes", msg)
|
||||
self.assertIn("alice/notes", msg)
|
||||
|
||||
def test_an_unknown_name_falls_back_to_your_own_namespace(self):
|
||||
self.assertEqual(
|
||||
client.resolve_granted(self.cfg, "brand-new", self.repos),
|
||||
"alice/brand-new")
|
||||
|
||||
def test_an_unreachable_listing_does_not_block_a_clone(self):
|
||||
"""A network blip must not stop someone cloning a repo they named."""
|
||||
def boom(cfg):
|
||||
raise SystemExit("listing repos failed (HTTP 502)")
|
||||
with mock.patch.object(client, "list_repos", boom), \
|
||||
mock.patch("sys.stdout", new_callable=io.StringIO):
|
||||
self.assertEqual(client.resolve_granted(self.cfg, "notes"),
|
||||
"alice/notes")
|
||||
self.assertEqual(
|
||||
client.resolve_granted(self.cfg, "Nirlabinc/Ai-Assistant"),
|
||||
"Nirlabinc/Ai-Assistant")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user