164 lines
5.0 KiB
Markdown
164 lines
5.0 KiB
Markdown
# Plan 5 — Observability & Test Coverage
|
||
|
||
**Items covered:** #10 (no logging), #12 (missing test coverage)
|
||
|
||
---
|
||
|
||
## Context
|
||
|
||
You are working on a FastAPI backend for a baby monitor / sleep tracker app. The codebase lives under `app/`. This is the final plan. Plans 1–4 must already be complete — this plan assumes auth returns 401, malformed parameters return 422, and `updated_at` is updated on PATCH/PUT.
|
||
|
||
Do the logging setup first (it is quick), then work through the missing tests methodically. Add tests one group at a time and run `pytest` after each group.
|
||
|
||
---
|
||
|
||
## Tasks
|
||
|
||
### 1. Add structured logging (`app/main.py` and other files)
|
||
|
||
#### Step 1 — Read `app/config.py` and `app/main.py` first
|
||
|
||
Confirm that `LOG_LEVEL` already exists in `Settings` (it does — it is currently defined but never consumed). Note its default value.
|
||
|
||
#### Step 2 — Configure logging in `main.py`
|
||
|
||
At the top of `app/main.py`, before the app is created, add:
|
||
|
||
```python
|
||
import logging
|
||
|
||
logging.basicConfig(
|
||
level=settings.LOG_LEVEL,
|
||
format="%(asctime)s %(levelname)s %(name)s %(message)s",
|
||
)
|
||
logger = logging.getLogger(__name__)
|
||
```
|
||
|
||
#### Step 3 — Add request logging middleware
|
||
|
||
Add a simple middleware to log every request and response status:
|
||
|
||
```python
|
||
import time
|
||
from fastapi import Request
|
||
|
||
@app.middleware("http")
|
||
async def log_requests(request: Request, call_next):
|
||
start = time.time()
|
||
response = await call_next(request)
|
||
duration_ms = (time.time() - start) * 1000
|
||
logger.info(
|
||
"%s %s → %s (%.1fms)",
|
||
request.method,
|
||
request.url.path,
|
||
response.status_code,
|
||
duration_ms,
|
||
)
|
||
return response
|
||
```
|
||
|
||
#### Step 4 — Add business event logging
|
||
|
||
In the relevant router files, add a `logger = logging.getLogger(__name__)` at the module level and log the key business events:
|
||
|
||
- Session started: `logger.info("Session started id=%s", session.id)`
|
||
- Session ended: `logger.info("Session ended id=%s duration_min=%.1f", session.id, ...)`
|
||
- Session deleted: `logger.info("Session deleted id=%s", session_id)`
|
||
|
||
Keep log messages concise. Do not log sensitive user data.
|
||
|
||
#### Step 5 — Verify
|
||
|
||
Run the tests:
|
||
|
||
```bash
|
||
pytest
|
||
```
|
||
|
||
Logging must not break any existing tests.
|
||
|
||
---
|
||
|
||
### 2. Add missing test coverage (`tests/`)
|
||
|
||
Read the existing test files before adding anything so you understand the test client setup, fixtures, and conventions already in use. Add all new tests to appropriate existing files (or a new file if clearly warranted — e.g. `tests/test_stats.py` if one does not exist).
|
||
|
||
Work through each group below. After adding each group, run `pytest` and fix any failures before continuing.
|
||
|
||
---
|
||
|
||
#### Group A — Stats endpoints: empty date ranges
|
||
|
||
For each stats endpoint (`/api/stats/daily-summary`, `/api/stats/timetable`, `/api/stats/wake-sleep-times`), add a test that:
|
||
- Calls the endpoint with a valid date range that has no sessions
|
||
- Asserts a 200 response
|
||
- Asserts the response body represents an empty result (no days, empty list, etc.)
|
||
|
||
---
|
||
|
||
#### Group B — Stats endpoints: session spanning midnight
|
||
|
||
Add a test that:
|
||
- Creates a session with `start_time` on day N at 23:00 and `end_time` on day N+1 at 07:00
|
||
- Calls `/api/stats/timetable` for a range covering both days
|
||
- Asserts that the session appears in both days' slot data
|
||
|
||
---
|
||
|
||
#### Group C — Stats endpoints: session partially overlapping the query range
|
||
|
||
Add a test that:
|
||
- Creates a session that starts before the query range and ends inside it
|
||
- Calls each stats endpoint with the range
|
||
- Asserts the session is counted/included correctly (it should be, since it overlaps the range)
|
||
|
||
---
|
||
|
||
#### Group D — Stats endpoints: multiple sessions on the same day
|
||
|
||
Add a test that:
|
||
- Creates two or more sessions on the same day
|
||
- Calls `/api/stats/daily-summary` for that day
|
||
- Asserts that the totals correctly aggregate across both sessions
|
||
|
||
---
|
||
|
||
#### Group E — Invalid query parameters return 422
|
||
|
||
These tests depend on Plan 2 (silent suppression replaced with 422). Add a parametrised test (or individual tests) for:
|
||
- `/api/stats/daily-summary` with `day_start=not-a-time` → expect 422
|
||
- `/api/stats/timetable` with `day_start=not-a-time` → expect 422
|
||
- Any other endpoint that previously silently suppressed a `ValueError`
|
||
|
||
---
|
||
|
||
#### Group F — Bounded `limit` parameter
|
||
|
||
This test depends on Plan 4 (Query bound added). Add tests for:
|
||
- `GET /api/sessions?limit=0` → expect 422
|
||
- `GET /api/sessions?limit=1000001` → expect 422
|
||
- `GET /api/sessions?limit=1` → expect 200
|
||
- `GET /api/sessions?limit=1000` → expect 200
|
||
|
||
---
|
||
|
||
#### Group G — `updated_at` regression
|
||
|
||
This test depends on Plan 1 (updated_at bug fixed). Add a test that:
|
||
- Creates a session
|
||
- Records the `updated_at` value
|
||
- Sends a PUT/PATCH request to update it
|
||
- Asserts that `updated_at` in the response is strictly greater than the original value
|
||
|
||
---
|
||
|
||
## Final verification
|
||
|
||
Run the complete test suite one last time:
|
||
|
||
```bash
|
||
pytest --tb=short
|
||
```
|
||
|
||
All tests must pass. Review the output for any warnings. This completes all 15 improvements from the backend improvement report.
|