feat: implement location events & error handling (Step 10.2)
- Add LocationService with create/rename/archive methods - Add LocationProjection for event handling - Add admin-only location management routes at /locations - Add error response helpers (error_response, error_toast, success_toast) - Add toast handler JS to base template for HX-Trigger notifications - Update seeds.py to emit LocationCreated events per spec §23 - Archived locations block animal moves with 422 error 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
109
tests/test_web_responses.py
Normal file
109
tests/test_web_responses.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# ABOUTME: Tests for web response helper functions.
|
||||
# ABOUTME: Tests error_response, error_toast, and success_toast helpers.
|
||||
|
||||
import json
|
||||
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
from animaltrack.web.responses import error_response, error_toast, success_toast
|
||||
|
||||
|
||||
class TestErrorResponse:
|
||||
"""Tests for error_response()."""
|
||||
|
||||
def test_returns_html_response(self):
|
||||
"""error_response returns an HTMLResponse."""
|
||||
response = error_response("<div>Error</div>")
|
||||
assert isinstance(response, HTMLResponse)
|
||||
|
||||
def test_default_status_422(self):
|
||||
"""error_response defaults to 422 status code."""
|
||||
response = error_response("<div>Validation error</div>")
|
||||
assert response.status_code == 422
|
||||
|
||||
def test_custom_status_code(self):
|
||||
"""error_response accepts custom status code."""
|
||||
response = error_response("<div>Conflict</div>", status_code=409)
|
||||
assert response.status_code == 409
|
||||
|
||||
def test_403_status_code(self):
|
||||
"""error_response works with 403 status code."""
|
||||
response = error_response("<div>Forbidden</div>", status_code=403)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_401_status_code(self):
|
||||
"""error_response works with 401 status code."""
|
||||
response = error_response("<div>Unauthorized</div>", status_code=401)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_content_in_body(self):
|
||||
"""error_response includes content in body."""
|
||||
response = error_response("<p>Error message</p>")
|
||||
assert b"<p>Error message</p>" in response.body
|
||||
|
||||
|
||||
class TestErrorToast:
|
||||
"""Tests for error_toast()."""
|
||||
|
||||
def test_returns_html_response(self):
|
||||
"""error_toast returns an HTMLResponse."""
|
||||
response = error_toast("Something went wrong")
|
||||
assert isinstance(response, HTMLResponse)
|
||||
|
||||
def test_default_status_422(self):
|
||||
"""error_toast defaults to 422 status code."""
|
||||
response = error_toast("Validation failed")
|
||||
assert response.status_code == 422
|
||||
|
||||
def test_custom_status_code(self):
|
||||
"""error_toast accepts custom status code."""
|
||||
response = error_toast("Not allowed", status_code=403)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_has_hx_trigger_header(self):
|
||||
"""error_toast sets HX-Trigger header."""
|
||||
response = error_toast("Error message")
|
||||
assert "HX-Trigger" in response.headers
|
||||
|
||||
def test_toast_message_in_header(self):
|
||||
"""error_toast includes message in HX-Trigger header."""
|
||||
response = error_toast("Error message")
|
||||
trigger = json.loads(response.headers["HX-Trigger"])
|
||||
assert trigger["showToast"]["message"] == "Error message"
|
||||
|
||||
def test_toast_type_error(self):
|
||||
"""error_toast sets toast type to error."""
|
||||
response = error_toast("Error message")
|
||||
trigger = json.loads(response.headers["HX-Trigger"])
|
||||
assert trigger["showToast"]["type"] == "error"
|
||||
|
||||
|
||||
class TestSuccessToast:
|
||||
"""Tests for success_toast()."""
|
||||
|
||||
def test_returns_dict(self):
|
||||
"""success_toast returns a dict (for HX-Trigger header)."""
|
||||
result = success_toast("Action completed")
|
||||
assert isinstance(result, dict)
|
||||
|
||||
def test_has_show_toast_key(self):
|
||||
"""success_toast dict has showToast key."""
|
||||
result = success_toast("Action completed")
|
||||
assert "showToast" in result
|
||||
|
||||
def test_message_in_toast(self):
|
||||
"""success_toast includes message."""
|
||||
result = success_toast("Created successfully")
|
||||
assert result["showToast"]["message"] == "Created successfully"
|
||||
|
||||
def test_toast_type_success(self):
|
||||
"""success_toast sets toast type to success."""
|
||||
result = success_toast("Done")
|
||||
assert result["showToast"]["type"] == "success"
|
||||
|
||||
def test_can_json_dumps(self):
|
||||
"""success_toast result can be JSON serialized."""
|
||||
result = success_toast("Test message")
|
||||
serialized = json.dumps(result)
|
||||
assert '"showToast"' in serialized
|
||||
assert '"Test message"' in serialized
|
||||
Reference in New Issue
Block a user