| name | weaver-backend-test |
| description | Run and write backend tests for Weaver. Use when implementing backend features, fixing bugs, or when asked to test backend code. Covers unit tests (pytest + SQLite), integration tests (pytest + Docker PostgreSQL), and API tests. |
Weaver Backend Testing
Announce at start: "Running weaver-backend-test skill."
Decision Tree
What are you doing?
│
├─ Writing NEW code → Go to TDD Flow
├─ Fixing a bug → Go to Bug Fix Flow
├─ Running existing tests → Go to Run Tests
└─ Investigating a test failure → Go to Debug Flow
TDD Flow
Iron rule: No implementation without a failing test first.
Step 1: Document
cp docs/testing/test-plan-template.md docs/testing/plans/<feature>-test-plan.md
Fill in: test IDs, inputs, expected outputs, error cases.
Step 2: Write Failing Test
Location rules:
- Unit tests →
app/backend/tests/unit/test_<feature>.py
- Domain service tests →
app/backend/tests/unit/domain/services/test_<service>.py
- Infrastructure tests →
app/backend/tests/unit/infrastructure/<layer>/test_<module>.py
- API/Integration tests →
app/backend/tests/integration/test_<feature>.py
Unit test template:
"""Tests for <feature>."""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
class TestFeatureName:
"""Tests for FeatureName."""
@pytest.fixture
def mock_dependency(self):
"""Create mock dependency."""
mock = AsyncMock()
mock.some_method.return_value = expected_value
return mock
async def test_happy_path(self, mock_dependency):
"""Test normal operation."""
input_data = ...
result = await function_under_test(input_data, mock_dependency)
assert result == expected
mock_dependency.some_method.assert_called_once_with(...)
async def test_error_case(self):
"""Test error handling."""
with pytest.raises(ExpectedException):
await function_under_test(bad_input)
Integration test template (hits real DB):
"""Integration tests for <feature> API."""
import pytest
from httpx import AsyncClient
class TestFeatureAPI:
"""API tests for <feature> — hits real PostgreSQL."""
async def test_create(self, client: AsyncClient, db_session):
"""Test POST endpoint creates resource in DB."""
response = await client.post("/api/v1/...", json={...})
assert response.status_code == 201
data = response.json()
assert data["id"] is not None
from sqlalchemy import select, text
result = await db_session.execute(
select(Model).where(Model.id == data["id"])
)
row = result.scalar_one()
assert row.field == expected_value
Step 3: Confirm RED
cd app/backend && uv run pytest tests/unit/test_<feature>.py -x -v
Step 4: Implement (minimal)
Write the minimum code to make the test pass.
Step 5: Confirm GREEN
cd app/backend && uv run pytest tests/unit/test_<feature>.py -x -v
Step 6: Refactor + Full Suite
make test-backend-unit
Step 7: Register
Add test IDs to docs/testing/test-registry.md.
Bug Fix Flow
- Write a test that reproduces the bug (RED)
- Confirm it fails for the right reason
- Fix the bug (GREEN)
- Run full suite to confirm no regressions
cd app/backend && uv run pytest tests/unit/test_<bug>.py::TestClass::test_bug -x -v
cd app/backend && uv run pytest tests/unit/test_<bug>.py::TestClass::test_bug -x -v
make test-backend-unit
Run Tests
make test-backend-unit
cd app/backend && uv run pytest tests/unit/test_foo.py -x -v
cd app/backend && uv run pytest tests/unit/test_foo.py::TestClass::test_method -x -v
cd app/backend && uv run pytest tests/unit/test_foo.py -x -v -s
make test-backend-integration
cd app/backend && uv run pytest -x -v
Debug Flow
cd app/backend && uv run pytest tests/unit/test_foo.py -x -v --tb=long
cd app/backend && uv run pytest tests/unit/test_foo.py -x -v -s
cd app/backend && uv run pytest tests/unit/ -x -v -k "test_keyword"
cd app/backend && uv run pytest tests/unit/test_foo.py -x -v --tb=short -l
Project-Specific Patterns
Fixtures (from conftest.py)
client — httpx.AsyncClient connected to FastAPI app
db_session — Async SQLAlchemy session with transaction rollback
sample_project — Pre-created project fixture
sample_canvas_data — Canvas with nodes/edges
Auth in Tests
headers = {"X-API-Key": "sk_test_key_here"}
response = await client.get("/api/v1/projects", headers=headers)
Async Testing
All tests run with asyncio_mode = "auto" — just write async def test_* and it works.
Database in Tests
- Unit tests: SQLite in-memory (auto-configured via conftest.py)
- Integration tests: Real PostgreSQL via Docker (
docker compose up -d first)
- NEVER mock the database in integration tests — verify real mutations
What to Mock
- LLM calls (OpenRouter, Anthropic) — always mock, never hit real APIs
- External services (Supabase Storage, TikHub, YouTube) — always mock
- File system — mock for unit tests, real for integration tests
@patch("research_agent.infrastructure.llm.openrouter.OpenRouterClient.chat")
async def test_with_mocked_llm(self, mock_chat):
mock_chat.return_value = AsyncMock(content="mocked response")
...
Checklist Before Done