Files
delphi-database/tests/test_search_validation.py
2025-08-14 19:16:28 -05:00

178 lines
5.7 KiB
Python

import os
import sys
from pathlib import Path
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")
# Ensure repository root on sys.path for direct test runs
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from app.main import app # noqa: E402
from app.auth.security import get_current_user # noqa: E402
from tests.helpers import assert_validation_error # noqa: E402
from app.config import settings # noqa: E402
@pytest.fixture(scope="module")
def client():
# Override auth to bypass JWT for these tests
class _User:
def __init__(self):
self.id = "test"
self.username = "tester"
self.is_admin = True
self.is_active = True
app.dependency_overrides[get_current_user] = lambda: _User()
# Disable cache to make validation tests deterministic
settings.cache_enabled = False
try:
yield TestClient(app)
finally:
app.dependency_overrides.pop(get_current_user, None)
def test_advanced_search_invalid_search_types(client: TestClient):
payload = {
"query": "anything",
"search_types": ["customer", "bogus"],
}
resp = client.post("/api/search/advanced", json=payload)
assert_validation_error(resp, "search_types")
def test_advanced_search_invalid_sort_options(client: TestClient):
# Invalid sort_by
payload = {
"query": "x",
"search_types": ["customer"],
"sort_by": "nope",
}
resp = client.post("/api/search/advanced", json=payload)
assert_validation_error(resp, "sort_by")
# Invalid sort_order
payload = {
"query": "x",
"search_types": ["customer"],
"sort_order": "sideways",
}
resp = client.post("/api/search/advanced", json=payload)
assert_validation_error(resp, "sort_order")
def test_advanced_search_limit_bounds(client: TestClient):
# Too low
payload = {
"query": "x",
"search_types": ["customer"],
"limit": 0,
}
resp = client.post("/api/search/advanced", json=payload)
assert_validation_error(resp, "limit")
# Too high
payload["limit"] = 201
resp = client.post("/api/search/advanced", json=payload)
assert_validation_error(resp, "limit")
def test_advanced_search_conflicting_flags_exact_phrase_and_whole_words(client: TestClient):
payload = {
"query": "apple pie",
"search_types": ["file"],
"exact_phrase": True,
"whole_words": True,
}
resp = client.post("/api/search/advanced", json=payload)
# Cannot rely on field location for model-level validation, check message text in details
assert resp.status_code == 422
body = resp.json()
assert body.get("success") is False
assert body.get("error", {}).get("code") == "validation_error"
msgs = [d.get("msg", "") for d in body.get("error", {}).get("details", [])]
assert any("exact_phrase and whole_words" in m for m in msgs)
def test_advanced_search_inverted_date_range(client: TestClient):
payload = {
"search_types": ["file"],
"date_field": "created",
"date_from": "2024-02-01",
"date_to": "2024-01-31",
}
resp = client.post("/api/search/advanced", json=payload)
assert resp.status_code == 422
body = resp.json()
assert body.get("success") is False
assert body.get("error", {}).get("code") == "validation_error"
msgs = [d.get("msg", "") for d in body.get("error", {}).get("details", [])]
assert any("date_from must be less than or equal to date_to" in m for m in msgs)
def test_advanced_search_inverted_amount_range(client: TestClient):
payload = {
"search_types": ["file"],
"amount_field": "amount",
"amount_min": 100.0,
"amount_max": 50.0,
}
resp = client.post("/api/search/advanced", json=payload)
assert resp.status_code == 422
body = resp.json()
assert body.get("success") is False
assert body.get("error", {}).get("code") == "validation_error"
msgs = [d.get("msg", "") for d in body.get("error", {}).get("details", [])]
assert any("amount_min must be less than or equal to amount_max" in m for m in msgs)
def test_advanced_search_date_field_supported_per_type(client: TestClient):
# 'opened' is only valid for files
payload = {
"search_types": ["customer", "ledger"],
"date_field": "opened",
"date_from": "2024-01-01",
"date_to": "2024-12-31",
}
resp = client.post("/api/search/advanced", json=payload)
assert resp.status_code == 422
body = resp.json()
msgs = [d.get("msg", "") for d in body.get("error", {}).get("details", [])]
assert any("date_field 'opened' is not supported" in m for m in msgs)
# Valid when 'file' included
payload["search_types"] = ["file"]
resp = client.post("/api/search/advanced", json=payload)
assert resp.status_code == 200
def test_advanced_search_amount_field_supported_per_type(client: TestClient):
# 'amount' is only valid for ledger
payload = {
"search_types": ["file"],
"amount_field": "amount",
"amount_min": 1,
"amount_max": 10,
}
resp = client.post("/api/search/advanced", json=payload)
assert resp.status_code == 422
body = resp.json()
msgs = [d.get("msg", "") for d in body.get("error", {}).get("details", [])]
assert any("amount_field 'amount' is not supported" in m for m in msgs)
# Valid when 'ledger' included
payload["search_types"] = ["ledger"]
resp = client.post("/api/search/advanced", json=payload)
assert resp.status_code == 200