fix: correct "bugs" to "issues" in routing table and README references across multiple documentation files

This commit is contained in:
2026-06-14 07:48:10 +00:00
parent f79a427f32
commit f2b910ea75
162 changed files with 7011 additions and 1134 deletions
+4 -3
View File
@@ -157,9 +157,10 @@ def test_record_never_raises(monkeypatch):
raise RuntimeError("db exploded")
monkeypatch.setattr(store, "insert_event", boom)
# must swallow the error and return None rather than propagate
assert audit.record_system("test.unit.raise", actor_kind="system") is None
assert audit.record(None, "test.unit.raise2", user=None) is None
# persistence is deferred to the background task queue (inline under tests); a
# failing write is swallowed there and must never propagate into the caller
audit.record_system("test.unit.raise", actor_kind="system")
audit.record(None, "test.unit.raise2", user=None)
def test_record_with_none_request_uses_explicit_user():
+1
View File
@@ -0,0 +1 @@
# retoor <retoor@molodetz.nl>
+96
View File
@@ -0,0 +1,96 @@
# retoor <retoor@molodetz.nl>
import random
from devplacepy.services.bot.config import SEARCH_TERMS
from devplacepy.services.bot.handles import (
MAX_HANDLE_LEN,
MIN_HANDLE_LEN,
apply_leet,
make_handle,
sanitize_handle,
)
SAMPLE_COUNT = 300
def _is_valid_username(handle: str) -> bool:
return (
handle.isascii()
and bool(handle)
and all(character.isalnum() or character in ("-", "_") for character in handle)
)
def test_make_handle_only_valid_username_characters():
generator = random.Random(1)
for _ in range(SAMPLE_COUNT):
handle = make_handle([], generator)
assert _is_valid_username(handle), handle
def test_make_handle_never_contains_dot():
generator = random.Random(2)
for _ in range(SAMPLE_COUNT):
handle = make_handle(["first app", "raspberry pi"], generator)
assert "." not in handle
def test_make_handle_respects_length_bounds():
generator = random.Random(3)
for _ in range(SAMPLE_COUNT):
handle = make_handle([], generator)
assert MIN_HANDLE_LEN <= len(handle) <= MAX_HANDLE_LEN
def test_make_handle_valid_for_every_persona():
generator = random.Random(4)
for persona, interests in SEARCH_TERMS.items():
for _ in range(SAMPLE_COUNT):
handle = make_handle(interests, generator)
assert _is_valid_username(handle), (persona, handle)
assert MIN_HANDLE_LEN <= len(handle) <= MAX_HANDLE_LEN
def test_make_handle_is_deterministic_for_seeded_rng():
first = [make_handle(["rust", "kubernetes"], random.Random(99)) for _ in range(20)]
second = [make_handle(["rust", "kubernetes"], random.Random(99)) for _ in range(20)]
assert first == second
def test_make_handle_produces_variety():
generator = random.Random(5)
handles = {make_handle([], generator) for _ in range(SAMPLE_COUNT)}
assert len(handles) > SAMPLE_COUNT // 2
def test_sanitize_handle_strips_dots_and_invalid_characters():
assert sanitize_handle("john.smith.dev!!") == "johnsmithdev"
assert sanitize_handle("hello world") == "helloworld"
assert sanitize_handle("a@b#c$d") == "abcd"
def test_sanitize_handle_collapses_and_trims_separators():
assert sanitize_handle("__null__byte__") == "null_byte"
assert sanitize_handle("--seg--fault--") == "seg-fault"
def test_sanitize_handle_caps_length():
assert len(sanitize_handle("a" * 50)) == MAX_HANDLE_LEN
def test_sanitize_handle_empty_input():
assert sanitize_handle("...") == ""
assert sanitize_handle("") == ""
def test_apply_leet_only_substitutes_mapped_characters():
generator = random.Random(7)
result = apply_leet("xyzq", generator)
assert result == "xyzq"
def test_apply_leet_can_substitute_known_characters():
always = random.Random(0)
seen = {apply_leet("aeiost", always) for _ in range(50)}
assert any(character.isdigit() for handle in seen for character in handle)
+50
View File
@@ -0,0 +1,50 @@
# retoor <retoor@molodetz.nl>
import json
from devplacepy.services.bot.handles import MAX_HANDLE_LEN
from devplacepy.services.bot.llm import LLMClient
def _client(reply: str) -> LLMClient:
client = LLMClient(
api_key="test",
api_url="http://example.invalid",
model="molodetz",
input_cost_per_1m=0.0,
output_cost_per_1m=0.0,
)
client._raw_call = lambda system, prompt, temperature=1.0: reply
return client
def test_generate_handle_candidates_parses_and_sanitizes():
reply = json.dumps({"handles": ["null_byte", "john.smith", "seg fault!!", "c0d3r"]})
handles = _client(reply).generate_handle_candidates("grumpy_senior")
assert handles == ["null_byte", "johnsmith", "segfault", "c0d3r"]
def test_generate_handle_candidates_dedupes_case_insensitively():
reply = json.dumps({"handles": ["NullByte", "nullbyte", "Daemon", "daemon"]})
handles = _client(reply).generate_handle_candidates("minimalist")
assert [handle.lower() for handle in handles] == ["nullbyte", "daemon"]
def test_generate_handle_candidates_drops_empty_results():
reply = json.dumps({"handles": ["", "...", "!!!", "kernelpanic"]})
handles = _client(reply).generate_handle_candidates("rebel")
assert handles == ["kernelpanic"]
def test_generate_handle_candidates_caps_length():
reply = json.dumps({"handles": ["x" * 40]})
handles = _client(reply).generate_handle_candidates("mentor")
assert len(handles[0]) == MAX_HANDLE_LEN
def test_generate_handle_candidates_handles_bad_json():
assert _client("not json at all").generate_handle_candidates("academic_type") == []
def test_generate_handle_candidates_handles_missing_key():
assert _client(json.dumps({"other": []})).generate_handle_candidates("mentor") == []
+4 -4
View File
@@ -10,7 +10,7 @@ def _msgs(text):
def test_channels_isolate_one_owner(local_db):
store = ConversationStore()
owner = f"chan-{generate_uid()[:8]}"
owner = f"chan-{generate_uid()[-12:]}"
store.save("user", owner, _msgs("main thread"), "main")
store.save("user", owner, _msgs("docs thread"), "docs")
assert store.load("user", owner, "main") == _msgs("main thread")
@@ -19,7 +19,7 @@ def test_channels_isolate_one_owner(local_db):
def test_clear_one_channel_keeps_other(local_db):
store = ConversationStore()
owner = f"chan-{generate_uid()[:8]}"
owner = f"chan-{generate_uid()[-12:]}"
store.save("user", owner, _msgs("main"), "main")
store.save("user", owner, _msgs("docs"), "docs")
store.clear("user", owner, "docs")
@@ -29,7 +29,7 @@ def test_clear_one_channel_keeps_other(local_db):
def test_save_updates_in_place_per_channel(local_db):
store = ConversationStore()
owner = f"chan-{generate_uid()[:8]}"
owner = f"chan-{generate_uid()[-12:]}"
store.save("user", owner, _msgs("first"), "docs")
store.save("user", owner, _msgs("second"), "docs")
assert store.load("user", owner, "docs") == _msgs("second")
@@ -37,7 +37,7 @@ def test_save_updates_in_place_per_channel(local_db):
def test_default_channel_is_main(local_db):
store = ConversationStore()
owner = f"chan-{generate_uid()[:8]}"
owner = f"chan-{generate_uid()[-12:]}"
store.save("user", owner, _msgs("default"))
assert store.load("user", owner) == _msgs("default")
assert store.load("user", owner, "main") == _msgs("default")
+32 -32
View File
@@ -12,13 +12,13 @@ from devplacepy.services.gitea import runtime, store
from devplacepy.services.gitea.config import gitea_config
from devplacepy.services.gitea.enhance import enhance_ticket
from devplacepy.services.gitea.fake import FakeGiteaClient
from devplacepy.services.gitea.service import BugTrackerService
from devplacepy.services.gitea.service import IssueTrackerService
from devplacepy.services.jobs import queue
from devplacepy.services.jobs.bug_create_service import BugCreateService
from devplacepy.services.jobs.issue_create_service import IssueCreateService
from tests.conftest import run_async
_counter_bugs_gitea = [0]
_counter_issues_gitea = [0]
@pytest.fixture(autouse=True)
def _init_db_bugs_gitea():
def _init_db_issues_gitea():
init_db()
yield
@pytest.fixture
@@ -29,18 +29,18 @@ def gitea_env():
set_setting("gitea_owner", "retoor")
set_setting("gitea_repo", "pydevplace")
set_setting("gitea_token", "test-token")
set_setting("bug_ai_enhance", "0")
set_setting("issue_ai_enhance", "0")
yield fake
runtime.set_client(None)
for table in ("bug_tickets", "bug_comment_authors"):
for table in ("issue_tickets", "issue_comment_authors"):
for row in list(get_table(table).find()):
get_table(table).delete(uid=row["uid"])
for row in list(get_table("jobs").find(kind="bug_create")):
for row in list(get_table("jobs").find(kind="issue_create")):
get_table("jobs").delete(uid=row["uid"])
def _make_user_bugs_gitea():
_counter_bugs_gitea[0] += 1
uid = f"bugtest-user-{_counter_bugs_gitea[0]}"
username = f"bugtester{_counter_bugs_gitea[0]}"
def _make_user_issues_gitea():
_counter_issues_gitea[0] += 1
uid = f"issuetest-user-{_counter_issues_gitea[0]}"
username = f"issuetester{_counter_issues_gitea[0]}"
get_table("users").insert(
{
"uid": uid,
@@ -56,17 +56,17 @@ def _unread(user_uid):
return [
n
for n in get_table("notifications").find(user_uid=user_uid)
if n.get("type") == "bug"
if n.get("type") == "issue"
]
def _drive_jobs():
async def run():
svc = BugCreateService()
svc = IssueCreateService()
for _ in range(200):
await svc.run_once()
refresh_snapshot()
pending = [
r
for r in get_table("jobs").find(kind="bug_create")
for r in get_table("jobs").find(kind="issue_create")
if r["status"] in ("pending", "running")
]
if not pending and not svc._inflight:
@@ -75,7 +75,7 @@ def _drive_jobs():
run_async(run())
def _role_user(role: str):
uid, username = _make_user_bugs_gitea()
uid, username = _make_user_issues_gitea()
get_table("users").update({"uid": uid, "role": role}, ["uid"])
return uid, username
def _open_ticket(fake, role: str):
@@ -130,7 +130,7 @@ def test_fake_client_state_and_comments(gitea_env):
def test_store_records_and_maps(gitea_env):
uid, _ = _make_user_bugs_gitea()
uid, _ = _make_user_issues_gitea()
store.record_ticket(7, uid, "orig", "desc", "Better", "https://x/7", "open")
assert store.author_uid_for_issue(7) == uid
assert store.author_map([7, 8]) == {7: uid}
@@ -149,9 +149,9 @@ def test_enhance_fallback_when_disabled(gitea_env):
def test_create_job_files_issue(gitea_env):
uid, username = _make_user_bugs_gitea()
uid, username = _make_user_issues_gitea()
job_uid = queue.enqueue(
"bug_create",
"issue_create",
{"author_uid": uid, "title": "Login broken", "description": "cannot log in"},
"user",
uid,
@@ -171,9 +171,9 @@ def test_create_job_files_issue(gitea_env):
def test_create_job_fails_without_config(gitea_env):
set_setting("gitea_token", "")
uid, _ = _make_user_bugs_gitea()
uid, _ = _make_user_issues_gitea()
job_uid = queue.enqueue(
"bug_create",
"issue_create",
{"author_uid": uid, "title": "x", "description": "y"},
"user",
uid,
@@ -186,45 +186,45 @@ def test_create_job_fails_without_config(gitea_env):
def test_poller_notifies_on_developer_reply(gitea_env):
uid, _ = _make_user_bugs_gitea()
issue = run_async(gitea_env.create_issue("bug", "body"))
uid, _ = _make_user_issues_gitea()
issue = run_async(gitea_env.create_issue("issue", "body"))
number = issue["number"]
store.record_ticket(number, uid, "bug", "body", "bug", issue["html_url"], "open")
store.record_ticket(number, uid, "issue", "body", "issue", issue["html_url"], "open")
store.update_ticket_cache(number, "open", 0)
gitea_env.add_external_comment(number, "developer", "looking into it")
before = len(_unread(uid))
run_async(BugTrackerService().run_once())
run_async(IssueTrackerService().run_once())
refresh_snapshot()
assert len(_unread(uid)) == before + 1
assert int(store.get_ticket(number)["last_comment_count"]) == 1
def test_poller_ignores_local_comment(gitea_env):
uid, _ = _make_user_bugs_gitea()
issue = run_async(gitea_env.create_issue("bug", "body"))
uid, _ = _make_user_issues_gitea()
issue = run_async(gitea_env.create_issue("issue", "body"))
number = issue["number"]
store.record_ticket(number, uid, "bug", "body", "bug", issue["html_url"], "open")
store.record_ticket(number, uid, "issue", "body", "issue", issue["html_url"], "open")
store.update_ticket_cache(number, "open", 0)
comment = run_async(gitea_env.create_comment(number, "me again"))
store.record_comment_author(comment["id"], number, uid)
before = len(_unread(uid))
run_async(BugTrackerService().run_once())
run_async(IssueTrackerService().run_once())
refresh_snapshot()
assert len(_unread(uid)) == before
def test_poller_notifies_on_status_change(gitea_env):
uid, _ = _make_user_bugs_gitea()
issue = run_async(gitea_env.create_issue("bug", "body"))
uid, _ = _make_user_issues_gitea()
issue = run_async(gitea_env.create_issue("issue", "body"))
number = issue["number"]
store.record_ticket(number, uid, "bug", "body", "bug", issue["html_url"], "open")
store.record_ticket(number, uid, "issue", "body", "issue", issue["html_url"], "open")
store.update_ticket_cache(number, "open", 0)
run_async(gitea_env.set_state(number, "closed"))
before = len(_unread(uid))
run_async(BugTrackerService().run_once())
run_async(IssueTrackerService().run_once())
refresh_snapshot()
assert len(_unread(uid)) == before + 1
assert store.get_ticket(number)["last_status"] == "closed"
@@ -0,0 +1,62 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services.jobs.deepsearch.pdf import extract_pdf_text, is_pdf
def _make_pdf(message: str) -> bytes:
content = b"BT /F1 24 Tf 72 720 Td (" + message.encode("latin-1") + b") Tj ET"
bodies = [
b"<< /Type /Catalog /Pages 2 0 R >>",
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] "
b"/Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>",
b"<< /Length " + str(len(content)).encode() + b" >>\nstream\n" + content + b"\nendstream",
b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
]
pdf = bytearray(b"%PDF-1.4\n")
offsets: list[int] = []
for index, body in enumerate(bodies, start=1):
offsets.append(len(pdf))
pdf += str(index).encode() + b" 0 obj\n" + body + b"\nendobj\n"
xref_pos = len(pdf)
pdf += b"xref\n0 " + str(len(bodies) + 1).encode() + b"\n"
pdf += b"0000000000 65535 f \n"
for offset in offsets:
pdf += ("%010d 00000 n \n" % offset).encode()
pdf += b"trailer\n<< /Size " + str(len(bodies) + 1).encode() + b" /Root 1 0 R >>\n"
pdf += b"startxref\n" + str(xref_pos).encode() + b"\n%%EOF"
return bytes(pdf)
def test_is_pdf_detects_content_type():
assert is_pdf("application/pdf", "https://example.com/file", b"") is True
assert is_pdf("application/x-pdf; charset=binary", "https://example.com/file", b"") is True
def test_is_pdf_detects_url_suffix():
assert is_pdf("text/html", "https://example.com/report.PDF", b"<html>") is True
assert is_pdf("", "https://example.com/docs/spec.pdf?v=2", b"") is True
def test_is_pdf_detects_magic_bytes():
assert is_pdf("application/octet-stream", "https://example.com/download", b"%PDF-1.7") is True
def test_is_pdf_rejects_html():
assert is_pdf("text/html", "https://example.com/page", b"<!DOCTYPE html>") is False
def test_extract_pdf_text_returns_content():
title, text = extract_pdf_text(_make_pdf("Hello DeepSearch PDF indexing"))
assert "Hello DeepSearch PDF indexing" in text
def test_extract_pdf_text_rejects_non_pdf():
assert extract_pdf_text(b"this is not a pdf at all") == ("", "")
assert extract_pdf_text(b"") == ("", "")
def test_extract_pdf_text_handles_corrupt_pdf():
title, text = extract_pdf_text(b"%PDF-1.4\ngarbage that is not valid pdf structure")
assert title == ""
assert text == ""
+1 -1
View File
@@ -16,7 +16,7 @@ def _init_db_fork_jobs():
@pytest.fixture
def fork_env(tmp_path, monkeypatch):
monkeypatch.setattr(
"devplacepy.services.jobs.fork_service.STAGING_DIR", tmp_path / "staging"
"devplacepy.services.jobs.fork_service.FORK_STAGING_DIR", tmp_path / "staging"
)
monkeypatch.setattr("devplacepy.project_files.PROJECT_FILES_DIR", tmp_path / "pf")
yield tmp_path
+1 -1
View File
@@ -22,7 +22,7 @@ def zip_env(tmp_path, monkeypatch):
"devplacepy.services.jobs.zip_service.ZIPS_DIR", tmp_path / "zips"
)
monkeypatch.setattr(
"devplacepy.services.jobs.zip_service.STAGING_DIR", tmp_path / "staging"
"devplacepy.services.jobs.zip_service.ZIP_STAGING_DIR", tmp_path / "staging"
)
monkeypatch.setattr("devplacepy.project_files.PROJECT_FILES_DIR", tmp_path / "pf")
yield tmp_path