Houndarr testing patterns

SkillFiles & storage

Houndarr's pytest patterns. Loads when reading or editing tests/ files. Covers the fixture dependency graph, the FK seeding pattern for cooldowns/search_log tests, the local _login() and CSRF helpers in route tests, and the auth-state reset in test_settings.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Houndarr testing patterns skill

What this skill tells your AI

The instructions your AI receives, as published by av1155/houndarr in .agents/skills/houndarr-testing/SKILL.md and read by ahel’s review.

Framework

  • pytest + pytest-asyncio: asyncio_mode = "auto", asyncio_default_fixture_loop_scope = "function", addopts = "-q --tb=short" (set in pyproject.toml).
  • Async tests: use @pytest.mark.asyncio() (with parens), return -> None.
  • HTTP mocking: respx for httpx calls; use @respx.mock decorator.
  • App testing: TestClient (sync) or AsyncClient via ASGITransport.

Fixture dependency graph

tmp_data_dir          (temp directory, no deps)
  ├── db              (init SQLite, depends on tmp_data_dir)
  └── test_settings   (AppSettings + auth state reset, depends on tmp_data_dir)
        ├── app       (TestClient, depends on test_settings)
        └── async_client (AsyncClient, depends on test_settings)

db and test_settings are siblings; both depend on tmp_data_dir independently. Tests that need a database AND the app must request both db and app (or use fixtures that depend on db).

The test_settings fixture resets _auth._serializer, _auth._setup_complete, and _auth._login_attempts so auth state does not bleed between tests.

FK constraint pattern

Tests touching cooldowns or search_log must seed the instances table first via the seeded_instances fixture (defined locally in tests/test_engine/test_search_loop.py, tests/test_engine/test_golden_search_log.py, tests/test_engine/test_supervisor.py, and tests/test_services/test_cooldown.py):

@pytest_asyncio.fixture()
async def seeded_instances(db: None) -> AsyncGenerator[None, None]:
    async with get_db() as conn:
        await conn.executemany(
            "INSERT INTO instances (id, name, type, url, encrypted_api_key)"
            " VALUES (?, ?, ?, ?, ?)",
            [(1, "Sonarr Test", "sonarr", "http://sonarr:8989", _ENC_KEY),
             (2, "Radarr Test", "radarr", "http://radarr:7878", _ENC_KEY)],
        )
        await conn.commit()
    yield

Engine tests set encrypted_api_key to a valid Fernet-encrypted value (_ENC_KEY). The simpler 4-column form (without encrypted_api_key) is used in test_cooldown.py where only FK constraints matter.

Login helper for route tests

A _login() helper is defined locally in each route test file that needs it (test_logs.py, test_settings.py, test_status.py):

def _login(client: TestClient) -> None:
    client.post("/setup", data={"username": "admin", "password": "ValidPass1!", ...})
    client.post("/login", data={"username": "admin", "password": "ValidPass1!"})

CSRF helper for route tests

Mutating authenticated routes require a valid CSRF token. Use the helpers from tests/conftest.py:

from tests.conftest import csrf_headers, get_csrf_token

resp = client.post("/settings/instances", data=form, headers=csrf_headers(client))
resp = client.delete("/settings/instances/1", headers=csrf_headers(client))

Current CSRF exemptions: POST /logout, /login, /setup.

Markers

  • @pytest.mark.integration: 12 async engine-cycle cases in tests/test_e2e/ plus 15 Playwright flows in tests/e2e_browser/ (browser tree excluded from default collection via norecursedirs; test_e2e/ is collected and filterable).
  • @pytest.mark.pinning: characterisation tests pinning current behaviour before a refactor batch. Unit-scope; runs in the default suite. Add one whenever a refactor needs a behavioural lock.

Running

just test, just test-quick, just test-integration, and just pin run with pytest -n auto by default (pytest-xdist). Override with PYTEST_WORKERS=0 for serial triage, or PYTEST_WORKERS=4 to constrain. just test-browser chromium runs the Playwright e2e flows serially (shared stack on fixed ports).

Signals

GitHub stars
284
Forks
4
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
houndarr-testing
Source
github.com/av1155/houndarr