Set cookies on client instance instead of passing per-request to avoid the deprecation warning about ambiguous cookie persistence behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
224 lines
7.4 KiB
Python
224 lines
7.4 KiB
Python
# ABOUTME: Integration tests for user defaults feature.
|
|
# ABOUTME: Verifies that form defaults are saved and loaded correctly.
|
|
|
|
import os
|
|
import time
|
|
|
|
import pytest
|
|
from starlette.testclient import TestClient
|
|
|
|
from animaltrack.events.payloads import FeedPurchasedPayload
|
|
from animaltrack.events.store import EventStore
|
|
from animaltrack.models.reference import UserDefault
|
|
from animaltrack.projections import ProjectionRegistry
|
|
from animaltrack.projections.feed import FeedInventoryProjection
|
|
from animaltrack.repositories.user_defaults import UserDefaultsRepository
|
|
from animaltrack.services.feed import FeedService
|
|
|
|
|
|
def make_test_settings(
|
|
csrf_secret: str = "test-secret",
|
|
trusted_proxy_ips: str = "127.0.0.1",
|
|
dev_mode: bool = False, # Disable dev_mode to test real auth
|
|
):
|
|
"""Create Settings for testing by setting env vars temporarily."""
|
|
from animaltrack.config import Settings
|
|
|
|
old_env = os.environ.copy()
|
|
try:
|
|
os.environ["CSRF_SECRET"] = csrf_secret
|
|
os.environ["TRUSTED_PROXY_IPS"] = trusted_proxy_ips
|
|
os.environ["DEV_MODE"] = str(dev_mode).lower()
|
|
return Settings()
|
|
finally:
|
|
os.environ.clear()
|
|
os.environ.update(old_env)
|
|
|
|
|
|
@pytest.fixture
|
|
def client(seeded_db):
|
|
"""Create a test client for the app with real auth enabled."""
|
|
from animaltrack.web.app import create_app
|
|
|
|
settings = make_test_settings(trusted_proxy_ips="testclient", dev_mode=False)
|
|
app, rt = create_app(settings=settings, db=seeded_db)
|
|
return TestClient(app, raise_server_exceptions=True)
|
|
|
|
|
|
@pytest.fixture
|
|
def location_strip1_id(seeded_db):
|
|
"""Get Strip 1 location ID from seeded data."""
|
|
row = seeded_db.execute("SELECT id FROM locations WHERE name = 'Strip 1'").fetchone()
|
|
return row[0]
|
|
|
|
|
|
@pytest.fixture
|
|
def location_strip2_id(seeded_db):
|
|
"""Get Strip 2 location ID from seeded data."""
|
|
row = seeded_db.execute("SELECT id FROM locations WHERE name = 'Strip 2'").fetchone()
|
|
return row[0]
|
|
|
|
|
|
@pytest.fixture
|
|
def feed_purchase_in_db(seeded_db):
|
|
"""Create a feed purchase so give_feed can work."""
|
|
event_store = EventStore(seeded_db)
|
|
registry = ProjectionRegistry()
|
|
registry.register(FeedInventoryProjection(seeded_db))
|
|
feed_service = FeedService(seeded_db, event_store, registry)
|
|
|
|
payload = FeedPurchasedPayload(
|
|
feed_type_code="layer",
|
|
bag_size_kg=20,
|
|
bags_count=5,
|
|
bag_price_cents=2400,
|
|
)
|
|
ts_utc = int(time.time() * 1000) - 86400000
|
|
feed_service.purchase_feed(payload, ts_utc, "ppetru")
|
|
return payload
|
|
|
|
|
|
def make_csrf_headers(csrf_token: str = "test-csrf-token"):
|
|
"""Make headers with CSRF token for POST requests."""
|
|
return {
|
|
"X-CSRF-Token": csrf_token,
|
|
"Origin": "http://testserver", # Match TestClient's default host
|
|
}
|
|
|
|
|
|
class TestFeedUserDefaults:
|
|
"""Tests for feed form user defaults."""
|
|
|
|
def test_defaults_saved_on_successful_give(
|
|
self, client, seeded_db, location_strip1_id, feed_purchase_in_db
|
|
):
|
|
"""Successful feed-given saves user defaults."""
|
|
csrf_token = "test-csrf-token"
|
|
client.cookies.set("csrf_token", csrf_token)
|
|
response = client.post(
|
|
"/actions/feed-given",
|
|
data={
|
|
"location_id": location_strip1_id,
|
|
"feed_type_code": "layer",
|
|
"amount_kg": "15",
|
|
},
|
|
headers={
|
|
"X-Oidc-Username": "ppetru",
|
|
**make_csrf_headers(csrf_token),
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
# Verify defaults were saved
|
|
defaults = UserDefaultsRepository(seeded_db).get("ppetru", "feed_given")
|
|
assert defaults is not None
|
|
assert defaults.location_id == location_strip1_id
|
|
assert defaults.feed_type_code == "layer"
|
|
assert defaults.amount_kg == 15
|
|
|
|
def test_defaults_loaded_on_feed_page(self, client, seeded_db, location_strip1_id):
|
|
"""GET /feed loads saved user defaults."""
|
|
# First set some defaults
|
|
now_utc = int(time.time() * 1000)
|
|
UserDefaultsRepository(seeded_db).upsert(
|
|
UserDefault(
|
|
username="ppetru",
|
|
action="feed_given",
|
|
location_id=location_strip1_id,
|
|
feed_type_code="grower",
|
|
amount_kg=25,
|
|
updated_at_utc=now_utc,
|
|
)
|
|
)
|
|
|
|
# Load the feed page
|
|
response = client.get(
|
|
"/feed",
|
|
headers={"X-Oidc-Username": "ppetru"},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
# Check that the form has pre-selected values
|
|
content = response.text
|
|
assert f'value="{location_strip1_id}"' in content or "selected" in content
|
|
assert "grower" in content
|
|
|
|
def test_no_defaults_for_unknown_user(
|
|
self, client, seeded_db, location_strip1_id, feed_purchase_in_db
|
|
):
|
|
"""Unknown users are rejected by auth middleware."""
|
|
csrf_token = "test-csrf-token"
|
|
client.cookies.set("csrf_token", csrf_token)
|
|
response = client.post(
|
|
"/actions/feed-given",
|
|
data={
|
|
"location_id": location_strip1_id,
|
|
"feed_type_code": "layer",
|
|
"amount_kg": "10",
|
|
},
|
|
headers={
|
|
"X-Oidc-Username": "unknown_user",
|
|
**make_csrf_headers(csrf_token),
|
|
},
|
|
)
|
|
# Unknown user is rejected by auth middleware
|
|
assert response.status_code == 401
|
|
|
|
# Verify no defaults were saved
|
|
defaults = UserDefaultsRepository(seeded_db).get("unknown_user", "feed_given")
|
|
assert defaults is None
|
|
|
|
|
|
class TestEggUserDefaults:
|
|
"""Tests for egg form user defaults."""
|
|
|
|
def test_defaults_loaded_on_egg_page(self, client, seeded_db, location_strip1_id):
|
|
"""GET / loads saved user defaults for egg collection."""
|
|
# First set some defaults
|
|
now_utc = int(time.time() * 1000)
|
|
UserDefaultsRepository(seeded_db).upsert(
|
|
UserDefault(
|
|
username="ppetru",
|
|
action="collect_egg",
|
|
location_id=location_strip1_id,
|
|
updated_at_utc=now_utc,
|
|
)
|
|
)
|
|
|
|
# Load the egg page
|
|
response = client.get(
|
|
"/",
|
|
headers={"X-Oidc-Username": "ppetru"},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
# Check that the form has pre-selected location
|
|
content = response.text
|
|
assert location_strip1_id in content
|
|
|
|
def test_query_param_overrides_defaults(
|
|
self, client, seeded_db, location_strip1_id, location_strip2_id
|
|
):
|
|
"""Query param location_id overrides saved defaults."""
|
|
# Set defaults to Strip 1
|
|
now_utc = int(time.time() * 1000)
|
|
UserDefaultsRepository(seeded_db).upsert(
|
|
UserDefault(
|
|
username="ppetru",
|
|
action="collect_egg",
|
|
location_id=location_strip1_id,
|
|
updated_at_utc=now_utc,
|
|
)
|
|
)
|
|
|
|
# Load the egg page with Strip 2 in query params
|
|
response = client.get(
|
|
f"/?location_id={location_strip2_id}",
|
|
headers={"X-Oidc-Username": "ppetru"},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
# Query param should take precedence - Strip 2 should be selected
|
|
content = response.text
|
|
assert location_strip2_id in content
|