Spaces:
Running
Running
| """ | |
| Tests for Phase 16: observability, inconclusive verdict, image type classifier. | |
| """ | |
| import numpy as np | |
| from PIL import Image | |
| from io import BytesIO | |
| def _make_image(width=128, height=128, seed=42, fmt="JPEG"): | |
| rng = np.random.default_rng(seed) | |
| arr = rng.integers(30, 220, (height, width, 3), dtype=np.uint8) | |
| buf = BytesIO() | |
| Image.fromarray(arr, "RGB").save(buf, format=fmt, quality=85 if fmt=="JPEG" else None) | |
| return buf.getvalue() | |
| def _make_blank(): | |
| arr = np.zeros((128, 128, 3), dtype=np.uint8) | |
| buf = BytesIO() | |
| Image.fromarray(arr, "RGB").save(buf, format="PNG") | |
| return buf.getvalue() | |
| # ββ Image type classifier ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_classifier_returns_valid_type(): | |
| from backend.services.image_type_classifier import classify_image_type | |
| result = classify_image_type(_make_image(), "test.jpg") | |
| assert result["image_type"] in {"photo", "screenshot_ui", "illustration", | |
| "document", "low_info", "unknown"} | |
| def test_classifier_blank_is_low_info(): | |
| from backend.services.image_type_classifier import classify_image_type | |
| result = classify_image_type(_make_blank(), "blank.png") | |
| assert result["image_type"] == "low_info" | |
| assert result["run_prnu"] is False | |
| assert result["run_ela"] is False | |
| def test_classifier_jpeg_photo(): | |
| from backend.services.image_type_classifier import classify_image_type | |
| result = classify_image_type(_make_image(256, 256, fmt="JPEG"), "photo.jpg") | |
| assert result["image_type"] in {"photo", "unknown"} | |
| assert 0.0 <= result["confidence"] <= 1.0 | |
| def test_classifier_corrupt_safe(): | |
| from backend.services.image_type_classifier import classify_image_type | |
| result = classify_image_type(b"corrupt", "bad.jpg") | |
| assert result["image_type"] == "unknown" | |
| def test_classifier_all_fields_present(): | |
| from backend.services.image_type_classifier import classify_image_type | |
| result = classify_image_type(_make_image(), "test.jpg") | |
| for key in ("image_type", "confidence", "is_photo", "run_prnu", | |
| "run_ela", "run_metadata", "explanation"): | |
| assert key in result | |
| # ββ Metrics collector βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_metrics_initial_state(): | |
| from backend.services.metrics_collector import get_metrics, reset_metrics | |
| reset_metrics() | |
| m = get_metrics() | |
| assert m["requests"]["total_in_window"] == 0 | |
| assert m["detection"]["n_scored"] == 0 | |
| def test_metrics_record_analysis(): | |
| from backend.services.metrics_collector import record_analysis, get_metrics, reset_metrics | |
| reset_metrics() | |
| record_analysis( | |
| ai_probability=0.85, | |
| classification="likely_ai_generated", | |
| latency_ms=250.0, | |
| predicted_generator="stylegan", | |
| platform_origin="instagram", | |
| signal_scores=[{"signal_name": "FFT Radial Spectrum", "score": 0.7}], | |
| ) | |
| m = get_metrics() | |
| assert m["requests"]["total_in_window"] == 1 | |
| assert m["detection"]["n_scored"] == 1 | |
| assert m["detection"]["mean_ai_probability"] == 0.85 | |
| assert "likely_ai_generated" in m["classification_breakdown"] | |
| assert m["classification_breakdown"]["likely_ai_generated"] == 1 | |
| def test_metrics_error_recording(): | |
| from backend.services.metrics_collector import record_analysis, get_metrics, reset_metrics | |
| reset_metrics() | |
| record_analysis(ai_probability=0.0, classification="error", | |
| latency_ms=0.0, error=True) | |
| m = get_metrics() | |
| assert m["requests"]["errors_in_window"] == 1 | |
| assert m["requests"]["error_rate"] == 1.0 | |
| def test_metrics_reset(): | |
| from backend.services.metrics_collector import record_analysis, reset_metrics, get_metrics | |
| record_analysis(0.5, "unknown", 100.0) | |
| reset_metrics() | |
| m = get_metrics() | |
| assert m["requests"]["total_in_window"] == 0 | |
| def test_metrics_endpoint(client): | |
| response = client.get("/api/v1/metrics") | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "uptime_seconds" in data | |
| assert "requests" in data | |
| assert "detection" in data | |
| assert "performance" in data | |
| def test_metrics_reset_endpoint(client): | |
| response = client.post("/api/v1/metrics/reset", | |
| headers={"X-Admin-Key": "test-admin-key-12345"}) | |
| assert response.status_code == 200 | |
| assert "reset" in response.json()["message"].lower() | |
| # ββ Inconclusive verdict ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_inconclusive_appears_in_valid_labels(): | |
| """Classification must include inconclusive as a valid label.""" | |
| valid = {"likely_ai_generated", "possibly_ai_generated", | |
| "possibly_authentic", "likely_authentic", "inconclusive"} | |
| from backend.services.advanced_ensemble_detector import AdvancedEnsembleDetector | |
| det = AdvancedEnsembleDetector(_make_image(), "test.jpg") | |
| r = det.detect() | |
| det.cleanup() | |
| assert r["classification"] in valid | |
| # ββ Image type in forensic report ββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_image_type_in_forensic_report(): | |
| from backend.services.image_forensics import ImageForensics | |
| rng = np.random.default_rng(seed=5555) | |
| arr = rng.integers(30, 220, (128, 128, 3), dtype=np.uint8) | |
| buf = BytesIO() | |
| Image.fromarray(arr, "RGB").save(buf, format="PNG") | |
| report = ImageForensics(buf.getvalue(), "type_test.png").generate_forensic_report() | |
| assert "image_type" in report | |
| assert report["image_type"]["image_type"] in { | |
| "photo", "screenshot_ui", "illustration", "document", "low_info", "unknown" | |
| } | |
| assert "image_type" in report["summary"] | |