156 lines
5.9 KiB
Python
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")
|