fix(ws1): skip fastapi TestClient tests when fastapi absent + ruff format
CI has no fastapi (box-only dependency); api.py imports it lazily. Guard the
7 TestClient smoke tests with skipif(find_spec('fastapi') is None) so they
skip in CI instead of failing collection, leaving the 14 invoker_multi tests
running. Also apply ruff format to the 5 WS1 files CI flagged.
This commit is contained in:
parent
e5a8949d0d
commit
366d07a84e
5 changed files with 41 additions and 8 deletions
|
|
@ -63,6 +63,7 @@ _DEFAULT_PORT = 8765
|
|||
# Pydantic request/response models (module-level so FastAPI resolves them).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TaskRequest(BaseModel):
|
||||
task: str
|
||||
transport: str = "claude_code"
|
||||
|
|
@ -89,6 +90,7 @@ class OrchestratorInvokeResponse(BaseModel):
|
|||
# Token helper.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _get_token() -> str:
|
||||
"""Read the API bearer token from the environment.
|
||||
|
||||
|
|
@ -139,6 +141,7 @@ def _build_coordinator(db_path: str | Path | None = None) -> Any:
|
|||
# App factory.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def make_app(
|
||||
*,
|
||||
coordinator: Any = None,
|
||||
|
|
@ -179,7 +182,9 @@ def make_app(
|
|||
) -> None:
|
||||
expected = _get_token()
|
||||
if creds is None or not hmac.compare_digest(creds.credentials, expected):
|
||||
raise HTTPException(status_code=401, detail="invalid or missing bearer token")
|
||||
raise HTTPException(
|
||||
status_code=401, detail="invalid or missing bearer token"
|
||||
)
|
||||
|
||||
# Coordinator is built lazily and cached on first use so test clients that
|
||||
# inject a pre-built coordinator never trigger the DB path.
|
||||
|
|
@ -194,7 +199,9 @@ def make_app(
|
|||
_state["ready"] = True
|
||||
return coord
|
||||
|
||||
@app.post("/tasks", response_model=TaskResponse, dependencies=[Depends(_check_token)])
|
||||
@app.post(
|
||||
"/tasks", response_model=TaskResponse, dependencies=[Depends(_check_token)]
|
||||
)
|
||||
def create_task(request: TaskRequest) -> TaskResponse:
|
||||
"""Intake a new agent-team task and run it to the first human gate."""
|
||||
coord = _get_coordinator()
|
||||
|
|
@ -233,7 +240,9 @@ def make_app(
|
|||
response_model=OrchestratorInvokeResponse,
|
||||
dependencies=[Depends(_check_token)],
|
||||
)
|
||||
def orchestrator_invoke(request: OrchestratorInvokeRequest) -> OrchestratorInvokeResponse:
|
||||
def orchestrator_invoke(
|
||||
request: OrchestratorInvokeRequest,
|
||||
) -> OrchestratorInvokeResponse:
|
||||
"""Route a one-shot prompt through the root orchestrator run.py.
|
||||
|
||||
Shells out to the orchestrator root's ``run.py`` with the prompt text.
|
||||
|
|
@ -272,6 +281,7 @@ def make_app(
|
|||
# Attended deploy helper (do NOT call in tests or CI).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def serve(
|
||||
*,
|
||||
host: str = _DEFAULT_HOST,
|
||||
|
|
|
|||
|
|
@ -96,8 +96,7 @@ def _load_model(key: str) -> Any:
|
|||
}
|
||||
if key not in factories:
|
||||
raise ValueError(
|
||||
f"unknown model key {key!r}; must be one of: "
|
||||
+ ", ".join(sorted(MODELS))
|
||||
f"unknown model key {key!r}; must be one of: " + ", ".join(sorted(MODELS))
|
||||
)
|
||||
return factories[key]()
|
||||
|
||||
|
|
|
|||
|
|
@ -177,7 +177,9 @@ def make_fast_coder_invoker() -> "BuildCallable":
|
|||
return _invoke
|
||||
|
||||
|
||||
def subprocess_build(instruction: str, *, route: _OrchestratorRoute | None = None) -> str:
|
||||
def subprocess_build(
|
||||
instruction: str, *, route: _OrchestratorRoute | None = None
|
||||
) -> str:
|
||||
"""Opt-in fallback :data:`BuildCallable`: shell out to ``run.py fast_coder``.
|
||||
|
||||
The original subprocess-based build path, retained as an opt-in alternative
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ def test_default_build_is_the_injection_default() -> None:
|
|||
src = inspect.getsource(default_build)
|
||||
assert "make_fast_coder_invoker" in src
|
||||
from agent_team.nodes.builders_llm import subprocess_build
|
||||
|
||||
subprocess_src = inspect.getsource(subprocess_build)
|
||||
assert "subprocess.run" in subprocess_src
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ Covers:
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
|
@ -180,7 +181,9 @@ def test_review_loop_llm_default_is_cross_reviewer_invoker() -> None:
|
|||
saved = sys.modules.pop("models", None)
|
||||
try:
|
||||
with patch.dict(sys.modules, {"models": None}): # type: ignore[dict-item]
|
||||
with pytest.raises((RuntimeError, AttributeError, TypeError, ModuleNotFoundError)):
|
||||
with pytest.raises(
|
||||
(RuntimeError, AttributeError, TypeError, ModuleNotFoundError)
|
||||
):
|
||||
fn("any prompt")
|
||||
finally:
|
||||
if saved is not None:
|
||||
|
|
@ -232,6 +235,15 @@ def test_builders_llm_default_build_uses_in_process() -> None:
|
|||
# api.py: bearer-token auth + endpoint smoke tests via FastAPI TestClient
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# fastapi is a box-only dependency (installed on the R720 during attended
|
||||
# deploy, never in CI or the root requirements). api.py imports it lazily, so
|
||||
# these TestClient-based smoke tests are the only thing that hard-requires it.
|
||||
# Skip them when it is absent rather than failing collection in CI.
|
||||
requires_fastapi = pytest.mark.skipif(
|
||||
importlib.util.find_spec("fastapi") is None,
|
||||
reason="fastapi not installed (box-only dependency)",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def api_token(monkeypatch: Any) -> str:
|
||||
|
|
@ -249,6 +261,7 @@ def _make_stub_coordinator(thread_id: str = "t-test-123") -> MagicMock:
|
|||
return coord
|
||||
|
||||
|
||||
@requires_fastapi
|
||||
def test_api_401_without_token(api_token: str) -> None:
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
|
@ -260,6 +273,7 @@ def test_api_401_without_token(api_token: str) -> None:
|
|||
assert resp.status_code == 401
|
||||
|
||||
|
||||
@requires_fastapi
|
||||
def test_api_401_wrong_token(api_token: str) -> None:
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
|
@ -275,6 +289,7 @@ def test_api_401_wrong_token(api_token: str) -> None:
|
|||
assert resp.status_code == 401
|
||||
|
||||
|
||||
@requires_fastapi
|
||||
def test_api_post_tasks_200(api_token: str) -> None:
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
|
@ -293,6 +308,7 @@ def test_api_post_tasks_200(api_token: str) -> None:
|
|||
stub.start_task.assert_called_once()
|
||||
|
||||
|
||||
@requires_fastapi
|
||||
def test_api_get_task_404_no_state(api_token: str) -> None:
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
|
@ -309,6 +325,7 @@ def test_api_get_task_404_no_state(api_token: str) -> None:
|
|||
assert resp.status_code == 503
|
||||
|
||||
|
||||
@requires_fastapi
|
||||
def test_api_get_task_with_graph_state(api_token: str) -> None:
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
|
@ -332,7 +349,10 @@ def test_api_get_task_with_graph_state(api_token: str) -> None:
|
|||
assert data["state"]["task"] == "add login"
|
||||
|
||||
|
||||
def test_api_orchestrator_invoke_calls_subprocess(api_token: str, tmp_path: Any) -> None:
|
||||
@requires_fastapi
|
||||
def test_api_orchestrator_invoke_calls_subprocess(
|
||||
api_token: str, tmp_path: Any
|
||||
) -> None:
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from agent_team.api import make_app
|
||||
|
|
@ -357,6 +377,7 @@ def test_api_orchestrator_invoke_calls_subprocess(api_token: str, tmp_path: Any)
|
|||
assert "hello world" in resp.json()["text"]
|
||||
|
||||
|
||||
@requires_fastapi
|
||||
def test_api_no_token_env_raises_on_request(monkeypatch: Any) -> None:
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
|
|
|||
Reference in a new issue