456 lines
15 KiB
Python
Raw Normal View History

2026-07-07 15:28:28 +02:00
# 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)