test+docs: startup-refusal exit code, real-body 413, pre-1.1.0 migration note
- main() exits 2 on permissive config (not just the predicate) - 413 proven with an actual over-limit wire body, not header-only - README: machine-user accounts need a seeded state.json mapping; only granthi-sync-e2e required it in beta Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
74d206cb4a
commit
14ccbd59f0
@@ -74,6 +74,16 @@ A Gitea token is **never minted before the binding rule passes**, and a
|
|||||||
corrupt/unreadable `state.json` fails closed (500) instead of falling back
|
corrupt/unreadable `state.json` fails closed (500) instead of falling back
|
||||||
to an empty map.
|
to an empty map.
|
||||||
|
|
||||||
|
**Migration (pre-1.1.0 accounts):** identities whose userinfo carries no
|
||||||
|
verified `email` (e.g. Zitadel *machine* users) cannot self-adopt an
|
||||||
|
existing forge login under rule (c) — for them the first link after the
|
||||||
|
upgrade would 409 forever. Any forge account the v1 service created before
|
||||||
|
this change must be seeded into `state.json` once, as
|
||||||
|
`{"<sub>": {"login": "<login>", "created_by_service": true, ...}}`, written
|
||||||
|
0600 atomically. As of the beta rollout the only such account is the E2E
|
||||||
|
machine user `granthi-sync-e2e` (seeded); the forge's human admin `nirpa`
|
||||||
|
is never provisioned through `/v1/link`, so nothing else needed seeding.
|
||||||
|
|
||||||
Empirically verified mechanics on Gitea **1.27.1** (beta forge):
|
Empirically verified mechanics on Gitea **1.27.1** (beta forge):
|
||||||
|
|
||||||
* Token minting: `POST /api/v1/users/{login}/tokens` returns **401 for
|
* Token minting: `POST /api/v1/users/{login}/tokens` returns **401 for
|
||||||
|
|||||||
@@ -364,6 +364,22 @@ class TestConfigPerms(unittest.TestCase):
|
|||||||
err = granthi_link.check_config_perms(self.tmp.name, euid=not_me)
|
err = granthi_link.check_config_perms(self.tmp.name, euid=not_me)
|
||||||
self.assertIn("owned by uid", err)
|
self.assertIn("owned by uid", err)
|
||||||
|
|
||||||
|
def test_main_exits_2_on_permissive_config(self):
|
||||||
|
"""The refusal must actually stop startup (exit nonzero), not just
|
||||||
|
return a string -- verified through main()."""
|
||||||
|
with open(self.tmp.name, "w") as f:
|
||||||
|
json.dump({"gitea_base": "http://x", "admin_token": "t",
|
||||||
|
"admin_login": "r", "admin_password": "p"}, f)
|
||||||
|
os.chmod(self.tmp.name, 0o644)
|
||||||
|
with mock.patch.object(granthi_link.sys, "argv",
|
||||||
|
["granthi_link.py", self.tmp.name]), \
|
||||||
|
mock.patch.object(granthi_link, "serve") as served, \
|
||||||
|
self.assertLogs("granthi-link", level="ERROR"):
|
||||||
|
with self.assertRaises(SystemExit) as cm:
|
||||||
|
granthi_link.main()
|
||||||
|
self.assertEqual(cm.exception.code, 2)
|
||||||
|
served.assert_not_called() # never reached serve()
|
||||||
|
|
||||||
|
|
||||||
class HandlerTestBase(ServiceTestBase):
|
class HandlerTestBase(ServiceTestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -397,6 +413,18 @@ class TestBodyLimits(HandlerTestBase):
|
|||||||
self.assertEqual(status, 413)
|
self.assertEqual(status, 413)
|
||||||
self.assertIn("too large", resp["error"])
|
self.assertIn("too large", resp["error"])
|
||||||
|
|
||||||
|
def test_oversized_real_body_413(self):
|
||||||
|
"""Send an actual over-limit payload on the wire (not just the
|
||||||
|
header), so the response is proven, not the predicate alone."""
|
||||||
|
body = json.dumps(
|
||||||
|
{"pad": "x" * (granthi_link.MAX_BODY_BYTES + 2000)}).encode()
|
||||||
|
self.assertGreater(len(body), granthi_link.MAX_BODY_BYTES)
|
||||||
|
status, resp = self.raw_post(
|
||||||
|
"/v1/link", body_bytes=body,
|
||||||
|
headers={"Content-Length": str(len(body)),
|
||||||
|
"Content-Type": "application/json"})
|
||||||
|
self.assertEqual(status, 413)
|
||||||
|
|
||||||
def test_missing_content_length_411(self):
|
def test_missing_content_length_411(self):
|
||||||
status, _ = self.raw_post("/v1/link")
|
status, _ = self.raw_post("/v1/link")
|
||||||
self.assertEqual(status, 411)
|
self.assertEqual(status, 411)
|
||||||
|
|||||||
Reference in New Issue
Block a user