mirror of
https://github.com/kenhuangus/wikiskill
synced 2026-09-22 23:34:25 +08:00
174 lines
6.4 KiB
Python
174 lines
6.4 KiB
Python
import os
|
|
import time
|
|
import types
|
|
|
|
import pytest
|
|
|
|
from wikiskill import __version__
|
|
from wikiskill.webapp import create_app
|
|
from wikiskill.webapp.api import get_manager
|
|
from wikiskill.webapp.runner import RunManager, _is_fresh_root
|
|
|
|
|
|
def _wait_status(client, timeout=20.0):
|
|
deadline = time.time() + timeout
|
|
while time.time() < deadline:
|
|
r = client.get("/api/status")
|
|
assert r.status_code == 200
|
|
st = r.get_json()
|
|
if st["state"] and st["state"]["status"] in ("done", "error"):
|
|
return st
|
|
time.sleep(0.05)
|
|
raise AssertionError("run did not finish in time")
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(tmp_path, monkeypatch):
|
|
monkeypatch.chdir(tmp_path) # relative workspace roots land under tmp_path
|
|
app = create_app({"TESTING": True})
|
|
get_manager()._reset_for_tests()
|
|
with app.test_client() as c:
|
|
yield c
|
|
|
|
|
|
# ---------------------------------------------------------------- basics
|
|
def test_index_serves_spa(client):
|
|
r = client.get("/")
|
|
assert r.status_code == 200
|
|
html = r.get_data(as_text=True)
|
|
assert "WikiSkill" in html
|
|
assert 'id="run-form"' in html
|
|
assert 'id="console"' in html
|
|
assert 'id="chart-rbest"' in html
|
|
|
|
|
|
def test_health(client):
|
|
r = client.get("/api/health")
|
|
# ---------------------------------------------------------------- lifecycle
|
|
def test_full_run_lifecycle(client, tmp_path):
|
|
root = "ui_test_run"
|
|
r = client.post("/api/run", json={"llm_backend": "mock", "iterations": 3,
|
|
"workspace_root": root})
|
|
assert r.status_code == 202
|
|
assert r.get_json()["active"] is True
|
|
|
|
# single-flight: second start while active must be rejected
|
|
r2 = client.post("/api/run", json={"llm_backend": "mock", "iterations": 3,
|
|
"workspace_root": root + "_2"})
|
|
assert r2.status_code == 409
|
|
|
|
st = _wait_status(client)
|
|
assert st["state"]["status"] == "done"
|
|
result = st["state"]["result"]
|
|
assert "unit-conversion" in result["evolution"]["final_skills"]
|
|
assert result["evaluation"]["significant"] is True
|
|
|
|
# workspace artifacts exist on disk
|
|
assert (tmp_path / root / "wiki" / "index.md").is_file()
|
|
assert (tmp_path / root / "skills" / "unit-conversion" / "SKILL.md").is_file()
|
|
# string-tag subdirs for post-hoc test evaluation
|
|
assert (tmp_path / root / "raw" / "eval_skilled").is_dir()
|
|
|
|
|
|
def test_status_empty(client):
|
|
st = client.get("/api/status").get_json()
|
|
assert st["active"] is False
|
|
assert st["state"] is None
|
|
# ---------------------------------------------------------------- workspace & eval
|
|
def test_workspace_endpoint(client, tmp_path):
|
|
root = "ui_ws_test"
|
|
client.post("/api/run", json={"llm_backend": "mock", "iterations": 2,
|
|
"workspace_root": root})
|
|
_wait_status(client)
|
|
|
|
r = client.get(f"/api/workspace?root={root}")
|
|
assert r.status_code == 200
|
|
data = r.get_json()
|
|
assert data["root"] == root
|
|
names = {c["name"] for c in data["tree"]["children"]}
|
|
assert {"raw", "wiki", "skills"} <= names
|
|
assert "wiki/index.md" in data["files"]
|
|
assert "wiki/skill-impact.md" in data["files"]
|
|
assert any(k.endswith("SKILL.md") for k in data["files"])
|
|
|
|
assert client.get("/api/workspace?root=ui_does_not_exist").status_code == 404
|
|
assert client.get("/api/workspace?root=../x").status_code == 400
|
|
|
|
|
|
def test_evaluate_endpoint(client, tmp_path):
|
|
root = "ui_eval_test"
|
|
client.post("/api/run", json={"llm_backend": "mock", "iterations": 2,
|
|
"workspace_root": root})
|
|
_wait_status(client)
|
|
|
|
r = client.post("/api/evaluate", json={"llm_backend": "mock",
|
|
"workspace_root": root})
|
|
assert r.status_code == 200
|
|
ev = r.get_json()["evaluation"]
|
|
assert ev["skilled_accuracy"] == 1.0
|
|
assert ev["baseline_accuracy"] == 0.0
|
|
assert ev["significant"] is True
|
|
assert client.post("/api/evaluate", json={"llm_backend": "mock",
|
|
"workspace_root": "missing"}).status_code == 404
|
|
|
|
|
|
# ---------------------------------------------------------------- SSE
|
|
def test_sse_stream(client):
|
|
r = client.post("/api/run", json={"llm_backend": "mock", "iterations": 1,
|
|
"workspace_root": "ui_sse_test"})
|
|
assert r.status_code == 202
|
|
stream = client.get("/api/stream")
|
|
body = b""
|
|
for chunk in stream.response:
|
|
body += chunk
|
|
if b'"type": "done"' in body:
|
|
break
|
|
assert b"data:" in body
|
|
assert b'"done"' in body
|
|
|
|
|
|
# ---------------------------------------------------------------- RunManager units
|
|
def test_runmanager_double_start_rejected():
|
|
mgr = RunManager()
|
|
mgr._reset_for_tests()
|
|
mgr._execute = types.MethodType(lambda self, rid, cfg: time.sleep(2), mgr)
|
|
a = mgr.start({"llm_backend": "mock"})
|
|
assert a["active"] is True
|
|
with pytest.raises(PermissionError):
|
|
mgr.start({"llm_backend": "mock"})
|
|
mgr.cancel()
|
|
mgr._reset_for_tests()
|
|
|
|
|
|
def test_runmanager_fresh_root_guard(tmp_path):
|
|
assert _is_fresh_root(str(tmp_path / "new")) is True
|
|
raw = tmp_path / "used" / "raw"
|
|
raw.mkdir(parents=True)
|
|
(raw / "iter_000").mkdir()
|
|
assert _is_fresh_root(str(tmp_path / "used")) is False
|
|
|
|
|
|
def test_cancel(client, tmp_path, monkeypatch):
|
|
# Slow the orchestrator so cancel lands mid-run (cooperative stop).
|
|
import wikiskill.orchestrator as orch_mod
|
|
original_run = orch_mod.EvolutionOrchestrator.run
|
|
|
|
def slow_run(self):
|
|
time.sleep(1.5)
|
|
return original_run(self)
|
|
|
|
monkeypatch.setattr(orch_mod.EvolutionOrchestrator, "run", slow_run)
|
|
client.post("/api/run", json={"llm_backend": "mock", "iterations": 4,
|
|
"workspace_root": "ui_cancel_run"})
|
|
assert client.post("/api/cancel", json={}).status_code == 200
|
|
st = _wait_status(client, timeout=20)
|
|
assert st["state"]["status"] in ("done", "error")
|
|
|
|
|
|
def test_run_validation_400s(client):
|
|
assert client.post("/api/run", json={"llm_backend": "bogus"}).status_code == 400
|
|
assert client.post("/api/run", json={"llm_backend": "openai"}).status_code == 400 # no model
|
|
assert client.post("/api/run", json={"llm_backend": "mock",
|
|
"workspace_root": "../escape"}).status_code == 400
|
|
assert client.post("/api/run", json={"llm_backend": "mock",
|
|
"workspace_root": "C:\\abs"}).status_code == 400 |