# 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" 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), }, cookies={"csrf_token": 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" 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), }, cookies={"csrf_token": 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