import json import os from datetime import datetime from zoneinfo import ZoneInfo import django from django.conf import settings from django.test import TestCase, Client os.environ.setdefault("DJANGO_SETTINGS_MODULE", "timetracker.settings") django.setup() from games.models import Device, Game, Platform, Purchase, Session from django.contrib.auth.models import User class MiddlewareIntegrationTest(TestCase): """Integration tests for HTMXMessagesMiddleware. These tests hit real endpoints that use messages.success() to verify the full chain: API endpoint → messages → middleware → HX-Trigger header. """ @staticmethod def _create_user(): return User.objects.create_user( username="testuser", password="testpass123" ) def setUp(self): self.client = Client() self.user = self._create_user() self.client.force_login(self.user) pl = Platform(name="Test Platform") pl.save() self.game = Game(name="Test Game", platform=pl) self.game.save() def test_non_htmx_request_with_message_gets_hx_trigger(self): """ Regression test: vanilla fetch() requests that set Django messages must receive HX-Trigger so fetchWithHtmxTriggers can read them. """ response = self.client.patch( f"/api/games/{self.game.id}/status", data=json.dumps({"status": "played"}), content_type="application/json", ) self.assertEqual(response.status_code, 204) self.assertIn("HX-Trigger", response) data = json.loads(response["HX-Trigger"]) self.assertIn("show-toast", data) self.assertEqual(data["show-toast"]["type"], "success") def test_session_device_api_endpoint_sends_hx_trigger(self): """ Verify the session device API endpoint also produces HX-Trigger. This is the exact endpoint used by sessiondevice_selector.html. """ device = Device(name="Test Device") device.save() zt = ZoneInfo(settings.TIME_ZONE) session = Session( game=self.game, device=device, timestamp_start=datetime(2022, 9, 26, 14, 58, tzinfo=zt), ) session.save() response = self.client.patch( f"/api/session/{session.id}/device", data=json.dumps({"device_id": device.id}), content_type="application/json", ) self.assertEqual(response.status_code, 204) self.assertIn("HX-Trigger", response) data = json.loads(response["HX-Trigger"]) self.assertIn("show-toast", data) self.assertEqual(data["show-toast"]["message"], "Device updated") def test_refund_purchase_returns_updated_row_with_hx_trigger( self, ): """ Verify the refund endpoint returns the updated row HTML so the page swaps it in place without navigating away (preserving URL/query params). """ purchase = Purchase.objects.create( date_purchased=datetime(2023, 1, 1), platform=Platform.objects.first() or pl, ) purchase.games.set([self.game]) response = self.client.post( f"/tracker/purchase/{purchase.id}/refund", data={"set_abandoned": ""}, ) self.assertEqual(response.status_code, 200) self.assertNotIn("HX-Redirect", response) self.assertIn("HX-Trigger", response) data = json.loads(response["HX-Trigger"]) self.assertIn("show-toast", data) self.assertEqual(data["show-toast"]["message"], "Purchase refunded") # Verify the row HTML contains the updated row id body = response.content.decode() self.assertIn(f'purchase-row-{purchase.id}', body) # Verify OoO modal close element self.assertIn('hx-swap-oob', body) self.assertIn('refund-confirmation-modal', body) # Verify the purchase is actually refunded purchase.refresh_from_db() self.assertIsNotNone(purchase.date_refunded)