Files
beer_data/tests/conftest.py
T
hermes bbd9fe17e5 fix(analysis): exclude suspect runs from every average, not just attenuation
Review finding, and it was right. The PR claimed that runs with impossible
readings were "excluded from the averages". That was only true of the
attenuation average. Run 12's bad final gravity was still poisoning three
other statistics:

- **Brew time** - its 56 days were folded into the average, making a
  typical brew look like 13.7 days when it is 10.9.
- **Time to 1.010** - its 56 days did the same to that figure (8.4d).
- **The history page's ABV** - an 18.64% ABV, which made run 12 the
  "strongest" beer in the history and pulled the average up.
- **The fermentation curve** - its curve plunges from 1.042 to 0.900, which
  dragged the median line down and stretched the min-max envelope to cover
  a gravity below water.

One bad reading was reaching four different statistics.

What changed
------------
- The suspect runs are computed once and excluded from every aggregate:
  the attenuation, brew-time and time-to-1.010 averages, the fastest and
  slowest brews, the history page's ABV average, and its strongest and
  lightest.
- `/api/runs` now carries a `suspect` flag, and the charts skip the flagged
  runs - the attenuation chart and the fermentation curve.
- The suspect run stays in the table, with a "not counted" chip and a tinted
  row, so it can be corrected.  It is excluded from the figures, not
  hidden.  The card header says "16 brews (15 counted)" and the prose says
  how many brews the figures are based on.

Tests
-----
133 (up from 127).  New coverage: the suspect run is excluded from the brew
time and time-to-1.010 averages, it is still listed for review, it is
flagged in the API, it is not the strongest brew in the history, and the
charts filter it out.

The `logged_in` fixture moved to conftest.py, where both test modules can
use it.
2026-11-09 14:03:07 +10:00

85 lines
2.4 KiB
Python

"""Shared pytest fixtures for the beer_data test suite."""
from __future__ import annotations
import os
import sys
import pytest
# The Flask app lives in src/flask and imports its siblings by bare name
# (``import brewing``), so that directory has to be on the path.
APP_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"src", "flask")
if APP_DIR not in sys.path:
sys.path.insert(0, APP_DIR)
@pytest.fixture(autouse=True)
def secret_key_env(monkeypatch):
"""The app refuses to start without SECRET_KEY — give the tests one."""
monkeypatch.setenv("SECRET_KEY", "test-secret-key-not-a-real-secret")
@pytest.fixture()
def mongo_client():
"""An in-memory Mongo, shared by the repository the app builds."""
import mongomock
return mongomock.MongoClient()
@pytest.fixture()
def client(mongo_client):
"""A Flask test client backed by an in-memory Mongo (mongomock)."""
from app_factory import create_app
app = create_app({
"TESTING": True,
"WTF_CSRF_ENABLED": False,
"SECRET_KEY": "test-secret",
"MONGO_CLIENT": mongo_client,
})
with app.test_client() as test_client:
yield test_client
@pytest.fixture()
def repo(mongo_client):
"""A repository backed by an in-memory Mongo."""
from mongo.repository import BeerRepository
return BeerRepository(client=mongo_client)
@pytest.fixture()
def logged_in(client, repo):
"""A client with an authenticated session and a user in the database."""
repo.users.insert_one({"username": "beer", "password": "beer_pass"})
response = client.post("/login", data={"username": "beer", "password": "beer_pass"},
follow_redirects=True)
assert response.status_code == 200
return client
def seed_readings(repository, rows):
"""Insert reading documents directly, bypassing the app.
Each row is ``(beer_run_id, beer_type, sg, date, final_reading, comment)``.
"""
docs = []
for run_id, beer_type, sg, date, final, comment in rows:
docs.append({
"beer_run_id": str(run_id),
"beer_type": beer_type,
"sg": str(sg),
"date": date,
"final_reading": "True" if final else "False",
"comment": comment,
})
if docs:
repository.readings.insert_many(docs)
return docs