feat: add TTLCache for get_cache_version and TEMPLATE_AUTO_RELOAD config with Makefile worker count variables
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
@@ -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"] == []
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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]}",
|
||||
|
||||
@@ -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> & 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": "",
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user