Add personal notes, DeepSearch history, backup offload, and gateway auth throttling

Also streamline the top navigation: drop the Tools dropdown, make Quizzes
and Battles icon-only entries, and remove the Workspace, Containers and
Editor entry points from the project detail page.
This commit is contained in:
2026-09-12 20:32:31 +02:00
parent c4f7d01b2d
commit cae139ead9
158 changed files with 6156 additions and 377 deletions
View File
+36
View File
@@ -0,0 +1,36 @@
from devplacepy.docs_api import field, endpoint
def test_field_defaults_nullable_false():
spec = field("username", "path", "string", True, "alice", "A username.")
assert spec["nullable"] is False
def test_field_nullable_true_is_threaded_into_spec():
spec = field(
"avatar_seed",
"response",
"string",
description="Null falls back to the username.",
nullable=True,
)
assert spec["nullable"] is True
assert spec["location"] == "response"
def test_endpoint_carries_response_field_specs_in_params():
ep = endpoint(
id="example",
method="GET",
path="/example",
title="Example",
summary="An example endpoint.",
auth="public",
params=[
field("uid", "response", "string", nullable=False),
field("bio", "response", "string", nullable=True),
],
)
by_name = {p["name"]: p for p in ep["params"]}
assert by_name["uid"]["nullable"] is False
assert by_name["bio"]["nullable"] is True
+24
View File
@@ -518,6 +518,30 @@ def test_sweep_orphan_project_file_blobs_keeps_blob_referenced_only_by_soft_dele
assert (tmp_path / directory / stored_name).exists()
def test_export_to_dir_summarizes_missing_blobs(local_db, tmp_path, monkeypatch, caplog):
import logging
monkeypatch.setattr(pf, "PROJECT_FILES_DIR", tmp_path)
uid = _insert_binary_node("export-missing-proj", "ab/cd", "gone.bin")
dest = tmp_path / "out"
dest.mkdir()
pf._table().update(
{
"uid": uid,
"path": "gone.bin",
"name": "gone.bin",
"parent_path": "",
},
["uid"],
)
with caplog.at_level(logging.WARNING, logger="devplacepy.project_files"):
written = pf.export_to_dir("export-missing-proj", "", dest)
assert written == 0
messages = [record.getMessage() for record in caplog.records]
assert any("Skipped 1 missing blob" in message for message in messages)
assert not any("Blob file missing" in message for message in messages)
def test_sweep_orphan_project_file_blobs_dry_run_changes_nothing(local_db, tmp_path, monkeypatch):
monkeypatch.setattr(pf, "PROJECT_FILES_DIR", tmp_path)
directory = "ab/cd"
+69 -1
View File
@@ -2,7 +2,12 @@
from html.parser import HTMLParser
from devplacepy.rendering import render_content, render_title
from devplacepy.rendering import (
markdown_structure_signature,
render_content,
render_title,
safe_truncate,
)
def test_emoji_shortcodes():
@@ -12,6 +17,37 @@ def test_emoji_shortcodes():
assert ":rocket:" not in out
def test_markdown_structure_signature_counts_elements():
text = (
"# Title\n\n"
"- one\n- two\n\n"
"1. first\n2. second\n\n"
"```python\nprint(1)\n```\n\n"
"[link](https://example.com)\n"
)
assert markdown_structure_signature(text) == {
"code_fences": 1,
"list_items": 4,
"headers": 1,
"links": 1,
}
def test_markdown_structure_signature_blank_text():
assert markdown_structure_signature("") == {
"code_fences": 0,
"list_items": 0,
"headers": 0,
"links": 0,
}
def test_markdown_structure_signature_ignores_word_changes():
original = markdown_structure_signature("- alpha\n- beta\n- gamma\n")
reworded = markdown_structure_signature("- Alpha\n- beta\n- gamma\n")
assert original == reworded
def test_markdown_bold_and_italic():
out = str(render_content("this is **bold** and _em_"))
assert "<strong>bold</strong>" in out
@@ -47,6 +83,38 @@ def test_plain_url_autolinked():
assert '<a href="https://example.com/page" target="_blank" rel="noopener noreferrer">' in out
def test_safe_truncate_short_text_unchanged():
text = "short content"
assert safe_truncate(text, 300) == text
def test_safe_truncate_backs_off_to_word_boundary():
text = "a" * 45 + " " + "b" * 45
truncated = safe_truncate(text, 50)
assert truncated == "a" * 45
def test_safe_truncate_does_not_split_a_url_straddling_the_cutoff():
prefix = "word " * 58
url = "https://example.com/gallery/a-very-long-descriptive-photo-name.jpg"
text = prefix + url + " more words after the link that get cut off entirely"
truncated = safe_truncate(text, 300)
assert url not in truncated
assert "example.com" not in truncated
out = str(render_content(truncated + "..."))
assert "<img" not in out
assert "<a href=" not in out
def test_safe_truncate_keeps_a_url_that_fits_before_the_cutoff():
url = "https://example.com/image.png"
text = url + " " + ("word " * 60)
truncated = safe_truncate(text, 300)
assert url in truncated
out = str(render_content(truncated))
assert f'<img src="{url}"' in out
def test_mention_links_to_profile():
out = str(render_content("hello @alice_test there"))
assert '<a href="/profile/alice_test" class="mention-link">@alice_test</a>' in out
+32
View File
@@ -0,0 +1,32 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services import ai_modifier, correction
class _FakeResponse:
def __init__(self, content: str) -> None:
self._content = content
self.headers: dict = {}
def raise_for_status(self) -> None:
return None
def json(self) -> dict:
return {"choices": [{"message": {"content": self._content}}]}
class _FakeClient:
def __init__(self) -> None:
self.calls: list[dict] = []
def post(self, url, json=None, headers=None, timeout=None):
self.calls.append({"url": url, "json": json, "headers": headers})
return _FakeResponse("modified text")
def test_modify_text_requests_the_preamble_bypass(local_db, monkeypatch):
fake = _FakeClient()
monkeypatch.setattr(correction, "_client", lambda: fake)
ai_modifier.modify_text("user-api-key", "", "@ai fix this")
sent = fake.calls[-1]
assert sent["json"]["bypass_preamble"] is True
+58
View File
@@ -0,0 +1,58 @@
# retoor <retoor@molodetz.nl>
import tempfile
from pathlib import Path
from tests.conftest import run_async
from devplacepy.services.backup import offload, store
def _make_backup(target):
job_uid = f"job-{store.generate_uid()}"
uid = store.create_backup(target=target, created_by="test", job_uid=job_uid)
path = Path(tempfile.gettempdir()) / f"backup-offload-test-{uid}.tar.gz"
path.write_bytes(b"x" * 10)
store.finalize_backup(
uid,
filename=path.name,
local_path=str(path),
stats={
"bytes_out": 10,
"bytes_in": 10,
"file_count": 1,
"dir_count": 0,
"sha256": "abc",
},
)
return uid, path
def test_upload_pending_stops_after_auth_error(local_db, monkeypatch):
first_uid, first_path = _make_backup("database")
second_uid, second_path = _make_backup("uploads")
calls = []
async def fake_rclone(*args, timeout=1800.0):
calls.append(args)
return 1, "", "401 Unauthorized from Apache"
monkeypatch.setattr(offload, "_run_rclone", fake_rclone)
logs = []
try:
uploaded = run_async(offload.upload_pending(log=logs.append))
assert uploaded == 0
assert len(calls) == 1
assert any("halted" in line.lower() for line in logs)
finally:
first_path.unlink(missing_ok=True)
second_path.unlink(missing_ok=True)
store.delete_backup(first_uid)
store.delete_backup(second_uid)
def test_is_auth_error_detects_rclone_html_401():
assert offload._is_auth_error(
'read metadata failed: <title>401 Unauthorized</title>: 401 Unauthorized'
)
assert offload._is_auth_error("didn't find section in config file")
assert not offload._is_auth_error("connection timed out")
+72
View File
@@ -0,0 +1,72 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services.backup import service, store
def _stats(used_percent):
return {
"disk": {
"total_bytes": 100,
"used_bytes": used_percent,
"free_bytes": 100 - used_percent,
"total_human": "100 B",
"used_human": f"{used_percent} B",
"free_human": f"{100 - used_percent} B",
"used_percent": used_percent,
}
}
def _disk(used_percent):
return _stats(used_percent)["disk"]
def test_check_disk_usage_warns_once_and_recovers_once(local_db, monkeypatch):
svc = service.BackupService()
assert svc.disk_warn_percent_field.default == service.DEFAULT_DISK_WARN_PERCENT
logs = []
monkeypatch.setattr(svc, "log", lambda message: logs.append(message))
monkeypatch.setattr(store, "disk_usage", lambda: _disk(50))
svc._check_disk_usage()
assert logs == []
assert svc._disk_warned is False
monkeypatch.setattr(store, "disk_usage", lambda: _disk(95))
svc._check_disk_usage()
assert len(logs) == 1
assert "critical" in logs[0].lower()
assert svc._disk_warned is True
svc._check_disk_usage()
assert len(logs) == 1
monkeypatch.setattr(store, "disk_usage", lambda: _disk(40))
svc._check_disk_usage()
assert len(logs) == 2
assert "under threshold" in logs[1].lower()
assert svc._disk_warned is False
def test_collect_metrics_includes_disk_usage_stat(local_db, monkeypatch):
svc = service.BackupService()
monkeypatch.setattr(store, "disk_usage", lambda: _disk(62))
metrics = svc.collect_metrics()
labels = [stat["label"] for stat in metrics["stats"]]
assert "Disk usage" in labels
disk_stat = next(stat for stat in metrics["stats"] if stat["label"] == "Disk usage")
assert "62%" in disk_stat["value"]
def test_collect_metrics_never_walks_storage_tree(local_db, monkeypatch):
svc = service.BackupService()
monkeypatch.setattr(store, "disk_usage", lambda: _disk(10))
def boom():
raise AssertionError("compute_storage_stats must not run on the service tick")
monkeypatch.setattr(store, "compute_storage_stats", boom)
svc.collect_metrics()
svc._check_disk_usage()
+88
View File
@@ -1,5 +1,6 @@
# retoor <retoor@molodetz.nl>
import os
import tempfile
from pathlib import Path
@@ -65,3 +66,90 @@ def test_rotate_schedule_never_removes_a_backup_without_a_confirmed_remote_copy(
finally:
store.delete_backup(older_uid)
store.delete_backup(newer_uid)
def _point_storage_at(monkeypatch, root: Path):
uploads = root / "uploads"
attachments = uploads / "attachments"
project_files = uploads / "project_files"
keys = root / "keys"
zips = root / "zips"
deepsearch = root / "deepsearch"
workspaces = root / "container_workspaces"
backups = root / "backups"
for path in (
attachments,
project_files,
keys,
zips,
deepsearch,
workspaces,
backups,
):
path.mkdir(parents=True, exist_ok=True)
db_file = root / "devplace.db"
db_file.write_bytes(b"db")
tasks = root / "devii_tasks.db"
tasks.write_bytes(b"t")
lessons = root / "devii_lessons.db"
lessons.write_bytes(b"l")
(project_files / "a.bin").write_bytes(b"x" * 10)
(attachments / "b.bin").write_bytes(b"y" * 5)
monkeypatch.setattr(store.config, "DATA_DIR", root)
monkeypatch.setattr(store.config, "UPLOADS_DIR", uploads)
monkeypatch.setattr(store.config, "ATTACHMENTS_DIR", attachments)
monkeypatch.setattr(store.config, "PROJECT_FILES_DIR", project_files)
monkeypatch.setattr(store.config, "KEYS_DIR", keys)
monkeypatch.setattr(store.config, "ZIPS_DIR", zips)
monkeypatch.setattr(store.config, "DEEPSEARCH_DIR", deepsearch)
monkeypatch.setattr(store.config, "CONTAINER_WORKSPACES_DIR", workspaces)
monkeypatch.setattr(store.config, "BACKUPS_DIR", backups)
monkeypatch.setattr(store.config, "DEVII_TASKS_DB", tasks)
monkeypatch.setattr(store.config, "DEVII_LESSONS_DB", lessons)
monkeypatch.setattr(store.config, "DATABASE_URL", f"sqlite:///{db_file}")
store.clear_storage_stats_cache()
return {
"uploads": uploads,
"attachments": attachments,
"project_files": project_files,
"db_file": db_file,
}
def test_disk_usage_does_not_walk(monkeypatch, tmp_path):
monkeypatch.setattr(store.config, "DATA_DIR", tmp_path)
store.clear_storage_stats_cache()
def boom(*_args, **_kwargs):
raise AssertionError("disk_usage must not walk the data tree")
monkeypatch.setattr(store.os, "walk", boom)
disk = store.disk_usage()
assert disk["total_bytes"] > 0
assert "used_percent" in disk
def test_compute_storage_stats_walks_data_dir_once(local_db, monkeypatch, tmp_path):
root = tmp_path / "data"
_point_storage_at(monkeypatch, root)
walks = []
real_walk = os.walk
def counting_walk(path, *args, **kwargs):
walks.append(os.path.realpath(path))
return real_walk(path, *args, **kwargs)
monkeypatch.setattr(store.os, "walk", counting_walk)
stats = store.compute_storage_stats()
assert walks == [os.path.realpath(root)]
by_key = {row["key"]: row for row in stats["paths"]}
assert by_key["project_files"]["file_count"] == 1
assert by_key["project_files"]["size_bytes"] == 10
assert by_key["attachments"]["file_count"] == 1
assert by_key["uploads"]["file_count"] >= 2
assert by_key["database"]["file_count"] == 1
assert stats["data_dir"]["file_count"] >= 3
walks.clear()
again = store.compute_storage_stats()
assert walks == []
assert again["data_dir"]["file_count"] == stats["data_dir"]["file_count"]
+29
View File
@@ -0,0 +1,29 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services.bot.browser import NATIVE_FILL_TYPES, uses_native_fill
def test_uses_native_fill_for_date_widgets():
assert uses_native_fill("date")
assert uses_native_fill("DATE")
assert uses_native_fill("datetime-local")
assert uses_native_fill("month")
assert uses_native_fill("time")
assert uses_native_fill("week")
def test_uses_native_fill_skips_text_fields():
assert not uses_native_fill("text")
assert not uses_native_fill("email")
assert not uses_native_fill("password")
assert not uses_native_fill("")
def test_native_fill_types_are_the_date_family():
assert NATIVE_FILL_TYPES == {
"date",
"datetime-local",
"month",
"time",
"week",
}
+158
View File
@@ -0,0 +1,158 @@
# retoor <retoor@molodetz.nl>
from devplacepy.database import set_setting
from devplacepy.services import correction
class _FakeResponse:
def __init__(self, content: str) -> None:
self._content = content
self.headers: dict = {}
def raise_for_status(self) -> None:
return None
def json(self) -> dict:
return {"choices": [{"message": {"content": self._content}}]}
class _FakeClient:
def __init__(self) -> None:
self.calls: list[dict] = []
def post(self, url, json=None, headers=None, timeout=None):
self.calls.append({"url": url, "json": json, "headers": headers})
return _FakeResponse("corrected text")
def test_gateway_complete_sets_bypass_preamble_and_internal_header(local_db, monkeypatch):
set_setting("gateway_internal_key", "test-internal-secret")
fake = _FakeClient()
monkeypatch.setattr(correction, "_client", lambda: fake)
try:
content, _usage = correction.gateway_complete(
"user-api-key",
"system prompt",
"hello world",
5.0,
bypass_preamble=True,
)
finally:
set_setting("gateway_internal_key", "")
assert content == "corrected text"
sent = fake.calls[-1]
assert sent["json"]["bypass_preamble"] is True
assert sent["headers"]["Authorization"] == "Bearer user-api-key"
assert sent["headers"]["X-Gateway-Internal-Key"] == "test-internal-secret"
def test_gateway_complete_without_bypass_preamble_omits_it(local_db, monkeypatch):
fake = _FakeClient()
monkeypatch.setattr(correction, "_client", lambda: fake)
correction.gateway_complete("user-api-key", "system prompt", "hello world", 5.0)
sent = fake.calls[-1]
assert "bypass_preamble" not in sent["json"]
assert "X-Gateway-Internal-Key" not in sent["headers"]
def test_correct_text_requests_the_preamble_bypass(local_db, monkeypatch):
fake = _FakeClient()
monkeypatch.setattr(correction, "_client", lambda: fake)
correction.correct_text("user-api-key", "", "hello world")
sent = fake.calls[-1]
assert sent["json"]["bypass_preamble"] is True
class _ScriptedClient:
def __init__(self, content: str) -> None:
self._content = content
self.calls: list[dict] = []
def post(self, url, json=None, headers=None, timeout=None):
self.calls.append({"url": url, "json": json, "headers": headers})
return _FakeResponse(self._content)
def test_structure_diverges_false_on_reworded_list_item():
original = "- alpha\n- beta\n- gamma\n"
corrected = "- Alpha\n- beta\n- gamma\n"
assert correction.structure_diverges(original, corrected) is False
def test_structure_diverges_true_on_flattened_list():
original = "- alpha\n- beta\n- gamma\n"
corrected = "alpha, beta and gamma."
assert correction.structure_diverges(original, corrected) is True
def test_gateway_complete_structure_check_disabled_by_default(local_db, monkeypatch):
original = "- alpha\n- beta\n"
corrected = "alpha and beta."
fake = _ScriptedClient(corrected)
monkeypatch.setattr(correction, "_client", lambda: fake)
content, _usage = correction.gateway_complete(
"user-api-key", "system prompt", original, 5.0
)
assert content == corrected
def test_gateway_complete_structure_check_passes_typo_fix(local_db, monkeypatch):
original = "- alpha\n- beta\n- gamma\n"
corrected = "- Alpha\n- beta\n- gamma\n"
fake = _ScriptedClient(corrected)
monkeypatch.setattr(correction, "_client", lambda: fake)
content, _usage = correction.gateway_complete(
"user-api-key", "system prompt", original, 5.0, check_structure=True
)
assert content == corrected.strip()
def test_gateway_complete_structure_check_rejects_flattened_list(local_db, monkeypatch):
original = "- alpha\n- beta\n- gamma\n"
corrected = "alpha, beta and gamma."
fake = _ScriptedClient(corrected)
monkeypatch.setattr(correction, "_client", lambda: fake)
content, _usage = correction.gateway_complete(
"user-api-key", "system prompt", original, 5.0, check_structure=True
)
assert content == original
def test_gateway_complete_structure_check_rejects_dropped_code_fence(local_db, monkeypatch):
original = "Here is the fix:\n\n```python\nprint(1)\n```\n"
corrected = "Here is the fix:\n\nprint(1)\n"
fake = _ScriptedClient(corrected)
monkeypatch.setattr(correction, "_client", lambda: fake)
content, _usage = correction.gateway_complete(
"user-api-key", "system prompt", original, 5.0, check_structure=True
)
assert content == original
def test_gateway_complete_structure_check_rejects_removed_link(local_db, monkeypatch):
original = "Check out [DevPlace](https://example.com) for details."
corrected = "Check out DevPlace for details."
fake = _ScriptedClient(corrected)
monkeypatch.setattr(correction, "_client", lambda: fake)
content, _usage = correction.gateway_complete(
"user-api-key", "system prompt", original, 5.0, check_structure=True
)
assert content == original
def test_correct_text_rejects_corrupted_structure(local_db, monkeypatch):
original = "# Setup\n\n- step one\n- step two\n"
corrected = "Setup: step one, step two."
fake = _ScriptedClient(corrected)
monkeypatch.setattr(correction, "_client", lambda: fake)
content, _usage = correction.correct_text("user-api-key", "", original)
assert content == original
def test_correct_text_keeps_grammar_only_correction(local_db, monkeypatch):
original = "# Setup\n\n- step one\n- step two\n"
corrected = "# Setup\n\n- Step one\n- Step two\n"
fake = _ScriptedClient(corrected)
monkeypatch.setattr(correction, "_client", lambda: fake)
content, _usage = correction.correct_text("user-api-key", "", original)
assert content == corrected.strip()
+84
View File
@@ -190,3 +190,87 @@ def test_cli_reset_quota_all(local_db):
_seed_ledger("user", user_id, 0.5, n=1)
cmd_devii_reset_quota(SimpleNamespace(username=None, guests=False, all=True))
assert get_table(LEDGER).count() == 0
def _quota_warnings(uid):
from devplacepy.services.devii.service import QUOTA_WARNING_NOTIFICATION_TYPE
return list(
get_table("notifications").find(
user_uid=uid, type=QUOTA_WARNING_NOTIFICATION_TYPE
)
)
def test_quota_warning_fires_once_past_80_percent(local_db):
svc = _ensure_devii()
set_setting("devii_user_daily_usd", "1.0")
try:
uid, _ = _make_user_devii_quota()
_seed_ledger("user", uid, 0.85)
svc.maybe_warn_quota_threshold("user", uid, False)
assert len(_quota_warnings(uid)) == 1
finally:
set_setting("devii_user_daily_usd", "1.0")
def test_quota_warning_not_refired_within_same_window(local_db):
svc = _ensure_devii()
set_setting("devii_user_daily_usd", "1.0")
try:
uid, _ = _make_user_devii_quota()
_seed_ledger("user", uid, 0.85)
svc.maybe_warn_quota_threshold("user", uid, False)
_seed_ledger("user", uid, 0.05)
svc.maybe_warn_quota_threshold("user", uid, False)
assert len(_quota_warnings(uid)) == 1
finally:
set_setting("devii_user_daily_usd", "1.0")
def test_quota_warning_skips_below_threshold(local_db):
svc = _ensure_devii()
set_setting("devii_user_daily_usd", "1.0")
try:
uid, _ = _make_user_devii_quota()
_seed_ledger("user", uid, 0.5)
svc.maybe_warn_quota_threshold("user", uid, False)
assert _quota_warnings(uid) == []
finally:
set_setting("devii_user_daily_usd", "1.0")
def test_quota_warning_skips_admin(local_db):
svc = _ensure_devii()
set_setting("devii_admin_daily_usd", "1.0")
try:
uid, _ = _make_user_devii_quota(role="Admin")
_seed_ledger("user", uid, 0.95)
svc.maybe_warn_quota_threshold("user", uid, True)
assert _quota_warnings(uid) == []
finally:
set_setting("devii_admin_daily_usd", "0.0")
def test_quota_warning_skips_unlimited_cap(local_db):
svc = _ensure_devii()
set_setting("devii_user_daily_usd", "0")
try:
uid, _ = _make_user_devii_quota()
_seed_ledger("user", uid, 50.0)
svc.maybe_warn_quota_threshold("user", uid, False)
assert _quota_warnings(uid) == []
finally:
set_setting("devii_user_daily_usd", "1.0")
def test_quota_warning_skips_guests(local_db):
svc = _ensure_devii()
set_setting("devii_guest_daily_usd", "0.10")
try:
guest_id = f"guest-{generate_uid()[:8]}"
_seed_ledger("guest", guest_id, 0.09)
svc.maybe_warn_quota_threshold("guest", guest_id, False)
assert get_table("notifications").find_one(user_uid=guest_id) is None
finally:
set_setting("devii_guest_daily_usd", "0.05")
@@ -0,0 +1,41 @@
# retoor <retoor@molodetz.nl>
import json
from devplacepy.seo_meta_text import plain_seo_defaults
from devplacepy.services.jobs import seo_meta_service
def test_generate_requests_the_preamble_bypass(local_db, monkeypatch):
seen = {}
def fake_gateway_complete(
api_key, system, text, timeout, model=None, bypass_preamble=False
):
seen["bypass_preamble"] = bypass_preamble
return (
json.dumps(
{
"seo_title": "A generated title",
"seo_description": "A generated description that is reasonably long.",
"seo_keywords": "one, two, three, four, five",
}
),
None,
)
monkeypatch.setattr(seo_meta_service, "gateway_complete", fake_gateway_complete)
svc = seo_meta_service.SeoMetaService()
defaults = plain_seo_defaults("Title", "Body text")
totals = seo_meta_service.new_usage_totals()
svc._generate(
"post",
"nonexistent-uid",
{"user_uid": ""},
"Title",
"Body text",
defaults,
totals,
"molodetz",
)
assert seen["bypass_preamble"] is True
@@ -0,0 +1,105 @@
# retoor <retoor@molodetz.nl>
from datetime import datetime, timedelta, timezone
import uuid_utils
from devplacepy.database import get_table
from devplacepy.services.messaging import active_conversation
def _make_user():
uid = str(uuid_utils.uuid7())
get_table("users").insert(
{
"uid": uid,
"username": f"actconv_{uid[:8]}",
"email": f"{uid[:8]}@actconv.test",
"terms_version": "1",
}
)
return uid
def test_touch_writes_marker_then_throttles(local_db):
viewer = _make_user()
other = _make_user()
active_conversation._last_write.pop(f"{viewer}:{other}", None)
active_conversation.touch_active_conversation(viewer, other)
row = get_table("users").find_one(uid=viewer)
assert row["active_conversation_uid"] == other
first_stamp = row["active_conversation_at"]
assert first_stamp
active_conversation.touch_active_conversation(viewer, other)
assert get_table("users").find_one(uid=viewer)["active_conversation_at"] == first_stamp
active_conversation._last_write[f"{viewer}:{other}"] = (
active_conversation.time.monotonic()
- active_conversation.WRITE_THROTTLE_SECONDS
- 1
)
active_conversation.touch_active_conversation(viewer, other)
second_stamp = get_table("users").find_one(uid=viewer)["active_conversation_at"]
assert second_stamp >= first_stamp
def test_touch_is_a_noop_without_both_uids(local_db):
viewer = _make_user()
active_conversation.touch_active_conversation("", "someone")
active_conversation.touch_active_conversation(viewer, "")
row = get_table("users").find_one(uid=viewer)
assert not row.get("active_conversation_uid")
def test_is_actively_viewing_true_when_fresh(local_db):
viewer = _make_user()
other = _make_user()
get_table("users").update(
{
"uid": viewer,
"active_conversation_uid": other,
"active_conversation_at": datetime.now(timezone.utc).isoformat(),
},
["uid"],
)
assert active_conversation.is_actively_viewing(viewer, other) is True
def test_is_actively_viewing_false_when_stale(local_db):
viewer = _make_user()
other = _make_user()
stale = datetime.now(timezone.utc) - timedelta(
seconds=active_conversation.FRESH_SECONDS + 5
)
get_table("users").update(
{
"uid": viewer,
"active_conversation_uid": other,
"active_conversation_at": stale.isoformat(),
},
["uid"],
)
assert active_conversation.is_actively_viewing(viewer, other) is False
def test_is_actively_viewing_false_for_a_different_conversation(local_db):
viewer = _make_user()
other = _make_user()
someone_else = _make_user()
get_table("users").update(
{
"uid": viewer,
"active_conversation_uid": someone_else,
"active_conversation_at": datetime.now(timezone.utc).isoformat(),
},
["uid"],
)
assert active_conversation.is_actively_viewing(viewer, other) is False
def test_is_actively_viewing_false_when_missing_or_unknown(local_db):
assert active_conversation.is_actively_viewing("", "other") is False
assert active_conversation.is_actively_viewing("viewer", "") is False
assert active_conversation.is_actively_viewing(str(uuid_utils.uuid7()), "other") is False
+99 -1
View File
@@ -4,7 +4,24 @@ from datetime import datetime, timezone
from devplacepy.database import get_table
from devplacepy.utils import generate_uid
from devplacepy.services.messaging.persist import stamp_content_revision
from devplacepy.services.messaging.persist import persist_message, stamp_content_revision
def _make_user(prefix):
uid = generate_uid()
get_table("users").insert(
{
"uid": uid,
"username": f"{prefix}_{uid[:8]}",
"email": f"{uid[:8]}@{prefix}.test",
"terms_version": "1",
}
)
return uid
def _sender(uid):
return {"uid": uid, "username": "sender", "role": "Member"}
def test_stamp_content_revision_sets_updated_at(local_db):
@@ -26,3 +43,84 @@ def test_stamp_content_revision_sets_updated_at(local_db):
stored = get_table("messages").find_one(uid=uid)
assert stored["updated_at"] == row["updated_at"]
assert stored["content"] == "hello"
def test_persist_message_dedupes_same_sender_and_client_id(local_db):
sender_uid = _make_user("dedupe_sender")
receiver_uid = _make_user("dedupe_receiver")
sender = _sender(sender_uid)
client_id = f"cid-{generate_uid()}"
first = persist_message(sender, receiver_uid, "hello once", client_id=client_id)
assert first is not None
second = persist_message(sender, receiver_uid, "hello once", client_id=client_id)
assert second is not None
assert second["uid"] == first["uid"]
rows = list(
get_table("messages").find(sender_uid=sender_uid, receiver_uid=receiver_uid)
)
assert len(rows) == 1
def test_persist_message_does_not_dedupe_without_client_id(local_db):
sender_uid = _make_user("nodedupe_sender")
receiver_uid = _make_user("nodedupe_receiver")
sender = _sender(sender_uid)
first = persist_message(sender, receiver_uid, "no client id here")
second = persist_message(sender, receiver_uid, "no client id here")
assert first["uid"] != second["uid"]
rows = list(
get_table("messages").find(sender_uid=sender_uid, receiver_uid=receiver_uid)
)
assert len(rows) == 2
def test_persist_message_does_not_dedupe_a_different_client_id(local_db):
sender_uid = _make_user("diffid_sender")
receiver_uid = _make_user("diffid_receiver")
sender = _sender(sender_uid)
first = persist_message(sender, receiver_uid, "a", client_id="cid-a")
second = persist_message(sender, receiver_uid, "b", client_id="cid-b")
assert first["uid"] != second["uid"]
def test_persist_message_suppresses_notification_when_receiver_is_actively_viewing(local_db):
sender_uid = _make_user("active_sender")
receiver_uid = _make_user("active_receiver")
sender = _sender(sender_uid)
get_table("users").update(
{
"uid": receiver_uid,
"active_conversation_uid": sender_uid,
"active_conversation_at": datetime.now(timezone.utc).isoformat(),
},
["uid"],
)
result = persist_message(sender, receiver_uid, "are you watching?", client_id="cid-watch")
assert result is not None
notifications = list(
get_table("notifications").find(user_uid=receiver_uid, type="message")
)
assert notifications == []
def test_persist_message_notifies_when_receiver_is_not_actively_viewing(local_db):
sender_uid = _make_user("inactive_sender")
receiver_uid = _make_user("inactive_receiver")
sender = _sender(sender_uid)
result = persist_message(sender, receiver_uid, "hello there", client_id="cid-plain")
assert result is not None
notifications = list(
get_table("notifications").find(user_uid=receiver_uid, type="message")
)
assert len(notifications) == 1
@@ -0,0 +1,69 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services.openai_gateway import auth_throttle
def test_is_throttled_false_below_threshold():
auth_throttle.clear()
try:
for _ in range(9):
auth_throttle.record_failure("1.1.1.1", 60)
assert auth_throttle.is_throttled("1.1.1.1", 10, 60) is False
finally:
auth_throttle.clear()
def test_is_throttled_true_at_threshold():
auth_throttle.clear()
try:
for _ in range(10):
auth_throttle.record_failure("2.2.2.2", 60)
assert auth_throttle.is_throttled("2.2.2.2", 10, 60) is True
finally:
auth_throttle.clear()
def test_is_throttled_scoped_per_ip():
auth_throttle.clear()
try:
for _ in range(10):
auth_throttle.record_failure("3.3.3.3", 60)
assert auth_throttle.is_throttled("3.3.3.3", 10, 60) is True
assert auth_throttle.is_throttled("4.4.4.4", 10, 60) is False
finally:
auth_throttle.clear()
def test_record_failure_returns_running_count():
auth_throttle.clear()
try:
assert auth_throttle.record_failure("5.5.5.5", 60) == 1
assert auth_throttle.record_failure("5.5.5.5", 60) == 2
assert auth_throttle.record_failure("5.5.5.5", 60) == 3
finally:
auth_throttle.clear()
def test_throttle_resets_after_window_passes(monkeypatch):
auth_throttle.clear()
try:
now = [1000.0]
monkeypatch.setattr(auth_throttle.time, "time", lambda: now[0])
for _ in range(10):
auth_throttle.record_failure("6.6.6.6", 60)
assert auth_throttle.is_throttled("6.6.6.6", 10, 60) is True
now[0] += 61
assert auth_throttle.is_throttled("6.6.6.6", 10, 60) is False
finally:
auth_throttle.clear()
def test_reset_clears_one_ip():
auth_throttle.clear()
try:
for _ in range(10):
auth_throttle.record_failure("7.7.7.7", 60)
auth_throttle.reset("7.7.7.7")
assert auth_throttle.is_throttled("7.7.7.7", 10, 60) is False
finally:
auth_throttle.clear()
@@ -7,6 +7,7 @@ from devplacepy.database import get_table, set_setting
from devplacepy.utils import generate_uid
import devplacepy.services.openai_gateway.gateway as gwmod
from devplacepy.services.openai_gateway import GatewayService
from devplacepy.services.openai_gateway import auth_throttle
class FakeResp_openai_gateway:
def __init__(
self,
@@ -257,6 +258,7 @@ def test_authorize_static_access_key(local_db):
finally:
set_setting("gateway_access_key", "")
set_setting("gateway_require_auth", "1")
auth_throttle.clear()
def test_authorize_admin_and_user_toggles(local_db):
@@ -282,6 +284,7 @@ def test_authorize_admin_and_user_toggles(local_db):
set_setting("gateway_allow_users", "1")
set_setting("gateway_require_auth", "1")
set_setting("gateway_access_key", "")
auth_throttle.clear()
def test_authorize_require_auth_off_is_open(local_db):
@@ -293,6 +296,124 @@ def test_authorize_require_auth_off_is_open(local_db):
set_setting("gateway_require_auth", "1")
def test_authorize_throttles_repeated_failed_auth_from_one_ip(local_db):
import pytest
from fastapi import HTTPException
set_setting("gateway_require_auth", "1")
set_setting("gateway_access_key", "")
set_setting("gateway_auth_throttle_max_failures", "3")
set_setting("gateway_auth_throttle_window_seconds", "60")
auth_throttle.clear()
try:
svc = GatewayService()
ip = "203.0.113.10"
for _ in range(3):
assert (
svc.authorize(_make_request_openai_gateway(headers={"X-Real-IP": ip}))
is False
)
with pytest.raises(HTTPException) as excinfo:
svc.authorize(_make_request_openai_gateway(headers={"X-Real-IP": ip}))
assert excinfo.value.status_code == 429
assert "Retry-After" in excinfo.value.headers
finally:
set_setting("gateway_auth_throttle_max_failures", "10")
set_setting("gateway_auth_throttle_window_seconds", "60")
set_setting("gateway_require_auth", "1")
auth_throttle.clear()
def test_authorize_throttle_does_not_affect_other_ips(local_db):
set_setting("gateway_require_auth", "1")
set_setting("gateway_access_key", "")
set_setting("gateway_auth_throttle_max_failures", "3")
set_setting("gateway_auth_throttle_window_seconds", "60")
auth_throttle.clear()
try:
svc = GatewayService()
throttled_ip = "203.0.113.20"
other_ip = "203.0.113.21"
for _ in range(3):
svc.authorize(_make_request_openai_gateway(headers={"X-Real-IP": throttled_ip}))
assert auth_throttle.is_throttled(throttled_ip, 3, 60) is True
assert (
svc.authorize(_make_request_openai_gateway(headers={"X-Real-IP": other_ip}))
is False
)
assert auth_throttle.is_throttled(other_ip, 3, 60) is False
finally:
set_setting("gateway_auth_throttle_max_failures", "10")
set_setting("gateway_auth_throttle_window_seconds", "60")
set_setting("gateway_require_auth", "1")
auth_throttle.clear()
def test_authorize_valid_credentials_succeed_from_throttled_ip(local_db):
set_setting("gateway_require_auth", "1")
set_setting("gateway_access_key", "")
set_setting("gateway_allow_users", "1")
set_setting("gateway_auth_throttle_max_failures", "3")
set_setting("gateway_auth_throttle_window_seconds", "60")
auth_throttle.clear()
try:
svc = GatewayService()
ip = "203.0.113.30"
_, member_key = _make_admin_openai_gateway(role="Member")
for _ in range(3):
svc.authorize(_make_request_openai_gateway(headers={"X-Real-IP": ip}))
assert auth_throttle.is_throttled(ip, 3, 60) is True
assert (
svc.authorize(
_make_request_openai_gateway(
headers={"X-Real-IP": ip, "X-API-KEY": member_key}
)
)
is True
)
set_setting("gateway_access_key", "topsecret")
assert (
svc.authorize(
_make_request_openai_gateway(
headers={"X-Real-IP": ip, "X-API-KEY": "topsecret"}
)
)
is True
)
finally:
set_setting("gateway_access_key", "")
set_setting("gateway_auth_throttle_max_failures", "10")
set_setting("gateway_auth_throttle_window_seconds", "60")
set_setting("gateway_require_auth", "1")
auth_throttle.clear()
def test_authorize_throttle_resets_after_window(local_db, monkeypatch):
set_setting("gateway_require_auth", "1")
set_setting("gateway_access_key", "")
set_setting("gateway_auth_throttle_max_failures", "3")
set_setting("gateway_auth_throttle_window_seconds", "60")
auth_throttle.clear()
try:
now = [2000.0]
monkeypatch.setattr(auth_throttle.time, "time", lambda: now[0])
svc = GatewayService()
ip = "203.0.113.40"
for _ in range(3):
svc.authorize(_make_request_openai_gateway(headers={"X-Real-IP": ip}))
assert auth_throttle.is_throttled(ip, 3, 60) is True
now[0] += 61
assert (
svc.authorize(_make_request_openai_gateway(headers={"X-Real-IP": ip}))
is False
)
finally:
set_setting("gateway_auth_throttle_max_failures", "10")
set_setting("gateway_auth_throttle_window_seconds", "60")
set_setting("gateway_require_auth", "1")
auth_throttle.clear()
class FakeEmbedClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
@@ -381,6 +502,36 @@ def test_embeddings_disabled_returns_503(local_db, monkeypatch):
assert resp.status_code == 503
def test_embeddings_missing_key_returns_clean_error_not_unauthenticated_call(
local_db, monkeypatch
):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
set_setting("gateway_api_key", "")
set_setting("gateway_vision_key", "")
set_setting("gateway_embed_key", "")
try:
svc = GatewayService()
cfg = svc.effective_config()
assert cfg["gateway_embed_key"] == ""
cfg["gateway_embed_enabled"] = True
rt = svc.runtime()
resp = run_async(
rt.handle_embeddings(
{"input": "hello"}, cfg, ("guest", "no_key_probe"), "test", "default"
)
)
assert resp.status_code == 503
payload = json.loads(bytes(resp.body).decode())
assert payload["error"]["type"] == "embeddings_not_configured"
assert rt._client is None
finally:
set_setting("gateway_api_key", "")
set_setting("gateway_vision_key", "")
set_setting("gateway_embed_key", "")
class FakeImageClient_openai_gateway:
def __init__(self, *a, **k):
self.calls = []
@@ -1942,3 +2093,121 @@ def test_hostile_upstream_model_header_is_ignored_end_to_end(local_db, monkeypat
)
)
assert resp.headers["X-Gateway-Model"] == "deepseek-chat"
def test_bypass_preamble_skips_the_operator_preamble_when_allowed(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_system_preamble"] = "OPERATOR-PREAMBLE-TEXT"
rt = svc.runtime()
run_async(
rt.handle_chat(
{
"messages": [
{"role": "system", "content": "Reply with strict JSON only."},
{"role": "user", "content": "hi"},
],
"bypass_preamble": True,
},
cfg,
("internal", "devii"),
"test",
"default",
bypass_allowed=True,
)
)
sent = rt._client.calls[-1][1]
system_message = sent["messages"][0]
assert system_message["role"] == "system"
assert "OPERATOR-PREAMBLE-TEXT" not in system_message["content"]
assert "Current date" in system_message["content"]
assert "Reply with strict JSON only." in system_message["content"]
assert "bypass_preamble" not in sent
def test_bypass_preamble_ignored_without_internal_credentials(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_system_preamble"] = "OPERATOR-PREAMBLE-TEXT"
rt = svc.runtime()
run_async(
rt.handle_chat(
{"messages": [{"role": "user", "content": "hi"}], "bypass_preamble": True},
cfg,
("user", "someuser"),
"test",
"default",
bypass_allowed=False,
)
)
sent = rt._client.calls[-1][1]
system_message = sent["messages"][0]
assert "OPERATOR-PREAMBLE-TEXT" in system_message["content"]
assert "bypass_preamble" not in sent
def test_bypass_preamble_keeps_the_clients_own_system_content(local_db, monkeypatch):
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeClient_openai_gateway)
svc = GatewayService()
cfg = svc.effective_config()
cfg["gateway_system_preamble"] = "OPERATOR-PREAMBLE-TEXT"
rt = svc.runtime()
run_async(
rt.handle_chat(
{
"messages": [
{"role": "system", "content": "Grade strictly. Reply JSON only."},
{"role": "user", "content": "hi"},
],
"bypass_preamble": True,
},
cfg,
("internal", "devii"),
"test",
"default",
bypass_allowed=True,
)
)
sent = rt._client.calls[-1][1]
system_message = sent["messages"][0]
assert "OPERATOR-PREAMBLE-TEXT" not in system_message["content"]
assert "Grade strictly. Reply JSON only." in system_message["content"]
def test_internal_bypass_allowed_requires_the_exact_internal_key(local_db):
set_setting("gateway_internal_key", "test-internal-secret")
try:
svc = GatewayService()
cfg = svc.effective_config()
assert (
svc.internal_bypass_allowed(
_make_request_openai_gateway(
headers={"X-Gateway-Internal-Key": "test-internal-secret"}
),
cfg,
)
is True
)
assert (
svc.internal_bypass_allowed(
_make_request_openai_gateway(
headers={"X-Gateway-Internal-Key": "wrong-secret"}
),
cfg,
)
is False
)
assert (
svc.internal_bypass_allowed(
_make_request_openai_gateway(
headers={"Authorization": "Bearer test-internal-secret"}
),
cfg,
)
is False
)
assert svc.internal_bypass_allowed(_make_request_openai_gateway(), cfg) is False
finally:
set_setting("gateway_internal_key", "")
@@ -267,6 +267,32 @@ def test_embed_route_and_kind_isolation(local_db):
_cleanup(["utemb"], ["ut-embed"])
def test_embed_overlay_blank_provider_key_preserves_top_level_key(local_db):
r.provider_store.set(
r.ProviderIn(
name="utemblank",
base_url="https://emb.example/v1/chat/completions",
api_key="",
)
)
r.model_store.set(
r.ModelRouteIn(
source_model="ut-embed-blank",
provider="utemblank",
target_model="vendor/embed",
kind="embed",
)
)
try:
base_cfg = {"gateway_embed_key": "top-level-fallback-key"}
overlay = r.embed_overlay("ut-embed-blank", base_cfg)
assert "gateway_embed_key" not in overlay
merged = {**base_cfg, **overlay}
assert merged["gateway_embed_key"] == "top-level-fallback-key"
finally:
_cleanup(["utemblank"], ["ut-embed-blank"])
def test_inactive_route_ignored(local_db):
r.model_store.set(
r.ModelRouteIn(
+14 -1
View File
@@ -134,8 +134,9 @@ def test_grade_free_text_uses_a_valid_verdict(monkeypatch):
def test_grade_free_text_passes_the_answering_key_through(monkeypatch):
seen = {}
def capture(api_key, system, text, timeout, model=None):
def capture(api_key, system, text, timeout, model=None, bypass_preamble=False):
seen["api_key"] = api_key
seen["bypass_preamble"] = bypass_preamble
return json.dumps({"score": 1.0}), None
monkeypatch.setattr(grading, "gateway_complete", capture)
@@ -143,6 +144,18 @@ def test_grade_free_text_passes_the_answering_key_through(monkeypatch):
assert seen["api_key"] == "member-key"
def test_grade_free_text_bypasses_the_operator_preamble(monkeypatch):
seen = {}
def capture(api_key, system, text, timeout, model=None, bypass_preamble=False):
seen["bypass_preamble"] = bypass_preamble
return json.dumps({"score": 1.0}), None
monkeypatch.setattr(grading, "gateway_complete", capture)
grading.grade_free_text("member-key", QUESTION, "answer")
assert seen["bypass_preamble"] is True
def test_the_fallback_score_is_the_deterministic_overlap():
expected = scoring.token_overlap_score(QUESTION["expected_answer"], "one bucket index")
result = grading.grade_free_text("", QUESTION, "one bucket index")