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