Spaces:
Sleeping
Sleeping
| ο»Ώ""" | |
| Sync inspection E2E testleri. | |
| Akis: register/login -> /api/v1/inspect/sync -> response sema dogrulamasi | |
| (parts[], summary, total_cost_range_tl) | |
| ML servisi conftest'te mocklanmistir; gercek YOLO calismaz, sabit JSON doner. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| import httpx | |
| import pytest | |
| from models import SyncInspectionResponse | |
| # ---------------- yardimcilar ---------------- | |
| def _install_rich_ml_mock(monkeypatch) -> None: | |
| """ML mock'unu parts dolu bir sonuc dondurecek sekilde gucverin β | |
| response sema dogrulamasini gercek bir aggregate sonucu uzerinden yap. | |
| """ | |
| from ml_service import ml_pipeline | |
| rich = { | |
| "inspection_id": "will-be-overwritten", | |
| "timestamp": "2026-05-16T00:00:00Z", | |
| "image": {"url": "test://img", "width": 640, "height": 480}, | |
| "parts": [ | |
| { | |
| "name": "front_bumper", | |
| "name_tr": "On tampon", | |
| "confidence": 0.92, | |
| "status": "moderate_damage", | |
| "damage_count": 1, | |
| "polygon_normalized": [], | |
| "bbox": [10.0, 10.0, 100.0, 100.0], | |
| "damages": [ | |
| { | |
| "id": 1, | |
| "type": "dent", | |
| "type_tr": "Gocuk", | |
| "confidence": 0.88, | |
| "severity": { | |
| "level": "orta", | |
| "level_tr": "Orta", | |
| "confidence": 0.81, | |
| "method": "rule", | |
| }, | |
| "bbox": [12.0, 12.0, 80.0, 80.0], | |
| "polygon_normalized": [], | |
| "area_ratio": 0.02, | |
| "cost": { | |
| "min_tl": 1500.0, | |
| "max_tl": 3500.0, | |
| "midpoint_tl": 2500.0, | |
| "confidence": "medium", | |
| "source": "cost_table.yaml", | |
| }, | |
| } | |
| ], | |
| "part_cost_min_tl": 1500.0, | |
| "part_cost_max_tl": 3500.0, | |
| "cost_note": None, | |
| } | |
| ], | |
| "summary": { | |
| "total_parts_inspected": 1, | |
| "damaged_parts_count": 1, | |
| "clean_parts_count": 0, | |
| "total_damage_count": 1, | |
| "unknown_part_damages_count": 0, | |
| "multi_part_damages_count": 0, | |
| "most_severe_level": "orta", | |
| "most_severe_level_tr": "Orta", | |
| "total_damage_area_ratio": 0.02, | |
| "total_cost_range_tl": [1500.0, 3500.0], | |
| "total_cost_midpoint_tl": 2500.0, | |
| "cost_confidence": "medium", | |
| "repair_recommendation": "tamir_boya", | |
| "repair_recommendation_tr": "Tamir + boya gerekli", | |
| "estimated_repair_days": 1, | |
| }, | |
| } | |
| monkeypatch.setattr(ml_pipeline, "analyze", lambda img, retries=2: dict(rich)) | |
| def _bearer(token: str) -> dict[str, str]: | |
| return {"Authorization": f"Bearer {token}"} | |
| # ---------------- tests ---------------- | |
| async def test_sync_single_image_returns_valid_schema( | |
| async_client: httpx.AsyncClient, png_bytes: bytes, monkeypatch, | |
| reset_user_store, reset_inspection_store | |
| ): | |
| _install_rich_ml_mock(monkeypatch) | |
| # Register | |
| reg = await async_client.post( | |
| "/auth/register", | |
| json={"email": "syncuser@test.example.com", "password": "strong-pass-1234"}, | |
| ) | |
| token = reg.json()["access_token"] | |
| # POST sync β multipart upload | |
| files = {"file": ("car.png", png_bytes, "image/png")} | |
| r = await async_client.post( | |
| "/api/v1/inspect/sync", | |
| files=files, | |
| headers=_bearer(token), | |
| ) | |
| assert r.status_code == 200, r.text | |
| # Pydantic ile sema dogrulamasi β SyncInspectionResponse validate eder | |
| body = r.json() | |
| parsed = SyncInspectionResponse.model_validate(body) | |
| # Top-level fields | |
| assert parsed.inspection_id | |
| assert parsed.processed_at | |
| assert parsed.result is not None | |
| # parts[] | |
| assert len(parsed.result.parts) == 1 | |
| part = parsed.result.parts[0] | |
| assert part.name == "front_bumper" | |
| assert part.status == "moderate_damage" | |
| assert part.damage_count == 1 | |
| assert len(part.damages) == 1 | |
| # summary | |
| summary = parsed.result.summary | |
| assert summary.total_parts_inspected == 1 | |
| assert summary.damaged_parts_count == 1 | |
| assert summary.total_damage_count == 1 | |
| assert summary.most_severe_level == "orta" | |
| # total_cost_range_tl β sema gerektirir | |
| cost_range = summary.total_cost_range_tl | |
| assert isinstance(cost_range, tuple) | |
| assert len(cost_range) == 2 | |
| assert cost_range[0] == 1500.0 | |
| assert cost_range[1] == 3500.0 | |
| assert summary.total_cost_midpoint_tl == 2500.0 | |
| async def test_sync_response_has_x_inspection_id_header( | |
| async_client: httpx.AsyncClient, png_bytes: bytes, | |
| reset_user_store, reset_inspection_store | |
| ): | |
| reg = await async_client.post( | |
| "/auth/register", | |
| json={"email": "hdr@test.example.com", "password": "strong-pass-1234"}, | |
| ) | |
| token = reg.json()["access_token"] | |
| files = {"file": ("a.png", png_bytes, "image/png")} | |
| r = await async_client.post( | |
| "/api/v1/inspect/sync", | |
| files=files, | |
| headers=_bearer(token), | |
| ) | |
| assert r.status_code == 200 | |
| # main.py _process_sync header'i ekliyor β ama /sync wrapper'in JSONResponse'unda | |
| # body dondurur. Hala JSON icinde inspection_id olmali. | |
| assert r.json()["inspection_id"] | |
| async def test_sync_inspect_multi_endpoint_with_sync_mode( | |
| async_client: httpx.AsyncClient, png_bytes: bytes, monkeypatch, | |
| reset_user_store, reset_inspection_store | |
| ): | |
| """/api/v1/inspect?mode=sync de calismali β ayni semayi dondurmeli.""" | |
| _install_rich_ml_mock(monkeypatch) | |
| reg = await async_client.post( | |
| "/auth/register", | |
| json={"email": "msync@test.example.com", "password": "strong-pass-1234"}, | |
| ) | |
| token = reg.json()["access_token"] | |
| files = [("files", ("a.png", png_bytes, "image/png"))] | |
| r = await async_client.post( | |
| "/api/v1/inspect?mode=sync", | |
| files=files, | |
| headers=_bearer(token), | |
| ) | |
| assert r.status_code == 200, r.text | |
| body = r.json() | |
| assert "inspection_id" in body | |
| assert "result" in body | |
| assert body["result"]["summary"]["total_cost_range_tl"] == [1500.0, 3500.0] | |
| async def test_sync_rejects_empty_file( | |
| async_client: httpx.AsyncClient, | |
| reset_user_store, reset_inspection_store | |
| ): | |
| reg = await async_client.post( | |
| "/auth/register", | |
| json={"email": "empty@test.example.com", "password": "strong-pass-1234"}, | |
| ) | |
| token = reg.json()["access_token"] | |
| files = {"file": ("empty.png", b"", "image/png")} | |
| r = await async_client.post( | |
| "/api/v1/inspect/sync", | |
| files=files, | |
| headers=_bearer(token), | |
| ) | |
| assert r.status_code == 400 | |
| async def test_sync_rejects_non_image_mime( | |
| async_client: httpx.AsyncClient, | |
| reset_user_store, reset_inspection_store | |
| ): | |
| reg = await async_client.post( | |
| "/auth/register", | |
| json={"email": "txt@test.example.com", "password": "strong-pass-1234"}, | |
| ) | |
| token = reg.json()["access_token"] | |
| files = {"file": ("doc.txt", b"hello world content", "text/plain")} | |
| r = await async_client.post( | |
| "/api/v1/inspect/sync", | |
| files=files, | |
| headers=_bearer(token), | |
| ) | |
| assert r.status_code == 400 | |