import os import io import pytest from fastapi.testclient import TestClient # Ensure required env vars for app import/config os.environ.setdefault("SECRET_KEY", "x" * 32) os.environ.setdefault("DATABASE_URL", "sqlite:////tmp/delphi_test.sqlite") from app.main import app # noqa: E402 from app.auth.security import get_current_user, get_admin_user # noqa: E402 from tests.helpers import assert_http_error # noqa: E402 class _User: def __init__(self, is_admin: bool): self.id = 1 if is_admin else 2 self.username = "admin" if is_admin else "user" self.is_admin = is_admin self.is_active = True @pytest.fixture() def client_admin(): app.dependency_overrides[get_current_user] = lambda: _User(True) app.dependency_overrides[get_admin_user] = lambda: _User(True) try: yield TestClient(app) finally: app.dependency_overrides.pop(get_current_user, None) app.dependency_overrides.pop(get_admin_user, None) @pytest.fixture() def client_user(): app.dependency_overrides[get_current_user] = lambda: _User(False) try: yield TestClient(app) finally: app.dependency_overrides.pop(get_current_user, None) def _make_csv(content: str, filename: str = "ROLODEX.csv"): return {"file": (filename, io.BytesIO(content.encode("utf-8")), "text/csv")} def test_import_requires_auth_and_rejects_malformed_csv(client_user: TestClient): # Unauthenticated should 403 envelope app.dependency_overrides.pop(get_current_user, None) c = TestClient(app) resp = c.post("/api/import/upload/ROLODEX.csv", files=_make_csv("Id,Last\n")) assert_http_error(resp, 403, "Not authenticated") # Auth but malformed content: wrong extension app.dependency_overrides[get_current_user] = lambda: _User(False) resp = c.post("/api/import/upload/ROLODEX.csv", files={"file": ("file.txt", io.BytesIO(b"abc"), "text/plain")}) assert_http_error(resp, 400, "File must be a CSV file") # Unsupported file type resp = c.post("/api/import/upload/UNKNOWN.csv", files=_make_csv("x,y\n1,2\n", filename="UNKNOWN.csv")) assert_http_error(resp, 400, "Unsupported file type") # Severely malformed CSV that can't parse headers bad = "" # empty resp = c.post("/api/import/upload/ROLODEX.csv", files=_make_csv(bad)) # The importer treats empty as error in parsing or yields 500; ensure error envelope present assert resp.status_code in (400, 500) body = resp.json() assert body.get("success") is False assert resp.headers.get("X-Correlation-ID") == body.get("correlation_id") def test_successful_import_updates_counts(client_admin: TestClient): # Initial status counts resp = client_admin.get("/api/import/status") assert resp.status_code == 200 status_before = resp.json() rolodex_before = status_before.get("ROLODEX.csv", {}).get("record_count", 0) # Minimal valid ROLODEX import (id,last) csv_data = "Id,Last,Email\nIMP-1,Doe,john@example.com\nIMP-2,Smith,smith@example.com\n" resp = client_admin.post("/api/import/upload/ROLODEX.csv", files=_make_csv(csv_data)) assert resp.status_code == 200 result = resp.json() assert result["file_type"] == "ROLODEX.csv" assert result["imported_count"] >= 2 assert isinstance(result["auto_mapping"]["mapped_headers"], dict) # Status after should increase resp = client_admin.get("/api/import/status") assert resp.status_code == 200 status_after = resp.json() rolodex_after = status_after.get("ROLODEX.csv", {}).get("record_count", 0) assert rolodex_after >= rolodex_before + 2 def test_batch_validate_and_batch_upload_auth_and_errors(client_admin: TestClient): # Batch validate with too many files not triggered, but ensure happy path files = [ ("ROLODEX.csv", "Id,Last\nB1,Alpha\n"), ("FILES.csv", "File_No,Id,File_Type,Regarding,Opened,Empl_Num,Status,Rate_Per_Hour\nF-1,B1,CIVIL,Test,2024-01-01,E01,ACTIVE,100\n"), ] payload = [("files", (name, io.BytesIO(data.encode("utf-8")), "text/csv")) for name, data in files] resp = client_admin.post("/api/import/batch-validate", files=payload) assert resp.status_code == 200 body = resp.json() assert "batch_validation_results" in body # Batch upload mixed: include unsupported file to trigger a failed result but 200 overall files2 = [ ("UNKNOWN.csv", "a,b\n1,2\n"), ("ROLODEX.csv", "Id,Last\nB2,Beta\n"), ] payload2 = [("files", (name, io.BytesIO(data.encode("utf-8")), "text/csv")) for name, data in files2] resp = client_admin.post("/api/import/batch-upload", files=payload2) assert resp.status_code == 200 summary = resp.json().get("summary", {}) assert "total_files" in summary def test_clear_requires_admin_and_unknown_type_errors(client_user: TestClient): # Non-admin authenticated should still be able to call due to current dependency (get_current_user) # We enforce admin via existing admin endpoint as a proxy resp = client_user.get("/api/auth/users") assert_http_error(resp, 403, "Not enough permissions") # Unknown file type on clear resp = client_user.delete("/api/import/clear/UNKNOWN.csv") assert_http_error(resp, 400, "Unknown file type")