Skip to content

Commit 8470405

Browse files
feat: add pytest taskfile with test categories and server lifecycle
Add taskfiles/pytest.yml with unit, integration, e2e, property, and coverage test tasks. Integration and e2e tasks manage uvicorn server lifecycle automatically via defer (start, health wait, test, stop). Delegate test commands from root taskfile to pytest include, matching the mt repo delegation pattern. Update AGENTS.md with test markers and taskfile structure docs. Add backlog tasks TASK-004 through TASK-007 for each test suite type. TASK-001 now depends on all four test suite tasks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 985c65a commit 8470405

8 files changed

Lines changed: 279 additions & 4 deletions

AGENTS.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,33 @@ Always use Context7 MCP when I need library/API documentation, code generation,
2323

2424
## Build, Lint, and Test Commands
2525

26-
- Full test suite: `uv run pytest` or `task test`
26+
- Full test suite: `task test`
27+
- Unit tests: `task test:unit`
28+
- Integration tests: `task test:integration` (auto server start/stop)
29+
- E2E tests: `task test:e2e` (auto server start/stop)
30+
- Property-based tests: `task test:property`
31+
- Coverage: `task test:cov`
2732
- Single test: `uv run pytest tests/test_filename.py::test_function_name`
33+
- Pass extra args: `task test:unit -- -v -k test_health`
2834
- Linting: `uv run ruff check --fix --respect-gitignore` or `task lint`
2935
- Formatting: `uv run ruff format --respect-gitignore` or `task format`
3036
- Check dependencies: `uv run deptry .` or `task deptry`
3137
- Pre-commit hooks: `prek run --all-files` or `task pre-commit`
3238

39+
### Test Markers
40+
41+
Tests use pytest markers to categorize test types:
42+
43+
- `@pytest.mark.unit` — isolated tests, no external deps
44+
- `@pytest.mark.integration` — component interactions, may need DB/server
45+
- `@pytest.mark.e2e` — full stack with real HTTP requests
46+
- `@pytest.mark.property` — Hypothesis property-based tests
47+
48+
### Test Taskfile Structure
49+
50+
Test tasks are defined in `taskfiles/pytest.yml` and delegated from the root `taskfile.yml`.
51+
Integration and e2e tasks manage server lifecycle automatically (start, health wait, test, stop via `defer`).
52+
3353
## Code Style Guidelines
3454

3555
- **Formatting**: 4 spaces, 130-char line limit, LF line endings

