CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Successful in 15s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / canary (push) Successful in 34s
CI Pipeline / lint (push) Failing after 39s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 1m2s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 38s
- tests/test_gitea_auth.py: ruff format (CI format-gate on added files) - auto-version.yml release step: f-string with escaped quotes is a SyntaxError on py<3.12 runners; compute tag name without backslash escapes (release job exit 22, run 6776)
110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
"""Canonical Gitea auth path (handoff 2026-09-11 Part B).
|
|
|
|
Proves: env credential path works; credential-helper fallback works;
|
|
unavailable credentials fail clearly; no token ever appears in exception
|
|
or log output.
|
|
"""
|
|
|
|
import importlib.util
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
_spec = importlib.util.spec_from_file_location("gitea_auth", ROOT / "scripts" / "gitea_auth.py")
|
|
ga = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(ga)
|
|
|
|
SECRET = "unit-test-secret-token-value"
|
|
|
|
|
|
class TestGiteaAuth(unittest.TestCase):
|
|
def setUp(self):
|
|
import os
|
|
|
|
os_env = {k: v for k, v in os.environ.items() if k not in ("GITEA_TOKEN", "GITEA_TOKEN_FILE")}
|
|
self.patcher = mock.patch.dict(os.environ, os_env, clear=True)
|
|
self.patcher.start()
|
|
self.addCleanup(self.patcher.stop)
|
|
# Isolate from the machine's real token file and real credential store.
|
|
self._nogit = mock.patch.object(ga, "DEFAULT_TOKEN_FILE", Path("/nonexistent/gitea-token-isolation"))
|
|
self._nogit.start()
|
|
self.addCleanup(self._nogit.stop)
|
|
|
|
def test_env_token_used(self):
|
|
import os
|
|
|
|
os.environ["GITEA_TOKEN"] = SECRET
|
|
self.assertEqual(ga.get_token(), SECRET)
|
|
|
|
def test_token_file_fallback(self):
|
|
"""Env absent, GITEA_TOKEN_FILE set → file token used."""
|
|
import os
|
|
import tempfile
|
|
|
|
fd, name = tempfile.mkstemp()
|
|
os.close(fd)
|
|
os.chmod(name, 0o600)
|
|
Path(name).write_text(SECRET)
|
|
try:
|
|
os.environ["GITEA_TOKEN_FILE"] = name
|
|
self.assertEqual(ga.get_token(), SECRET)
|
|
finally:
|
|
os.unlink(name)
|
|
|
|
def test_token_file_bad_mode_rejected(self):
|
|
import os
|
|
import tempfile
|
|
|
|
fd, name = tempfile.mkstemp()
|
|
os.close(fd)
|
|
os.chmod(name, 0o644)
|
|
Path(name).write_text(SECRET)
|
|
try:
|
|
os.environ["GITEA_TOKEN_FILE"] = name
|
|
with self.assertRaises(PermissionError) as ctx:
|
|
ga.get_token()
|
|
self.assertNotIn(SECRET, str(ctx.exception))
|
|
finally:
|
|
os.unlink(name)
|
|
|
|
def test_credential_helper_fallback(self):
|
|
"""No env, no file → git credential fill is consulted."""
|
|
import os
|
|
|
|
fake = subprocess.CompletedProcess([], 0, stdout=f"password={SECRET}\n", stderr="")
|
|
with mock.patch.object(ga.subprocess, "run", return_value=fake) as m:
|
|
token = ga.get_token()
|
|
self.assertEqual(token, SECRET)
|
|
self.assertIn("credential", m.call_args[0][0])
|
|
|
|
def test_unavailable_fails_clearly_without_token(self):
|
|
err = None
|
|
with mock.patch.object(
|
|
ga.subprocess, "run", return_value=subprocess.CompletedProcess([], 0, stdout="", stderr="")
|
|
):
|
|
try:
|
|
ga.get_token()
|
|
except ga.GiteaCredentialError as e:
|
|
err = e
|
|
self.assertIsNotNone(err, "must raise GiteaCredentialError when no credential source answers")
|
|
self.assertNotIn(SECRET, str(err))
|
|
self.assertIn("GITEA_TOKEN", str(err)) # actionable, names the remedy
|
|
|
|
def test_no_token_in_any_error_path(self):
|
|
"""The helper must never leak a token through an exception message."""
|
|
import os
|
|
|
|
os.environ["GITEA_TOKEN"] = SECRET
|
|
try:
|
|
with mock.patch.object(ga.Path, "is_file", side_effect=RuntimeError(SECRET)):
|
|
ga.get_token()
|
|
except Exception as e:
|
|
self.fail(f"unexpected exception leaked through: {e}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|