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
+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)