Files
Ali Khokhar 51157f91bd Refactor admin config into catalog-driven package (#926)
## Problem

Admin config was a single responsibility hub with manually duplicated
provider metadata. Provider labels, fields, template loading,
validation, persistence, and status lived in one place.

## Changes

| Before | After |
| --- | --- |
| Admin config lived in one large `api/admin_config.py` module. | Admin
config lives in package modules for manifest, sources, values,
validation, persistence, and status. |
| Provider admin fields and UI labels were manually duplicated. |
Provider admin fields and display names derive from `PROVIDER_CATALOG`
with admin-only help overrides. |
| `fcc-init` and Admin UI loaded `.env.example` separately. | `fcc-init`
and Admin UI use shared `config.env_template` loading. |
| Architecture docs pointed to the old admin config module. |
Architecture docs describe the package owners and catalog-driven
provider manifest. |

<!-- greptile_comment -->

<details open><summary><h3>Greptile Summary</h3></summary>

This PR refactors admin configuration into a catalog-driven package. The
main changes are:

- Split the former monolithic `api/admin_config.py` into manifest,
source loading, value presentation, validation, persistence, and
provider status modules.
- Generate provider admin fields and display names from
`PROVIDER_CATALOG` with admin-specific help overrides.
- Share `.env.example` loading between `fcc-init` and Admin UI defaults
through `config.env_template`.
- Update admin routes, Admin UI provider labels, architecture docs,
version metadata, and contract/API tests for the new module layout.
</details>

<h3>Confidence Score: 5/5</h3>

The refactor appears merge-safe with no code issues identified in the
reviewed changes.

The package split, catalog-driven provider metadata, shared environment
template loading, route updates, and tests/docs changes are cohesive and
covered by corresponding contract/API/CLI test updates.

<details><summary><h3><a href="https://www.greptile.com/trex"><img
alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="20" align="absmiddle"></a> T-Rex Logs</h3></summary>

**What T-Rex did**
- T-Rex ran manifest validation for catalog provider before and after
routes, capturing base and head responses and catalog-alignment checks,
and confirmed the validation completed successfully.
- T-Rex evaluated the shared-env-template scenarios, observing the
before run with no config.env\_template module and the after run with
the module present, with patched loader values and all consistency
checks passing, and the run exited with code 0.
- T-Rex executed the package-admin-workflow validation, verifying the
base and after import paths, the load/validate/write workflow produced
matching outputs, and the run completed with exit code 0.

<a
href="https://app.greptile.com/trex/runs/12529845/artifacts"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=1"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=1"><img
alt="View all artifacts"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=1"
height="32"></picture></a>

<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="14" align="absmiddle"></a> Ran code and verified through
T-Rex</sub>
</details>

<sub>Reviews (1): Last reviewed commit: ["Refactor admin config into
catalog-drive..."](https://github.com/alishahryar1/free-claude-code/commit/d6239d7953fce75d435b8d6a20536c1aff53aa88)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=40315222)</sub>

<!-- /greptile_comment -->
2026-06-27 15:37:29 -07:00

568 lines
20 KiB
Python

"""Tests for cli/entrypoints.py — fcc-init scaffolding logic."""
import json
import tomllib
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
from urllib.error import URLError
from urllib.request import Request
import pytest
from config.settings import Settings
def _launcher_settings(
*,
port: int = 8082,
token: str = "freecc",
) -> Settings:
return Settings.model_construct(
host="0.0.0.0",
port=port,
anthropic_auth_token=token,
model="nvidia_nim/test-model",
)
def _run_init(tmp_home: Path) -> tuple[str, Path]:
"""Run init() with home directory redirected to tmp_home. Returns (printed output, env_file path)."""
from cli.entrypoints import init
env_file = tmp_home / ".fcc" / ".env"
printed: list[str] = []
with (
patch("pathlib.Path.home", return_value=tmp_home),
patch(
"builtins.print",
side_effect=lambda *a: printed.append(" ".join(str(x) for x in a)),
),
):
init()
return "\n".join(printed), env_file
class _JsonResponse:
def __init__(self, payload: dict[str, object]) -> None:
self._payload = payload
def __enter__(self) -> _JsonResponse:
return self
def __exit__(self, *_args: object) -> None:
return None
def read(self) -> bytes:
return json.dumps(self._payload).encode("utf-8")
def test_init_creates_env_file(tmp_path: Path) -> None:
"""init() creates .env from the bundled template when it doesn't exist yet."""
output, env_file = _run_init(tmp_path)
assert env_file.exists()
assert env_file.stat().st_size > 0
assert str(env_file) in output
def test_init_copies_template_content(tmp_path: Path) -> None:
"""init() writes the canonical root env.example content, not an empty file."""
template = (Path(__file__).resolve().parents[2] / ".env.example").read_text(
encoding="utf-8"
)
_, env_file = _run_init(tmp_path)
assert env_file.read_text("utf-8") == template
def test_init_migrates_home_checkout_env_before_template(tmp_path: Path) -> None:
"""init() preserves users who kept config in ~/free-claude-code/.env."""
legacy_env = tmp_path / "free-claude-code" / ".env"
legacy_env.parent.mkdir(parents=True)
legacy_env.write_text("MODEL=deepseek/deepseek-chat\n", encoding="utf-8")
output, env_file = _run_init(tmp_path)
assert env_file.read_text("utf-8") == "MODEL=deepseek/deepseek-chat\n"
assert f"Config migrated from {legacy_env}" in output
def test_init_migrates_legacy_xdg_env_before_template(tmp_path: Path) -> None:
"""init() preserves users who kept config in ~/.config/free-claude-code/.env."""
legacy_env = tmp_path / ".config" / "free-claude-code" / ".env"
legacy_env.parent.mkdir(parents=True)
legacy_env.write_text("MODEL=open_router/free-model\n", encoding="utf-8")
output, env_file = _run_init(tmp_path)
assert env_file.read_text("utf-8") == "MODEL=open_router/free-model\n"
assert f"Config migrated from {legacy_env}" in output
def test_legacy_env_migration_does_not_overwrite_managed_env(
tmp_path: Path,
) -> None:
"""Legacy migration never overwrites an existing ~/.fcc/.env."""
from cli.entrypoints import _migrate_legacy_env_if_missing
managed_env = tmp_path / ".fcc" / ".env"
managed_env.parent.mkdir(parents=True)
managed_env.write_text("MODEL=nvidia_nim/current\n", encoding="utf-8")
legacy_env = tmp_path / "free-claude-code" / ".env"
legacy_env.parent.mkdir(parents=True)
legacy_env.write_text("MODEL=deepseek/legacy\n", encoding="utf-8")
with patch("pathlib.Path.home", return_value=tmp_path):
migrated_from = _migrate_legacy_env_if_missing()
assert migrated_from is None
assert managed_env.read_text("utf-8") == "MODEL=nvidia_nim/current\n"
def test_env_template_loader_uses_root_template_in_source_checkout() -> None:
"""Source checkout fallback uses the root .env.example as the single source."""
from config.env_template import load_env_template
template = (Path(__file__).resolve().parents[2] / ".env.example").read_text(
encoding="utf-8"
)
assert load_env_template() == template
def test_init_creates_parent_directories(tmp_path: Path) -> None:
"""init() creates ~/.fcc/ even if it doesn't exist."""
config_dir = tmp_path / ".fcc"
assert not config_dir.exists()
_run_init(tmp_path)
assert config_dir.is_dir()
def test_init_skips_if_env_already_exists(tmp_path: Path) -> None:
"""init() does not overwrite an existing .env and prints a warning."""
# Create it first
_run_init(tmp_path)
env_file = tmp_path / ".fcc" / ".env"
env_file.write_text("existing content", encoding="utf-8")
output, _ = _run_init(tmp_path)
assert env_file.read_text("utf-8") == "existing content"
assert "already exists" in output
def test_init_prints_next_step_hint(tmp_path: Path) -> None:
"""init() tells the user to run fcc-server after editing .env."""
output, _ = _run_init(tmp_path)
assert "fcc-server" in output
def test_cli_scripts_are_registered() -> None:
pyproject = tomllib.loads(
(Path(__file__).resolve().parents[2] / "pyproject.toml").read_text(
encoding="utf-8"
)
)
scripts = pyproject["project"]["scripts"]
assert scripts["fcc-server"] == "cli.entrypoints:serve"
assert scripts["free-claude-code"] == "cli.entrypoints:serve"
assert scripts["fcc-claude"] == "cli.launchers.claude:launch"
assert scripts["fcc-codex"] == "cli.launchers.codex:launch"
def test_schedule_open_admin_browser_opens_when_health_ready(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Opening /admin runs after /health preflight succeeds."""
monkeypatch.delenv("FCC_OPEN_BROWSER", raising=False)
from api.admin_urls import local_admin_url
from cli import entrypoints
settings = _launcher_settings(port=31337)
opened_urls: list[str] = []
class ImmediateThread:
def __init__(self, target=None, **_kwargs: object) -> None:
self._target = target
def start(self) -> None:
assert self._target is not None
self._target()
with (
patch.object(entrypoints.threading, "Thread", ImmediateThread),
patch.object(entrypoints, "preflight_proxy", return_value=None),
patch.object(
entrypoints.webbrowser,
"open",
side_effect=lambda url: opened_urls.append(url),
),
patch.object(entrypoints.time, "sleep"),
):
entrypoints._schedule_open_admin_browser(settings)
assert opened_urls == [local_admin_url(settings)]
def test_schedule_open_admin_browser_skips_when_disabled(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("FCC_OPEN_BROWSER", "0")
from cli import entrypoints
settings = _launcher_settings()
with patch.object(entrypoints.threading, "Thread") as thread_cls:
entrypoints._schedule_open_admin_browser(settings)
thread_cls.assert_not_called()
def test_serve_supervisor_restarts_when_app_requests_restart() -> None:
from cli import entrypoints
settings = _launcher_settings()
get_settings = MagicMock(side_effect=[settings, settings])
get_settings.cache_clear = MagicMock()
servers: list[object] = []
class FakeServer:
def __init__(self, config):
self.config = config
self.should_exit = False
servers.append(self)
def run(self):
if len(servers) == 1:
self.config.app.app.state.admin_restart_callback()
assert self.should_exit is True
def fake_config(app, **kwargs):
return SimpleNamespace(app=app, kwargs=kwargs)
with (
patch.object(entrypoints, "get_settings", get_settings),
patch.object(entrypoints.uvicorn, "Config", side_effect=fake_config),
patch.object(entrypoints.uvicorn, "Server", side_effect=FakeServer),
patch.object(entrypoints, "_schedule_open_admin_browser"),
patch.object(entrypoints, "kill_all_best_effort") as kill_all,
):
entrypoints.serve()
assert len(servers) == 2
get_settings.cache_clear.assert_called_once()
kill_all.assert_called_once()
def test_serve_migrates_legacy_env_before_loading_settings(tmp_path: Path) -> None:
from cli import entrypoints
legacy_env = tmp_path / "free-claude-code" / ".env"
legacy_env.parent.mkdir(parents=True)
legacy_env.write_text("MODEL=deepseek/deepseek-chat\n", encoding="utf-8")
settings = _launcher_settings()
get_settings = MagicMock(return_value=settings)
get_settings.cache_clear = MagicMock()
with (
patch("pathlib.Path.home", return_value=tmp_path),
patch.object(entrypoints, "get_settings", get_settings),
patch.object(entrypoints, "_run_supervised_server", return_value=False),
patch.object(entrypoints, "kill_all_best_effort"),
):
entrypoints.serve()
assert (tmp_path / ".fcc" / ".env").read_text("utf-8") == (
"MODEL=deepseek/deepseek-chat\n"
)
get_settings.assert_called_once_with()
def test_serve_handles_keyboard_interrupt_without_traceback() -> None:
from cli import entrypoints
settings = _launcher_settings()
get_settings = MagicMock(return_value=settings)
get_settings.cache_clear = MagicMock()
with (
patch.object(entrypoints, "get_settings", get_settings),
patch.object(
entrypoints,
"_run_supervised_server",
side_effect=KeyboardInterrupt,
),
patch.object(entrypoints, "kill_all_best_effort") as kill_all,
):
entrypoints.serve()
get_settings.cache_clear.assert_not_called()
kill_all.assert_called_once()
def test_claude_child_env_targets_current_proxy_config() -> None:
from cli.launchers.claude import build_claude_launcher_env
env = build_claude_launcher_env(
proxy_root_url="http://127.0.0.1:9090",
auth_token=" proxy-token ",
base_env={
"PATH": "keep",
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
"ANTHROPIC_AUTH_TOKEN": "old-token",
"ANTHROPIC_API_KEY": "official-key",
},
)
assert env["PATH"] == "keep"
assert env["ANTHROPIC_BASE_URL"] == "http://127.0.0.1:9090"
assert env["ANTHROPIC_AUTH_TOKEN"] == "proxy-token"
assert env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] == "1"
assert env["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] == "190000"
assert "ANTHROPIC_API_KEY" not in env
def test_claude_child_env_uses_sentinel_for_blank_configured_auth_token() -> None:
from cli.launchers.claude import build_claude_launcher_env
env = build_claude_launcher_env(
proxy_root_url="http://127.0.0.1:8082",
auth_token="",
base_env={
"ANTHROPIC_AUTH_TOKEN": "inherited-token",
"ANTHROPIC_API_KEY": "official-key",
},
)
assert env["ANTHROPIC_AUTH_TOKEN"] == "fcc-no-auth"
assert "ANTHROPIC_API_KEY" not in env
def test_launch_claude_passes_args_and_child_env(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from cli.launchers.claude import launch
monkeypatch.setenv("ANTHROPIC_BASE_URL", "https://api.anthropic.com")
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", "old-token")
monkeypatch.setenv("KEEP_ME", "yes")
settings = _launcher_settings(port=9191, token="proxy-token")
with (
patch("cli.launchers.claude.get_settings", return_value=settings),
patch("cli.launchers.claude.preflight_proxy", return_value=None),
patch("cli.launchers.common.shutil.which", return_value="resolved-claude.cmd"),
patch("cli.launchers.common.subprocess.Popen") as popen,
patch("cli.launchers.common.register_pid") as register_pid,
patch("cli.launchers.common.unregister_pid") as unregister_pid,
pytest.raises(SystemExit) as exc_info,
):
process = popen.return_value
process.pid = 12345
process.wait.return_value = 7
launch(["--model", "sonnet"])
assert exc_info.value.code == 7
popen.assert_called_once()
assert popen.call_args.args[0] == ["resolved-claude.cmd", "--model", "sonnet"]
child_env = popen.call_args.kwargs["env"]
assert child_env["ANTHROPIC_BASE_URL"] == "http://127.0.0.1:9191"
assert child_env["ANTHROPIC_AUTH_TOKEN"] == "proxy-token"
assert child_env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] == "1"
assert child_env["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] == "190000"
assert child_env["KEEP_ME"] == "yes"
register_pid.assert_called_once_with(12345)
unregister_pid.assert_called_once_with(12345)
def test_launch_codex_passes_responses_config_and_child_env(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
from cli.launchers.codex import launch
monkeypatch.setenv("OPENAI_API_KEY", "official-key")
monkeypatch.setenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
monkeypatch.setenv("CODEX_HOME", "keep-home")
settings = _launcher_settings(port=9191, token="proxy-token")
catalog_path = tmp_path / "codex-model-catalog.json"
requests: list[Request] = []
def fake_urlopen(request: Request, *, timeout: float) -> _JsonResponse:
requests.append(request)
assert timeout == 1.5
return _JsonResponse(
{
"data": [
{
"id": "anthropic/nvidia_nim/provider-model",
"display_name": "NVIDIA model",
},
{
"id": ("claude-3-freecc-no-thinking/nvidia_nim/provider-model"),
"display_name": "NVIDIA model (no thinking)",
},
{
"id": "claude-opus-4-20250514",
"display_name": "Claude Opus 4",
},
]
}
)
with (
patch("cli.launchers.codex.get_settings", return_value=settings),
patch("cli.launchers.codex.preflight_proxy", return_value=None),
patch("cli.launchers.common.shutil.which", return_value="resolved-codex.cmd"),
patch(
"cli.launchers.codex.codex_model_catalog_path", return_value=catalog_path
),
patch("cli.launchers.codex.urlopen", side_effect=fake_urlopen),
patch("cli.launchers.common.subprocess.Popen") as popen,
patch("cli.launchers.common.register_pid") as register_pid,
patch("cli.launchers.common.unregister_pid") as unregister_pid,
pytest.raises(SystemExit) as exc_info,
):
process = popen.return_value
process.pid = 12345
process.wait.return_value = 0
launch(["exec", "hello"])
assert exc_info.value.code == 0
command = popen.call_args.args[0]
assert command[0] == "resolved-codex.cmd"
assert 'model_provider="fcc"' in command
assert 'model_providers.fcc.base_url="http://127.0.0.1:9191/v1"' in command
assert 'model_providers.fcc.wire_api="responses"' in command
assert f"model_catalog_json={json.dumps(str(catalog_path))}" in command
assert command[-2:] == ["exec", "hello"]
assert len(requests) == 1
request = requests[0]
assert request.full_url == "http://127.0.0.1:9191/v1/models"
headers = {key.lower(): value for key, value in request.header_items()}
assert headers["x-api-key"] == "proxy-token"
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
assert [model["slug"] for model in catalog["models"]] == [
"nvidia_nim/provider-model"
]
child_env = popen.call_args.kwargs["env"]
assert child_env["FCC_CODEX_API_KEY"] == "proxy-token"
assert child_env["CODEX_HOME"] == "keep-home"
assert "OPENAI_API_KEY" not in child_env
assert "OPENAI_BASE_URL" not in child_env
register_pid.assert_called_once_with(12345)
unregister_pid.assert_called_once_with(12345)
def test_launch_codex_catalog_failure_warns_and_continues(
capsys: pytest.CaptureFixture[str],
tmp_path: Path,
) -> None:
from cli.launchers.codex import launch
settings = _launcher_settings(port=9191, token="proxy-token")
with (
patch("cli.launchers.codex.get_settings", return_value=settings),
patch("cli.launchers.codex.preflight_proxy", return_value=None),
patch("cli.launchers.common.shutil.which", return_value="resolved-codex.cmd"),
patch(
"cli.launchers.codex.codex_model_catalog_path",
return_value=tmp_path / "codex-model-catalog.json",
),
patch("cli.launchers.codex.urlopen", side_effect=URLError("boom")),
patch("cli.launchers.common.subprocess.Popen") as popen,
patch("cli.launchers.common.register_pid"),
patch("cli.launchers.common.unregister_pid"),
pytest.raises(SystemExit) as exc_info,
):
process = popen.return_value
process.pid = 12345
process.wait.return_value = 0
launch(["exec", "hello"])
assert exc_info.value.code == 0
command = popen.call_args.args[0]
assert not any("model_catalog_json=" in arg for arg in command)
captured = capsys.readouterr()
assert "could not prepare Codex model catalog" in captured.err
assert "launching without model picker catalog" in captured.err
def test_launch_claude_keyboard_interrupt_kills_child_tree() -> None:
from cli.launchers.claude import launch
settings = _launcher_settings(port=9191, token="proxy-token")
with (
patch("cli.launchers.claude.get_settings", return_value=settings),
patch("cli.launchers.claude.preflight_proxy", return_value=None),
patch("cli.launchers.common.shutil.which", return_value="resolved-claude.cmd"),
patch("cli.launchers.common.subprocess.Popen") as popen,
patch("cli.launchers.common.register_pid"),
patch("cli.launchers.common.kill_pid_tree_best_effort") as kill_tree,
patch("cli.launchers.common.unregister_pid") as unregister_pid,
pytest.raises(KeyboardInterrupt),
):
process = popen.return_value
process.pid = 12345
process.wait.side_effect = [KeyboardInterrupt, 0]
launch([])
kill_tree.assert_called_once_with(12345)
unregister_pid.assert_called_once_with(12345)
def test_launch_claude_exits_when_command_cannot_be_resolved(
capsys: pytest.CaptureFixture[str],
) -> None:
from cli.launchers.claude import launch
settings = _launcher_settings()
with (
patch("cli.launchers.claude.get_settings", return_value=settings),
patch("cli.launchers.claude.preflight_proxy", return_value=None),
patch("cli.launchers.common.shutil.which", return_value=None),
patch("cli.launchers.common.subprocess.Popen") as popen,
pytest.raises(SystemExit) as exc_info,
):
launch([])
assert exc_info.value.code == 127
popen.assert_not_called()
captured = capsys.readouterr()
assert "Could not find Claude Code command: claude" in captured.err
assert "npm install -g @anthropic-ai/claude-code" in captured.err
def test_launch_claude_unreachable_proxy_exits_with_hint(
capsys: pytest.CaptureFixture[str],
) -> None:
from cli.launchers.claude import launch
settings = _launcher_settings(port=9393)
with (
patch("cli.launchers.claude.get_settings", return_value=settings),
patch(
"cli.launchers.claude.preflight_proxy", return_value="connection refused"
),
patch("cli.launchers.common.subprocess.Popen") as popen,
pytest.raises(SystemExit) as exc_info,
):
launch([])
assert exc_info.value.code == 1
popen.assert_not_called()
captured = capsys.readouterr()
assert "http://127.0.0.1:9393" in captured.err
assert "fcc-server" in captured.err