📦 EqualifyEverything / equalify-reflow

📄 test_observation_lifecycle.py · 176 lines
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176"""Tests for Observation model lifecycle (simplified 2-field version)."""

import pytest
from src.shared.models.observation import Observation, ObservationLocation


class TestObservationLifecycle:
    """Tests for the simplified Observation lifecycle."""

    def _create_observation(self, **kwargs) -> Observation:
        """Helper to create a test observation."""
        defaults = {
            "job_id": "job-123",
            "agent": "figures",
            "visual_description": "Image shows a chart",
            "markup_description": "Empty alt text",
            "location": ObservationLocation(
                location_type="element",
                value="img[src='fig1.png']",
                page_num=1,
            ),
        }
        defaults.update(kwargs)
        return Observation(**defaults)

    def test_default_status_is_open(self):
        """Test that new observations start with open status."""
        obs = self._create_observation()
        assert obs.status == "open"
        assert obs.resolution is None

    def test_close_with_fixed(self):
        """Test closing observation with 'fixed' resolution."""
        obs = self._create_observation()
        obs.close("fixed")

        assert obs.status == "closed"
        assert obs.resolution == "fixed"

    def test_close_with_kept_original(self):
        """Test closing observation with 'kept_original' resolution."""
        obs = self._create_observation()
        obs.close("kept_original")

        assert obs.status == "closed"
        assert obs.resolution == "kept_original"

    def test_close_with_skipped(self):
        """Test closing observation with 'skipped' resolution."""
        obs = self._create_observation()
        obs.close("skipped")

        assert obs.status == "closed"
        assert obs.resolution == "skipped"

    def test_cannot_close_twice(self):
        """Test that closing an already-closed observation raises error."""
        obs = self._create_observation()
        obs.close("fixed")

        with pytest.raises(ValueError, match="already closed"):
            obs.close("kept_original")

    def test_category_field(self):
        """Test the category field for grouping."""
        obs = self._create_observation(category="alt_text")
        assert obs.category == "alt_text"

    def test_default_category_is_general(self):
        """Test that default category is 'general'."""
        obs = self._create_observation()
        assert obs.category == "general"

    def test_affected_pages_field(self):
        """Test the affected_pages field for multi-page issues."""
        obs = self._create_observation(affected_pages=[1, 2, 3])
        assert obs.affected_pages == [1, 2, 3]

    def test_severity_levels(self):
        """Test different severity levels."""
        critical = self._create_observation(severity="critical")
        major = self._create_observation(severity="major")
        minor = self._create_observation(severity="minor")

        assert critical.severity == "critical"
        assert major.severity == "major"
        assert minor.severity == "minor"

    def test_confidence_bounds(self):
        """Test confidence must be between 0 and 1."""
        obs = self._create_observation(confidence=0.9)
        assert obs.confidence == 0.9

        with pytest.raises(ValueError):
            self._create_observation(confidence=1.5)

        with pytest.raises(ValueError):
            self._create_observation(confidence=-0.1)

    def test_json_serialization(self):
        """Test that Observation serializes to JSON correctly."""
        obs = self._create_observation(
            category="ocr",
            confidence=0.85,
            severity="major",
        )

        json_str = obs.model_dump_json()
        assert "ocr" in json_str
        assert "0.85" in json_str

        # Deserialize and verify
        restored = Observation.model_validate_json(json_str)
        assert restored.category == obs.category
        assert restored.confidence == obs.confidence
        assert restored.status == "open"

    def test_serialization_after_close(self):
        """Test serialization preserves closed state."""
        obs = self._create_observation()
        obs.close("fixed")

        json_str = obs.model_dump_json()
        restored = Observation.model_validate_json(json_str)

        assert restored.status == "closed"
        assert restored.resolution == "fixed"


class TestObservationLocation:
    """Tests for ObservationLocation model."""

    def test_element_location(self):
        """Test element-type location."""
        loc = ObservationLocation(
            location_type="element",
            value="img[alt='']",
            page_num=3,
        )
        assert loc.location_type == "element"
        assert loc.value == "img[alt='']"
        assert loc.page_num == 3

    def test_range_location(self):
        """Test range-type location."""
        loc = ObservationLocation(
            location_type="range",
            value="10-15",
            page_num=2,
        )
        assert loc.location_type == "range"

    def test_region_location(self):
        """Test region-type location (default)."""
        loc = ObservationLocation(
            value="top-right corner",
            page_num=1,
        )
        assert loc.location_type == "region"

    def test_value_must_not_be_empty(self):
        """Test that value cannot be empty."""
        with pytest.raises(ValueError):
            ObservationLocation(
                value="",
                page_num=1,
            )

    def test_page_num_must_be_positive(self):
        """Test that page_num must be >= 1."""
        with pytest.raises(ValueError):
            ObservationLocation(
                value="test",
                page_num=0,
            )