Hermes WebUI v0.1.0 — initial public release
This commit is contained in:
156
tests/test_sprint4.py
Normal file
156
tests/test_sprint4.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""Sprint 4 tests: relocation, session rename, search, file ops, validation."""
|
||||
import json, pathlib, uuid, urllib.request, urllib.error
|
||||
|
||||
BASE = "http://127.0.0.1:8788" # test server (isolated from production)
|
||||
|
||||
def get(path):
|
||||
with urllib.request.urlopen(BASE + path, timeout=10) as r:
|
||||
return json.loads(r.read()), r.status
|
||||
|
||||
def get_raw(path):
|
||||
with urllib.request.urlopen(BASE + path, timeout=10) as r:
|
||||
return r.read(), r.headers.get("Content-Type",""), r.status
|
||||
|
||||
def post(path, body=None):
|
||||
data = json.dumps(body or {}).encode()
|
||||
req = urllib.request.Request(BASE + path, data=data, headers={"Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as r:
|
||||
return json.loads(r.read()), r.status
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read()), e.code
|
||||
|
||||
def make_session_tracked(created_list, ws=None):
|
||||
"""Create a session and register it with the cleanup fixture."""
|
||||
import pathlib as _pathlib
|
||||
body = {}
|
||||
if ws: body["workspace"] = str(ws)
|
||||
d, _ = post("/api/session/new", body)
|
||||
sid = d["session"]["session_id"]
|
||||
created_list.append(sid)
|
||||
return sid, _pathlib.Path(d["session"]["workspace"])
|
||||
|
||||
|
||||
def test_server_running_from_new_location():
|
||||
data, status = get("/health")
|
||||
assert status == 200 and data["status"] == "ok"
|
||||
|
||||
def test_static_css_served():
|
||||
raw, ct, status = get_raw("/static/style.css")
|
||||
assert status == 200 and "text/css" in ct and b"--bg" in raw
|
||||
|
||||
def test_static_unknown_file_404():
|
||||
try:
|
||||
get_raw("/static/doesnotexist.xyz")
|
||||
assert False
|
||||
except urllib.error.HTTPError as e:
|
||||
assert e.code == 404
|
||||
|
||||
def test_session_rename(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
result, status = post("/api/session/rename", {"session_id": sid, "title": "Renamed Session"})
|
||||
assert status == 200 and result["session"]["title"] == "Renamed Session"
|
||||
|
||||
def test_session_rename_persists(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
post("/api/session/rename", {"session_id": sid, "title": "Persisted"})
|
||||
loaded, _ = get(f"/api/session?session_id={sid}")
|
||||
assert loaded["session"]["title"] == "Persisted"
|
||||
|
||||
def test_session_rename_truncates(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
result, status = post("/api/session/rename", {"session_id": sid, "title": "A" * 200})
|
||||
assert status == 200 and len(result["session"]["title"]) <= 80
|
||||
|
||||
def test_session_rename_requires_fields():
|
||||
result, status = post("/api/session/rename", {"session_id": "x"})
|
||||
assert status == 400
|
||||
result2, status2 = post("/api/session/rename", {"title": "hi"})
|
||||
assert status2 == 400
|
||||
|
||||
def test_session_rename_unknown_id():
|
||||
result, status = post("/api/session/rename", {"session_id": "nosuchid", "title": "hi"})
|
||||
assert status == 404
|
||||
|
||||
def test_session_search_returns_matches(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
uid = uuid.uuid4().hex[:8]
|
||||
post("/api/session/rename", {"session_id": sid, "title": f"s4-search-{uid}"})
|
||||
data, status = get(f"/api/sessions/search?q=s4-search-{uid}")
|
||||
assert status == 200
|
||||
sids = [s["session_id"] for s in data["sessions"]]
|
||||
assert sid in sids
|
||||
|
||||
def test_session_search_empty_query_returns_all():
|
||||
data, status = get("/api/sessions/search?q=")
|
||||
assert status == 200 and "sessions" in data
|
||||
|
||||
def test_session_search_no_results():
|
||||
data, status = get("/api/sessions/search?q=zzznomatchzzz9999")
|
||||
assert status == 200 and data["sessions"] == []
|
||||
|
||||
def test_file_create(cleanup_test_sessions):
|
||||
sid, ws = make_session_tracked(cleanup_test_sessions)
|
||||
fname = f"test_{uuid.uuid4().hex[:6]}.txt"
|
||||
result, status = post("/api/file/create", {"session_id": sid, "path": fname, "content": "hello sprint4"})
|
||||
assert status == 200 and result["ok"] is True
|
||||
assert (ws / fname).read_text() == "hello sprint4"
|
||||
|
||||
def test_file_create_requires_fields(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
result, status = post("/api/file/create", {"session_id": sid})
|
||||
assert status == 400
|
||||
result2, status2 = post("/api/file/create", {"path": "x.txt"})
|
||||
assert status2 == 400
|
||||
|
||||
def test_file_create_duplicate_rejected(cleanup_test_sessions):
|
||||
sid, ws = make_session_tracked(cleanup_test_sessions)
|
||||
fname = f"dup_{uuid.uuid4().hex[:6]}.txt"
|
||||
post("/api/file/create", {"session_id": sid, "path": fname, "content": ""})
|
||||
result, status = post("/api/file/create", {"session_id": sid, "path": fname, "content": ""})
|
||||
assert status == 400
|
||||
|
||||
def test_file_delete(cleanup_test_sessions):
|
||||
sid, ws = make_session_tracked(cleanup_test_sessions)
|
||||
(ws / "to_delete.txt").write_text("bye")
|
||||
result, status = post("/api/file/delete", {"session_id": sid, "path": "to_delete.txt"})
|
||||
assert status == 200 and not (ws / "to_delete.txt").exists()
|
||||
|
||||
def test_file_delete_missing_returns_404(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
result, status = post("/api/file/delete", {"session_id": sid, "path": "nosuchfile.txt"})
|
||||
assert status == 404
|
||||
|
||||
def test_file_delete_path_traversal_blocked(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
result, status = post("/api/file/delete", {"session_id": sid, "path": "../../etc/passwd"})
|
||||
assert status in (400, 500)
|
||||
|
||||
def test_list_requires_session_id():
|
||||
try:
|
||||
get("/api/list?path=.")
|
||||
assert False
|
||||
except urllib.error.HTTPError as e:
|
||||
assert e.code == 400
|
||||
|
||||
def test_file_requires_session_id():
|
||||
try:
|
||||
get("/api/file?path=readme.txt")
|
||||
assert False
|
||||
except urllib.error.HTTPError as e:
|
||||
assert e.code == 400
|
||||
|
||||
def test_file_requires_path(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
try:
|
||||
get(f"/api/file?session_id={sid}")
|
||||
assert False
|
||||
except urllib.error.HTTPError as e:
|
||||
assert e.code == 400
|
||||
|
||||
def test_new_session_inherits_workspace(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
post("/api/session/update", {"session_id": sid, "workspace": "/tmp", "model": "openai/gpt-5.4-mini"})
|
||||
sid2, _ = make_session_tracked(cleanup_test_sessions)
|
||||
data, _ = get(f"/api/session?session_id={sid2}")
|
||||
assert data["session"]["workspace"] == "/tmp"
|
||||
Reference in New Issue
Block a user