From 856fc1cfa087874d0702513542936f5781481f9d Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 26 Apr 2026 02:36:27 +0200 Subject: [PATCH] guarded repository delete flow --- src/repo_registry/web_ui/views.py | 28 ++++++++++++++++++++++++++-- tests/test_web_api.py | 17 +++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/repo_registry/web_ui/views.py b/src/repo_registry/web_ui/views.py index 8ed4a01..4d6dbaa 100644 --- a/src/repo_registry/web_ui/views.py +++ b/src/repo_registry/web_ui/views.py @@ -3,10 +3,11 @@ from __future__ import annotations from dataclasses import asdict from html import escape -from fastapi import APIRouter, Depends, Form +from fastapi import APIRouter, Depends, Form, HTTPException from fastapi.responses import HTMLResponse, RedirectResponse from repo_registry.core.service import RegistryService +from repo_registry.storage.sqlite import NotFoundError from repo_registry.web_api.app import get_service @@ -264,7 +265,10 @@ def repository_detail( repository_id: int, service: RegistryService = Depends(get_service), ) -> HTMLResponse: - repository = service.get_repository(repository_id) + try: + repository = service.get_repository(repository_id) + except NotFoundError as exc: + raise HTTPException(status_code=404, detail=str(exc)) from exc runs = service.list_analysis_runs(repository_id) ability_map = service.ability_map(repository_id) decisions = service.list_review_decisions(repository_id) @@ -358,6 +362,13 @@ def repository_detail(

Review Decisions

{render_review_decisions(decisions)} +
+

Delete Repository

+
+ + +
+
""" return page(repository.name, body) @@ -379,6 +390,19 @@ def edit_repository_from_form( return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303) +@router.post("/ui/repos/{repository_id}/delete") +def delete_repository_from_form( + repository_id: int, + confirm_name: str = Form(...), + service: RegistryService = Depends(get_service), +) -> RedirectResponse: + repository = service.get_repository(repository_id) + if confirm_name != repository.name: + return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303) + service.delete_repository(repository_id) + return RedirectResponse("/ui", status_code=303) + + @router.post("/ui/repos/{repository_id}/abilities") def create_ability_from_form( repository_id: int, diff --git a/tests/test_web_api.py b/tests/test_web_api.py index 3bd6bd5..8a67465 100644 --- a/tests/test_web_api.py +++ b/tests/test_web_api.py @@ -594,6 +594,23 @@ def test_ui_manual_registry_entry_loop(tmp_path): detail_response = client.get(repository_path) assert "Edited Manual API" not in detail_response.text assert "tests/test_manual.py" not in detail_response.text + + failed_delete_response = client.post( + f"{repository_path}/delete", + data={"confirm_name": "wrong name"}, + follow_redirects=False, + ) + assert failed_delete_response.status_code == 303 + assert client.get(repository_path).status_code == 200 + + delete_repository_response = client.post( + f"{repository_path}/delete", + data={"confirm_name": "Manual Repo"}, + follow_redirects=False, + ) + assert delete_repository_response.status_code == 303 + assert delete_repository_response.headers["location"] == "/ui" + assert client.get(repository_path).status_code == 404 finally: app.dependency_overrides.clear()