import os import pytest from pathlib import Path from fraud_hunter_env.server.fraud_hunter_env_environment import FraudHunterEnvironment from fraud_hunter_env.models import FraudHunterAction, ActionKind, EntityKind def test_environment_dynamic_generation(tmp_path): # 1. Initialize environment with a deliberately non-existent bank dir so # reset() falls through to on-the-fly generation (not bank-pick). Stale # cases in the real bank may pre-date the multimodal compiler and lack # intercepted_comms/, which would spuriously fail the structure asserts # below. env = FraudHunterEnvironment(case_bank_dir=str(tmp_path / "no_bank_here")) # 2. Reset generates a case dynamically obs = env.reset() assert obs.difficulty_tier == 1 assert "case_id" in obs.info # 3. Verify directory structure case = env._case assert case is not None case_dir = case.db_path.parent assert case_dir.exists() assert case.db_path.exists() comms_dir = case_dir / "intercepted_comms" scanned_dir = case_dir / "scanned_claims" assert comms_dir.exists() assert scanned_dir.exists() # 4. Verify code sandbox can access the files # The agent should be able to list files in the case_dir and read a PDF test_code = """ files = listdir("scanned_claims") if files: pdf_path = path_join("scanned_claims", files[0]) with pdfplumber.open(pdf_path) as pdf: text = pdf.pages[0].extract_text() or "" print("PDF Extracted:", len(text), "chars") else: print("No PDFs found") """ action = FraudHunterAction( think_trace="testing sandbox", kind=ActionKind.CODE_ACT, python_code=test_code ) step_obs = env.step(action) assert "PDF Extracted:" in step_obs.tool_output or "No PDFs found" in step_obs.tool_output assert "SECURITY_VIOLATION" not in step_obs.tool_output def test_evidence_graph_entities_are_typed_dicts(): """Confirmed entity extractions should appear as {name, kind} dicts.""" env = FraudHunterEnvironment() try: env.reset() # Probe corporate_registry for a real entity name from the active case. probe = FraudHunterAction( kind=ActionKind.SQL_QUERY, sql_statement="SELECT entity_name FROM corporate_registry LIMIT 1", think_trace="probe registry", ) probe_obs = env.step(probe) # Pull the first row of tool_output (header line is skipped); fall back # to the demo-case ground-truth name when parsing the table fails. name = "Acme Shell LLC" if probe_obs.tool_output: lines = [ln.strip() for ln in probe_obs.tool_output.splitlines() if ln.strip()] if len(lines) >= 2: name = lines[1] ext = FraudHunterAction( kind=ActionKind.EXTRACT_ENTITY, extracted_name=name, extracted_kind=EntityKind.CORPORATION, think_trace="flag this corporation", ) obs = env.step(ext) assert obs.evidence_graph is not None entities = obs.evidence_graph["entities"] assert isinstance(entities, list) # Each entry (if any) must be a dict with both `name` and `kind`. for item in entities: assert isinstance(item, dict) assert "name" in item and "kind" in item assert isinstance(item["name"], str) and isinstance(item["kind"], str) finally: env.close() def test_agentic_recall_ignores_sql_substrings_without_access(): env = FraudHunterEnvironment(case_bank_dir=None) env.reset() obs = env.step(FraudHunterAction.model_validate({ "kind": "sql_query", "sql_statement": "SELECT 'corporate_registry', 'beneficiary_summary'", "think_trace": "Test literal strings without touching any tables.", })) assert obs.info is not None assert obs.info["agentic_recall"] == 0.0