-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
documentationImprovements or additions to documentationImprovements or additions to documentationenhancementNew feature or requestNew feature or request
Description
Goal
Make app/api/routers/slack_agent.py testable without real network calls by introducing small dependency-injection seams that tests can override. This enables reliable integration tests that mock Slack and the LLM model.
Why
- The router currently constructs a real
WebClientandAgentat import time, making tests slow and flaky and risking external calls. - With DI seams, tests can inject fakes/mocks; production behavior remains unchanged.
Acceptance Criteria
- Add provider functions
get_client()andget_agent()used via FastAPIDependsin the/slackroute. - No real network calls during integration tests when providers are overridden.
- Runtime behavior of the API remains unchanged when using defaults.
- Clear examples in docstring/comments for how tests override dependencies.
Proposed Implementation
Refactor app/api/routers/slack_agent.py:
# app/api/routers/slack_agent.py
from pathlib import Path
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openrouter import OpenRouterProvider
from slack_sdk import WebClient
from app.tools import slack
from app.core.config import get_settings
prompt_path = Path(__file__).resolve().parents[2] / "pompts" / "slack.md"
prompt_instructions = prompt_path.read_text(encoding="utf-8")
router = APIRouter()
class Prompt(BaseModel):
user_instruction: str
# Providers (override in tests)
def get_client() -> WebClient:
settings = get_settings()
return WebClient(token=settings.slack_token or "")
def get_agent() -> Agent:
settings = get_settings()
model = OpenAIChatModel(
settings.model_name,
provider=OpenRouterProvider(api_key=settings.open_router_token or ""),
)
return Agent(model=model, deps_type=slack.Deps, instructions=prompt_instructions)
@router.post("/slack", tags=["slack"])
def prompt_slack_agent(
prompt: Prompt,
client: WebClient = Depends(get_client),
agent: Agent = Depends(get_agent),
):
try:
result = agent.run_sync(
prompt.user_instruction,
deps=slack.Deps(client=client),
toolsets=[slack.slack_toolset], # type: ignore
)
return {"content": result.output, "status": "ok"}
except Exception as e:
return {"msg": str(e), "status": "failed"}Notes:
- Keep the same prompt and toolset usage; only the construction shifts into providers.
- Tests can override
get_clientandget_agenteasily viaapp.dependency_overrides.
Test Override Examples
Override both dependencies in tests:
# tests/conftest.py (or inside the specific test)
from types import SimpleNamespace
from fastapi.testclient import TestClient
from app.api.routers import slack_agent as rtr
class FakeClient:
def __init__(self):
self.calls = []
def chat_postMessage(self, channel: str, text: str):
self.calls.append({"channel": channel, "text": text})
return {"ok": True}
class FakeAgent:
def __init__(self, output: str = "ok"):
self.calls = []
self.output = output
def run_sync(self, instruction, deps, toolsets=None):
self.calls.append({"instruction": instruction, "deps": deps, "toolsets": toolsets})
# return shape compatible with pydantic-ai result
return SimpleNamespace(output=f"stub:{self.output}")
fake_client = FakeClient()
fake_agent = FakeAgent()
# Apply overrides
from fastapi import FastAPI
app = FastAPI(title="testing API")
app.include_router(rtr.router)
app.dependency_overrides[rtr.get_client] = lambda: fake_client
app.dependency_overrides[rtr.get_agent] = lambda: fake_agent
client = TestClient(app)Then, in a test:
def test_slack_endpoint_with_fakes(client):
resp = client.post("/slack", json={"user_instruction": "say hi"})
assert resp.status_code == 200
assert resp.json()["status"] == "ok"Alternatives Considered
- Monkeypatch globals:
monkeypatch.setattr(slack_agent, "client", FakeClient())andmonkeypatch.setattr(slack_agent, "agent", FakeAgent()). This works today without refactor, but is more brittle as it depends on module import order and hidden globals. - Adapter wrapper: create a thin
SlackServiceclass that wrapsWebClientand inject that instead. Slightly more code; similar benefits.
Definition of Done
- Code refactor merged with providers and route wired to
Depends. - CI/tests pass, and integration tests can override dependencies to avoid network.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
documentationImprovements or additions to documentationImprovements or additions to documentationenhancementNew feature or requestNew feature or request