123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
"""
|
|
Sprint 13 Tests: cron recent endpoint, session duplicate, background alerts.
|
|
"""
|
|
import json, pathlib, urllib.error, urllib.request
|
|
|
|
from tests._pytest_port import BASE
|
|
|
|
|
|
def get(path):
|
|
with urllib.request.urlopen(BASE + path, timeout=10) as r:
|
|
return json.loads(r.read()), 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(created_list):
|
|
d, _ = post("/api/session/new", {})
|
|
sid = d["session"]["session_id"]
|
|
created_list.append(sid)
|
|
return sid, d["session"]
|
|
|
|
|
|
# ── Cron recent endpoint ──────────────────────────────────────────────────
|
|
|
|
def test_crons_recent_returns_200():
|
|
"""GET /api/crons/recent returns completions list."""
|
|
d, status = get("/api/crons/recent?since=0")
|
|
assert status == 200
|
|
assert 'completions' in d
|
|
assert isinstance(d['completions'], list)
|
|
assert 'since' in d
|
|
|
|
def test_crons_recent_with_future_since():
|
|
"""Completions list is empty when since is in the future."""
|
|
import time
|
|
d, _ = get(f"/api/crons/recent?since={time.time() + 99999}")
|
|
assert d['completions'] == []
|
|
|
|
def test_crons_recent_default_since():
|
|
"""Default since=0 returns all completions."""
|
|
d, status = get("/api/crons/recent")
|
|
assert status == 200
|
|
assert 'completions' in d
|
|
|
|
|
|
# ── Session duplicate ─────────────────────────────────────────────────────
|
|
|
|
def test_duplicate_session():
|
|
"""Duplicating a session creates a new one with same workspace/model."""
|
|
created = []
|
|
try:
|
|
sid, sess = make_session(created)
|
|
# Set a specific model on the session
|
|
post("/api/session/update", {
|
|
"session_id": sid, "model": "test/dup-model",
|
|
"workspace": sess["workspace"]
|
|
})
|
|
# Duplicate: create new session with same workspace/model
|
|
d2, status = post("/api/session/new", {
|
|
"workspace": sess["workspace"], "model": "test/dup-model"
|
|
})
|
|
assert status == 200
|
|
new_sid = d2["session"]["session_id"]
|
|
created.append(new_sid)
|
|
assert new_sid != sid
|
|
assert d2["session"]["model"] == "test/dup-model"
|
|
assert d2["session"]["workspace"] == sess["workspace"]
|
|
finally:
|
|
for s in created:
|
|
post("/api/session/delete", {"session_id": s})
|
|
|
|
|
|
# ── Session pinned field preserved across operations ──────────────────────
|
|
|
|
def test_pinned_survives_update():
|
|
"""Pinned status survives session update."""
|
|
created = []
|
|
try:
|
|
sid, sess = make_session(created)
|
|
post("/api/session/pin", {"session_id": sid, "pinned": True})
|
|
# Update workspace/model
|
|
post("/api/session/update", {
|
|
"session_id": sid, "model": "test/other",
|
|
"workspace": sess["workspace"]
|
|
})
|
|
d, _ = get(f"/api/session?session_id={sid}")
|
|
assert d["session"]["pinned"] is True
|
|
finally:
|
|
for s in created:
|
|
post("/api/session/delete", {"session_id": s})
|
|
|
|
|
|
# ── Workspace symlink validation ──────────────────────────────────────────
|
|
|
|
def test_workspace_add_rejects_nonexistent():
|
|
"""Adding a non-existent path returns 400."""
|
|
d, status = post("/api/workspaces/add", {"path": "/nonexistent/path/12345"})
|
|
assert status == 400
|
|
|
|
def test_workspace_add_accepts_real_dir():
|
|
"""Adding a real directory under the trusted workspace root succeeds."""
|
|
d, _ = post("/api/session/new", {})
|
|
root = pathlib.Path(d["session"]["workspace"])
|
|
tmp = root / "trusted-add-test"
|
|
tmp.mkdir(parents=True, exist_ok=True)
|
|
try:
|
|
d, status = post("/api/workspaces/add", {"path": str(tmp), "name": "test-ws"})
|
|
assert status == 200
|
|
assert d["ok"] is True
|
|
finally:
|
|
post("/api/workspaces/remove", {"path": str(tmp)})
|
|
import shutil
|
|
shutil.rmtree(tmp, ignore_errors=True)
|