2026-06-26 17:02:21 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-06-27 10:21:00 +08:00
|
|
|
import json
|
2026-06-26 17:02:21 +08:00
|
|
|
import shutil
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
2026-06-27 14:32:52 +08:00
|
|
|
from nexus_claude_api import __version__
|
2026-06-26 17:02:21 +08:00
|
|
|
from nexus_claude_api.cli import main
|
2026-06-27 10:21:00 +08:00
|
|
|
from nexus_claude_api.config import (
|
|
|
|
|
Settings,
|
|
|
|
|
load_local_config,
|
|
|
|
|
load_user_config,
|
|
|
|
|
write_user_config,
|
|
|
|
|
)
|
2026-06-26 17:02:21 +08:00
|
|
|
from nexus_claude_api.shell import generate_claude_code_powershell
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_claude_code_command() -> None:
|
|
|
|
|
settings = Settings.from_values(
|
|
|
|
|
host="127.0.0.1",
|
|
|
|
|
port=4141,
|
|
|
|
|
api_key="test",
|
|
|
|
|
require_api_key=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
command = generate_claude_code_powershell(settings)
|
|
|
|
|
|
|
|
|
|
assert "ANTHROPIC_BASE_URL" in command
|
2026-06-26 22:36:09 +08:00
|
|
|
assert "ANTHROPIC_AUTH_TOKEN='dummy'" in command
|
|
|
|
|
assert "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY='1'" in command
|
|
|
|
|
assert "claude-opus-4.6" in command
|
|
|
|
|
assert command.endswith("claude --model claude-opus-4.6")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_claude_code_command_uses_custom_model() -> None:
|
|
|
|
|
settings = Settings.from_values(
|
|
|
|
|
host="127.0.0.1",
|
|
|
|
|
port=4141,
|
|
|
|
|
api_key="test",
|
|
|
|
|
model="claude-opus-4.6",
|
|
|
|
|
require_api_key=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
command = generate_claude_code_powershell(settings)
|
|
|
|
|
|
|
|
|
|
assert command.endswith("claude --model claude-opus-4.6")
|
2026-06-26 17:02:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_missing_api_key_fails(monkeypatch) -> None:
|
|
|
|
|
tmp_path = _workspace_tmp("missing-key")
|
2026-06-27 10:21:00 +08:00
|
|
|
user_config = tmp_path / ".config" / "nexus-claude-api" / "config.json"
|
|
|
|
|
monkeypatch.setattr("nexus_claude_api.config.USER_CONFIG_FILE", user_config)
|
2026-06-26 17:02:21 +08:00
|
|
|
monkeypatch.delenv("NEXUS_API_KEY", raising=False)
|
|
|
|
|
monkeypatch.delenv("AWS_BEARER_TOKEN_BEDROCK", raising=False)
|
|
|
|
|
monkeypatch.chdir(tmp_path)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
exit_code = main(["start", "--dry-run"])
|
|
|
|
|
finally:
|
|
|
|
|
monkeypatch.chdir(Path(__file__).parents[1])
|
|
|
|
|
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
assert exit_code == 2
|
|
|
|
|
|
|
|
|
|
|
2026-06-27 14:32:52 +08:00
|
|
|
def test_version_prints_package_version(capsys) -> None:
|
|
|
|
|
try:
|
|
|
|
|
main(["--version"])
|
|
|
|
|
except SystemExit as exc:
|
|
|
|
|
assert exc.code == 0
|
|
|
|
|
|
|
|
|
|
output = capsys.readouterr()
|
|
|
|
|
assert output.out.strip() == f"nexus-claude-api {__version__}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_invalid_user_config_returns_clean_error(monkeypatch, capsys) -> None:
|
|
|
|
|
tmp_path = _workspace_tmp("invalid-user-config")
|
|
|
|
|
user_config = tmp_path / ".config" / "nexus-claude-api" / "config.json"
|
|
|
|
|
user_config.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
user_config.write_text("{not json", encoding="utf-8")
|
|
|
|
|
monkeypatch.setattr("nexus_claude_api.config.USER_CONFIG_FILE", user_config)
|
|
|
|
|
monkeypatch.delenv("NEXUS_API_KEY", raising=False)
|
|
|
|
|
monkeypatch.delenv("AWS_BEARER_TOKEN_BEDROCK", raising=False)
|
|
|
|
|
monkeypatch.chdir(tmp_path)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
exit_code = main(["start", "--dry-run"])
|
|
|
|
|
finally:
|
|
|
|
|
monkeypatch.chdir(Path(__file__).parents[1])
|
|
|
|
|
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
output = capsys.readouterr()
|
|
|
|
|
assert exit_code == 2
|
|
|
|
|
assert "Invalid configuration: Invalid JSON" in output.err
|
|
|
|
|
assert "Traceback" not in output.err
|
|
|
|
|
|
|
|
|
|
|
2026-06-27 10:21:00 +08:00
|
|
|
def test_user_config_api_key(monkeypatch) -> None:
|
|
|
|
|
tmp_path = _workspace_tmp("user-config")
|
|
|
|
|
user_config = tmp_path / ".config" / "nexus-claude-api" / "config.json"
|
|
|
|
|
monkeypatch.setattr("nexus_claude_api.config.USER_CONFIG_FILE", user_config)
|
|
|
|
|
write_user_config({"api_key": "user-test-key"})
|
|
|
|
|
|
|
|
|
|
settings = Settings.from_values(require_api_key=False)
|
|
|
|
|
|
|
|
|
|
assert settings.api_key == "user-test-key"
|
|
|
|
|
assert load_user_config()["api_key"] == "user-test-key"
|
|
|
|
|
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_dev_local_config_api_key(monkeypatch) -> None:
|
|
|
|
|
tmp_path = _workspace_tmp("dev-local-config")
|
2026-06-26 17:02:21 +08:00
|
|
|
monkeypatch.chdir(tmp_path)
|
|
|
|
|
(tmp_path / "nexus-claude-api.local.json").write_text(
|
|
|
|
|
'{"api_key": "local-test-key"}',
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
2026-06-27 10:21:00 +08:00
|
|
|
settings = Settings.from_values(require_api_key=False, dev=True)
|
2026-06-26 17:02:21 +08:00
|
|
|
|
|
|
|
|
assert settings.api_key == "local-test-key"
|
|
|
|
|
assert load_local_config()["api_key"] == "local-test-key"
|
|
|
|
|
finally:
|
|
|
|
|
monkeypatch.chdir(Path(__file__).parents[1])
|
|
|
|
|
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
2026-06-27 10:21:00 +08:00
|
|
|
def test_default_mode_ignores_local_config(monkeypatch) -> None:
|
|
|
|
|
tmp_path = _workspace_tmp("default-ignores-local")
|
|
|
|
|
user_config = tmp_path / ".config" / "nexus-claude-api" / "config.json"
|
|
|
|
|
monkeypatch.setattr("nexus_claude_api.config.USER_CONFIG_FILE", user_config)
|
|
|
|
|
monkeypatch.delenv("NEXUS_API_KEY", raising=False)
|
|
|
|
|
monkeypatch.delenv("AWS_BEARER_TOKEN_BEDROCK", raising=False)
|
|
|
|
|
monkeypatch.chdir(tmp_path)
|
|
|
|
|
(tmp_path / "nexus-claude-api.local.json").write_text(
|
|
|
|
|
'{"api_key": "local-test-key"}',
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
settings = Settings.from_values(require_api_key=False)
|
|
|
|
|
|
|
|
|
|
assert settings.api_key is None
|
|
|
|
|
finally:
|
|
|
|
|
monkeypatch.chdir(Path(__file__).parents[1])
|
|
|
|
|
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_api_key_precedence_config_before_environment(monkeypatch) -> None:
|
|
|
|
|
tmp_path = _workspace_tmp("config-precedence")
|
|
|
|
|
user_config = tmp_path / ".config" / "nexus-claude-api" / "config.json"
|
|
|
|
|
monkeypatch.setattr("nexus_claude_api.config.USER_CONFIG_FILE", user_config)
|
|
|
|
|
monkeypatch.setenv("NEXUS_API_KEY", "env-key")
|
|
|
|
|
write_user_config({"api_key": "config-key"})
|
|
|
|
|
|
|
|
|
|
settings = Settings.from_values(require_api_key=False)
|
|
|
|
|
|
|
|
|
|
assert settings.api_key == "config-key"
|
|
|
|
|
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_api_key_argument_overrides_config(monkeypatch) -> None:
|
|
|
|
|
tmp_path = _workspace_tmp("arg-precedence")
|
|
|
|
|
user_config = tmp_path / ".config" / "nexus-claude-api" / "config.json"
|
|
|
|
|
monkeypatch.setattr("nexus_claude_api.config.USER_CONFIG_FILE", user_config)
|
|
|
|
|
write_user_config({"api_key": "config-key"})
|
|
|
|
|
|
|
|
|
|
settings = Settings.from_values(api_key="arg-key", require_api_key=False)
|
|
|
|
|
|
|
|
|
|
assert settings.api_key == "arg-key"
|
|
|
|
|
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_config_set_writes_user_config(monkeypatch) -> None:
|
|
|
|
|
tmp_path = _workspace_tmp("config-set")
|
|
|
|
|
user_config = tmp_path / ".config" / "nexus-claude-api" / "config.json"
|
|
|
|
|
monkeypatch.setattr("nexus_claude_api.config.USER_CONFIG_FILE", user_config)
|
|
|
|
|
|
|
|
|
|
exit_code = main(["config", "set", "--api-key", "written-key"])
|
|
|
|
|
|
|
|
|
|
assert exit_code == 0
|
|
|
|
|
assert json.loads(user_config.read_text(encoding="utf-8")) == {
|
|
|
|
|
"api_key": "written-key"
|
|
|
|
|
}
|
|
|
|
|
shutil.rmtree(tmp_path, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
2026-06-26 17:02:21 +08:00
|
|
|
def _workspace_tmp(name: str) -> Path:
|
|
|
|
|
path = Path(__file__).parents[1] / ".test-tmp" / name
|
|
|
|
|
shutil.rmtree(path, ignore_errors=True)
|
|
|
|
|
path.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
return path
|