This commit is contained in:
2026-07-07 15:28:28 +02:00
parent 499f91e16a
commit 32c8bbe0a9
52 changed files with 3420 additions and 328 deletions
+362
View File
@@ -0,0 +1,362 @@
# retoor <retoor@molodetz.nl>
import time
import requests
from tests.conftest import BASE_URL
from devplacepy.database import (
get_table,
get_primary_admin_uid,
invalidate_admins_cache,
refresh_snapshot,
)
from devplacepy.utils import clear_user_cache
JSON = {"Accept": "application/json"}
_counter = [0]
def _signup():
_counter[0] += 1
name = f"acm{int(time.time() * 1000)}{_counter[0]}"
requests.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
},
allow_redirects=True,
)
row = get_table("users").find_one(username=name)
return row["uid"], row["api_key"]
def _make_admin():
uid, key = _signup()
get_table("users").update({"uid": uid, "role": "Admin"}, ["uid"])
clear_user_cache(uid)
invalidate_admins_cache()
return uid, key
def _primary_admin_key():
refresh_snapshot()
uid = get_primary_admin_uid()
return get_table("users").find_one(uid=uid)["api_key"]
def _headers(key):
return {**JSON, "X-API-KEY": key}
def _create_project(key, title, is_private=False):
data = {
"title": title,
"description": "admin container manage isolation test",
"project_type": "software",
"status": "In Development",
}
if is_private:
data["is_private"] = "on"
r = requests.post(f"{BASE_URL}/projects/create", headers=_headers(key), data=data)
assert r.status_code == 200, r.text
return r.json()["data"]
def _insert_instance(project_uid, created_by):
uid = f"acm-{int(time.time() * 1000000)}"
get_table("instances").insert(
{
"uid": uid,
"slug": uid,
"name": "manage-test",
"project_uid": project_uid,
"created_by": created_by,
"owner_uid": "",
"status": "created",
"container_id": "",
"workspace_dir": "",
"restart_policy": "never",
"deleted_at": None,
"deleted_by": None,
}
)
refresh_snapshot()
return uid
def _cleanup(inst_uid):
get_table("instances").delete(uid=inst_uid)
refresh_snapshot()
def test_data_flags_can_manage_for_owner_and_hides_for_other_on_public_project(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Data Public", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
owner_rows = requests.get(
f"{BASE_URL}/admin/containers/data", headers=_headers(owner_key)
).json()["instances"]
other_rows = requests.get(
f"{BASE_URL}/admin/containers/data", headers=_headers(other_key)
).json()["instances"]
owner_row = next(r for r in owner_rows if r["uid"] == inst_uid)
other_row = next(r for r in other_rows if r["uid"] == inst_uid)
assert owner_row["can_manage"] is True
assert other_row["can_manage"] is False
finally:
_cleanup(inst_uid)
def test_data_excludes_others_private_project_instance(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Data Private", is_private=True)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
other_rows = requests.get(
f"{BASE_URL}/admin/containers/data", headers=_headers(other_key)
).json()["instances"]
assert all(r["uid"] != inst_uid for r in other_rows)
owner_rows = requests.get(
f"{BASE_URL}/admin/containers/data", headers=_headers(owner_key)
).json()["instances"]
assert any(r["uid"] == inst_uid for r in owner_rows)
finally:
_cleanup(inst_uid)
def test_primary_admin_sees_and_can_manage_others_private_instance(app_server):
owner_uid, owner_key = _make_admin()
primary_key = _primary_admin_key()
project = _create_project(owner_key, "Manage Data Primary", is_private=True)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
rows = requests.get(
f"{BASE_URL}/admin/containers/data", headers=_headers(primary_key)
).json()["instances"]
row = next(r for r in rows if r["uid"] == inst_uid)
assert row["can_manage"] is True
finally:
_cleanup(inst_uid)
def test_instance_detail_404_for_non_viewer_on_private_project(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Detail Private", is_private=True)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.get(
f"{BASE_URL}/admin/containers/{inst_uid}", headers=_headers(other_key)
)
assert r.status_code == 404
r = requests.get(
f"{BASE_URL}/admin/containers/{inst_uid}", headers=_headers(owner_key)
)
assert r.status_code == 200
finally:
_cleanup(inst_uid)
def test_instance_detail_view_only_for_non_owner_on_public_project(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Detail Public", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.get(
f"{BASE_URL}/admin/containers/{inst_uid}", headers=_headers(other_key)
)
assert r.status_code == 200
assert r.json()["can_manage"] is False
r = requests.get(
f"{BASE_URL}/admin/containers/{inst_uid}", headers=_headers(owner_key)
)
assert r.json()["can_manage"] is True
finally:
_cleanup(inst_uid)
def test_lifecycle_actions_denied_for_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Lifecycle Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
for action in ("start", "stop", "pause", "resume", "restart"):
r = requests.post(
f"{BASE_URL}/admin/containers/{inst_uid}/{action}",
headers=_headers(other_key),
)
assert r.status_code == 403, action
finally:
_cleanup(inst_uid)
def test_lifecycle_actions_allowed_for_owner(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Manage Lifecycle Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
for action in ("start", "stop", "pause", "resume", "restart"):
r = requests.post(
f"{BASE_URL}/admin/containers/{inst_uid}/{action}",
headers=_headers(owner_key),
)
assert r.status_code == 200, action
finally:
_cleanup(inst_uid)
def test_delete_denied_for_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Delete Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
f"{BASE_URL}/admin/containers/{inst_uid}/delete", headers=_headers(other_key)
)
assert r.status_code == 403
assert get_table("instances").find_one(uid=inst_uid) is not None
finally:
_cleanup(inst_uid)
def test_delete_allowed_for_owner(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Manage Delete Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
f"{BASE_URL}/admin/containers/{inst_uid}/delete", headers=_headers(owner_key)
)
assert r.status_code == 200
finally:
_cleanup(inst_uid)
def test_sync_denied_for_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Sync Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
f"{BASE_URL}/admin/containers/{inst_uid}/sync", headers=_headers(other_key)
)
assert r.status_code == 403
finally:
_cleanup(inst_uid)
def test_sync_allowed_for_owner_reaches_workspace_check(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Manage Sync Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
f"{BASE_URL}/admin/containers/{inst_uid}/sync", headers=_headers(owner_key)
)
assert r.status_code == 400
assert "workspace" in r.json()["error"]["message"]
finally:
_cleanup(inst_uid)
def test_configure_denied_for_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Configure Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
f"{BASE_URL}/admin/containers/{inst_uid}/edit",
headers=_headers(other_key),
data={"restart_policy": "always"},
)
assert r.status_code == 403
finally:
_cleanup(inst_uid)
def test_configure_allowed_for_owner(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Manage Configure Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
f"{BASE_URL}/admin/containers/{inst_uid}/edit",
headers=_headers(owner_key),
data={"restart_policy": "always"},
)
assert r.status_code == 200
finally:
_cleanup(inst_uid)
def test_edit_page_redirects_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Edit Page Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.get(
f"{BASE_URL}/admin/containers/{inst_uid}/edit",
headers=_headers(other_key),
allow_redirects=False,
)
assert r.status_code == 302
assert r.headers["location"] == f"/admin/containers/{inst_uid}"
finally:
_cleanup(inst_uid)
def test_edit_page_allowed_for_owner(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Manage Edit Page Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.get(
f"{BASE_URL}/admin/containers/{inst_uid}/edit", headers=_headers(owner_key)
)
assert r.status_code == 200
finally:
_cleanup(inst_uid)
def test_create_rejects_others_private_project(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Manage Create Denied", is_private=True)
r = requests.post(
f"{BASE_URL}/admin/containers/create",
headers=_headers(other_key),
data={"project_slug": project["slug"], "name": "denied-instance"},
)
assert r.status_code == 404
assert get_table("instances").find_one(name="denied-instance") is None
def test_projects_search_excludes_others_private_project(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
title = f"Manage Search Unique {int(time.time() * 1000)}"
project = _create_project(owner_key, title, is_private=True)
r = requests.get(
f"{BASE_URL}/admin/containers/projects/search",
headers=_headers(other_key),
params={"q": title},
)
assert all(res["slug"] != project["slug"] for res in r.json()["results"])
r = requests.get(
f"{BASE_URL}/admin/containers/projects/search",
headers=_headers(owner_key),
params={"q": title},
)
assert any(res["slug"] == project["slug"] for res in r.json()["results"])
+269
View File
@@ -0,0 +1,269 @@
# retoor <retoor@molodetz.nl>
import time
import requests
from tests.conftest import BASE_URL
from devplacepy.database import (
get_table,
get_primary_admin_uid,
invalidate_admins_cache,
refresh_snapshot,
)
from devplacepy.utils import clear_user_cache
JSON = {"Accept": "application/json"}
_counter = [0]
def _signup():
_counter[0] += 1
name = f"pcm{int(time.time() * 1000)}{_counter[0]}"
requests.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
},
allow_redirects=True,
)
row = get_table("users").find_one(username=name)
return row["uid"], row["api_key"]
def _make_admin():
uid, key = _signup()
get_table("users").update({"uid": uid, "role": "Admin"}, ["uid"])
clear_user_cache(uid)
invalidate_admins_cache()
return uid, key
def _primary_admin_key():
refresh_snapshot()
uid = get_primary_admin_uid()
return get_table("users").find_one(uid=uid)["api_key"]
def _headers(key):
return {**JSON, "X-API-KEY": key}
def _create_project(key, title, is_private=False):
data = {
"title": title,
"description": "project container manage isolation test",
"project_type": "software",
"status": "In Development",
}
if is_private:
data["is_private"] = "on"
r = requests.post(f"{BASE_URL}/projects/create", headers=_headers(key), data=data)
assert r.status_code == 200, r.text
return r.json()["data"]
def _insert_instance(project_uid, created_by):
uid = f"pcm-{int(time.time() * 1000000)}"
get_table("instances").insert(
{
"uid": uid,
"slug": uid,
"name": "manage-test",
"project_uid": project_uid,
"created_by": created_by,
"owner_uid": "",
"status": "created",
"container_id": "",
"workspace_dir": "",
"restart_policy": "never",
"deleted_at": None,
"deleted_by": None,
}
)
refresh_snapshot()
return uid
def _cleanup(inst_uid):
get_table("instances").delete(uid=inst_uid)
get_table("instance_schedules").delete(instance_uid=inst_uid)
refresh_snapshot()
def _url(slug, inst_uid, tail=""):
base = f"{BASE_URL}/projects/{slug}/containers/instances/{inst_uid}"
return f"{base}{tail}" if tail else base
def test_delete_denied_for_non_owner_admin_on_public_project(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Project Manage Delete Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(_url(project["slug"], inst_uid, "/delete"), headers=_headers(other_key))
assert r.status_code == 403
assert get_table("instances").find_one(uid=inst_uid) is not None
finally:
_cleanup(inst_uid)
def test_delete_allowed_for_owner(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Project Manage Delete Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(_url(project["slug"], inst_uid, "/delete"), headers=_headers(owner_key))
assert r.status_code == 200
finally:
_cleanup(inst_uid)
def test_lifecycle_actions_denied_for_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Project Manage Lifecycle Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
for action in ("start", "stop", "pause", "resume", "restart"):
r = requests.post(_url(project["slug"], inst_uid, f"/{action}"), headers=_headers(other_key))
assert r.status_code == 403, action
finally:
_cleanup(inst_uid)
def test_lifecycle_actions_allowed_for_owner(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Project Manage Lifecycle Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
for action in ("start", "stop", "pause", "resume", "restart"):
r = requests.post(_url(project["slug"], inst_uid, f"/{action}"), headers=_headers(owner_key))
assert r.status_code == 200, action
finally:
_cleanup(inst_uid)
def test_exec_denied_for_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Project Manage Exec Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
_url(project["slug"], inst_uid, "/exec"),
headers=_headers(other_key),
data={"command": "ls"},
)
assert r.status_code == 403
finally:
_cleanup(inst_uid)
def test_exec_allowed_for_owner_reaches_running_check(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Project Manage Exec Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
_url(project["slug"], inst_uid, "/exec"),
headers=_headers(owner_key),
data={"command": "ls"},
)
assert r.status_code == 400
assert "not running" in r.json()["error"]["message"]
finally:
_cleanup(inst_uid)
def test_sync_denied_for_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Project Manage Sync Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(_url(project["slug"], inst_uid, "/sync"), headers=_headers(other_key))
assert r.status_code == 403
finally:
_cleanup(inst_uid)
def test_sync_allowed_for_owner_reaches_workspace_check(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Project Manage Sync Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(_url(project["slug"], inst_uid, "/sync"), headers=_headers(owner_key))
assert r.status_code == 400
assert "workspace" in r.json()["error"]["message"]
finally:
_cleanup(inst_uid)
def test_schedule_create_denied_for_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Project Manage Schedule Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
_url(project["slug"], inst_uid, "/schedules"),
headers=_headers(other_key),
data={"action": "start", "kind": "cron", "cron": "0 3 * * *"},
)
assert r.status_code == 403
assert get_table("instance_schedules").find_one(instance_uid=inst_uid) is None
finally:
_cleanup(inst_uid)
def test_schedule_create_allowed_for_owner(app_server):
owner_uid, owner_key = _make_admin()
project = _create_project(owner_key, "Project Manage Schedule Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(
_url(project["slug"], inst_uid, "/schedules"),
headers=_headers(owner_key),
data={"action": "start", "kind": "cron", "cron": "0 3 * * *"},
)
assert r.status_code == 200
assert get_table("instance_schedules").find_one(instance_uid=inst_uid) is not None
finally:
_cleanup(inst_uid)
def test_schedule_delete_denied_for_non_owner_admin(app_server):
owner_uid, owner_key = _make_admin()
_, other_key = _make_admin()
project = _create_project(owner_key, "Project Manage Schedule Delete Denied", is_private=False)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
created = requests.post(
_url(project["slug"], inst_uid, "/schedules"),
headers=_headers(owner_key),
data={"action": "start", "kind": "cron", "cron": "0 3 * * *"},
)
sched_uid = created.json()["data"]["schedule"]["uid"]
r = requests.post(
_url(project["slug"], inst_uid, f"/schedules/{sched_uid}/delete"),
headers=_headers(other_key),
)
assert r.status_code == 403
assert get_table("instance_schedules").find_one(uid=sched_uid) is not None
finally:
_cleanup(inst_uid)
def test_primary_admin_can_manage_others_private_instance(app_server):
owner_uid, owner_key = _make_admin()
primary_key = _primary_admin_key()
project = _create_project(owner_key, "Project Manage Primary Private", is_private=True)
inst_uid = _insert_instance(project["uid"], owner_uid)
try:
r = requests.post(_url(project["slug"], inst_uid, "/stop"), headers=_headers(primary_key))
assert r.status_code == 200
finally:
_cleanup(inst_uid)
-1
View File
@@ -33,7 +33,6 @@ def _seed_done(owner_id="ds-export-owner"):
"query": "q",
"summary": "A grounded summary.",
"findings": [{"title": "F1", "detail": "d", "confidence": 0.8, "citations": [1]}],
"gaps": ["gap"],
"sources": [{"url": "https://example.com", "title": "Example", "source": "httpx"}],
"score": 70,
"confidence": 0.7,
+58 -1
View File
@@ -33,7 +33,6 @@ def _seed_done_session(owner_id="ds-session-owner"):
"findings": [
{"title": "Finding one", "detail": "Detail.", "confidence": 0.8, "citations": [1]}
],
"gaps": ["An open gap."],
"sources": [{"url": "https://example.com", "title": "Example", "source": "httpx"}],
"score": 70,
"confidence": 0.7,
@@ -113,6 +112,64 @@ def test_session_html_renders_without_jinja_global_collision(app_server):
_clear()
def test_session_reads_disk_report_before_result_commit(app_server):
from pathlib import Path
import shutil
from devplacepy.config import DEEPSEARCH_DIR
owner_id = "ds-race-owner"
uid = queue.enqueue(
"deepsearch",
{"query": "race question", "depth": 2, "max_pages": 10},
"user",
owner_id,
"DeepSearch: race question",
)
create_deepsearch_session(
uid, "user", owner_id, "race question", 2, 10, f"ds_{uid.replace('-', '')}"
)
report = {
"query": "race question",
"summary": "A grounded summary from disk.",
"findings": [
{"title": "Disk finding", "detail": "D.", "confidence": 0.8, "citations": [1]}
],
"sources": [{"url": "https://example.com", "title": "Example", "source": "httpx"}],
"score": 84,
"confidence": 0.85,
"source_diversity": 0.75,
"synthesis": "agents",
"page_count": 12,
"chunk_count": 61,
}
session_dir = DEEPSEARCH_DIR / uid
session_dir.mkdir(parents=True, exist_ok=True)
(session_dir / "report.json").write_text(json.dumps(report), encoding="utf-8")
get_table("deepsearch_sessions").update(
{"uid": uid, "status": "done"}, ["uid"]
)
refresh_snapshot()
try:
r = requests.get(
f"{BASE_URL}/tools/deepsearch/{uid}/session", headers=_json_headers()
)
assert r.status_code == 200, r.text
body = r.json()
assert body["status"] == "done"
assert body["score"] == 84
assert body["chunk_count"] == 61
assert body["findings"]
assert body["sources"]
assert body["chat_ws_url"] == f"/tools/deepsearch/{uid}/chat"
md = requests.get(f"{BASE_URL}/tools/deepsearch/{uid}/export.md")
assert md.status_code == 200, md.text
assert "Disk finding" in md.text
finally:
shutil.rmtree(session_dir, ignore_errors=True)
_clear()
def test_session_unknown_uid_404(app_server):
r = requests.get(
f"{BASE_URL}/tools/deepsearch/nope/session", headers=_json_headers()
+226
View File
@@ -0,0 +1,226 @@
# retoor <retoor@molodetz.nl>
import time
import requests
from tests.conftest import BASE_URL, login_user
from devplacepy.database import (
get_table,
get_primary_admin_uid,
invalidate_admins_cache,
refresh_snapshot,
)
from devplacepy.utils import clear_user_cache
_counter = [0]
def _make_admin():
_counter[0] += 1
name = f"ecm{int(time.time() * 1000)}{_counter[0]}"
password = "secret123"
requests.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": password,
"confirm_password": password,
},
allow_redirects=True,
)
refresh_snapshot()
row = get_table("users").find_one(username=name)
get_table("users").update({"uid": row["uid"], "role": "Admin"}, ["uid"])
clear_user_cache(row["uid"])
invalidate_admins_cache()
return {
"email": f"{name}@t.dev",
"password": password,
"uid": row["uid"],
"api_key": row["api_key"],
}
def _demote_existing_admins():
existing = [r["uid"] for r in get_table("users").find(role="Admin")]
for uid in existing:
get_table("users").update({"uid": uid, "role": "Member"}, ["uid"])
invalidate_admins_cache()
return existing
def _restore_admins(uids):
for uid in uids:
get_table("users").update({"uid": uid, "role": "Admin"}, ["uid"])
invalidate_admins_cache()
def _create_project(api_key, title, is_private=False):
data = {
"title": title,
"description": "e2e container manage isolation",
"project_type": "software",
"status": "In Development",
}
if is_private:
data["is_private"] = "on"
r = requests.post(
f"{BASE_URL}/projects/create",
headers={"Accept": "application/json", "X-API-KEY": api_key},
data=data,
)
assert r.status_code == 200, r.text
return r.json()["data"]
def _insert_instance(project_uid, created_by):
uid = f"ecm-{int(time.time() * 1000000)}"
get_table("instances").insert(
{
"uid": uid,
"slug": uid,
"name": "e2e-manage-test",
"project_uid": project_uid,
"created_by": created_by,
"owner_uid": "",
"status": "stopped",
"container_id": "",
"workspace_dir": "",
"restart_policy": "never",
"deleted_at": None,
"deleted_by": None,
}
)
refresh_snapshot()
return uid
def _cleanup(inst_uid):
get_table("instances").delete(uid=inst_uid)
refresh_snapshot()
def test_admin_list_shows_view_only_for_non_owned_public_instance(page, app_server):
owner = _make_admin()
other = _make_admin()
project = _create_project(owner["api_key"], "E2E Manage List Public", is_private=False)
inst_uid = _insert_instance(project["uid"], owner["uid"])
try:
login_user(page, other)
page.goto(f"{BASE_URL}/admin/containers", wait_until="domcontentloaded")
row = page.locator(f"tr.cm-row[data-uid='{inst_uid}']")
row.wait_for(state="visible", timeout=15000)
assert "view only" in row.inner_text().lower()
assert row.locator("[data-cm-action='delete']").count() == 0
finally:
_cleanup(inst_uid)
def test_admin_list_shows_actions_for_owner(page, app_server):
owner = _make_admin()
project = _create_project(owner["api_key"], "E2E Manage List Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner["uid"])
try:
login_user(page, owner)
page.goto(f"{BASE_URL}/admin/containers", wait_until="domcontentloaded")
row = page.locator(f"tr.cm-row[data-uid='{inst_uid}']")
row.wait_for(state="visible", timeout=15000)
assert row.locator("[data-cm-action='delete']").count() == 1
finally:
_cleanup(inst_uid)
def test_admin_list_excludes_others_private_project_instance(page, app_server):
owner = _make_admin()
other = _make_admin()
project = _create_project(owner["api_key"], "E2E Manage List Private", is_private=True)
inst_uid = _insert_instance(project["uid"], owner["uid"])
try:
login_user(page, other)
page.goto(f"{BASE_URL}/admin/containers", wait_until="domcontentloaded")
assert page.locator(f"tr.cm-row[data-uid='{inst_uid}']").count() == 0
finally:
_cleanup(inst_uid)
def test_admin_detail_page_view_only_hides_manage_controls(page, app_server):
owner = _make_admin()
other = _make_admin()
project = _create_project(owner["api_key"], "E2E Manage Detail Public", is_private=False)
inst_uid = _insert_instance(project["uid"], owner["uid"])
try:
login_user(page, other)
page.goto(f"{BASE_URL}/admin/containers/{inst_uid}", wait_until="domcontentloaded")
actions = page.locator("#ci-actions")
actions.wait_for(state="visible")
assert "view only" in actions.inner_text().lower()
assert page.locator("#ci-term-toggle").count() == 0
assert page.locator("[data-modal='ci-schedule-modal']").count() == 0
assert page.locator("a:has-text('Edit configuration')").count() == 0
finally:
_cleanup(inst_uid)
def test_admin_detail_page_owner_sees_manage_controls(page, app_server):
owner = _make_admin()
project = _create_project(owner["api_key"], "E2E Manage Detail Owner", is_private=False)
inst_uid = _insert_instance(project["uid"], owner["uid"])
try:
login_user(page, owner)
page.goto(f"{BASE_URL}/admin/containers/{inst_uid}", wait_until="domcontentloaded")
page.locator("[data-modal='ci-schedule-modal']").wait_for(state="visible")
assert page.locator("a:has-text('Edit configuration')").count() == 1
assert page.locator("#ci-term-toggle").count() == 1
finally:
_cleanup(inst_uid)
def test_admin_detail_page_404_for_non_viewer_on_private_project(page, app_server):
owner = _make_admin()
other = _make_admin()
project = _create_project(owner["api_key"], "E2E Manage Detail Private", is_private=True)
inst_uid = _insert_instance(project["uid"], owner["uid"])
try:
login_user(page, other)
page.goto(f"{BASE_URL}/admin/containers/{inst_uid}", wait_until="domcontentloaded")
assert page.is_visible("text=Not Found") or page.is_visible("text=404")
finally:
_cleanup(inst_uid)
def test_admin_edit_page_redirects_non_owner_to_detail(page, app_server):
owner = _make_admin()
other = _make_admin()
project = _create_project(owner["api_key"], "E2E Manage Edit Redirect", is_private=False)
inst_uid = _insert_instance(project["uid"], owner["uid"])
try:
login_user(page, other)
page.goto(f"{BASE_URL}/admin/containers/{inst_uid}/edit", wait_until="domcontentloaded")
page.wait_for_url(f"**/admin/containers/{inst_uid}", wait_until="domcontentloaded")
finally:
_cleanup(inst_uid)
def test_primary_admin_sees_and_manages_others_private_instance(page, app_server):
restore = _demote_existing_admins()
try:
primary = _make_admin()
owner = _make_admin()
assert get_primary_admin_uid() == primary["uid"]
project = _create_project(
owner["api_key"], "E2E Manage Primary Private", is_private=True
)
inst_uid = _insert_instance(project["uid"], owner["uid"])
try:
login_user(page, primary)
page.goto(f"{BASE_URL}/admin/containers", wait_until="domcontentloaded")
row = page.locator(f"tr.cm-row[data-uid='{inst_uid}']")
row.wait_for(state="visible", timeout=15000)
assert row.locator("[data-cm-action='delete']").count() == 1
page.goto(f"{BASE_URL}/admin/containers/{inst_uid}", wait_until="domcontentloaded")
page.locator("a:has-text('Edit configuration')").wait_for(state="visible")
finally:
_cleanup(inst_uid)
finally:
_restore_admins(restore)
+42
View File
@@ -917,3 +917,45 @@ def test_feed_online_now_widget_lists_current_user(alice):
page.locator(f".online-user[title='{user['username']}']")
).to_have_count(1)
expect(page.locator(".online-users .presence-dot.online").first).to_be_visible()
def _scroll_and_open_post(page):
_seed_feed_posts(30)
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.locator(".post-card").first.wait_for(state="visible")
page.evaluate("window.scrollTo(0, 1200)")
page.wait_for_function("window.scrollY > 1000")
card_link = page.locator(".post-card .card-link").nth(5)
card_link.scroll_into_view_if_needed()
page.wait_for_timeout(400)
y_before = page.evaluate("window.scrollY")
assert y_before > 0
card_link.click()
page.wait_for_url(f"{BASE_URL}/posts/*", wait_until="domcontentloaded")
return y_before
def test_feed_scroll_restored_via_back_link(alice):
page, user = alice
y_before = _scroll_and_open_post(page)
back = page.locator("a.back-link")
back.wait_for(state="visible")
back.click()
page.wait_for_url(f"{BASE_URL}/feed*", wait_until="domcontentloaded")
page.wait_for_function(f"Math.abs(window.scrollY - {y_before}) < 60")
def test_feed_scroll_restored_via_browser_back(alice):
page, user = alice
y_before = _scroll_and_open_post(page)
page.go_back(wait_until="domcontentloaded")
page.wait_for_function(f"Math.abs(window.scrollY - {y_before}) < 60")
def test_feed_scroll_not_restored_on_fresh_visit(alice):
page, user = alice
_scroll_and_open_post(page)
page.goto(f"{BASE_URL}/gists", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.wait_for_timeout(600)
assert page.evaluate("window.scrollY") < 60
+83
View File
@@ -932,3 +932,86 @@ def test_projects_list_preserves_comment_hierarchy(page, app_server):
expect(card.locator(".post-card-comments")).not_to_contain_text(f"{marker}-excluded")
nested = card.locator(".post-card-comments .comment-replies .comment-text")
expect(nested).to_contain_text(f"{marker}-reply")
import time as _time_containers_menu
import requests as _requests_containers_menu
from tests.conftest import login_user
from devplacepy.database import get_primary_admin_uid, invalidate_admins_cache
from devplacepy.utils import clear_user_cache
_counter_containers_menu = [0]
def _signup_containers_menu(prefix):
_counter_containers_menu[0] += 1
name = f"{prefix}{int(_time_containers_menu.time() * 1000)}{_counter_containers_menu[0]}"
_requests_containers_menu.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
},
allow_redirects=True,
)
row = get_table("users").find_one(username=name)
return name, row["uid"], row["api_key"]
def _make_admin_containers_menu(prefix):
name, uid, key = _signup_containers_menu(prefix)
get_table("users").update({"uid": uid, "role": "Admin"}, ["uid"])
clear_user_cache(uid)
invalidate_admins_cache()
return name, uid, key
def _create_project_containers_menu(key, title, is_private=False):
data = {
"title": title,
"description": "containers menu gating test",
"project_type": "software",
"status": "In Development",
}
if is_private:
data["is_private"] = "on"
r = _requests_containers_menu.post(
f"{BASE_URL}/projects/create",
headers={"Accept": "application/json", "X-API-KEY": key},
data=data,
)
assert r.status_code == 200, r.text
return r.json()["data"]["slug"]
def test_containers_menu_visible_for_admin_on_own_private_project(page, app_server):
name, uid, key = _make_admin_containers_menu("cmown")
slug = _create_project_containers_menu(key, "Containers Menu Own Private", is_private=True)
login_user(page, {"email": f"{name}@t.dev", "password": "secret123"})
page.goto(f"{BASE_URL}/projects/{slug}", wait_until="domcontentloaded")
page.locator(".project-actions-more").click()
expect(page.locator(".context-menu-item:has-text('Containers')")).to_be_visible()
def test_containers_menu_visible_for_any_admin_on_public_project(page, app_server):
_, _, owner_key = _make_admin_containers_menu("cmpubowner")
other_name, _, _ = _make_admin_containers_menu("cmpubother")
slug = _create_project_containers_menu(owner_key, "Containers Menu Public", is_private=False)
login_user(page, {"email": f"{other_name}@t.dev", "password": "secret123"})
page.goto(f"{BASE_URL}/projects/{slug}", wait_until="domcontentloaded")
page.locator(".project-actions-more").click()
expect(page.locator(".context-menu-item:has-text('Containers')")).to_be_visible()
def test_containers_menu_hidden_for_non_owner_admin_on_member_private_project(page, app_server):
_, member_uid, member_key = _signup_containers_menu("cmmember")
slug = _create_project_containers_menu(member_key, "Containers Menu Member Private", is_private=True)
admin_name, admin_uid, _ = _make_admin_containers_menu("cmviewer")
assert get_primary_admin_uid() != admin_uid
login_user(page, {"email": f"{admin_name}@t.dev", "password": "secret123"})
page.goto(f"{BASE_URL}/projects/{slug}", wait_until="domcontentloaded")
page.locator(".project-actions-more").click()
expect(page.locator(".context-menu-item:has-text('Containers')")).to_have_count(0)
+212 -2
View File
@@ -1,14 +1,18 @@
# retoor <retoor@molodetz.nl>
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from devplacepy.content import (
is_owner,
can_view_project,
can_manage_instance,
can_view_instance,
can_view_project_containers,
canonical_redirect,
first_image_url,
enrich_items,
owns_instance,
)
from devplacepy.database import get_table, get_users_by_uids
from devplacepy.database import get_table, get_users_by_uids, invalidate_admins_cache
from devplacepy.utils import generate_uid
import time
import pytest
@@ -145,6 +149,212 @@ def test_can_view_admin_private_visible_to_owner_admin_only(local_db):
assert can_view_project(project, None) is False
def _make_user_at(role, created_at):
uid = generate_uid()
username = f"cv_{uid[:8]}"
get_table("users").insert(
{
"uid": uid,
"username": username,
"email": f"{username}@t.dev",
"role": role,
"created_at": created_at.isoformat(),
}
)
invalidate_admins_cache()
return get_table("users").find_one(uid=uid)
def _demote_existing_admins():
existing = [r["uid"] for r in get_table("users").find(role="Admin")]
for uid in existing:
get_table("users").update({"uid": uid, "role": "Member"}, ["uid"])
invalidate_admins_cache()
return existing
def _restore_admins(uids):
for uid in uids:
get_table("users").update({"uid": uid, "role": "Admin"}, ["uid"])
invalidate_admins_cache()
def _purge_users(*rows):
for row in rows:
if row:
get_table("users").delete(uid=row["uid"])
def test_owns_instance_true_for_creator():
owner = {"uid": "cnt-u1"}
other = {"uid": "cnt-u2"}
instance = {"created_by": owner["uid"]}
project = {"user_uid": other["uid"]}
assert owns_instance(instance, project, owner) is True
assert owns_instance(instance, project, other) is False
def test_owns_instance_true_for_project_owner():
owner = {"uid": "cnt-u1"}
creator = {"uid": "cnt-u2"}
instance = {"created_by": creator["uid"]}
project = {"user_uid": owner["uid"]}
assert owns_instance(instance, project, owner) is True
def test_owns_instance_false_when_missing_data():
assert owns_instance(None, {}, {"uid": "cnt-u1"}) is False
assert owns_instance({"created_by": "cnt-u1"}, {}, None) is False
assert owns_instance({"created_by": "cnt-u1"}, {}, {}) is False
assert owns_instance({"created_by": "cnt-u1"}, None, {"uid": "cnt-u2"}) is False
def test_can_view_project_containers_requires_admin(local_db):
owner = _make_user()
project = {"user_uid": owner["uid"], "is_private": 0}
try:
assert can_view_project_containers(project, owner) is False
assert can_view_project_containers(project, None) is False
assert can_view_project_containers(None, owner) is False
finally:
_purge_users(owner)
def test_can_view_project_containers_public_visible_to_any_admin(local_db):
restore = _demote_existing_admins()
owner_admin = other_admin = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = {"user_uid": owner_admin["uid"], "is_private": 0}
assert can_view_project_containers(project, owner_admin) is True
assert can_view_project_containers(project, other_admin) is True
finally:
_purge_users(owner_admin, other_admin)
_restore_admins(restore)
def test_can_view_project_containers_private_hidden_from_other_admin(local_db):
restore = _demote_existing_admins()
owner_admin = other_admin = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = {"user_uid": owner_admin["uid"], "is_private": 1}
assert can_view_project_containers(project, owner_admin) is True
assert can_view_project_containers(project, other_admin) is False
finally:
_purge_users(owner_admin, other_admin)
_restore_admins(restore)
def test_can_view_project_containers_primary_admin_sees_all(local_db):
restore = _demote_existing_admins()
primary = other_admin = None
try:
base = datetime.now(timezone.utc)
primary = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = {"user_uid": other_admin["uid"], "is_private": 1}
assert can_view_project_containers(project, primary) is True
finally:
_purge_users(primary, other_admin)
_restore_admins(restore)
def test_can_view_instance_requires_admin(local_db):
owner = _make_user()
instance = {"created_by": owner["uid"], "project_uid": "p1"}
project = {"user_uid": owner["uid"], "is_private": 0}
try:
assert can_view_instance(instance, project, owner) is False
assert can_manage_instance(instance, project, owner) is False
finally:
_purge_users(owner)
def test_can_view_instance_owner_sees_own_private_hidden_from_other_admin(local_db):
restore = _demote_existing_admins()
owner_admin = other_admin = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = {"user_uid": owner_admin["uid"], "is_private": 1}
instance = {"created_by": owner_admin["uid"], "project_uid": "p1"}
assert can_view_instance(instance, project, owner_admin) is True
assert can_view_instance(instance, project, other_admin) is False
finally:
_purge_users(owner_admin, other_admin)
_restore_admins(restore)
def test_can_view_instance_public_project_visible_to_any_admin(local_db):
restore = _demote_existing_admins()
creator = other_admin = None
try:
base = datetime.now(timezone.utc)
creator = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = {"user_uid": creator["uid"], "is_private": 0}
instance = {"created_by": creator["uid"], "project_uid": "p1"}
assert can_view_instance(instance, project, other_admin) is True
finally:
_purge_users(creator, other_admin)
_restore_admins(restore)
def test_can_view_instance_primary_admin_sees_others_private_instance(local_db):
restore = _demote_existing_admins()
primary = owner_admin = None
try:
base = datetime.now(timezone.utc)
primary = _make_user_at("Admin", base)
owner_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = {"user_uid": owner_admin["uid"], "is_private": 1}
instance = {"created_by": owner_admin["uid"], "project_uid": "p1"}
assert can_view_instance(instance, project, primary) is True
finally:
_purge_users(primary, owner_admin)
_restore_admins(restore)
def test_can_manage_instance_only_owner_or_primary_admin(local_db):
restore = _demote_existing_admins()
primary = owner_admin = other_admin = None
try:
base = datetime.now(timezone.utc)
primary = _make_user_at("Admin", base)
owner_admin = _make_user_at("Admin", base + timedelta(seconds=1))
other_admin = _make_user_at("Admin", base + timedelta(seconds=2))
project = {"user_uid": owner_admin["uid"], "is_private": 1}
instance = {"created_by": owner_admin["uid"], "project_uid": "p1"}
assert can_manage_instance(instance, project, owner_admin) is True
assert can_manage_instance(instance, project, primary) is True
assert can_manage_instance(instance, project, other_admin) is False
finally:
_purge_users(primary, owner_admin, other_admin)
_restore_admins(restore)
def test_can_manage_instance_other_admin_denied_even_on_public_project(local_db):
restore = _demote_existing_admins()
owner_admin = other_admin = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = {"user_uid": owner_admin["uid"], "is_private": 0}
instance = {"created_by": owner_admin["uid"], "project_uid": "p1"}
assert can_view_instance(instance, project, other_admin) is True
assert can_manage_instance(instance, project, other_admin) is False
finally:
_purge_users(owner_admin, other_admin)
_restore_admins(restore)
def test_canonical_redirect_when_slug_differs():
item = {"slug": "abcd1234-title", "uid": "abcd1234"}
assert canonical_redirect("posts", item, "abcd1234-title") is None
@@ -0,0 +1,46 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services.deepsearch.citations import link_citations
def test_single_marker_links_to_source_anchor():
out = str(link_citations("Server components are default [1].", 5))
assert '<a class="ds-cite" href="#ds-source-1" data-cite="1">[1]</a>' in out
def test_consecutive_and_range_markers_all_link():
out = str(link_citations("leaving TODOs in production [3][9][1-2].", 12))
for n in (1, 2, 3, 9):
assert f'href="#ds-source-{n}"' in out
assert "[1-2]" not in out
def test_out_of_range_marker_is_not_linked():
out = str(link_citations("cited [99] and [3]", 5))
assert "#ds-source-99" not in out
assert "[99]" in out
assert 'href="#ds-source-3"' in out
def test_markers_inside_code_and_anchors_are_left_alone():
html = 'see <a href="http://x">link [3]</a> and <code>arr[3]</code> and text [3]'
out = str(link_citations(html, 12))
assert out.count('class="ds-cite"') == 1
assert "<code>arr[3]</code>" in out
assert '<a href="http://x">link [3]</a>' in out
def test_range_clamped_to_source_count():
out = str(link_citations("range [4-6]", 5))
assert 'href="#ds-source-4"' in out
assert 'href="#ds-source-5"' in out
assert "#ds-source-6" not in out
def test_zero_sources_returns_input_unchanged():
assert str(link_citations("text [1]", 0)) == "text [1]"
def test_reversed_range_left_alone():
out = str(link_citations("weird [5-2] marker", 10))
assert out == "weird [5-2] marker"
@@ -0,0 +1 @@
# retoor <retoor@molodetz.nl>
@@ -0,0 +1,455 @@
# retoor <retoor@molodetz.nl>
import json
from datetime import datetime, timedelta, timezone
import pytest
from devplacepy.database import get_table, invalidate_admins_cache
from devplacepy.services.devii.container.controller import ContainerController
from devplacepy.services.devii.errors import ToolInputError
from devplacepy.utils import generate_uid
from tests.conftest import run_async
def _make_user_at(role, created_at):
uid = generate_uid()
username = f"cc_{uid[:8]}"
get_table("users").insert(
{
"uid": uid,
"username": username,
"email": f"{username}@t.dev",
"role": role,
"created_at": created_at.isoformat(),
}
)
invalidate_admins_cache()
return get_table("users").find_one(uid=uid)
def _demote_existing_admins():
existing = [r["uid"] for r in get_table("users").find(role="Admin")]
for uid in existing:
get_table("users").update({"uid": uid, "role": "Member"}, ["uid"])
invalidate_admins_cache()
return existing
def _restore_admins(uids):
for uid in uids:
get_table("users").update({"uid": uid, "role": "Admin"}, ["uid"])
invalidate_admins_cache()
def _purge(*rows, project_uids=(), instance_uids=()):
for row in rows:
if row:
get_table("users").delete(uid=row["uid"])
for uid in project_uids:
get_table("projects").delete(uid=uid)
for uid in instance_uids:
get_table("instances").delete(uid=uid)
def _make_project(owner_uid, is_private):
uid = generate_uid()
slug = f"cc-{uid[:10]}"
get_table("projects").insert(
{
"uid": uid,
"slug": slug,
"title": "Controller Test Project",
"user_uid": owner_uid,
"is_private": 1 if is_private else 0,
"deleted_at": None,
"deleted_by": None,
}
)
return get_table("projects").find_one(uid=uid)
def _make_instance(project_uid, created_by):
uid = generate_uid()
get_table("instances").insert(
{
"uid": uid,
"slug": f"cci-{uid[:10]}",
"name": "controller-test",
"project_uid": project_uid,
"created_by": created_by,
"owner_uid": "",
"status": "created",
"container_id": "",
"deleted_at": None,
"deleted_by": None,
}
)
return uid
def _controller(owner_uid):
return ContainerController(client=None, owner_id=owner_uid)
def test_project_lookup_denied_for_non_viewer_on_private_project(local_db):
restore = _demote_existing_admins()
owner_admin = other_admin = None
project = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = _make_project(owner_admin["uid"], is_private=True)
controller = _controller(other_admin["uid"])
with pytest.raises(ToolInputError):
run_async(
controller.dispatch(
"container_list_instances", {"project_slug": project["slug"]}
)
)
finally:
_purge(
owner_admin,
other_admin,
project_uids=[project["uid"]] if project else [],
)
_restore_admins(restore)
def test_project_lookup_allowed_for_owner(local_db):
restore = _demote_existing_admins()
owner_admin = None
project = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
project = _make_project(owner_admin["uid"], is_private=True)
controller = _controller(owner_admin["uid"])
out = json.loads(
run_async(
controller.dispatch(
"container_list_instances", {"project_slug": project["slug"]}
)
)
)
assert out["instances"] == []
finally:
_purge(owner_admin, project_uids=[project["uid"]] if project else [])
_restore_admins(restore)
def test_project_lookup_allowed_for_primary_admin_on_others_private_project(local_db):
restore = _demote_existing_admins()
primary = other_admin = None
project = None
try:
base = datetime.now(timezone.utc)
primary = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = _make_project(other_admin["uid"], is_private=True)
controller = _controller(primary["uid"])
out = json.loads(
run_async(
controller.dispatch(
"container_list_instances", {"project_slug": project["slug"]}
)
)
)
assert out["instances"] == []
finally:
_purge(
primary,
other_admin,
project_uids=[project["uid"]] if project else [],
)
_restore_admins(restore)
def test_instance_action_denied_for_non_owner_admin_on_public_project(local_db):
restore = _demote_existing_admins()
owner_admin = other_admin = None
project = None
inst_uid = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = _make_project(owner_admin["uid"], is_private=False)
inst_uid = _make_instance(project["uid"], owner_admin["uid"])
controller = _controller(other_admin["uid"])
with pytest.raises(ToolInputError):
run_async(
controller.dispatch(
"container_instance_action",
{
"project_slug": project["slug"],
"instance": inst_uid,
"action": "stop",
},
)
)
finally:
_purge(
owner_admin,
other_admin,
project_uids=[project["uid"]] if project else [],
instance_uids=[inst_uid] if inst_uid else [],
)
_restore_admins(restore)
def test_instance_action_allowed_for_owner(local_db):
restore = _demote_existing_admins()
owner_admin = None
project = None
inst_uid = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
project = _make_project(owner_admin["uid"], is_private=False)
inst_uid = _make_instance(project["uid"], owner_admin["uid"])
controller = _controller(owner_admin["uid"])
out = json.loads(
run_async(
controller.dispatch(
"container_instance_action",
{
"project_slug": project["slug"],
"instance": inst_uid,
"action": "stop",
},
)
)
)
assert out["status"] == "ok"
finally:
_purge(
owner_admin,
project_uids=[project["uid"]] if project else [],
instance_uids=[inst_uid] if inst_uid else [],
)
_restore_admins(restore)
def test_instance_action_allowed_for_primary_admin_on_others_instance(local_db):
restore = _demote_existing_admins()
primary = owner_admin = None
project = None
inst_uid = None
try:
base = datetime.now(timezone.utc)
primary = _make_user_at("Admin", base)
owner_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = _make_project(owner_admin["uid"], is_private=True)
inst_uid = _make_instance(project["uid"], owner_admin["uid"])
controller = _controller(primary["uid"])
out = json.loads(
run_async(
controller.dispatch(
"container_instance_action",
{
"project_slug": project["slug"],
"instance": inst_uid,
"action": "stop",
},
)
)
)
assert out["status"] == "ok"
finally:
_purge(
primary,
owner_admin,
project_uids=[project["uid"]] if project else [],
instance_uids=[inst_uid] if inst_uid else [],
)
_restore_admins(restore)
def test_configure_instance_denied_for_non_owner(local_db):
restore = _demote_existing_admins()
owner_admin = other_admin = None
project = None
inst_uid = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = _make_project(owner_admin["uid"], is_private=False)
inst_uid = _make_instance(project["uid"], owner_admin["uid"])
controller = _controller(other_admin["uid"])
with pytest.raises(ToolInputError):
run_async(
controller.dispatch(
"container_configure_instance",
{
"project_slug": project["slug"],
"instance": inst_uid,
"restart_policy": "always",
},
)
)
finally:
_purge(
owner_admin,
other_admin,
project_uids=[project["uid"]] if project else [],
instance_uids=[inst_uid] if inst_uid else [],
)
_restore_admins(restore)
def test_exec_denied_for_non_owner_before_running_check(local_db):
restore = _demote_existing_admins()
owner_admin = other_admin = None
project = None
inst_uid = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = _make_project(owner_admin["uid"], is_private=False)
inst_uid = _make_instance(project["uid"], owner_admin["uid"])
controller = _controller(other_admin["uid"])
with pytest.raises(ToolInputError) as exc:
run_async(
controller.dispatch(
"container_exec",
{
"project_slug": project["slug"],
"instance": inst_uid,
"command": "ls",
},
)
)
message = str(exc.value).lower()
assert "owner" in message or "primary" in message
finally:
_purge(
owner_admin,
other_admin,
project_uids=[project["uid"]] if project else [],
instance_uids=[inst_uid] if inst_uid else [],
)
_restore_admins(restore)
def test_exec_allowed_for_owner_reaches_running_check(local_db):
restore = _demote_existing_admins()
owner_admin = None
project = None
inst_uid = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
project = _make_project(owner_admin["uid"], is_private=False)
inst_uid = _make_instance(project["uid"], owner_admin["uid"])
controller = _controller(owner_admin["uid"])
with pytest.raises(ToolInputError) as exc:
run_async(
controller.dispatch(
"container_exec",
{
"project_slug": project["slug"],
"instance": inst_uid,
"command": "ls",
},
)
)
assert "not running" in str(exc.value).lower()
finally:
_purge(
owner_admin,
project_uids=[project["uid"]] if project else [],
instance_uids=[inst_uid] if inst_uid else [],
)
_restore_admins(restore)
def test_schedule_denied_for_non_owner(local_db):
restore = _demote_existing_admins()
owner_admin = other_admin = None
project = None
inst_uid = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
other_admin = _make_user_at("Admin", base + timedelta(seconds=1))
project = _make_project(owner_admin["uid"], is_private=False)
inst_uid = _make_instance(project["uid"], owner_admin["uid"])
controller = _controller(other_admin["uid"])
with pytest.raises(ToolInputError):
run_async(
controller.dispatch(
"container_schedule",
{
"project_slug": project["slug"],
"instance": inst_uid,
"action": "start",
"kind": "cron",
"cron": "0 3 * * *",
},
)
)
finally:
_purge(
owner_admin,
other_admin,
project_uids=[project["uid"]] if project else [],
instance_uids=[inst_uid] if inst_uid else [],
)
_restore_admins(restore)
def test_schedule_allowed_for_owner(local_db):
restore = _demote_existing_admins()
owner_admin = None
project = None
inst_uid = None
try:
base = datetime.now(timezone.utc)
owner_admin = _make_user_at("Admin", base)
project = _make_project(owner_admin["uid"], is_private=False)
inst_uid = _make_instance(project["uid"], owner_admin["uid"])
controller = _controller(owner_admin["uid"])
out = json.loads(
run_async(
controller.dispatch(
"container_schedule",
{
"project_slug": project["slug"],
"instance": inst_uid,
"action": "start",
"kind": "cron",
"cron": "0 3 * * *",
},
)
)
)
assert out["schedule"]["action"] == "start"
finally:
_purge(
owner_admin,
project_uids=[project["uid"]] if project else [],
instance_uids=[inst_uid] if inst_uid else [],
)
_restore_admins(restore)
def test_unknown_project_slug_raises(local_db):
restore = _demote_existing_admins()
owner_admin = None
try:
owner_admin = _make_user_at("Admin", datetime.now(timezone.utc))
controller = _controller(owner_admin["uid"])
with pytest.raises(ToolInputError):
run_async(
controller.dispatch(
"container_list_instances", {"project_slug": "no-such-slug"}
)
)
finally:
_purge(owner_admin)
_restore_admins(restore)
@@ -0,0 +1,112 @@
# retoor <retoor@molodetz.nl>
from tests.conftest import run_async
from devplacepy.services.jobs.deepsearch import crawl as crawl_module
from devplacepy.services.jobs.deepsearch.crawl import (
CrawledPage,
_interleave,
_is_hostile,
_snippet_page,
crawl,
)
async def _no_stop() -> bool:
return False
def test_interleave_round_robins_and_dedupes():
buckets = [
[{"url": "a"}, {"url": "b"}],
[{"url": "c"}, {"url": "a"}],
[{"url": "d"}],
]
assert [item["url"] for item in _interleave(buckets)] == ["a", "c", "d", "b"]
def test_is_hostile_matches_social_domains():
assert _is_hostile("https://x.com/user/status/1")
assert _is_hostile("https://www.youtube.com/watch?v=abc")
assert _is_hostile("https://old.reddit.com/r/x")
assert not _is_hostile("https://blog.example.com/post")
def test_snippet_page_prefers_content_over_description():
candidate = {
"url": "https://x.com/a/status/1",
"title": "Tweet",
"description": "short",
"content": "This is the full rsearch content, deliberately written long enough to clear the snippet minimum length floor so that it is kept as a real source easily.",
}
page = _snippet_page(candidate, 0)
assert page is not None
assert page.source == "search"
assert "full rsearch content" in page.text
def test_snippet_page_none_when_too_thin():
assert _snippet_page({"url": "https://x.com/a", "content": "tiny"}, 0) is None
def test_crawl_uses_snippet_for_hostile_and_skips_fetch(monkeypatch):
fetched = []
async def fake_fetch(url, depth):
fetched.append(url)
return CrawledPage(url=url, title="T", text="x " * 200, source="httpx", status=200, depth=depth)
monkeypatch.setattr(crawl_module, "fetch_page", fake_fetch)
candidates = [
{
"url": "https://x.com/ThePrimeagen/status/1",
"title": "Prime",
"description": "",
"content": "The real tweet text returned by rsearch for this social post, well past the snippet minimum length threshold so it survives as a usable source here.",
}
]
outcome = run_async(
crawl(candidates, 6, lambda f: None, lambda u: False, _no_stop, query="q", depth=1)
)
assert fetched == []
assert len(outcome.pages) == 1
assert outcome.pages[0].source == "search"
def test_crawl_prefers_richer_crawl_over_snippet(monkeypatch):
async def fake_fetch(url, depth):
return CrawledPage(url=url, title="Article", text="Deep article body. " * 60, source="httpx", status=200, depth=depth)
monkeypatch.setattr(crawl_module, "fetch_page", fake_fetch)
candidates = [
{
"url": "https://blog.example.com/a",
"title": "Blog",
"description": "",
"content": "A short snippet that is longer than the floor but shorter than the crawled article body itself.",
}
]
outcome = run_async(
crawl(candidates, 6, lambda f: None, lambda u: False, _no_stop, query="q", depth=1)
)
assert outcome.pages[0].source == "httpx"
def test_crawl_falls_back_to_snippet_when_fetch_empty(monkeypatch):
async def fake_fetch(url, depth):
return None
monkeypatch.setattr(crawl_module, "fetch_page", fake_fetch)
candidates = [
{
"url": "https://walled.example.com/a",
"title": "Wall",
"description": "",
"content": "The search snippet holds the real content that the login-walled page refused to serve to the bot today, and it is comfortably past the snippet minimum length floor.",
}
]
outcome = run_async(
crawl(candidates, 6, lambda f: None, lambda u: False, _no_stop, query="q", depth=1)
)
assert len(outcome.pages) == 1
assert outcome.pages[0].source == "search"
@@ -0,0 +1,67 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services.jobs.deepsearch.extract import extract_html, relevant_links
PAGE = """
<html><head><title>Framework Trends - Blog</title></head><body>
<header><a href="/">Home</a> <a href="/about">About</a> <a href="/login">Login</a></header>
<nav><ul><li><a href="/blog">Blog</a></li><li><a href="/dev">Dev</a></li></ul></nav>
<div role="banner">We use cookies to improve your experience on this website today.</div>
<main><article>
<h1>Trends that define web development</h1>
<p>Server components became the default rendering model, cutting client bundles by forty percent according to the survey.</p>
<p>Signals-based reactivity landed in the standards pipeline; see the <a href="/signals-deep-dive">signals deep dive</a> article.</p>
</article></main>
<footer><p>Copyright. All rights reserved. Privacy. Terms. Subscribe to our newsletter now.</p></footer>
</body></html>
"""
def test_extract_prefers_article_content_and_drops_chrome():
page = extract_html(PAGE, base_url="https://blog.example.com/trends/")
assert page.title == "Framework Trends - Blog"
assert "Server components" in page.text
assert "Signals-based reactivity" in page.text
assert "cookies" not in page.text
assert "Copyright" not in page.text
assert "Home" not in page.text
def test_extract_emits_blank_line_paragraphs():
page = extract_html(PAGE, base_url="https://blog.example.com/trends/")
assert "\n\n" in page.text
def test_extract_resolves_links_absolute_and_skips_nav():
page = extract_html(PAGE, base_url="https://blog.example.com/trends/")
urls = [url for url, _text in page.links]
assert "https://blog.example.com/signals-deep-dive" in urls
assert "https://blog.example.com/blog" not in urls
def test_extract_unescapes_entities():
page = extract_html(
"<html><body><p>Ampersand &amp; arrow &#8594; and more text to pass the length gate.</p></body></html>"
)
assert "&amp;" not in page.text
assert "&#8594;" not in page.text
assert "&" in page.text
def test_extract_survives_malformed_html():
page = extract_html("<div><p>Unclosed paragraph with enough characters to be kept around.<div></span>")
assert "Unclosed paragraph" in page.text
def test_relevant_links_scores_by_query_overlap():
links = [
("https://a.example/web-frameworks-2026", "web frameworks in 2026"),
("https://a.example/cookie-policy", "cookie policy"),
("https://a.example/logo.png", "frameworks logo"),
]
picked = relevant_links(links, "latest web frameworks 2026", 2)
assert picked == ["https://a.example/web-frameworks-2026"]
def test_relevant_links_empty_query_returns_nothing():
assert relevant_links([("https://a.example/x", "text")], "", 3) == []
@@ -29,30 +29,27 @@ def test_source_diversity_capped_at_one():
assert source_diversity(pages) <= 1.0
def test_orchestrate_with_no_pages_returns_gap():
async def fake_complete(messages, api_key, **kwargs):
return {}, {}, 0
def test_orchestrate_with_no_pages_is_heuristic():
result = run_async(orchestrate("q", [], "k", lambda frame: None))
assert isinstance(result, Orchestration)
assert result.source_diversity == 0.0
assert result.gaps
assert result.synthesis == "heuristic"
assert not result.findings
def test_orchestrate_grounded_run_emits_agent_frames(monkeypatch):
frames = []
summary_payload = json.dumps(
report_payload = "## Answer\nA grounded answer [1]."
findings_payload = json.dumps(
{
"summary": "A grounded answer.",
"findings": [
{"title": "Finding", "detail": "Detail", "confidence": 0.7, "citations": [1]}
],
]
}
)
gaps_payload = json.dumps({"gaps": ["one open question"]})
link_payload = json.dumps({"confidence": 0.8})
replies = iter([summary_payload, gaps_payload, link_payload])
replies = iter([report_payload, findings_payload, link_payload])
async def fake_request_completion(messages, api_key, **kwargs):
return ({"choices": [{"message": {"content": next(replies)}}]}, {}, 5)
@@ -62,14 +59,14 @@ def test_orchestrate_grounded_run_emits_agent_frames(monkeypatch):
pages = [_page("https://a.example"), _page("https://b.example")]
result = run_async(orchestrate("question", pages, "k", frames.append))
assert result.summary == "A grounded answer."
assert result.summary == "## Answer\nA grounded answer [1]."
assert result.findings and result.findings[0]["citations"] == [1]
assert result.gaps == ["one open question"]
assert result.synthesis == "agents"
assert 0.0 < result.confidence <= 1.0
assert result.source_diversity > 0.0
assert result.score > 0
agents = {f["agent"] for f in frames if f.get("type") == "agent"}
assert agents == {"summarizer", "critic", "linker"}
assert agents == {"summarizer", "extractor", "linker"}
def test_orchestrate_falls_back_to_heuristic_on_failure(monkeypatch):
@@ -77,8 +74,85 @@ def test_orchestrate_falls_back_to_heuristic_on_failure(monkeypatch):
raise RuntimeError("upstream down")
monkeypatch.setattr(orchestrate_module, "request_completion", boom)
frames = []
pages = [_page("https://a.example"), _page("https://b.example")]
result = run_async(orchestrate("question", pages, "k", frames.append))
assert result.findings
assert result.synthesis == "heuristic"
assert any(f.get("status") == "failed" for f in frames if f.get("type") == "agent")
assert result.source_diversity > 0.0
def test_orchestrate_survives_linker_failure(monkeypatch):
replies = iter(
[
"A full report [1].",
json.dumps(
{
"findings": [
{"title": "F", "detail": "D", "confidence": 0.5, "citations": [1]}
]
}
),
]
)
async def flaky(messages, api_key, **kwargs):
try:
content = next(replies)
except StopIteration:
raise RuntimeError("upstream down")
return ({"choices": [{"message": {"content": content}}]}, {}, 5)
monkeypatch.setattr(orchestrate_module, "request_completion", flaky)
pages = [_page("https://a.example"), _page("https://b.example")]
result = run_async(orchestrate("question", pages, "k", lambda frame: None))
assert result.synthesis == "agents"
assert result.summary == "A full report [1]."
assert result.findings
assert result.gaps
assert result.source_diversity > 0.0
assert result.confidence >= 0.35
def test_numbered_source_digest_keeps_every_source_number_under_cap():
from devplacepy.services.jobs.deepsearch.orchestrate import _numbered_source_digest
import re
pages = [_page(f"https://s{i}.example", text="word " * 500) for i in range(1, 13)]
digest = _numbered_source_digest(pages, per_source=600, cap=4000)
present = {int(n) for n in re.findall(r"\[(\d+)\]", digest)}
assert present == set(range(1, 13))
def test_linker_receives_full_source_list(monkeypatch):
seen = {}
replies = iter(
[
"Report body [1].",
json.dumps(
{"findings": [{"title": "F", "detail": "D", "confidence": 1.0, "citations": [2]}]}
),
json.dumps({"confidence": 0.9}),
]
)
async def capture(messages, api_key, **kwargs):
content = messages[-1]["content"]
if content.startswith("FINDINGS:") and "SOURCES:" in content:
seen["linker"] = content
return ({"choices": [{"message": {"content": next(replies)}}]}, {}, 5)
monkeypatch.setattr(orchestrate_module, "request_completion", capture)
pages = [_page(f"https://s{i}.example") for i in range(1, 13)]
result = run_async(orchestrate("q", pages, "k", lambda f: None))
assert result.confidence >= 0.35
for n in range(1, 13):
assert f"[{n}]" in seen["linker"]
def test_parse_json_tolerates_fences_and_trailing_garbage():
from devplacepy.services.jobs.deepsearch.orchestrate import _parse_json
assert _parse_json('```json\n{"gaps": ["g"]}\n```') == {"gaps": ["g"]}
assert _parse_json('prose {"confidence": 0.7} more prose') == {"confidence": 0.7}
assert _parse_json('{"gaps": ["a"]} {"broken": ') == {"gaps": ["a"]}
assert _parse_json("no json here") == {}
@@ -20,7 +20,7 @@ def _patch_pipeline(monkeypatch, pages):
async def fake_search(queries, emit=lambda frame: None):
return [{"url": page.url, "title": page.title, "description": ""} for page in pages]
async def fake_crawl(candidates, max_pages, emit, is_cached, should_stop):
async def fake_crawl(candidates, max_pages, emit, is_cached, should_stop, query="", depth=1):
outcome = CrawlOutcome()
for page in pages[:max_pages]:
emit({"type": "page_loaded", "url": page.url, "done": 1, "total": len(pages)})
@@ -33,13 +33,12 @@ def _patch_pipeline(monkeypatch, pages):
async def fake_embed_async(texts, api_key, **kwargs):
return local_embed(texts)
async def fake_orchestrate(question, crawled, api_key, emit):
async def fake_orchestrate(question, crawled, api_key, emit, store=None, queries=None):
from devplacepy.services.jobs.deepsearch.orchestrate import Orchestration
return Orchestration(
summary="A summary.",
findings=[{"title": "F", "detail": "D", "confidence": 0.5, "citations": [1]}],
gaps=["gap"],
confidence=0.6,
source_diversity=0.5,
score=42,