Add household support: multi-household isolation, each with own admin and users, 38 tests

This commit is contained in:
agentbox
2026-07-31 21:44:29 +00:00
parent d58828e0ef
commit c48cd265ca
10 changed files with 459 additions and 126 deletions
+70 -37
View File
@@ -1,4 +1,4 @@
"""Tests for meal periods, responses, and changed-mind logic."""
"""Tests for meal periods, responses, changed-mind logic, and household scoping."""
from datetime import date, datetime
@@ -10,10 +10,25 @@ from models import (
ensure_meal_periods,
get_dashboard_data,
upsert_response,
get_user_by_username,
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():
@@ -34,94 +49,112 @@ class TestMealPeriods:
class TestResponses:
def test_first_response_no_changed_at(self, app):
hh, user = _setup_household_and_user(app)
with app.app_context():
from models import create_user
user = create_user("test", "hash", is_admin=False)
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():
from models import create_user
user = create_user("flipper", "hash", is_admin=False)
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():
from models import create_user
user = create_user("flipper2", "hash", is_admin=False)
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")
# changed_at should reflect the latest flip
assert r2["changed_at"] is not None
# The second flip should have a different timestamp (or at least not earlier)
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():
from models import create_user
user = create_user("same", "hash", is_admin=False)
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 TestDashboardData:
def test_dashboard_includes_all_users(self, app):
class TestHouseholdScoping:
def test_users_isolation(self, app):
"""Users in different households should not see each other."""
with app.app_context():
from models import create_user
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)
create_user("u1", "h1", is_admin=True)
create_user("u2", "h2", is_admin=False)
data1 = get_dashboard_data(date.today(), hh1["id"])
data2 = get_dashboard_data(date.today(), hh2["id"])
data = get_dashboard_data(date.today())
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
assert data["users"][0]["username"] == "u1"
assert data["users"][1]["username"] == "u2"
# 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():
from models import create_user
user = create_user("responder", "h", is_admin=True)
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())
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():
from models import create_user
hh = create_household("SilentHouse")
create_user("silent", "h", hh["id"], is_admin=False)
create_user("silent", "h", is_admin=False)
data = get_dashboard_data(date.today())
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