"""Tests für Pydantic v2 Schemas und SQLAlchemy Models der Vision Evidence Extraction (Stage 10).""" from __future__ import annotations import pytest from pydantic import ValidationError from nsct.models.vision import ( EvidenceLevel, VisionCaptureSchema, VisionCaptureType, VisionConfidence, VisionEntityCategory, VisionRequestSchema, VisionReportSchema, ) # --------------------------------------------------------------------------- # Enums # --------------------------------------------------------------------------- class TestVisionCaptureTypeEnum: """Prüft die Enums VisionCaptureType und VisionConfidence.""" def test_vision_capture_type_values(self): assert VisionCaptureType.RAW_IMAGE.value == "raw_image" assert VisionCaptureType.DIAGRAM.value == "diagram" assert VisionCaptureType.CHART.value == "chart" assert VisionCaptureType.SCREENSHOT.value == "screenshot" assert VisionCaptureType.INFOGRAPHIC.value == "infographic" assert VisionCaptureType.PDF_LAYOUT.value == "pdf_layout" def test_vision_confidence_values(self): assert VisionConfidence.HIGH.value == "high" assert VisionConfidence.MEDIUM.value == "medium" assert VisionConfidence.LOW.value == "low" assert VisionConfidence.UNCERTAIN.value == "uncertain" def test_evidence_level_values(self): assert EvidenceLevel.HIGH.value == "high" assert EvidenceLevel.MEDIUM.value == "medium" assert EvidenceLevel.LOW.value == "low" assert EvidenceLevel.UNCERTAIN.value == "uncertain" def test_vision_entity_category_values(self): assert VisionEntityCategory.DATE.value == "date" assert VisionEntityCategory.PERSON.value == "person" assert VisionEntityCategory.ORGANIZATION.value == "organization" assert VisionEntityCategory.LOCATION.value == "location" assert VisionEntityCategory.NUMBER.value == "number" assert VisionEntityCategory.STATISTIC.value == "statistic" assert VisionEntityCategory.GRAPH_ELEMENT.value == "graph_element" def test_invalid_capture_type(self): with pytest.raises(ValidationError): VisionCaptureSchema( capture_type="invalid_type", # type: ignore image_data_url="data:image/png;base64,abc", extracted_text="test", source_id="test-source", source_url="https://example.com", ) def test_invalid_confidence_label(self): with pytest.raises(ValidationError): VisionCaptureSchema( capture_type=VisionCaptureType.RAW_IMAGE, image_data_url="data:image/png;base64,abc", extracted_text="test", source_id="test-source", source_url="https://example.com", confidence_label="invalid", # type: ignore ) # --------------------------------------------------------------------------- # VisionCaptureSchema # --------------------------------------------------------------------------- class TestVisionCaptureSchema: """Tests für VisionCaptureSchema — Pflichtfelder, Defaults, Frozen.""" @pytest.fixture def valid_kwargs(self): return { "capture_type": VisionCaptureType.DIAGRAM, "image_data_url": "data:image/png;base64,abc123", "extracted_text": "This is a chart showing revenue growth.", "source_id": "source-uuid-001", "source_url": "https://example.com/chart.png", } def test_create_valid_schema(self, valid_kwargs): schema = VisionCaptureSchema(**valid_kwargs) assert schema.capture_type == VisionCaptureType.DIAGRAM assert schema.extracted_text == "This is a chart showing revenue growth." assert schema.source_id == "source-uuid-001" assert schema.entities == [] assert schema.confidence == 0.5 assert schema.confidence_label == VisionConfidence.MEDIUM assert schema.evidence_level == EvidenceLevel.MEDIUM assert schema.metadata == {} def test_defaults(self, valid_kwargs): schema = VisionCaptureSchema(**valid_kwargs) assert schema.entities == [] assert schema.confidence == 0.5 assert schema.confidence_label == VisionConfidence.MEDIUM assert schema.evidence_level == EvidenceLevel.MEDIUM assert schema.metadata == {} def test_frozen(self, valid_kwargs): schema = VisionCaptureSchema(**valid_kwargs) with pytest.raises(Exception): schema.capture_type = VisionCaptureType.CHART def test_missing_required_field(self, valid_kwargs): kwargs = {**valid_kwargs} del kwargs["extracted_text"] with pytest.raises(ValidationError): VisionCaptureSchema(**kwargs) def test_empty_extracted_text(self, valid_kwargs): kwargs = {**valid_kwargs, "extracted_text": " "} with pytest.raises(ValidationError, match="extracted_text darf nicht nur aus Whitespaces bestehen"): VisionCaptureSchema(**kwargs) def test_empty_source_url(self, valid_kwargs): kwargs = {**valid_kwargs, "source_url": " "} with pytest.raises(ValidationError, match="source_url darf nicht leer sein"): VisionCaptureSchema(**kwargs) def test_empty_image_data_url(self, valid_kwargs): kwargs = {**valid_kwargs, "image_data_url": " "} with pytest.raises(ValidationError, match="image_data_url darf nicht leer sein"): VisionCaptureSchema(**kwargs) def test_confidence_bounds(self, valid_kwargs): schema_low = VisionCaptureSchema(**valid_kwargs, confidence=0.0) assert schema_low.confidence == 0.0 schema_high = VisionCaptureSchema(**valid_kwargs, confidence=1.0) assert schema_high.confidence == 1.0 def test_confidence_out_of_bounds_low(self, valid_kwargs): with pytest.raises(ValidationError): VisionCaptureSchema(**valid_kwargs, confidence=-0.1) def test_confidence_out_of_bounds_high(self, valid_kwargs): with pytest.raises(ValidationError): VisionCaptureSchema(**valid_kwargs, confidence=1.1) def test_all_capture_types(self): for ct in VisionCaptureType: schema = VisionCaptureSchema( capture_type=ct, image_data_url="data:image/png;base64,abc", extracted_text="test content", source_id="src-1", source_url="https://example.com", ) assert schema.capture_type == ct def test_evidence_level_values(self): for level in EvidenceLevel: schema = VisionCaptureSchema( capture_type=VisionCaptureType.RAW_IMAGE, image_data_url="data:image/png;base64,abc", extracted_text="test", source_id="src-1", source_url="https://example.com", evidence_level=level, ) assert schema.evidence_level == level def test_confidence_label_values(self): for label in VisionConfidence: schema = VisionCaptureSchema( capture_type=VisionCaptureType.RAW_IMAGE, image_data_url="data:image/png;base64,abc", extracted_text="test", source_id="src-1", source_url="https://example.com", confidence_label=label, ) assert schema.confidence_label == label def test_entities_list(self, valid_kwargs): schema = VisionCaptureSchema( **valid_kwargs, entities=[ {"type": "NUMBER", "value": "42", "confidence": 0.9}, {"type": "DATE", "value": "2024-01-15", "confidence": 0.8}, ], ) assert len(schema.entities) == 2 assert schema.entities[0]["type"] == "NUMBER" assert schema.entities[1]["value"] == "2024-01-15" def test_metadata_dict(self, valid_kwargs): schema = VisionCaptureSchema( **valid_kwargs, metadata={"model": "qwen2.5-vl-3b", "processing_time": 2.3}, ) assert schema.metadata["model"] == "qwen2.5-vl-3b" assert schema.metadata["processing_time"] == 2.3 # --------------------------------------------------------------------------- # VisionReportSchema # --------------------------------------------------------------------------- class TestVisionReportSchema: """Tests für VisionReportSchema — Zusammenfassung aller visuellen Evidenzen.""" @pytest.fixture def valid_kwargs(self): return { "research_run_id": "run-uuid-001", "total_captures": 3, } def test_create_valid_report(self, valid_kwargs): report = VisionReportSchema(**valid_kwargs) assert report.research_run_id == "run-uuid-001" assert report.total_captures == 3 assert report.captures == [] assert report.entity_summary == {} assert report.summary_text == "" def test_frozen(self, valid_kwargs): report = VisionReportSchema(**valid_kwargs) with pytest.raises(Exception): report.research_run_id = "new-id" def test_empty_research_run_id(self, valid_kwargs): with pytest.raises(ValidationError, match="research_run_id darf nicht leer sein"): VisionReportSchema( **valid_kwargs, research_run_id=" ", ) def test_negative_total_captures(self, valid_kwargs): with pytest.raises(ValidationError): VisionReportSchema(**valid_kwargs, total_captures=-1) def test_with_captures(self, valid_kwargs): capture = VisionCaptureSchema( capture_type=VisionCaptureType.CHART, image_data_url="data:image/png;base64,xyz", extracted_text="Chart data", source_id="src-1", source_url="https://example.com", ) report = VisionReportSchema( **valid_kwargs, total_captures=1, captures=[capture], ) assert len(report.captures) == 1 assert report.captures[0].capture_type == VisionCaptureType.CHART def test_political_summary_rejected(self, valid_kwargs): with pytest.raises(ValidationError, match="politische Empfehlung"): VisionReportSchema( **valid_kwargs, summary_text="Die Regierung sollte handeln.", ) # --------------------------------------------------------------------------- # VisionRequestSchema # --------------------------------------------------------------------------- class TestVisionRequestSchema: """Tests für VisionRequestSchema — API-Request.""" @pytest.fixture def valid_kwargs(self): return { "research_run_id": "run-uuid-001", "source_id": "source-uuid-001", "source_url": "https://example.com/image.png", "image_data": "data:image/png;base64,iVBORw0KGgoAAA==", } def test_create_valid_request(self, valid_kwargs): request = VisionRequestSchema(**valid_kwargs) assert request.research_run_id == "run-uuid-001" assert request.source_id == "source-uuid-001" assert request.source_url == "https://example.com/image.png" assert request.capture_type == VisionCaptureType.RAW_IMAGE assert request.prompt is None def test_frozen(self, valid_kwargs): request = VisionRequestSchema(**valid_kwargs) with pytest.raises(Exception): request.research_run_id = "new-id" def test_defaults(self, valid_kwargs): request = VisionRequestSchema(**valid_kwargs) assert request.capture_type == VisionCaptureType.RAW_IMAGE assert request.prompt is None def test_with_prompt(self, valid_kwargs): request = VisionRequestSchema( **valid_kwargs, prompt="Extrahiere alle Zahlen und Daten aus dem Diagramm.", ) assert request.prompt == "Extrahiere alle Zahlen und Daten aus dem Diagramm." def test_empty_image_data(self, valid_kwargs): with pytest.raises(ValidationError, match="image_data darf nicht leer sein"): VisionRequestSchema(**valid_kwargs, image_data=" ") def test_empty_source_url(self, valid_kwargs): with pytest.raises(ValidationError, match="source_url darf nicht leer sein"): VisionRequestSchema(**valid_kwargs, source_url=" ") def test_empty_research_run_id(self, valid_kwargs): with pytest.raises(ValidationError): VisionRequestSchema(**valid_kwargs, research_run_id=" ") def test_empty_source_id(self, valid_kwargs): with pytest.raises(ValidationError): VisionRequestSchema(**valid_kwargs, source_id=" ") def test_capture_type_override(self, valid_kwargs): for ct in VisionCaptureType: request = VisionRequestSchema(**valid_kwargs, capture_type=ct) assert request.capture_type == ct