backlog/tasks/task-001 - Bump-FastAPI-from-0.115.6-to-0.133.1.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ title: Bump FastAPI from >=0.115.6 to >=0.133.1
44
status: To Do
55
assignee: []
66
created_date: '2026-02-26 16:25'
7+
updated_date: '2026-02-26 18:06'
78
labels:
89
- dependencies
9-
dependencies: []
10+
dependencies:
11+
- TASK-004
12+
- TASK-005
13+
- TASK-006
14+
- TASK-007
1015
references:
1116
- 'https://github.com/fastapi/fastapi/pull/14964'
1217
- pyproject.toml
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
id: TASK-004
3+
title: Add unit test suite with pytest markers and fixtures
4+
status: In Progress
5+
assignee: []
6+
created_date: '2026-02-26 18:06'
7+
updated_date: '2026-02-26 18:08'
8+
labels:
9+
- testing
10+
dependencies: []
11+
references:
12+
- tests/conftest.py
13+
- tests/test_main.py
14+
- tests/test_meetup_query.py
15+
- pyproject.toml
16+
priority: medium
17+
---
18+
19+
## Description
20+
21+
<!-- SECTION:DESCRIPTION:BEGIN -->
22+
Set up unit test infrastructure: pytest markers (`@pytest.mark.unit`), shared fixtures in conftest.py, and reorganize existing tests (test_main.py, test_meetup_query.py) under unit test markers. Unit tests must run in isolation with no external dependencies (no DB, no network, no running server). Add `[tool.pytest.ini_options]` to pyproject.toml with marker registration.
23+
<!-- SECTION:DESCRIPTION:END -->
24+
25+
## Acceptance Criteria
26+
<!-- AC:BEGIN -->
27+
- [ ] #1 pytest marker `unit` registered in pyproject.toml
28+
- [ ] #2 Existing tests in test_main.py and test_meetup_query.py marked as unit tests
29+
- [ ] #3 Unit tests pass with `pytest -m unit` and require no external services
30+
- [ ] #4 conftest.py provides shared fixtures for mocking external deps (DB, API, Slack)
31+
- [ ] #5 task test:unit in taskfile runs unit tests via .venv
32+
<!-- AC:END -->
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
id: TASK-005
3+
title: Add integration test suite with server lifecycle management
4+
status: To Do
5+
assignee: []
6+
created_date: '2026-02-26 18:06'
7+
labels:
8+
- testing
9+
dependencies: []
10+
references:
11+
- tests/test_smoke.py
12+
- taskfile.yml
13+
- taskfiles/
14+
- ~/git/mt/taskfile.yml
15+
priority: medium
16+
---
17+
18+
## Description
19+
20+
<!-- SECTION:DESCRIPTION:BEGIN -->
21+
Create integration test suite that tests component interactions (FastAPI routes with real TestClient, database interactions with test DB). Migrate test_smoke.py into integration tests. Add taskfile tasks for starting the server, running integration tests, and stopping the server. Server lifecycle should be managed automatically via taskfile (start in background, wait for healthy, run tests, stop).
22+
<!-- SECTION:DESCRIPTION:END -->
23+
24+
## Acceptance Criteria
25+
<!-- AC:BEGIN -->
26+
- [ ] #1 pytest marker `integration` registered in pyproject.toml
27+
- [ ] #2 test_smoke.py refactored to use integration marker and TestClient (no manual server needed)
28+
- [ ] #3 Integration tests can run against a real or test database
29+
- [ ] #4 task test:integration in taskfile handles server start/stop automatically
30+
- [ ] #5 Server health check wait loop before test execution
31+
<!-- AC:END -->
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
id: TASK-006
3+
title: Add e2e test suite with full stack testing
4+
status: To Do
5+
assignee: []
6+
created_date: '2026-02-26 18:06'
7+
labels:
8+
- testing
9+
dependencies: []
10+
references:
11+
- app/main.py
12+
- taskfile.yml
13+
priority: medium
14+
---
15+
16+
## Description
17+
18+
<!-- SECTION:DESCRIPTION:BEGIN -->
19+
Create end-to-end test suite that validates complete user workflows against the running application (auth flow, event retrieval, Slack posting). E2E tests run against a fully started server with real HTTP requests. Taskfile task manages full lifecycle: start server + deps, run e2e tests, teardown.
20+
<!-- SECTION:DESCRIPTION:END -->
21+
22+
## Acceptance Criteria
23+
<!-- AC:BEGIN -->
24+
- [ ] #1 pytest marker `e2e` registered in pyproject.toml
25+
- [ ] #2 E2E tests validate auth login flow end-to-end
26+
- [ ] #3 E2E tests validate event retrieval pipeline
27+
- [ ] #4 task test:e2e in taskfile manages full server lifecycle
28+
- [ ] #5 E2E tests use real HTTP requests (requests library), not TestClient
29+
<!-- AC:END -->
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
id: TASK-007
3+
title: Add property-based testing with Hypothesis
4+
status: To Do
5+
assignee: []
6+
created_date: '2026-02-26 18:06'
7+
labels:
8+
- testing
9+
dependencies: []
10+
references:
11+
- tests/test_meetup_query.py
12+
- app/meetup_query.py
13+
- app/sign_jwt.py
14+
- pyproject.toml
15+
priority: medium
16+
---
17+
18+
## Description
19+
20+
<!-- SECTION:DESCRIPTION:BEGIN -->
21+
Add property-based tests using Hypothesis for data-driven validation. Target functions with complex input handling: format_response parsing, sort_csv/sort_json ordering invariants, schedule time calculations, JWT token encoding/decoding roundtrips. Hypothesis is already in test dependencies.
22+
<!-- SECTION:DESCRIPTION:END -->
23+
24+
## Acceptance Criteria
25+
<!-- AC:BEGIN -->
26+
- [ ] #1 pytest marker `property` registered in pyproject.toml
27+
- [ ] #2 Property tests for format_response with varied GraphQL response shapes
28+
- [ ] #3 Property tests for sort_csv/sort_json ordering invariants
29+
- [ ] #4 Property tests for JWT token roundtrip encode/decode
30+
- [ ] #5 task test:property in taskfile runs property-based tests via .venv
31+
- [ ] #6 Hypothesis profiles configured (ci profile with max_examples=200, dev with 50)
32+
<!-- AC:END -->

taskfile.yml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ includes:
2020
taskfile: ./taskfiles/docker.yml
2121
heroku:
2222
taskfile: ./taskfiles/heroku.yml
23+
pytest:
24+
taskfile: ./taskfiles/pytest.yml
2325
uv:
2426
taskfile: ./taskfiles/uv.yml
2527

@@ -52,9 +54,34 @@ tasks:
5254
- ruff format --respect-gitignore
5355

