File size: 5,150 Bytes
b84ea83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""
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