"""Tests for canonical JSON runtime-profile configuration (gitea_config) and its integration into gitea_auth.get_profile / get_auth_header. Covers: legacy env-only, JSON profile config, profile selection, multiple profiles, missing profile, invalid JSON, env-override precedence, keychain and env auth-reference parsing/resolution, token/password redaction in errors, and no network calls during config load. """ import os import sys import json import tempfile import unittest from unittest.mock import patch sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent)) import gitea_config # noqa: E402 import gitea_auth # noqa: E402 CONFIG = { "version": 1, "profiles": { "prgs": { "base_url": "https://gitea.prgs.cc", "username": "jcwalker3", "auth": {"type": "keychain", "id": "prgs-gitea-token"}, "default_owner": "Scaled-Tech-Consulting", "execution_profile": "personal-prgs", }, "mdcps-env": { "base_url": "https://gitea.dadeschools.net", "username": "913443", "auth": {"type": "env", "name": "GITEA_TOKEN_MDCPS"}, "default_owner": "Contractor", "execution_profile": "mdcps", }, }, } class _ConfigBase(unittest.TestCase): def setUp(self): self._dir = tempfile.TemporaryDirectory() self.path = os.path.join(self._dir.name, "profiles.json") self._write(CONFIG) def tearDown(self): self._dir.cleanup() def _write(self, obj): with open(self.path, "w", encoding="utf-8") as fh: fh.write(obj if isinstance(obj, str) else json.dumps(obj)) def _env(self, profile="prgs", **extra): env = {"GITEA_MCP_CONFIG": self.path, "GITEA_MCP_PROFILE": profile} env.update(extra) return env # --------------------------------------------------------------------------- # Loading / selection # --------------------------------------------------------------------------- class TestLoadSelect(_ConfigBase): def test_legacy_env_only_returns_none(self): with patch.dict(os.environ, {}, clear=True): self.assertIsNone(gitea_config.load_config()) self.assertIsNone(gitea_config.resolve_profile()) def test_selected_profile_loads(self): with patch.dict(os.environ, self._env("prgs"), clear=True): p = gitea_config.resolve_profile() self.assertEqual(p["base_url"], "https://gitea.prgs.cc") self.assertEqual(p["execution_profile"], "personal-prgs") def test_multiple_profiles_select_correctly(self): with patch.dict(os.environ, self._env("mdcps-env"), clear=True): p = gitea_config.resolve_profile() self.assertEqual(p["username"], "913443") self.assertEqual(p["auth"]["type"], "env") def test_missing_file_unset_is_noop(self): with patch.dict(os.environ, {}, clear=True): self.assertIsNone(gitea_config.resolve_profile()) def test_missing_file_set_raises(self): env = {"GITEA_MCP_CONFIG": self.path + ".nope", "GITEA_MCP_PROFILE": "prgs"} with patch.dict(os.environ, env, clear=True): with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_profile() self.assertIn("missing file", str(ctx.exception)) def test_invalid_json_raises_without_leaking_content(self): self._write('{"version":1,"profiles":{"prgs":{"auth":{"type":"env" BROKEN') with patch.dict(os.environ, self._env("prgs"), clear=True): with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_profile() msg = str(ctx.exception) self.assertIn("invalid JSON", msg) self.assertNotIn("BROKEN", msg) # no file content leaked def test_missing_profile_raises_with_available(self): with patch.dict(os.environ, self._env("ghost"), clear=True): with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_profile() msg = str(ctx.exception) self.assertIn("ghost", msg) self.assertIn("prgs", msg) def test_config_set_but_profile_unset_raises(self): with patch.dict(os.environ, {"GITEA_MCP_CONFIG": self.path}, clear=True): with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_profile() self.assertIn("GITEA_MCP_PROFILE", str(ctx.exception)) def test_bad_top_level_shape_raises(self): self._write({"version": 1, "nope": 1}) with patch.dict(os.environ, self._env("prgs"), clear=True): with self.assertRaises(gitea_config.ConfigError): gitea_config.resolve_profile() def test_unsupported_version_raises(self): self._write({"version": 2, "profiles": {"prgs": {}}}) with patch.dict(os.environ, self._env("prgs"), clear=True): with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_profile() self.assertIn("version", str(ctx.exception)) def test_missing_version_defaults_ok(self): self._write({"profiles": {"prgs": {"base_url": "https://x"}}}) with patch.dict(os.environ, self._env("prgs"), clear=True): self.assertEqual( gitea_config.resolve_profile()["base_url"], "https://x") # --------------------------------------------------------------------------- # Auth reference parsing + resolution # --------------------------------------------------------------------------- class TestAuthReferences(_ConfigBase): def test_keychain_reference_parses(self): with patch.dict(os.environ, self._env("prgs"), clear=True): p = gitea_config.resolve_profile() self.assertEqual(p["auth"], {"type": "keychain", "id": "prgs-gitea-token"}) self.assertEqual(gitea_config.auth_source_name(p), "keychain:prgs-gitea-token") def test_env_reference_parses(self): with patch.dict(os.environ, self._env("mdcps-env"), clear=True): p = gitea_config.resolve_profile() self.assertEqual(gitea_config.auth_source_name(p), "GITEA_TOKEN_MDCPS") def test_keychain_token_resolved_via_injected_lookup(self): with patch.dict(os.environ, self._env("prgs"), clear=True): p = gitea_config.resolve_profile() token = gitea_config.resolve_token( p, keychain_lookup=lambda i: "kc-token" if i == "prgs-gitea-token" else None) self.assertEqual(token, "kc-token") def test_env_token_resolved_from_env(self): env = self._env("mdcps-env", GITEA_TOKEN_MDCPS="mdcps-token-value") with patch.dict(os.environ, env, clear=True): p = gitea_config.resolve_profile() self.assertEqual(gitea_config.resolve_token(p), "mdcps-token-value") def test_keychain_lookup_uses_security_binary(self): # _keychain_token shells out to `security find-generic-password`. class _Proc: returncode = 0 stdout = "secret-from-keychain\n" with patch("gitea_config.subprocess.run", return_value=_Proc()) as run: self.assertEqual(gitea_config._keychain_token("some-id"), "secret-from-keychain") args = run.call_args[0][0] self.assertEqual(args[0], "security") self.assertIn("some-id", args) def test_missing_env_secret_raises_clearly(self): with patch.dict(os.environ, self._env("mdcps-env"), clear=True): p = gitea_config.resolve_profile() with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_token(p) self.assertIn("GITEA_TOKEN_MDCPS", str(ctx.exception)) def test_missing_keychain_secret_raises_clearly(self): with patch.dict(os.environ, self._env("prgs"), clear=True): p = gitea_config.resolve_profile() with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_token(p, keychain_lookup=lambda i: None) self.assertIn("prgs-gitea-token", str(ctx.exception)) def test_invalid_auth_type_rejected(self): self._write({"version": 1, "profiles": {"p": {"auth": {"type": "vault"}}}}) with patch.dict(os.environ, self._env("p"), clear=True): with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_profile() self.assertIn("invalid auth type", str(ctx.exception)) def test_keychain_without_id_rejected(self): self._write({"version": 1, "profiles": {"p": {"auth": {"type": "keychain"}}}}) with patch.dict(os.environ, self._env("p"), clear=True): with self.assertRaises(gitea_config.ConfigError): gitea_config.resolve_profile() # --------------------------------------------------------------------------- # Secret redaction # --------------------------------------------------------------------------- class TestRedaction(_ConfigBase): def test_inline_token_rejected_without_echo(self): self._write({"version": 1, "profiles": {"prgs": {"token": "super-secret-token"}}}) with patch.dict(os.environ, self._env("prgs"), clear=True): with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_profile() msg = str(ctx.exception) self.assertIn("auth", msg) self.assertNotIn("super-secret-token", msg) def test_inline_password_rejected_without_echo(self): self._write({"version": 1, "profiles": {"prgs": {"password": "hunter2secret"}}}) with patch.dict(os.environ, self._env("prgs"), clear=True): with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.resolve_profile() self.assertNotIn("hunter2secret", str(ctx.exception)) def test_profile_metadata_has_no_token_value(self): env = self._env("mdcps-env", GITEA_TOKEN_MDCPS="mdcps-token-value") with patch.dict(os.environ, env, clear=True): p = gitea_config.resolve_profile() self.assertNotIn("mdcps-token-value", json.dumps(p)) # --------------------------------------------------------------------------- # gitea_auth integration # --------------------------------------------------------------------------- class TestAuthIntegration(_ConfigBase): def test_legacy_env_only_profile_unchanged(self): with patch.dict(os.environ, {"GITEA_PROFILE_NAME": "gitea-reviewer", "GITEA_ALLOWED_OPERATIONS": "read,review"}, clear=True): p = gitea_auth.get_profile() self.assertEqual(p["profile_name"], "gitea-reviewer") self.assertEqual(p["allowed_operations"], ["read", "review"]) self.assertIsNone(p["base_url"]) def test_json_fills_profile_when_env_absent(self): with patch.dict(os.environ, self._env("prgs"), clear=True): p = gitea_auth.get_profile() self.assertEqual(p["profile_name"], "personal-prgs") # from execution_profile self.assertEqual(p["base_url"], "https://gitea.prgs.cc") self.assertEqual(p["username"], "jcwalker3") self.assertEqual(p["default_owner"], "Scaled-Tech-Consulting") self.assertEqual(p["token_source_name"], "keychain:prgs-gitea-token") def test_env_overrides_json(self): env = self._env("prgs", GITEA_PROFILE_NAME="env-name", GITEA_BASE_URL="https://env.example") with patch.dict(os.environ, env, clear=True): p = gitea_auth.get_profile() self.assertEqual(p["profile_name"], "env-name") self.assertEqual(p["base_url"], "https://env.example") def test_get_profile_propagates_config_error(self): with patch.dict(os.environ, self._env("ghost"), clear=True): with self.assertRaises(gitea_config.ConfigError): gitea_auth.get_profile() def test_auth_header_uses_json_env_token(self): env = self._env("mdcps-env", GITEA_TOKEN_MDCPS="json-env-token") with patch.dict(os.environ, env, clear=True): header = gitea_auth.get_auth_header("gitea.example.com") self.assertEqual(header, "token json-env-token") def test_explicit_env_token_overrides_json(self): env = self._env("mdcps-env", GITEA_TOKEN_MDCPS="json-env-token", GITEA_TOKEN="process-token") with patch.dict(os.environ, env, clear=True): header = gitea_auth.get_auth_header("gitea.example.com") self.assertEqual(header, "token process-token") def test_auth_header_unresolvable_ref_fails_closed(self): # env token ref points at an unset var -> ConfigError inside resolve is # swallowed to "no token"; auth falls through to (mocked-empty) basic. with patch.dict(os.environ, self._env("mdcps-env"), clear=True): with patch("gitea_auth.get_credentials", return_value=("", "")): self.assertIsNone(gitea_auth.get_auth_header("gitea.example.com")) # --------------------------------------------------------------------------- # No network during config parsing # --------------------------------------------------------------------------- class TestNoNetwork(_ConfigBase): def test_config_load_makes_no_network_calls(self): boom = AssertionError("config parsing must not touch the network") with patch.dict(os.environ, self._env("prgs"), clear=True): with patch("gitea_auth.urllib.request.urlopen", side_effect=boom): self.assertIsNotNone(gitea_config.resolve_profile()) self.assertEqual( gitea_auth.get_profile()["profile_name"], "personal-prgs") if __name__ == "__main__": unittest.main()