""" REAL integration tests for notes CRUD over HTTP. Every test makes actual HTTP requests to a live Django server. """ import json from django.test import LiveServerTestCase from urllib.request import urlopen, Request class RealHTTPMixin: def _session_init(self): url = f"{self.live_server_url}/api/djarea/session/" resp = urlopen(Request(url)) cookies = resp.headers.get_all("Set-Cookie") or [] for cookie in cookies: if "csrftoken=" in cookie: self._csrf_token = cookie.split("csrftoken=")[1].split(";")[0] self._cookies = f"csrftoken={self._csrf_token}" return self._csrf_token = None self._cookies = "" def _call(self, fn: str, args: dict | None = None): url = f"{self.live_server_url}/api/djarea/call/" body = json.dumps({"fn": fn, "args": args or {}}).encode() req = Request(url, data=body, method="POST") req.add_header("Content-Type", "application/json") if self._csrf_token: req.add_header("X-CSRFToken", self._csrf_token) if self._cookies: req.add_header("Cookie", self._cookies) resp = urlopen(req) return json.loads(resp.read()) class NotesCRUDTests(RealHTTPMixin, LiveServerTestCase): """Full CRUD lifecycle over real HTTP.""" def setUp(self): self._session_init() def test_list_notes_empty(self): data = self._call("list_notes") self.assertFalse(data["error"]) self.assertEqual(data["data"]["notes"], []) self.assertEqual(data["data"]["count"], 0) def test_create_note(self): data = self._call("create_note", {"title": "First Note", "content": "Hello!"}) self.assertFalse(data["error"]) self.assertEqual(data["data"]["title"], "First Note") self.assertEqual(data["data"]["content"], "Hello!") self.assertFalse(data["data"]["pinned"]) self.assertIn("id", data["data"]) self.assertIn("created_at", data["data"]) def test_create_and_list(self): self._call("create_note", {"title": "Note A"}) self._call("create_note", {"title": "Note B"}) data = self._call("list_notes") self.assertFalse(data["error"]) self.assertEqual(data["data"]["count"], 2) titles = [n["title"] for n in data["data"]["notes"]] self.assertIn("Note A", titles) self.assertIn("Note B", titles) def test_get_note_by_id(self): create = self._call("create_note", {"title": "Get Me", "content": "Specific"}) note_id = create["data"]["id"] data = self._call("get_note", {"id": note_id}) self.assertFalse(data["error"]) self.assertEqual(data["data"]["id"], note_id) self.assertEqual(data["data"]["title"], "Get Me") def test_update_note(self): create = self._call("create_note", {"title": "Original"}) note_id = create["data"]["id"] data = self._call("update_note", {"id": note_id, "title": "Updated"}) self.assertFalse(data["error"]) self.assertEqual(data["data"]["title"], "Updated") def test_update_note_pin(self): create = self._call("create_note", {"title": "Pin Me"}) note_id = create["data"]["id"] data = self._call("update_note", {"id": note_id, "pinned": True}) self.assertFalse(data["error"]) self.assertTrue(data["data"]["pinned"]) def test_delete_note(self): create = self._call("create_note", {"title": "Delete Me"}) note_id = create["data"]["id"] data = self._call("delete_note", {"id": note_id}) self.assertFalse(data["error"]) self.assertTrue(data["data"]["deleted"]) # Verify it's gone from urllib.error import HTTPError try: get_data = self._call("get_note", {"id": note_id}) self.assertTrue(get_data["error"]) except HTTPError: pass # 500 is also a valid failure signal def test_pinned_notes_sort_first(self): self._call("create_note", {"title": "Unpinned"}) self._call("create_note", {"title": "Pinned", "pinned": True}) data = self._call("list_notes") self.assertFalse(data["error"]) self.assertEqual(data["data"]["notes"][0]["title"], "Pinned") def test_full_lifecycle(self): """Create -> update -> pin -> verify -> delete over real HTTP.""" # Create create = self._call("create_note", {"title": "Lifecycle", "content": "v1"}) note_id = create["data"]["id"] # Update self._call("update_note", {"id": note_id, "content": "v2"}) # Pin self._call("update_note", {"id": note_id, "pinned": True}) # Verify get = self._call("get_note", {"id": note_id}) self.assertEqual(get["data"]["title"], "Lifecycle") self.assertEqual(get["data"]["content"], "v2") self.assertTrue(get["data"]["pinned"]) # Delete delete = self._call("delete_note", {"id": note_id}) self.assertTrue(delete["data"]["deleted"])