feat: add TTLCache for get_cache_version and TEMPLATE_AUTO_RELOAD config with Makefile worker count variables

This commit is contained in:
2026-06-14 14:46:36 +00:00
parent c151325916
commit d75d5dc6a8
149 changed files with 9811 additions and 262 deletions
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
View File
+58
View File
@@ -0,0 +1,58 @@
# retoor <retoor@molodetz.nl>
import requests
from tests.conftest import BASE_URL
from devplacepy.database import get_table, refresh_snapshot
JSON = {"Accept": "application/json"}
def _admin():
refresh_snapshot()
key = get_table("users").find_one(username="alice_test")["api_key"]
s = requests.Session()
s.headers.update({"X-API-KEY": key})
return s
def test_bots_page_requires_login(app_server):
r = requests.get(f"{BASE_URL}/admin/bots", allow_redirects=False)
assert r.status_code in (302, 303)
def test_bots_data_denied_for_non_admin(app_server, seeded_db):
s = requests.Session()
s.post(
f"{BASE_URL}/auth/login",
data={
"email": seeded_db["bob"]["email"],
"password": seeded_db["bob"]["password"],
},
)
r = s.get(f"{BASE_URL}/admin/bots/data", allow_redirects=False)
assert r.status_code in (302, 303, 403)
r_json = s.get(f"{BASE_URL}/admin/bots/data", headers=JSON, allow_redirects=False)
assert r_json.status_code == 403
def test_bots_data_shape_for_admin(app_server, seeded_db):
admin = _admin()
r = admin.get(f"{BASE_URL}/admin/bots/data", headers=JSON)
assert r.status_code == 200, r.text[:300]
body = r.json()
assert "frames" in body
assert isinstance(body["frames"], list)
assert "enabled" in body
assert isinstance(body["enabled"], bool)
assert "service_status" in body
def test_bots_frame_missing_is_404(app_server, seeded_db):
admin = _admin()
r = admin.get(f"{BASE_URL}/admin/bots/999/frame.jpg", allow_redirects=False)
assert r.status_code == 404
def test_bots_frame_requires_admin(app_server, seeded_db):
r = requests.get(f"{BASE_URL}/admin/bots/0/frame.jpg", allow_redirects=False)
assert r.status_code in (302, 303)
+35
View File
@@ -0,0 +1,35 @@
# retoor <retoor@molodetz.nl>
import requests
from tests.conftest import BASE_URL
def _bob(seeded_db):
s = requests.Session()
s.post(
f"{BASE_URL}/auth/login",
data={
"email": seeded_db["bob"]["email"],
"password": seeded_db["bob"]["password"],
},
)
return s
def test_create_requires_admin(app_server, seeded_db):
s = _bob(seeded_db)
r = s.post(
f"{BASE_URL}/admin/containers/create",
data={"project_slug": "x", "name": "x"},
allow_redirects=False,
)
assert r.status_code in (302, 303, 403)
def test_create_requires_login(app_server):
r = requests.post(
f"{BASE_URL}/admin/containers/create",
data={"project_slug": "x", "name": "x"},
allow_redirects=False,
)
assert r.status_code in (302, 303)
@@ -0,0 +1 @@
# retoor <retoor@molodetz.nl>
@@ -0,0 +1,35 @@
# retoor <retoor@molodetz.nl>
import requests
from tests.conftest import BASE_URL
from devplacepy.database import get_table, refresh_snapshot
def _admin_session():
refresh_snapshot()
key = get_table("users").find_one(username="alice_test")["api_key"]
s = requests.Session()
s.headers.update({"X-API-KEY": key})
return s
def test_project_search_requires_admin(app_server, seeded_db):
s = requests.Session()
s.post(
f"{BASE_URL}/auth/login",
data={
"email": seeded_db["bob"]["email"],
"password": seeded_db["bob"]["password"],
},
)
r = s.get(
f"{BASE_URL}/admin/containers/projects/search?q=a", allow_redirects=False
)
assert r.status_code in (302, 303, 403)
def test_project_search_empty_query(app_server, seeded_db):
s = _admin_session()
r = s.get(f"{BASE_URL}/admin/containers/projects/search?q=")
assert r.status_code == 200
assert r.json()["results"] == []
@@ -0,0 +1 @@
# retoor <retoor@molodetz.nl>
@@ -0,0 +1,43 @@
# retoor <retoor@molodetz.nl>
import requests
from tests.conftest import BASE_URL
from devplacepy.database import get_table, refresh_snapshot
def _admin_session():
refresh_snapshot()
key = get_table("users").find_one(username="alice_test")["api_key"]
s = requests.Session()
s.headers.update({"X-API-KEY": key})
return s
def test_user_search_requires_admin(app_server, seeded_db):
s = requests.Session()
s.post(
f"{BASE_URL}/auth/login",
data={
"email": seeded_db["bob"]["email"],
"password": seeded_db["bob"]["password"],
},
)
r = s.get(
f"{BASE_URL}/admin/containers/users/search?q=alice", allow_redirects=False
)
assert r.status_code in (302, 303, 403)
def test_user_search_returns_results(app_server, seeded_db):
s = _admin_session()
r = s.get(f"{BASE_URL}/admin/containers/users/search?q=alice")
assert r.status_code == 200
results = r.json()["results"]
assert any(item["username"] == "alice_test" for item in results)
def test_user_search_empty_query(app_server, seeded_db):
s = _admin_session()
r = s.get(f"{BASE_URL}/admin/containers/users/search?q=")
assert r.status_code == 200
assert r.json()["results"] == []
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+6
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
@@ -138,6 +140,8 @@ def _make_post_bookmarks(owner_uid, title):
uid = generate_uid()
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner_uid,
"slug": f"{uid[:8]}-bookmark-post",
@@ -155,6 +159,8 @@ def _make_gist(owner_uid, title):
uid = generate_uid()
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner_uid,
"slug": f"{uid[:8]}-bookmark-gist",
+4
View File
@@ -29,6 +29,8 @@ def _make_post_bookmarks(owner_uid, title):
uid = generate_uid()
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner_uid,
"slug": f"{uid[:8]}-bookmark-post",
@@ -46,6 +48,8 @@ def _make_gist(owner_uid, title):
uid = generate_uid()
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner_uid,
"slug": f"{uid[:8]}-bookmark-gist",
+11 -2
View File
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
import pytest
import requests
from tests.conftest import BASE_URL
from devplacepy.database import get_table, refresh_snapshot, set_setting
from devplacepy.database import db, get_table, refresh_snapshot, set_setting
from devplacepy.utils import generate_uid, make_combined_slug
JSON_audit_log = {"Accept": "application/json"}
_counter_audit_log = [0]
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
@@ -224,6 +226,7 @@ def test_create_reply_to_comment(app_server):
},
allow_redirects=False,
)
refresh_snapshot()
parent = get_table("comments").find_one(target_uid=post_uid)
r = s.post(
f"{BASE_URL}/comments/create",
@@ -237,7 +240,13 @@ def test_create_reply_to_comment(app_server):
allow_redirects=False,
)
assert r.status_code in (302, 303)
replies = list(get_table("comments").find(parent_uid=parent["uid"]))
refresh_snapshot()
replies = list(
db.query(
"SELECT content FROM comments WHERE parent_uid = :p AND deleted_at IS NULL",
p=parent["uid"],
)
)
assert len(replies) == 1
assert replies[0]["content"] == "Reply to parent"
+2
View File
@@ -84,6 +84,8 @@ def test_http_ingress_proxy(app_server):
uid = f"ingtest-{port}"
get_table("instances").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"name": "ingress",
"project_uid": "ingtest",
+17 -3
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
@@ -297,6 +299,7 @@ def test_docs_operator_pages_require_admin(seeded_db):
assert "/docs/services.html" in admin_index.text
finally:
users.update({"uid": bob["uid"], "role": "Member"}, ["uid"])
clear_user_cache(bob["uid"])
def test_docs_endpoint_config_is_valid_json(app_server):
@@ -396,9 +399,20 @@ def test_media_moderation_doc_is_admin_only(seeded_db):
# hidden from guests and members
assert requests.get(f"{BASE_URL}/docs/media-moderation.html").status_code == 404
bob = get_table("users").find_one(username="bob_test")
member = requests.get(
f"{BASE_URL}/docs/media-moderation.html", headers={"X-API-KEY": bob["api_key"]}
)
clear_user_cache(bob["uid"])
# A prior test may have promoted bob and warmed the server's auth cache; the
# cache-version bump propagates cross-process with up to a one second delay,
# so poll until the server resolves bob back to a member (404).
deadline = time.time() + 5
member = None
while time.time() < deadline:
member = requests.get(
f"{BASE_URL}/docs/media-moderation.html",
headers={"X-API-KEY": bob["api_key"]},
)
if member.status_code == 404:
break
time.sleep(0.2)
assert member.status_code == 404
# visible to admins, and this is where soft-delete is explained
admin = get_table("users").find_one(username="alice_test")
+27 -1
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
@@ -170,6 +172,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -213,6 +217,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -237,6 +243,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -260,6 +268,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -278,6 +288,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -296,6 +308,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
@@ -315,9 +329,21 @@ def test_maintenance_block_recorded(seeded_db):
admin = _admin(seeded_db)
set_setting("maintenance_mode", "1")
try:
requests.get(f"{BASE_URL}/feed", allow_redirects=False)
deadline = time.time() + 5
status = None
while time.time() < deadline:
status = requests.get(f"{BASE_URL}/feed", allow_redirects=False).status_code
if status == 503:
break
time.sleep(0.2)
assert status == 503
finally:
set_setting("maintenance_mode", "0")
deadline = time.time() + 5
while time.time() < deadline:
if requests.get(f"{BASE_URL}/feed", allow_redirects=False).status_code != 503:
break
time.sleep(0.2)
event = _find(admin, "security.maintenance.block", lambda e: True)
assert event is not None
assert event["result"] == "denied"
+2
View File
@@ -121,6 +121,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -52,6 +52,8 @@ def _make_source_project(*, is_private=False, binary=False):
)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": pid,
"user_uid": owner_uid,
"slug": f"{pid}-source",
+4
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
@@ -159,6 +161,8 @@ def _seed_gists(count):
title = f"Pag Gist {i}"
gists.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(title, uid),
+12
View File
@@ -12,6 +12,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -55,6 +57,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -79,6 +83,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -102,6 +108,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -120,6 +128,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -138,6 +148,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+179
View File
@@ -0,0 +1,179 @@
# retoor <retoor@molodetz.nl>
import asyncio
import json
import time
import pytest
import requests
import websockets
from tests.conftest import BASE_URL, PORT
from devplacepy.database import get_table, refresh_snapshot, set_setting
WS_URL = f"ws://127.0.0.1:{PORT}/messages/ws"
_counter = [0]
@pytest.fixture(scope="module", autouse=True)
def _ws_settings(app_server):
for key, value in {
"rate_limit_per_minute": "1000000",
"rate_limit_window_seconds": "60",
"registration_open": "1",
"maintenance_mode": "0",
}.items():
set_setting(key, value)
yield
def _db_user(name):
refresh_snapshot()
return get_table("users").find_one(username=name)
def _unique(prefix="wsm"):
_counter[0] += 1
return f"{prefix}{int(time.time() * 1000)}{_counter[0]}"
def _member():
name = _unique()
s = requests.Session()
s.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
},
allow_redirects=True,
)
return s, name
def _cookie_header(session):
token = session.cookies.get("session")
assert token, "expected a session cookie after signup"
return {"Cookie": f"session={token}"}
async def _recv_until(ws, frame_type, timeout=5.0):
deadline = asyncio.get_event_loop().time() + timeout
while True:
remaining = deadline - asyncio.get_event_loop().time()
if remaining <= 0:
raise AssertionError(f"timed out waiting for {frame_type}")
raw = await asyncio.wait_for(ws.recv(), timeout=remaining)
frame = json.loads(raw)
if frame.get("type") == frame_type:
return frame
def test_ws_requires_authentication(app_server):
async def run():
async with websockets.connect(WS_URL) as ws:
with pytest.raises(websockets.exceptions.ConnectionClosed):
await asyncio.wait_for(ws.recv(), timeout=3.0)
asyncio.run(run())
def test_ws_send_broadcasts_to_both_parties(app_server):
sender_session, _ = _member()
receiver_session, receiver_name = _member()
receiver_uid = _db_user(receiver_name)["uid"]
client_id = "cid-" + _unique("c")
async def run():
async with websockets.connect(
WS_URL, additional_headers=_cookie_header(sender_session)
) as sender_ws, websockets.connect(
WS_URL, additional_headers=_cookie_header(receiver_session)
) as receiver_ws:
await _recv_until(sender_ws, "ready")
await _recv_until(receiver_ws, "ready")
await sender_ws.send(
json.dumps(
{
"type": "send",
"receiver_uid": receiver_uid,
"content": "hello over the wire",
"client_id": client_id,
}
)
)
echo = await _recv_until(sender_ws, "message")
assert echo["client_id"] == client_id
assert echo["content"] == "hello over the wire"
assert echo["receiver_uid"] == receiver_uid
delivered = await _recv_until(receiver_ws, "message")
assert delivered["content"] == "hello over the wire"
assert delivered["sender_uid"] == echo["sender_uid"]
asyncio.run(run())
def test_ws_typing_relays_to_receiver_only(app_server):
sender_session, sender_name = _member()
receiver_session, receiver_name = _member()
sender_uid = _db_user(sender_name)["uid"]
receiver_uid = _db_user(receiver_name)["uid"]
async def run():
async with websockets.connect(
WS_URL, additional_headers=_cookie_header(sender_session)
) as sender_ws, websockets.connect(
WS_URL, additional_headers=_cookie_header(receiver_session)
) as receiver_ws:
await _recv_until(sender_ws, "ready")
await _recv_until(receiver_ws, "ready")
await sender_ws.send(
json.dumps({"type": "typing", "receiver_uid": receiver_uid})
)
typing = await _recv_until(receiver_ws, "typing")
assert typing["from_uid"] == sender_uid
asyncio.run(run())
def test_ws_read_marks_and_notifies(app_server):
sender_session, sender_name = _member()
receiver_session, receiver_name = _member()
sender_uid = _db_user(sender_name)["uid"]
receiver_uid = _db_user(receiver_name)["uid"]
sender_session.post(
f"{BASE_URL}/messages/send",
headers={"Accept": "application/json"},
data={"content": "read me please", "receiver_uid": receiver_uid},
)
async def run():
async with websockets.connect(
WS_URL, additional_headers=_cookie_header(sender_session)
) as sender_ws, websockets.connect(
WS_URL, additional_headers=_cookie_header(receiver_session)
) as receiver_ws:
await _recv_until(sender_ws, "ready")
await _recv_until(receiver_ws, "ready")
await receiver_ws.send(
json.dumps({"type": "read", "with_uid": sender_uid})
)
receipt = await _recv_until(sender_ws, "read")
assert receipt["by_uid"] == receiver_uid
asyncio.run(run())
refresh_snapshot()
rows = list(
get_table("messages").find(
sender_uid=sender_uid, receiver_uid=receiver_uid, read=True
)
)
assert rows, "messages should be marked read after the read frame"
+12
View File
@@ -12,6 +12,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -55,6 +57,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -79,6 +83,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -102,6 +108,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -120,6 +128,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -138,6 +148,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+18
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
@@ -222,6 +224,8 @@ def _make_post_reactions(owner_uid):
uid = generate_uid()
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner_uid,
"slug": f"{uid[:8]}-reaction-post",
@@ -239,6 +243,8 @@ def _make_comment(post_uid, owner_uid):
uid = generate_uid()
get_table("comments").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"target_type": "post",
"target_uid": post_uid,
@@ -260,6 +266,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -303,6 +311,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -327,6 +337,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -350,6 +362,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -368,6 +382,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -386,6 +402,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+12
View File
@@ -12,6 +12,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -55,6 +57,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -79,6 +83,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -102,6 +108,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -120,6 +128,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -138,6 +148,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+4
View File
@@ -156,6 +156,8 @@ def _insert_post(user_uid, dt):
uid = generate_uid()
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": user_uid,
"slug": f"{uid[:8]}-streak",
@@ -173,6 +175,8 @@ def _insert_comment(user_uid, post_uid, dt):
uid = generate_uid()
get_table("comments").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"target_type": "post",
"target_uid": post_uid,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+14
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
@@ -197,6 +199,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -240,6 +244,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -264,6 +270,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -287,6 +295,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -305,6 +315,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -323,6 +335,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+6
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
@@ -158,6 +160,8 @@ def _make_post_reactions(owner_uid):
uid = generate_uid()
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner_uid,
"slug": f"{uid[:8]}-reaction-post",
@@ -175,6 +179,8 @@ def _make_comment(post_uid, owner_uid):
uid = generate_uid()
get_table("comments").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"target_type": "post",
"target_uid": post_uid,
+12
View File
@@ -25,6 +25,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -68,6 +70,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -92,6 +96,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -115,6 +121,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -133,6 +141,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -151,6 +161,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+12
View File
@@ -88,6 +88,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -131,6 +133,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -155,6 +159,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -178,6 +184,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -196,6 +204,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -214,6 +224,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+4
View File
@@ -34,6 +34,8 @@ def _insert_post(user_uid, dt):
uid = generate_uid()
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": user_uid,
"slug": f"{uid[:8]}-streak",
@@ -51,6 +53,8 @@ def _insert_comment(user_uid, post_uid, dt):
uid = generate_uid()
get_table("comments").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"target_type": "post",
"target_uid": post_uid,
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+2
View File
@@ -43,6 +43,8 @@ def _make_post_votes(owner_uid):
uid = generate_uid()
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner_uid,
"slug": f"{uid[:8]}-vote-post",
+2
View File
@@ -17,6 +17,8 @@ def seed_admin_news(count=3):
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
+2
View File
@@ -17,6 +17,8 @@ def seed_admin_news(count=3):
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
+2
View File
@@ -17,6 +17,8 @@ def seed_admin_news(count=3):
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
+4
View File
@@ -17,6 +17,8 @@ def seed_admin_news(count=3):
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -157,6 +159,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
View File
+23
View File
@@ -0,0 +1,23 @@
# retoor <retoor@molodetz.nl>
from tests.conftest import BASE_URL
def test_admin_bots_page_loads(alice):
page, _ = alice
page.goto(f"{BASE_URL}/admin/bots", wait_until="domcontentloaded")
assert page.is_visible("text=Bot Monitor")
assert page.locator("#bots-grid").count() == 1
def test_admin_bots_sidebar_link(alice):
page, _ = alice
page.goto(f"{BASE_URL}/admin/services", wait_until="domcontentloaded")
link = page.locator("a.sidebar-link[href='/admin/bots']")
link.wait_for(state="visible")
assert link.count() == 1
def test_admin_bots_redirect_unauth(page, app_server):
page.goto(f"{BASE_URL}/admin/bots", wait_until="domcontentloaded")
assert page.url.startswith(f"{BASE_URL}/auth/login")
+93
View File
@@ -1,9 +1,25 @@
# retoor <retoor@molodetz.nl>
import requests
from playwright.sync_api import expect
from tests.conftest import BASE_URL
from devplacepy.database import get_table
USER_INPUT = '#cm-admin-create-form input[data-search="user"]'
USER_OPTIONS = '#cm-admin-create-form [data-suggest="user"] li[data-value]'
RUN_AS_HIDDEN = '#cm-admin-create-form input[name="run_as_uid"]'
def _open_user_search(page):
page.goto(f"{BASE_URL}/admin/containers", wait_until="domcontentloaded")
page.click('[data-modal="cm-admin-create-modal"]')
inp = page.locator(USER_INPUT)
inp.wait_for(state="visible")
inp.fill("test")
options = page.locator(USER_OPTIONS)
options.first.wait_for(state="visible")
return inp, options
def test_containers_admin_page_loads(alice, app_server):
page, user = alice
@@ -15,3 +31,80 @@ def test_container_instance_page_404_for_missing(alice, app_server):
page, _ = alice
page.goto(f"{BASE_URL}/admin/containers/nonexistent", wait_until="domcontentloaded")
assert page.is_visible("text=Not Found") or page.is_visible("text=404")
def test_user_search_arrow_keys_highlight_and_enter_selects(alice, seeded_db):
page, _ = alice
inp, options = _open_user_search(page)
count = options.count()
assert count >= 1
inp.press("ArrowDown")
expect(options.nth(0)).to_have_class("cm-suggest-active")
target = options.nth(0)
if count >= 2:
inp.press("ArrowDown")
expect(options.nth(1)).to_have_class("cm-suggest-active")
expect(options.nth(0)).not_to_have_class("cm-suggest-active")
target = options.nth(1)
label = target.get_attribute("data-label")
inp.press("Enter")
expect(page.locator(USER_OPTIONS)).to_have_count(0)
assert inp.input_value() == label
assert page.locator(RUN_AS_HIDDEN).input_value() != ""
def test_user_search_arrow_up_wraps_to_last(alice, seeded_db):
page, _ = alice
inp, options = _open_user_search(page)
if options.count() < 2:
return
last = options.count() - 1
inp.press("ArrowUp")
expect(options.nth(last)).to_have_class("cm-suggest-active")
def test_user_search_tab_navigates_options(alice, seeded_db):
page, _ = alice
inp, options = _open_user_search(page)
inp.press("Tab")
expect(options.nth(0)).to_have_class("cm-suggest-active")
if options.count() >= 2:
inp.press("Tab")
expect(options.nth(1)).to_have_class("cm-suggest-active")
def test_user_search_tab_past_last_selects_and_releases(alice, seeded_db):
page, _ = alice
inp, options = _open_user_search(page)
count = options.count()
label = options.nth(count - 1).get_attribute("data-label")
for _ in range(count + 1):
inp.press("Tab")
expect(page.locator(USER_OPTIONS)).to_have_count(0)
assert inp.input_value() == label
assert page.locator(RUN_AS_HIDDEN).input_value() != ""
assert not inp.evaluate("el => el === document.activeElement")
def test_user_search_escape_closes_without_selecting(alice, seeded_db):
page, _ = alice
inp, _ = _open_user_search(page)
inp.press("Escape")
expect(page.locator(USER_OPTIONS)).to_have_count(0)
assert page.locator(RUN_AS_HIDDEN).input_value() == ""
def test_user_search_has_combobox_aria(alice, seeded_db):
page, _ = alice
inp, options = _open_user_search(page)
assert inp.get_attribute("role") == "combobox"
assert inp.get_attribute("aria-expanded") == "true"
inp.press("ArrowDown")
active = inp.get_attribute("aria-activedescendant")
assert active
assert options.nth(0).get_attribute("id") == active
+2
View File
@@ -17,6 +17,8 @@ def seed_admin_news(count=3):
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
+2
View File
@@ -17,6 +17,8 @@ def seed_admin_news(count=3):
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
+2
View File
@@ -17,6 +17,8 @@ def seed_admin_news(count=3):
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
+2
View File
@@ -17,6 +17,8 @@ def seed_admin_news(count=3):
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
+4
View File
@@ -17,6 +17,8 @@ def seed_admin_news(count=3):
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -157,6 +159,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+12
View File
@@ -20,6 +20,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -63,6 +65,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -87,6 +91,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -110,6 +116,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -128,6 +136,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -146,6 +156,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+12
View File
@@ -43,6 +43,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -86,6 +88,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -110,6 +114,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -133,6 +139,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -151,6 +159,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -169,6 +179,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+2
View File
@@ -98,6 +98,8 @@ def _seed_news_audit_log():
title = _unique("aunews")
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
+18
View File
@@ -85,6 +85,8 @@ def _seed_post_with_comments(comment_texts, topic="devlog"):
marker = f"seedpost-{post_uid[:8]}"
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": post_uid,
"user_uid": owner,
"slug": make_combined_slug(marker, post_uid),
@@ -102,6 +104,8 @@ def _seed_post_with_comments(comment_texts, topic="devlog"):
for i, text in enumerate(comment_texts):
comments.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": str(uuid4()),
"target_type": "post",
"target_uid": post_uid,
@@ -242,6 +246,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -285,6 +291,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -309,6 +317,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -332,6 +342,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -350,6 +362,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -368,6 +382,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
@@ -480,6 +496,7 @@ def test_feed_tab_switching(alice):
def test_feed_topic_filter_sidebar(alice):
page, user = alice
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.locator(".sidebar-more .sidebar-more-toggle").click()
topics = ["Devlog", "Showcase", "Question", "Rant", "Fun"]
for topic in topics:
link = page.locator(f"a:has-text('{topic}')").first
@@ -621,6 +638,7 @@ def test_feed_guest_reply_disabled(page, app_server):
def test_feed_signals_topic(alice):
page, _ = alice
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.locator(".sidebar-more .sidebar-more-toggle").click()
signals_link = page.locator("a:has-text('Signals')")
assert signals_link.is_visible()
+2
View File
@@ -48,6 +48,8 @@ def _seed_gists(count):
title = f"Pag Gist {i}"
gists.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(title, uid),
+12
View File
@@ -14,6 +14,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -57,6 +59,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -81,6 +85,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -104,6 +110,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -122,6 +130,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -140,6 +150,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+16
View File
@@ -17,6 +17,8 @@ def _seed_news_paginated(count):
titles.append(title)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": make_combined_slug(title, uid),
"title": title,
@@ -42,6 +44,8 @@ def seed_news():
slug = make_combined_slug(title, uid)
news_table.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -71,6 +75,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -114,6 +120,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -138,6 +146,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -161,6 +171,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -179,6 +191,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -197,6 +211,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+12
View File
@@ -15,6 +15,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -58,6 +60,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -82,6 +86,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -105,6 +111,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -123,6 +131,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -141,6 +151,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+8 -2
View File
@@ -58,7 +58,10 @@ def test_post_vote_increment(alice):
def test_post_upvote_voted_state_persists(alice):
page, _ = alice
create_post(page, "showcase", "Upvote persistence test")
page.locator(".post-action-btn.vote-up").first.click()
with page.expect_response(
lambda r: "/votes/" in r.url and r.request.method == "POST"
):
page.locator(".post-action-btn.vote-up").first.click()
expect(page.locator(".post-vote-count").first).to_have_text("1")
page.reload(wait_until="domcontentloaded")
expect(page.locator(".post-action-btn.vote-up").first).to_have_class(
@@ -72,7 +75,10 @@ def test_post_upvote_voted_state_persists(alice):
def test_post_downvote_voted_state_persists(alice):
page, _ = alice
create_post(page, "showcase", "Downvote persistence test")
page.locator(".post-action-btn.vote-down").first.click()
with page.expect_response(
lambda r: "/votes/" in r.url and r.request.method == "POST"
):
page.locator(".post-action-btn.vote-down").first.click()
expect(page.locator(".post-vote-count").first).to_have_text("-1")
page.reload(wait_until="domcontentloaded")
expect(page.locator(".post-action-btn.vote-down").first).to_have_class(
+12
View File
@@ -43,6 +43,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -86,6 +88,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -110,6 +114,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -133,6 +139,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -151,6 +159,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -169,6 +179,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+16
View File
@@ -72,6 +72,8 @@ def _seed_post_with_comments(comment_texts, topic="devlog"):
marker = f"seedpost-{post_uid[:8]}"
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": post_uid,
"user_uid": owner,
"slug": make_combined_slug(marker, post_uid),
@@ -89,6 +91,8 @@ def _seed_post_with_comments(comment_texts, topic="devlog"):
for i, text in enumerate(comment_texts):
comments.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": str(uuid4()),
"target_type": "post",
"target_uid": post_uid,
@@ -173,6 +177,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -216,6 +222,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -240,6 +248,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -263,6 +273,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -281,6 +293,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -299,6 +313,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+14
View File
@@ -91,6 +91,8 @@ def _seed_gists(count):
title = f"Pag Gist {i}"
gists.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(title, uid),
@@ -292,6 +294,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -335,6 +339,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -359,6 +365,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -382,6 +390,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -400,6 +410,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -418,6 +430,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
+6
View File
@@ -62,6 +62,8 @@ def _seed_post_with_comments(comment_texts, topic="devlog"):
marker = f"seedpost-{post_uid[:8]}"
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": post_uid,
"user_uid": owner,
"slug": make_combined_slug(marker, post_uid),
@@ -79,6 +81,8 @@ def _seed_post_with_comments(comment_texts, topic="devlog"):
for i, text in enumerate(comment_texts):
comments.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": str(uuid4()),
"target_type": "post",
"target_uid": post_uid,
@@ -266,6 +270,8 @@ def _seed_projects(count):
title = f"Pag Project {i}"
projects.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(title, uid),
+14
View File
@@ -15,6 +15,8 @@ def _seed_news_seo():
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
@@ -58,6 +60,8 @@ def _seed_post_seo(image=None):
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -82,6 +86,8 @@ def _seed_gist():
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -105,6 +111,8 @@ def _seed_project():
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
@@ -123,6 +131,8 @@ def _seed_news_image(news_uid):
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
@@ -141,6 +151,8 @@ def _seed_feed_posts(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
@@ -213,6 +225,8 @@ def test_landing_news_section(page, app_server):
uid = str(uuid4())
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": f"landing-news-{uid[:8]}",
"title": f"Landing News {uid[:8]}",
+6
View File
@@ -62,6 +62,8 @@ def test_news_sanitize_strips_html(local_db, capsys):
uid = generate_uid()
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"description": "<b>Bold</b> &amp; clean",
"content": "<p>Body</p>",
@@ -80,6 +82,8 @@ def test_attachments_prune_removes_only_stale_orphans(local_db, capsys):
attachments = get_table("attachments")
attachments.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": old_uid,
"target_type": "",
"target_uid": "",
@@ -88,6 +92,8 @@ def test_attachments_prune_removes_only_stale_orphans(local_db, capsys):
)
attachments.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": fresh_uid,
"target_type": "",
"target_uid": "",
+77
View File
@@ -0,0 +1,77 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services.bot.monitor import BotMonitor, BotFrame
def test_update_meta_creates_and_merges():
monitor = BotMonitor()
monitor.update_meta(0, username="bytewren", persona="grumpy_senior")
monitor.update_meta(0, action="POST: rant [1]")
frame = monitor.frames[0]
assert frame.username == "bytewren"
assert frame.persona == "grumpy_senior"
assert frame.action == "POST: rant [1]"
assert frame.label() == "bytewren"
def test_store_and_read_image(tmp_path, monkeypatch):
import devplacepy.services.bot.monitor as mod
monkeypatch.setattr(mod, "MONITOR_DIR", tmp_path)
monitor = BotMonitor()
monitor.update_meta(2, username="anon")
monitor.store_image(2, b"jpegbytes")
assert monitor.frames[2].has_image is True
assert monitor.read_image(2) == b"jpegbytes"
assert monitor.frame_path(2) == tmp_path / "slot2.jpg"
def test_store_image_overwrites(tmp_path, monkeypatch):
import devplacepy.services.bot.monitor as mod
monkeypatch.setattr(mod, "MONITOR_DIR", tmp_path)
monitor = BotMonitor()
monitor.store_image(4, b"first")
captured_first = monitor.frames[4].captured_at
monitor.store_image(4, b"second")
assert monitor.read_image(4) == b"second"
assert monitor.frames[4].captured_at >= captured_first
assert len(list(tmp_path.glob("slot4.jpg"))) == 1
def test_store_empty_image_is_noop(tmp_path, monkeypatch):
import devplacepy.services.bot.monitor as mod
monkeypatch.setattr(mod, "MONITOR_DIR", tmp_path)
monitor = BotMonitor()
monitor.store_image(1, b"")
assert 1 not in monitor.frames
assert monitor.read_image(1) is None
def test_drop_evicts_row_and_file(tmp_path, monkeypatch):
import devplacepy.services.bot.monitor as mod
monkeypatch.setattr(mod, "MONITOR_DIR", tmp_path)
monitor = BotMonitor()
monitor.update_meta(0, username="x")
monitor.store_image(0, b"data")
monitor.drop(0)
assert 0 not in monitor.frames
assert monitor.read_image(0) is None
def test_snapshot_sorted_and_serializable():
monitor = BotMonitor()
monitor.update_meta(3, username="c")
monitor.update_meta(1, username="a")
snap = monitor.snapshot()
assert [row["slot"] for row in snap] == [1, 3]
assert "frame_url" not in snap[0]
assert snap[0]["active"] is False
def test_frame_inactive_without_image():
frame = BotFrame(slot=0, username="z")
assert frame.is_active() is False
assert frame.label() == "z"
+116
View File
@@ -5,6 +5,7 @@ import pytest
import requests
from tests.conftest import BASE_URL, run_async
from devplacepy.database import db, get_table, init_db, refresh_snapshot
from devplacepy.utils import generate_uid
from devplacepy import config, project_files
from devplacepy.services.containers import api, store, runtime
from devplacepy.services.containers.backend.base import Mount, PortMapping, RunSpec
@@ -31,9 +32,27 @@ def env(tmp_path, monkeypatch):
pid = "ctest-p1"
project = {"uid": pid, "slug": "ctest", "title": "C", "user_uid": "ctest-u1"}
user = {"uid": "ctest-u1", "username": "ctestadmin"}
get_table("users").insert(
{
"uid": user["uid"],
"username": user["username"],
"email": "ctestadmin@example.com",
"api_key": generate_uid(),
"password_hash": "",
"role": "Member",
"is_active": True,
"level": 1,
"xp": 0,
"stars": 0,
"deleted_at": None,
"deleted_by": None,
}
)
refresh_snapshot()
project_files.write_text_file(pid, user, "app.py", "print(1)\n")
yield {"fake": fake, "project": project, "user": user}
runtime.set_backend(None)
get_table("users").delete(uid=user["uid"])
for table in _CONTAINER_TABLES:
if table in db.tables:
for row in [r for r in get_table(table).find()]:
@@ -188,3 +207,100 @@ def test_ingress_validation(env):
)
with pytest.raises(api.ContainerError):
api.validate_ingress("taken", None, [PortMapping(80, 80)])
def test_validate_boot_languages():
assert api.validate_boot("none", "ignored") == ("none", "")
assert api.validate_boot("python", "print(1)") == ("python", "print(1)")
with pytest.raises(api.ContainerError):
api.validate_boot("ruby", "puts 1")
with pytest.raises(api.ContainerError):
api.validate_boot("bash", " ")
def test_boot_script_precedence_in_run_spec(env):
inst = _ready_instance(
env,
boot_language="python",
boot_script="print('boot')\n",
boot_command="python other.py",
autostart=False,
)
spec = api.run_spec_for(inst, config.CONTAINER_IMAGE)
assert spec.command == ["python", "/app/.devplace_boot.py"]
inst2 = _ready_instance(
env, name="i2", boot_command="python serve.py", autostart=False
)
spec2 = api.run_spec_for(inst2, config.CONTAINER_IMAGE)
assert spec2.command == ["/bin/sh", "-c", "python serve.py"]
def test_materialize_boot_script_writes_file(env, tmp_path):
workspace = tmp_path / "boot-ws"
inst = _ready_instance(
env, name="boot", boot_language="bash", boot_script="echo hi\n", autostart=False
)
store.update_instance(inst["uid"], {"workspace_dir": str(workspace)})
inst = store.get_instance(inst["uid"])
api.materialize_boot_script(inst)
written = workspace / ".devplace_boot.sh"
assert written.is_file()
assert written.read_text() == "echo hi\n"
def test_run_as_uid_drives_pravda_env(env):
_promote_admin(env["user"]["username"])
key = _api_key(env["user"]["username"])
inst = _ready_instance(
env, name="runas", run_as_uid=env["user"]["uid"], autostart=False
)
pravda = api.pravda_env(inst)
assert pravda["PRAVDA_API_KEY"] == key
assert pravda["PRAVDA_USER_UID"] == env["user"]["uid"]
def test_run_as_uid_rejects_unknown_user(env):
with pytest.raises(api.ContainerError):
run_async(
api.create_instance(
env["project"], name="bad", run_as_uid="nope-uid", autostart=False
)
)
def test_start_on_boot_pass_forces_running(env):
inst = _ready_instance(env, name="boot", start_on_boot=True, autostart=False)
assert inst["desired_state"] == store.DESIRED_STOPPED
service = ContainerService()
service._boot_pass(store.all_instances())
refresh_snapshot()
assert store.get_instance(inst["uid"])["desired_state"] == store.DESIRED_RUNNING
def test_status_change_records_event(env):
inst = _ready_instance(env, name="hist", restart_policy="never", autostart=True)
service = ContainerService()
run_async(service.run_once())
refresh_snapshot()
inst = store.get_instance(inst["uid"])
api.set_desired_state(inst, store.DESIRED_STOPPED)
run_async(service.run_once())
refresh_snapshot()
events = [e["event"] for e in store.list_events(inst["uid"])]
assert "status_change" in events
def test_bidirectional_sync_newer_wins(env, tmp_path):
workspace = tmp_path / "sync-ws"
workspace.mkdir()
pid = env["project"]["uid"]
user = env["user"]
project_files.write_text_file(pid, user, "shared.txt", "from project\n")
fs_only = workspace / "fromfs.txt"
fs_only.write_text("from fs\n")
counts = project_files.sync_dir_bidirectional(pid, str(workspace), user)
assert counts["exported"] >= 1
assert counts["imported"] >= 1
assert (workspace / "shared.txt").read_text() == "from project\n"
imported = project_files.read_file(pid, "fromfs.txt")
assert imported["content"] == "from fs\n"
+3 -4
View File
@@ -1,7 +1,6 @@
# retoor <retoor@molodetz.nl>
import asyncio
from tests.conftest import run_async
from devplacepy.services.deepsearch import chat as chat_module
from devplacepy.services.deepsearch.chat import DeepsearchChat
from devplacepy.services.deepsearch.embeddings import local_embed
@@ -32,7 +31,7 @@ def test_answer_is_grounded_and_cited(monkeypatch):
monkeypatch.setattr(chat_module, "complete_chat", fake_complete)
chat = DeepsearchChat(collection, "k")
answer = asyncio.run(chat.answer("where was the transistor invented"))
answer = run_async(chat.answer("where was the transistor invented"))
assert "Bell Labs" in answer.text
assert answer.citations
assert answer.citations[0]["url"].startswith("https://")
@@ -48,7 +47,7 @@ def test_answer_when_no_chunks(monkeypatch):
monkeypatch.setattr(chat_module, "embed_texts", fake_embed)
chat = DeepsearchChat(collection, "k")
answer = asyncio.run(chat.answer("anything"))
answer = run_async(chat.answer("anything"))
assert answer.citations == []
assert "did not capture" in answer.text
finally:
@@ -1,10 +1,11 @@
# retoor <retoor@molodetz.nl>
import asyncio
import json
import tempfile
from pathlib import Path
from tests.conftest import run_async
from devplacepy.services.jobs.deepsearch import crawl as crawl_module
from devplacepy.services.jobs.deepsearch import worker as worker_module
from devplacepy.services.jobs.deepsearch.crawl import CrawledPage, CrawlOutcome
@@ -79,7 +80,7 @@ def test_worker_run_produces_report(monkeypatch):
"collection": "ds_worker_test_one",
"cached_hashes": [],
}
report = asyncio.run(worker_module._run(payload, output_dir))
report = run_async(worker_module._run(payload, output_dir))
assert report["query"] == "history of the transistor"
assert report["page_count"] == 2
assert report["chunk_count"] > 0
@@ -100,4 +101,4 @@ def test_worker_control_cancel_stops(monkeypatch):
output_dir = Path(tmp)
(output_dir / "control.json").write_text(json.dumps({"state": "cancelled"}))
should_stop = worker_module._make_stop(output_dir)
assert asyncio.run(should_stop()) is True
assert run_async(should_stop()) is True
+2
View File
@@ -52,6 +52,8 @@ def _make_source_project(*, is_private=False, binary=False):
)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": pid,
"user_uid": owner_uid,
"slug": f"{pid}-source",
+4
View File
@@ -240,6 +240,8 @@ def test_export_blocks_malicious_db_path(zip_env, tmp_path):
pid = "ziptest-evil"
get_table("project_files").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": "evil-node",
"project_uid": pid,
"user_uid": "u",
@@ -267,6 +269,8 @@ def test_traversal_payload_marks_job_failed(zip_env):
pid = "ziptest-evil2"
get_table("project_files").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": "evil-node2",
"project_uid": pid,
"user_uid": "u",
+2
View File
@@ -146,6 +146,8 @@ def test_run_once_updates_existing_news_row(local_db, monkeypatch):
existing_uid = generate_uid()
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": existing_uid,
"external_id": external_id,
"slug": "",
+2
View File
@@ -178,6 +178,8 @@ def test_check_milestone_badges_awards_prolific(local_db):
post_uid = generate_uid()
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": post_uid,
"user_uid": uid,
"slug": f"{post_uid[:8]}-p",