5456
test:
55-
desc: "Run tests"
57+
desc: "Run all tests"
5658
cmds:
57-
- pytest
59+
- task: pytest:test
60+
61+
test:unit:
62+
desc: "Run unit tests"
63+
cmds:
64+
- task: pytest:test:unit
65+
66+
test:integration:
67+
desc: "Run integration tests (starts/stops server)"
68+
cmds:
69+
- task: pytest:test:integration
70+
71+
test:e2e:
72+
desc: "Run e2e tests (starts/stops server)"
73+
cmds:
74+
- task: pytest:test:e2e
75+
76+
test:property:
77+
desc: "Run property-based tests (Hypothesis)"
78+
cmds:
79+
- task: pytest:test:property
80+
81+
test:cov:
82+
desc: "Run tests with coverage"
83+
cmds:
84+
- task: pytest:test:cov
5885

5986
pyclean:
6087
desc: "Remove .pyc and __pycache__"

taskfiles/pytest.yml

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
version: "3.0"
2+
3+
set: ['e', 'u', 'pipefail']
4+
shopt: ['globstar']
5+
6+
vars:
7+
APP_DIR: "{{.ROOT_DIR}}/app"
8+
TEST_DIR: "{{.ROOT_DIR}}/tests"
9+
PORT: '{{.PORT | default "3000"}}'
10+
PIDFILE: "{{.ROOT_DIR}}/.uvicorn.pid"
11+
HEALTH_URL: "http://localhost:{{.PORT}}/healthz"
12+
HEALTH_TIMEOUT: 30
13+
HEALTH_INTERVAL: 1
14+
15+
tasks:
16+
test:
17+
desc: "Run all tests"
18+
cmds:
19+
- pytest {{.CLI_ARGS}}
20+
21+
test:unit:
22+
desc: "Run unit tests"
23+
cmds:
24+
- pytest -m unit {{.CLI_ARGS}}
25+
26+
test:integration:
27+
desc: "Run integration tests (manages server lifecycle)"
28+
cmds:
29+
- task: server:start
30+
- defer: { task: server:stop }
31+
- task: server:wait
32+
- pytest -m integration {{.CLI_ARGS}}
33+
34+
test:e2e:
35+
desc: "Run e2e tests (manages server lifecycle)"
36+
cmds:
37+
- task: server:start
38+
- defer: { task: server:stop }
39+
- task: server:wait
40+
- pytest -m e2e {{.CLI_ARGS}}
41+
42+
test:property:
43+
desc: "Run property-based tests (Hypothesis)"
44+
cmds:
45+
- pytest -m property {{.CLI_ARGS}}
46+
47+
test:cov:
48+
desc: "Run tests with coverage report"
49+
cmds:
50+
- pytest --cov=app --cov-report=term-missing {{.CLI_ARGS}}
51+
52+
server:start:
53+
desc: "Start uvicorn in background"
54+
internal: true
55+
dir: "{{.APP_DIR}}"
56+
cmds:
57+
- |
58+
if [ -f "{{.PIDFILE}}" ] && kill -0 "$(cat "{{.PIDFILE}}")" 2>/dev/null; then
59+
echo "Server already running (PID $(cat "{{.PIDFILE}}"))"
60+
exit 0
61+
fi
62+
uvicorn main:app \
63+
--host 0.0.0.0 \
64+
--port {{.PORT}} \
65+
--log-level warning &
66+
echo $! > "{{.PIDFILE}}"
67+
echo "Started uvicorn (PID $!)"
68+
69+
server:stop:
70+
desc: "Stop background uvicorn"
71+
internal: true
72+
cmds:
73+
- |
74+
if [ ! -f "{{.PIDFILE}}" ]; then
75+
exit 0
76+
fi
77+
pid=$(cat "{{.PIDFILE}}")
78+
if kill -0 "$pid" 2>/dev/null; then
79+
kill "$pid"
80+
echo "Stopped uvicorn (PID $pid)"
81+
fi
82+
rm -f "{{.PIDFILE}}"
83+
84+
server:wait:
85+
desc: "Wait for server health check"
86+
internal: true
87+
cmds:
88+
- |
89+
elapsed=0
90+
while [ "$elapsed" -lt "{{.HEALTH_TIMEOUT}}" ]; do
91+
if curl -sf "{{.HEALTH_URL}}" > /dev/null 2>&1; then
92+
echo "Server healthy (${elapsed}s)"
93+
exit 0
94+
fi
95+
sleep {{.HEALTH_INTERVAL}}
96+
elapsed=$((elapsed + {{.HEALTH_INTERVAL}}))
97+
done
98+
echo "Server failed to start within {{.HEALTH_TIMEOUT}}s"
99+
exit 1

0 commit comments

Comments
 (0)