123 lines
4.0 KiB
Python
123 lines
4.0 KiB
Python
import asyncio
|
|
import time
|
|
|
|
import app.api.server as server
|
|
from app.api.server import chat, critic_feedback, health, list_events, resolve_permission, resolve_review, resolve_secret
|
|
from app.core.permission_resolution import PermissionResolutionRequest, ReviewResolutionRequest, SecretResolutionRequest
|
|
from app.api.server import CriticFeedbackRequest
|
|
from app.core.contracts import UserTask
|
|
|
|
|
|
def test_health_handler() -> None:
|
|
assert health() == {"status": "ok"}
|
|
|
|
|
|
def test_events_handler_returns_event_list() -> None:
|
|
body = list_events(limit=10)
|
|
assert "events" in body
|
|
assert isinstance(body["events"], list)
|
|
|
|
|
|
def test_chat_handler_returns_runtime_events() -> None:
|
|
body = chat(UserTask(input="hello from handler test"))
|
|
assert body["status"] in {"accepted", "completed"}
|
|
if body["status"] == "completed":
|
|
assert body["events"][0]["type"] == "task_received"
|
|
|
|
|
|
def test_chat_handler_submits_task_without_waiting_for_completion(monkeypatch) -> None:
|
|
class SlowRuntime:
|
|
def submit_task(self, task):
|
|
return {"task_id": task.task_id, "status": "accepted"}
|
|
|
|
def handle_task(self, task):
|
|
time.sleep(0.25)
|
|
return {"task_id": task.task_id, "status": "completed", "events": []}
|
|
|
|
monkeypatch.setattr("app.api.server.runtime", SlowRuntime())
|
|
started = time.monotonic()
|
|
body = chat(UserTask(input="long task"))
|
|
|
|
assert time.monotonic() - started < 0.1
|
|
assert body["status"] == "accepted"
|
|
|
|
|
|
def test_lifespan_loads_models_without_threadpool_executor(monkeypatch) -> None:
|
|
class FakeRuntime:
|
|
_memory_interface = None
|
|
|
|
def __init__(self) -> None:
|
|
self.loaded = False
|
|
|
|
def load_models_at_startup(self) -> None:
|
|
self.loaded = True
|
|
|
|
class FailingLoop:
|
|
def run_in_executor(self, *args, **kwargs):
|
|
raise AssertionError("lifespan must not load llama models via run_in_executor")
|
|
|
|
fake_runtime = FakeRuntime()
|
|
monkeypatch.setattr(server, "runtime", fake_runtime)
|
|
monkeypatch.setattr(server.asyncio, "get_event_loop", lambda: FailingLoop())
|
|
|
|
async def run_lifespan() -> None:
|
|
async with server.lifespan(None):
|
|
pass
|
|
|
|
asyncio.run(run_lifespan())
|
|
assert fake_runtime.loaded is True
|
|
|
|
|
|
def test_resolve_permission_handler_allows_completion() -> None:
|
|
initial = chat(UserTask(input="запусти pwd"))
|
|
if initial["status"] == "awaiting_permission":
|
|
body = resolve_permission(
|
|
PermissionResolutionRequest(task_id=initial["task_id"], decision="allow_once")
|
|
)
|
|
assert body["status"] in {"completed", "failed"}
|
|
|
|
|
|
def test_resolve_secret_handler_requires_pending_request() -> None:
|
|
body = resolve_secret(SecretResolutionRequest(task_id="missing", secret="x"))
|
|
assert body["status"] == "failed"
|
|
|
|
|
|
def test_resolve_review_handler_submits_review_resolution(monkeypatch) -> None:
|
|
class ReviewRuntime:
|
|
def submit_review_resolution(self, task_id, decision, correction=None):
|
|
return {
|
|
"task_id": task_id,
|
|
"status": "accepted",
|
|
"decision": decision,
|
|
"correction": correction,
|
|
}
|
|
|
|
monkeypatch.setattr("app.api.server.runtime", ReviewRuntime())
|
|
body = resolve_review(
|
|
ReviewResolutionRequest(
|
|
task_id="task-1",
|
|
decision="wrong_action",
|
|
correction="replan",
|
|
)
|
|
)
|
|
|
|
assert body["status"] == "accepted"
|
|
assert body["decision"] == "wrong_action"
|
|
|
|
|
|
def test_structured_feedback_can_be_accepted_without_memory_write() -> None:
|
|
initial = chat(UserTask(input="feedback target"))
|
|
body = critic_feedback(
|
|
CriticFeedbackRequest(
|
|
task_id=initial["task_id"],
|
|
feedback="wrong answer",
|
|
feedback_type="hallucination",
|
|
severity="major",
|
|
correction="check first",
|
|
remember=False,
|
|
)
|
|
)
|
|
assert body["status"] == "ok"
|
|
assert body["stored"] is False
|
|
assert "hallucination" in body["lesson"]
|