initial commits
This commit is contained in:
63
tests/test_cli.py
Normal file
63
tests/test_cli.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from nexus_claude_api.cli import main
|
||||
from nexus_claude_api.config import Settings, load_local_config
|
||||
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
|
||||
assert "claude-sonnet-4.6" in command
|
||||
assert command.endswith("claude")
|
||||
|
||||
|
||||
def test_missing_api_key_fails(monkeypatch) -> None:
|
||||
tmp_path = _workspace_tmp("missing-key")
|
||||
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
|
||||
|
||||
|
||||
def test_local_config_api_key(monkeypatch) -> None:
|
||||
tmp_path = _workspace_tmp("local-config")
|
||||
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 == "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)
|
||||
|
||||
|
||||
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
|
||||
96
tests/test_routes.py
Normal file
96
tests/test_routes.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from nexus_claude_api.config import Settings
|
||||
from nexus_claude_api.server import create_app
|
||||
|
||||
|
||||
class FakeNexusClient:
|
||||
def converse(self, request: dict) -> dict:
|
||||
assert request["messages"][0]["content"][0]["text"] == "Hello"
|
||||
return {
|
||||
"ResponseMetadata": {"RequestId": "req-route"},
|
||||
"output": {"message": {"content": [{"text": "Hi"}]}},
|
||||
"stopReason": "end_turn",
|
||||
"usage": {"inputTokens": 3, "outputTokens": 1},
|
||||
}
|
||||
|
||||
def converse_stream(self, request: dict):
|
||||
assert request["messages"][0]["content"][0]["text"] == "Hello"
|
||||
return [
|
||||
{"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "Hi"}}},
|
||||
{"contentBlockStop": {"contentBlockIndex": 0}},
|
||||
{"messageStop": {"stopReason": "end_turn"}},
|
||||
]
|
||||
|
||||
|
||||
def client() -> TestClient:
|
||||
settings = Settings.from_values(api_key="test", require_api_key=False)
|
||||
return TestClient(create_app(settings=settings, nexus_client=FakeNexusClient()))
|
||||
|
||||
|
||||
def test_health() -> None:
|
||||
response = client().get("/health")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"status": "ok"}
|
||||
|
||||
|
||||
def test_models() -> None:
|
||||
response = client().get("/v1/models")
|
||||
assert response.status_code == 200
|
||||
data = response.json()["data"]
|
||||
assert {model["id"] for model in data} >= {
|
||||
"claude-sonnet-4.6",
|
||||
"claude-opus-4.6",
|
||||
"claude-haiku-4.5",
|
||||
}
|
||||
|
||||
|
||||
def test_messages_non_stream() -> None:
|
||||
response = client().post(
|
||||
"/v1/messages",
|
||||
json={
|
||||
"model": "claude-sonnet-4.6",
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"max_tokens": 32,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["id"] == "req-route"
|
||||
assert body["content"][0]["text"] == "Hi"
|
||||
|
||||
|
||||
def test_messages_stream() -> None:
|
||||
with client().stream(
|
||||
"POST",
|
||||
"/v1/messages",
|
||||
json={
|
||||
"model": "claude-sonnet-4.6",
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"max_tokens": 32,
|
||||
"stream": True,
|
||||
},
|
||||
) as response:
|
||||
body = response.read().decode("utf-8")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "event: message_start" in body
|
||||
assert "event: content_block_delta" in body
|
||||
assert "event: message_stop" in body
|
||||
|
||||
|
||||
def test_count_tokens() -> None:
|
||||
response = client().post(
|
||||
"/v1/messages/count_tokens",
|
||||
json={
|
||||
"model": "claude-sonnet-4.6",
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["input_tokens"] > 0
|
||||
|
||||
148
tests/test_translators.py
Normal file
148
tests/test_translators.py
Normal file
@@ -0,0 +1,148 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
|
||||
from nexus_claude_api.models import AnthropicMessagesRequest
|
||||
from nexus_claude_api.translators.anthropic_to_bedrock import anthropic_to_bedrock_request
|
||||
from nexus_claude_api.translators.bedrock_to_anthropic import bedrock_to_anthropic_response
|
||||
from nexus_claude_api.translators.stream import bedrock_stream_to_anthropic_events
|
||||
|
||||
|
||||
def test_text_request_translation() -> None:
|
||||
payload = AnthropicMessagesRequest.model_validate(
|
||||
{
|
||||
"model": "claude-sonnet-4.6",
|
||||
"messages": [{"role": "user", "content": "Hello"}],
|
||||
"system": "You are helpful.",
|
||||
"max_tokens": 100,
|
||||
"temperature": 0.2,
|
||||
}
|
||||
)
|
||||
|
||||
request = anthropic_to_bedrock_request(payload)
|
||||
|
||||
assert request["modelId"] == "claude-sonnet-4.6"
|
||||
assert request["messages"] == [{"role": "user", "content": [{"text": "Hello"}]}]
|
||||
assert request["system"] == [{"text": "You are helpful."}]
|
||||
assert request["inferenceConfig"]["maxTokens"] == 100
|
||||
assert request["inferenceConfig"]["temperature"] == 0.2
|
||||
|
||||
|
||||
def test_image_request_translation() -> None:
|
||||
image_data = base64.b64encode(b"image-bytes").decode("ascii")
|
||||
payload = AnthropicMessagesRequest.model_validate(
|
||||
{
|
||||
"model": "claude-sonnet-4.6",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Describe this"},
|
||||
{
|
||||
"type": "image",
|
||||
"source": {
|
||||
"type": "base64",
|
||||
"media_type": "image/png",
|
||||
"data": image_data,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
"max_tokens": 100,
|
||||
}
|
||||
)
|
||||
|
||||
request = anthropic_to_bedrock_request(payload)
|
||||
|
||||
image_block = request["messages"][0]["content"][1]["image"]
|
||||
assert image_block["format"] == "png"
|
||||
assert image_block["source"]["bytes"] == b"image-bytes"
|
||||
|
||||
|
||||
def test_tool_translation() -> None:
|
||||
payload = AnthropicMessagesRequest.model_validate(
|
||||
{
|
||||
"model": "claude-sonnet-4.6",
|
||||
"messages": [
|
||||
{"role": "user", "content": "Use a tool"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_1",
|
||||
"name": "get_weather",
|
||||
"input": {"city": "Berlin"},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_result",
|
||||
"tool_use_id": "toolu_1",
|
||||
"content": "Sunny",
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "get_weather",
|
||||
"description": "Get weather",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {"city": {"type": "string"}},
|
||||
},
|
||||
}
|
||||
],
|
||||
"tool_choice": {"type": "auto"},
|
||||
"max_tokens": 100,
|
||||
}
|
||||
)
|
||||
|
||||
request = anthropic_to_bedrock_request(payload)
|
||||
|
||||
assert request["messages"][1]["content"][0]["toolUse"]["toolUseId"] == "toolu_1"
|
||||
assert request["messages"][2]["content"][0]["toolResult"]["toolUseId"] == "toolu_1"
|
||||
assert request["toolConfig"]["tools"][0]["toolSpec"]["name"] == "get_weather"
|
||||
assert request["toolConfig"]["toolChoice"] == {"auto": {}}
|
||||
|
||||
|
||||
def test_bedrock_response_translation() -> None:
|
||||
response = {
|
||||
"ResponseMetadata": {"RequestId": "req-1"},
|
||||
"output": {"message": {"content": [{"text": "Hello there"}]}},
|
||||
"stopReason": "end_turn",
|
||||
"usage": {"inputTokens": 10, "outputTokens": 3},
|
||||
}
|
||||
|
||||
translated = bedrock_to_anthropic_response(response, model="claude-sonnet-4.6")
|
||||
|
||||
assert translated.id == "req-1"
|
||||
assert translated.content[0].type == "text"
|
||||
assert translated.content[0].text == "Hello there"
|
||||
assert translated.usage.input_tokens == 10
|
||||
assert translated.usage.output_tokens == 3
|
||||
|
||||
|
||||
def test_stream_translation() -> None:
|
||||
events = [
|
||||
{"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"text": "Hi"}}},
|
||||
{"contentBlockStop": {"contentBlockIndex": 0}},
|
||||
{"metadata": {"usage": {"inputTokens": 4, "outputTokens": 1}}},
|
||||
{"messageStop": {"stopReason": "end_turn"}},
|
||||
]
|
||||
|
||||
translated = list(
|
||||
bedrock_stream_to_anthropic_events(events, model="claude-sonnet-4.6")
|
||||
)
|
||||
|
||||
assert translated[0]["type"] == "message_start"
|
||||
assert any(event["type"] == "content_block_delta" for event in translated)
|
||||
assert translated[-2]["type"] == "message_delta"
|
||||
assert translated[-2]["usage"]["input_tokens"] == 4
|
||||
assert translated[-1]["type"] == "message_stop"
|
||||
|
||||
Reference in New Issue
Block a user