161 lines
6.6 KiB
Python
161 lines
6.6 KiB
Python
"""Tests for meal periods, responses, changed-mind logic, and household scoping."""
|
|
|
|
from datetime import date, datetime
|
|
|
|
import pytest
|
|
|
|
from models import (
|
|
init_db,
|
|
get_db,
|
|
ensure_meal_periods,
|
|
get_dashboard_data,
|
|
upsert_response,
|
|
create_user,
|
|
create_household,
|
|
get_all_households,
|
|
get_household_users,
|
|
)
|
|
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
def _setup_household_and_user(app, username="test", household_name="Test House"):
|
|
"""Create a household + user. Returns (household, user)."""
|
|
with app.app_context():
|
|
hh = create_household(household_name)
|
|
user = create_user(username, "hash", hh["id"], is_admin=True)
|
|
return hh, user
|
|
|
|
|
|
# ── Tests ─────────────────────────────────────────────────────────────────────
|
|
|
|
class TestMealPeriods:
|
|
def test_ensure_creates_periods(self, app):
|
|
with app.app_context():
|
|
periods = ensure_meal_periods(date(2025, 6, 15))
|
|
assert len(periods) == 2
|
|
types = {p["meal_type"] for p in periods}
|
|
assert types == {"lunch", "dinner"}
|
|
for p in periods:
|
|
assert p["date"] == "2025-06-15"
|
|
|
|
def test_ensure_is_idempotent(self, app):
|
|
with app.app_context():
|
|
p1 = ensure_meal_periods(date(2025, 6, 15))
|
|
p2 = ensure_meal_periods(date(2025, 6, 15))
|
|
assert p1[0]["id"] == p2[0]["id"]
|
|
assert p1[1]["id"] == p2[1]["id"]
|
|
|
|
|
|
class TestResponses:
|
|
def test_first_response_no_changed_at(self, app):
|
|
hh, user = _setup_household_and_user(app)
|
|
with app.app_context():
|
|
periods = ensure_meal_periods(date.today())
|
|
lunch = periods[0]
|
|
resp = upsert_response(user["id"], lunch["id"], "yes")
|
|
assert resp["status"] == "yes"
|
|
assert resp["changed_at"] is None
|
|
|
|
def test_flip_yes_to_no_sets_changed_at(self, app):
|
|
hh, user = _setup_household_and_user(app)
|
|
with app.app_context():
|
|
periods = ensure_meal_periods(date.today())
|
|
lunch = periods[0]
|
|
upsert_response(user["id"], lunch["id"], "yes")
|
|
resp = upsert_response(user["id"], lunch["id"], "no")
|
|
assert resp["status"] == "no"
|
|
assert resp["changed_at"] is not None
|
|
|
|
def test_flip_twice_updates_changed_at(self, app):
|
|
hh, user = _setup_household_and_user(app, "flipper")
|
|
with app.app_context():
|
|
periods = ensure_meal_periods(date.today())
|
|
lunch = periods[0]
|
|
upsert_response(user["id"], lunch["id"], "yes")
|
|
r1 = upsert_response(user["id"], lunch["id"], "no")
|
|
r2 = upsert_response(user["id"], lunch["id"], "yes")
|
|
assert r2["changed_at"] is not None
|
|
assert r2["changed_at"] >= r1["changed_at"]
|
|
|
|
def test_same_status_no_change(self, app):
|
|
hh, user = _setup_household_and_user(app, "same")
|
|
with app.app_context():
|
|
periods = ensure_meal_periods(date.today())
|
|
lunch = periods[0]
|
|
r1 = upsert_response(user["id"], lunch["id"], "yes")
|
|
r2 = upsert_response(user["id"], lunch["id"], "yes")
|
|
assert r1["status"] == r2["status"]
|
|
assert r1["changed_at"] == r2["changed_at"]
|
|
|
|
|
|
class TestHouseholdScoping:
|
|
def test_users_isolation(self, app):
|
|
"""Users in different households should not see each other."""
|
|
with app.app_context():
|
|
hh1 = create_household("House Alpha")
|
|
hh2 = create_household("House Beta")
|
|
u1 = create_user("alice_a", "h", hh1["id"], is_admin=True)
|
|
u2 = create_user("bob_b", "h", hh2["id"], is_admin=True)
|
|
|
|
data1 = get_dashboard_data(date.today(), hh1["id"])
|
|
data2 = get_dashboard_data(date.today(), hh2["id"])
|
|
|
|
assert len(data1["users"]) == 1
|
|
assert data1["users"][0]["username"] == "alice_a"
|
|
assert len(data2["users"]) == 1
|
|
assert data2["users"][0]["username"] == "bob_b"
|
|
|
|
def test_join_household_starts_fresh(self, app):
|
|
"""New joiner sees household members but no prior responses."""
|
|
with app.app_context():
|
|
hh = create_household("Shared")
|
|
u1 = create_user("alice", "h", hh["id"], is_admin=True)
|
|
# Alice responds
|
|
periods = ensure_meal_periods(date.today())
|
|
lunch = next(p for p in periods if p["meal_type"] == "lunch")
|
|
upsert_response(u1["id"], lunch["id"], "yes")
|
|
|
|
# Bob joins
|
|
u2 = create_user("bob", "h", hh["id"], is_admin=False)
|
|
data = get_dashboard_data(date.today(), hh["id"])
|
|
assert len(data["users"]) == 2
|
|
# Bob's default status
|
|
bob_resp = [u for u in data["users"] if u["username"] == "bob"][0]
|
|
assert bob_resp["responses"]["lunch"]["status"] == "not_answered"
|
|
|
|
|
|
class TestDashboardData:
|
|
def test_dashboard_includes_all_household_users(self, app):
|
|
with app.app_context():
|
|
hh = create_household("DashHouse")
|
|
create_user("u1", "h1", hh["id"], is_admin=True)
|
|
create_user("u2", "h2", hh["id"], is_admin=False)
|
|
|
|
data = get_dashboard_data(date.today(), hh["id"])
|
|
assert len(data["users"]) == 2
|
|
usernames = {u["username"] for u in data["users"]}
|
|
assert usernames == {"u1", "u2"}
|
|
|
|
def test_dashboard_shows_responses(self, app):
|
|
with app.app_context():
|
|
hh = create_household("RespHouse")
|
|
user = create_user("responder", "h", hh["id"], is_admin=True)
|
|
periods = ensure_meal_periods(date.today())
|
|
lunch = next(p for p in periods if p["meal_type"] == "lunch")
|
|
upsert_response(user["id"], lunch["id"], "yes")
|
|
|
|
data = get_dashboard_data(date.today(), hh["id"])
|
|
resp = data["users"][0]["responses"]["lunch"]
|
|
assert resp["status"] == "yes"
|
|
|
|
def test_dashboard_defaults_not_answered(self, app):
|
|
with app.app_context():
|
|
hh = create_household("SilentHouse")
|
|
create_user("silent", "h", hh["id"], is_admin=False)
|
|
|
|
data = get_dashboard_data(date.today(), hh["id"])
|
|
resp = data["users"][0]["responses"]["dinner"]
|
|
assert resp["status"] == "not_answered"
|
|
assert resp["changed_at"] is None
|