feat(stage10): implement vision integration
This commit is contained in:
336
tests/models/test_vision.py
Normal file
336
tests/models/test_vision.py
Normal file
@@ -0,0 +1,336 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user