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
+1 -1
View File
@@ -21,7 +21,7 @@ def _session_content_negotiation(password="secret123"):
allow_redirects=True,
)
return s, name
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/bugs"]
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/issues"]
def test_admin_pages_negotiate(seeded_db):
+39 -27
View File
@@ -85,6 +85,15 @@ def _call(endpoint, headers):
elif data:
kwargs["data"] = data
return requests.request(endpoint["method"], f"{BASE_URL}{path}", **kwargs)
def _clear_probe_jobs():
from devplacepy.database import refresh_snapshot
refresh_snapshot()
jobs = get_table("jobs")
for row in list(jobs.find()):
jobs.delete(uid=row["uid"])
def _redirects_to(response, target):
return response.status_code in (
302,
@@ -110,38 +119,41 @@ def test_documented_minimal_role_matches_enforcement(seeded_db):
assert len(endpoints) >= 50
failures = []
for endpoint in endpoints:
auth = endpoint["auth"]
label = (
f"{endpoint['method']} {endpoint['path']} ({endpoint['id']}, doc={auth})"
)
anon = _call(endpoint, JSON_auth_matrix)
if auth == "public":
if not _is_allowed(anon):
failures.append(
f"{label}: public but anonymous was rejected ({anon.status_code})"
)
continue
if not _is_auth_rejected(anon):
failures.append(
f"{label}: requires {auth} but anonymous was NOT rejected ({anon.status_code})"
try:
for endpoint in endpoints:
auth = endpoint["auth"]
label = (
f"{endpoint['method']} {endpoint['path']} ({endpoint['id']}, doc={auth})"
)
anon = _call(endpoint, JSON_auth_matrix)
if auth == "user" and endpoint["method"] == "GET":
mem = _call(endpoint, member)
if not _is_allowed(mem):
if auth == "public":
if not _is_allowed(anon):
failures.append(
f"{label}: public but anonymous was rejected ({anon.status_code})"
)
continue
if not _is_auth_rejected(anon):
failures.append(
f"{label}: documented user but a member was rejected ({mem.status_code})"
f"{label}: requires {auth} but anonymous was NOT rejected ({anon.status_code})"
)
if auth == "admin":
mem = _call(endpoint, member)
if not _is_role_rejected(mem):
failures.append(
f"{label}: documented admin but a non-admin member was NOT rejected ({mem.status_code})"
)
if auth == "user" and endpoint["method"] == "GET":
mem = _call(endpoint, member)
if not _is_allowed(mem):
failures.append(
f"{label}: documented user but a member was rejected ({mem.status_code})"
)
if auth == "admin":
mem = _call(endpoint, member)
if not _is_role_rejected(mem):
failures.append(
f"{label}: documented admin but a non-admin member was NOT rejected ({mem.status_code})"
)
finally:
_clear_probe_jobs()
assert not failures, "Auth enforcement does not match documentation:\n" + "\n".join(
failures
+1 -1
View File
@@ -21,7 +21,7 @@ def _session_content_negotiation(password="secret123"):
allow_redirects=True,
)
return s, name
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/bugs"]
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/issues"]
def test_public_pages_serve_json_and_html(app_server):
+20
View File
@@ -340,3 +340,23 @@ def test_x_robots_tag_header(app_server):
def test_x_content_type_options_header(app_server):
r = requests.get(f"{BASE_URL}/feed", allow_redirects=True)
assert r.headers.get("X-Content-Type-Options") == "nosniff"
def test_strict_transport_security_header(app_server):
r = requests.get(f"{BASE_URL}/feed", allow_redirects=True)
assert "includeSubDomains" in r.headers.get("Strict-Transport-Security", "")
def test_referrer_policy_header(app_server):
r = requests.get(f"{BASE_URL}/feed", allow_redirects=True)
assert r.headers.get("Referrer-Policy") == "strict-origin-when-cross-origin"
def test_x_frame_options_header(app_server):
r = requests.get(f"{BASE_URL}/feed", allow_redirects=True)
assert r.headers.get("X-Frame-Options") == "DENY"
def test_x_frame_options_excluded_for_ingress_proxy(app_server):
r = requests.get(f"{BASE_URL}/p/nonexistent-instance", allow_redirects=False)
assert "X-Frame-Options" not in r.headers
@@ -124,13 +124,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
@@ -141,20 +141,20 @@ 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 key in ("gitea_base_url", "gitea_owner", "gitea_repo", "gitea_token"):
set_setting(key, "")
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,
@@ -170,17 +170,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:
@@ -189,7 +189,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):
@@ -214,14 +214,14 @@ def _open_ticket(fake, role: str):
return number, client
def test_bug_create_recorded(seeded_db):
# Filing is async + Gitea-gated: POST /bugs/create only enqueues a job (returns
# {uid, status_url}) and records the synchronous "bug.create.request" event; the
# final "bug.create" fires from BugCreateService, which the tests never run
def test_issue_create_recorded(seeded_db):
# Filing is async + Gitea-gated: POST /issues/create only enqueues a job (returns
# {uid, status_url}) and records the synchronous "issue.create.request" event; the
# final "issue.create" fires from IssueCreateService, which the tests never run
# (DEVPLACE_DISABLE_SERVICES=1). Configure Gitea so the enqueue path is taken
# (no network call happens here) and assert the request event. Restore after.
s, _ = _member()
title = _unique("aubug")
title = _unique("auissue")
for key, value in {
"gitea_base_url": "https://gitea.test",
"gitea_owner": "o",
@@ -231,9 +231,9 @@ def test_bug_create_recorded(seeded_db):
set_setting(key, value)
try:
resp = s.post(
f"{BASE_URL}/bugs/create",
f"{BASE_URL}/issues/create",
headers=JSON_audit_log,
data={"title": title, "description": "audited bug description text"},
data={"title": title, "description": "audited issue description text"},
)
assert resp.status_code == 200, resp.text[:300]
assert resp.json().get("uid")
@@ -241,7 +241,7 @@ def test_bug_create_recorded(seeded_db):
assert (
_find(
admin,
"bug.create.request",
"issue.create.request",
lambda e: title in (e.get("summary") or ""),
)
is not None
@@ -251,24 +251,24 @@ def test_bug_create_recorded(seeded_db):
set_setting(key, "")
def test_bug_detail_renders_for_member(gitea_env):
def test_issue_detail_renders_for_member(gitea_env):
number, client = _open_ticket(gitea_env, "Member")
html = client.get(f"/bugs/{number}")
html = client.get(f"/issues/{number}")
assert html.status_code == 200, html.text[:300]
assert "Submit does nothing" in html.text
assert "Close ticket" not in html.text
data = client.get(
f"/bugs/{number}", headers={"Accept": "application/json"}
f"/issues/{number}", headers={"Accept": "application/json"}
).json()
assert data["viewer_is_admin"] is False
def test_bug_detail_renders_for_admin(gitea_env):
def test_issue_detail_renders_for_admin(gitea_env):
number, client = _open_ticket(gitea_env, "Admin")
html = client.get(f"/bugs/{number}")
html = client.get(f"/issues/{number}")
assert html.status_code == 200, html.text[:300]
assert "Close ticket" in html.text
data = client.get(
f"/bugs/{number}", headers={"Accept": "application/json"}
f"/issues/{number}", headers={"Accept": "application/json"}
).json()
assert data["viewer_is_admin"] is True
+1 -1
View File
@@ -131,7 +131,7 @@ def _session_content_negotiation(password="secret123"):
allow_redirects=True,
)
return s, name
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/bugs"]
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/issues"]
def test_security_authz_denied_recorded(seeded_db):
+1 -1
View File
@@ -131,7 +131,7 @@ def _session_content_negotiation(password="secret123"):
allow_redirects=True,
)
return s, name
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/bugs"]
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/issues"]
_counter_polls = [0]
AJAX_polls = {"X-Requested-With": "fetch"}
def _session_polls():
+1 -1
View File
@@ -21,7 +21,7 @@ def _session_content_negotiation(password="secret123"):
allow_redirects=True,
)
return s, name
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/bugs"]
PUBLIC_PAGES = ["/feed", "/projects", "/gists", "/news", "/leaderboard", "/issues"]
def test_json_404_error_envelope(app_server):