""" Unit Tests — Pydantic Visit/Checklist/Photo Schemas Tests: - VisitCreate: required fields, validation rules, defaults - VisitUpdate: all-optional, partial updates - VisitResponse: from_attributes (ORM → Pydantic) - ChecklistCreate / ChecklistUpdate - PhotoModel schema """ from datetime import datetime, timezone import pytest from pydantic import ValidationError from models.visit_model import Visit, VisitCreate, VisitUpdate, VisitResponse from models.checklist_model import Checklist, ChecklistCreate, ChecklistUpdate # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # VisitCreate # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class TestVisitCreate: def _valid(self, **overrides): return { "title": "Site Inspection", "visit_date": datetime(2026, 5, 1, 9, 0, tzinfo=timezone.utc), **overrides, } def test_valid_minimal(self): v = VisitCreate(**self._valid()) assert v.title == "Site Inspection" assert v.status == "pending" # default assert v.description is None assert v.location is None def test_valid_full(self): v = VisitCreate(**self._valid( description="Monthly check", location="Gedung A", status="in_progress", )) assert v.location == "Gedung A" assert v.status == "in_progress" def test_title_required(self): with pytest.raises(ValidationError) as exc_info: VisitCreate(visit_date=datetime(2026, 5, 1, 9, 0)) errors = exc_info.value.errors() assert any(e["loc"] == ("title",) for e in errors) def test_title_empty_string_invalid(self): with pytest.raises(ValidationError): VisitCreate(**self._valid(title="")) def test_title_too_long(self): with pytest.raises(ValidationError): VisitCreate(**self._valid(title="x" * 256)) def test_visit_date_required(self): with pytest.raises(ValidationError): VisitCreate(title="Test") def test_location_max_length(self): with pytest.raises(ValidationError): VisitCreate(**self._valid(location="L" * 256)) # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # VisitUpdate # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class TestVisitUpdate: def test_all_optional_empty(self): u = VisitUpdate() assert u.title is None assert u.status is None def test_partial_update(self): u = VisitUpdate(status="completed") assert u.status == "completed" assert u.title is None def test_title_empty_invalid(self): with pytest.raises(ValidationError): VisitUpdate(title="") # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # VisitResponse — ORM round-trip # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class TestVisitResponse: def test_from_orm(self, sample_visit: Visit): """VisitResponse must serialize from an ORM object (from_attributes).""" resp = VisitResponse.model_validate(sample_visit) assert str(resp.id) == str(sample_visit.id) assert resp.title == sample_visit.title assert resp.status == sample_visit.status # Mark as async because sample_visit is an async fixture @pytest.mark.asyncio async def test_from_orm_async(self, sample_visit): resp = VisitResponse.model_validate(sample_visit) assert resp.title == "Test Site Inspection" assert resp.location == "Gedung A" # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # ChecklistCreate / ChecklistUpdate # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ class TestChecklistSchemas: def test_create_minimal(self): c = ChecklistCreate(item_name="Panel check") assert c.item_name == "Panel check" assert c.is_checked is False # default assert c.notes is None def test_create_with_notes(self): c = ChecklistCreate(item_name="Grounding", is_checked=True, notes="OK") assert c.is_checked is True assert c.notes == "OK" def test_create_empty_name_invalid(self): with pytest.raises(ValidationError): ChecklistCreate(item_name="") def test_update_all_optional(self): u = ChecklistUpdate() assert u.is_checked is None assert u.item_name is None def test_update_toggle(self): u = ChecklistUpdate(is_checked=True) assert u.is_checked is True