MicroFish/backend/tests/interviews/test_api_interview.py

156 lines
5.9 KiB
Python

import json
import os
from pathlib import Path
import pytest
@pytest.fixture
def client(tmp_path, monkeypatch):
monkeypatch.setenv("LLM_STUB_MODE", "true")
monkeypatch.setenv("UPLOADS_DIR", str(tmp_path))
from app.config import Config
Config.LLM_STUB_MODE = True
Config.UPLOADS_DIR = str(tmp_path)
# Seed a minimal reddit_profiles.json
sim_dir = tmp_path / "simulations" / "sim_test"
sim_dir.mkdir(parents=True)
profiles = [{"user_id": i, "user_name": f"u{i}", "name": f"A{i}",
"persona": "p", "profession": "fisher"} for i in range(3)]
(sim_dir / "reddit_profiles.json").write_text(json.dumps(profiles), encoding="utf-8")
from flask import Flask
from app.api import register_blueprints
app = Flask(__name__)
register_blueprints(app)
return app.test_client()
def test_post_pre_returns_task_id(client):
res = client.post("/api/interview/sim_test/pre")
assert res.status_code == 200
body = res.get_json()
assert body["success"] is True
assert "task_id" in body["data"]
def test_status_endpoint_returns_progress(client):
res = client.post("/api/interview/sim_test/pre")
task_id = res.get_json()["data"]["task_id"]
res2 = client.get(f"/api/interview/sim_test/status?task_id={task_id}")
assert res2.status_code == 200
assert "status" in res2.get_json()["data"]
def test_unknown_subagent_returns_400(client):
res = client.post("/api/interview/sim_test/rerun",
json={"subagent": "nonsense"})
assert res.status_code == 400
def test_build_orchestrator_reads_graph_id_from_state(tmp_path, monkeypatch):
"""C1+C2: ``_build_orchestrator`` must resolve the Zep graph_id from
``state.json`` (written by ``SimulationManager``), not from the
nonexistent ``graph_id.txt``. The graph_id then must reach the
``InterviewZepWriter`` instead of being silently swallowed.
"""
monkeypatch.setenv("LLM_STUB_MODE", "true")
monkeypatch.setenv("UPLOADS_DIR", str(tmp_path))
monkeypatch.setenv("ZEP_API_KEY", "test-fake-key")
from app.config import Config
Config.LLM_STUB_MODE = True
Config.UPLOADS_DIR = str(tmp_path)
Config.ZEP_API_KEY = "test-fake-key"
# SimulationManager's data dir is class-level — point it at tmp_path.
from app.services.simulation_manager import SimulationManager
sim_root = tmp_path / "simulations"
sim_root.mkdir(parents=True, exist_ok=True)
monkeypatch.setattr(SimulationManager, "SIMULATION_DATA_DIR", str(sim_root))
sim_id = "sim_graphid"
sim_dir = sim_root / sim_id
sim_dir.mkdir(parents=True)
# Seed a profile file so FileSystemPersonaProvider can work.
(sim_dir / "reddit_profiles.json").write_text(
json.dumps([
{"user_id": 0, "user_name": "u0", "name": "A0",
"persona": "p", "profession": "fisher",
"source_entity_uuid": "uuid-zero"},
{"user_id": 1, "user_name": "u1", "name": "A1",
"persona": "p", "profession": "fisher",
"source_entity_uuid": "uuid-one"},
]),
encoding="utf-8",
)
# Seed state.json with the graph_id.
state_doc = {
"simulation_id": sim_id,
"project_id": "p",
"graph_id": "graph-from-state",
"status": "ready",
"enable_twitter": False,
"enable_reddit": True,
}
(sim_dir / "state.json").write_text(json.dumps(state_doc), encoding="utf-8")
# Patch ZepGraphMemoryUpdater + ZepEntityReader so we don't hit the network.
import app.services.zep_graph_memory_updater as zgmu
import app.services.zep_entity_reader as zer
class _FakeUpdater:
def __init__(self, graph_id, api_key=None):
self.graph_id = graph_id
def add_text_episode(self, graph_id, text):
return None
class _FakeReader:
def __init__(self, api_key=None):
pass
def get_entity_with_context(self, graph_id, entity_uuid):
return None
monkeypatch.setattr(zgmu, "ZepGraphMemoryUpdater", _FakeUpdater)
monkeypatch.setattr(zer, "ZepEntityReader", _FakeReader)
from app.api.interview import _build_orchestrator
orch = _build_orchestrator(sim_id)
assert orch.zep_writer.graph_id == "graph-from-state"
# Updater on the writer must be the real (or fake) ZepGraphMemoryUpdater path,
# NOT the null updater — i.e. its graph_id must match.
assert getattr(orch.zep_writer.updater, "graph_id", None) == "graph-from-state"
# ZepMemoryProvider must have received the agent_to_entity map (C5).
assert hasattr(orch.memory, "map")
assert orch.memory.map == {0: "uuid-zero", 1: "uuid-one"}
def test_build_orchestrator_falls_back_when_state_missing(tmp_path, monkeypatch):
"""C1+C2: when ``state.json`` is missing, the orchestrator must still be
constructed with the null updater/memory path (not crash, not silently
pass a bare ``ZepGraphMemoryUpdater()`` that would error out).
"""
monkeypatch.setenv("LLM_STUB_MODE", "true")
monkeypatch.setenv("UPLOADS_DIR", str(tmp_path))
from app.config import Config
Config.LLM_STUB_MODE = True
Config.UPLOADS_DIR = str(tmp_path)
from app.services.simulation_manager import SimulationManager
sim_root = tmp_path / "simulations"
sim_root.mkdir(parents=True, exist_ok=True)
monkeypatch.setattr(SimulationManager, "SIMULATION_DATA_DIR", str(sim_root))
sim_id = "sim_no_state"
sim_dir = sim_root / sim_id
sim_dir.mkdir(parents=True)
(sim_dir / "reddit_profiles.json").write_text(
json.dumps([{"user_id": 0, "user_name": "u0", "name": "A0",
"persona": "p", "profession": "fisher"}]),
encoding="utf-8",
)
from app.api.interview import _build_orchestrator
orch = _build_orchestrator(sim_id)
assert orch.zep_writer.graph_id == ""
# Null updater path: writer must still respond to _emit without raising.
orch.zep_writer._emit("hello")