Return 404 for unknown repos in web view handlers
All three handlers (repo_redirect, run_list, run_detail) now check
repo existence via Repo::exists() after name validation. Previously
well-formed but nonexistent repo names returned 200 (empty list) or
a redirect instead of 404. Updated tests to assert the new status.

Assisted-by: GLM-5.1 via pi
change znztvvvvzuwnpykvrwlozskkxlxzomrm
commit 0bbd25badbd5e8f66bfdf67c26a78474befac013
author Alpha Chen <alpha@kejadlen.dev>
date
parent otylxppv
diff --git a/src/bin/quire/commands/dev.rs b/src/bin/quire/commands/dev.rs
index bb5b5fe..024ffa5 100644
--- a/src/bin/quire/commands/dev.rs
+++ b/src/bin/quire/commands/dev.rs
@@ -25,6 +25,12 @@ pub fn seed() -> Result<Quire> {
     tracing::info!(path = %base_dir.display(), "seeded tempdir");
 
     let quire = Quire::new(base_dir);
+
+    // Create the repos dir + a bare repo so the web view resolves the repo.
+    let bare_repo = quire.repos_dir().join("example.git");
+    fs_err::create_dir_all(&bare_repo)
+        .into_diagnostic()
+        .context("failed to create bare repo dir")?;
     let db_path = quire.db_path();
 
     // Open and migrate.
diff --git a/src/quire/web/handlers.rs b/src/quire/web/handlers.rs
index 62c18d8..207546c 100644
--- a/src/quire/web/handlers.rs
+++ b/src/quire/web/handlers.rs
@@ -10,8 +10,16 @@ use super::templates::*;
 use crate::Quire;
 use crate::error::display_chain;
 
-pub async fn repo_redirect(AxumPath(repo): AxumPath<String>) -> Redirect {
-    Redirect::temporary(&format!("/{}/ci", repo.trim_end_matches(".git")))
+pub async fn repo_redirect(
+    State(quire): State<Quire>,
+    AxumPath(repo): AxumPath<String>,
+) -> Response {
+    let repo_name = db::resolve_repo_name(&repo);
+    match quire.repo(&repo_name) {
+        Ok(r) if r.exists() => {}
+        _ => return StatusCode::NOT_FOUND.into_response(),
+    }
+    Redirect::temporary(&format!("/{}/ci", repo.trim_end_matches(".git"))).into_response()
 }
 
 /// Render a template into an HTML response, returning 500 on render failure.
@@ -76,9 +84,10 @@ fn render_error(repo: String, status: StatusCode, title: &str, detail: String) -
 pub async fn run_list(State(quire): State<Quire>, AxumPath(repo): AxumPath<String>) -> Response {
     let repo_display = repo.trim_end_matches(".git").to_string();
     let repo_name = db::resolve_repo_name(&repo);
-    if quire.repo(&repo_name).is_err() {
-        return StatusCode::NOT_FOUND.into_response();
-    }
+    match quire.repo(&repo_name) {
+        Ok(r) if r.exists() => {}
+        _ => return StatusCode::NOT_FOUND.into_response(),
+    };
 
     let runs = match db::load_runs(&quire, &repo_name) {
         Ok(r) => r,
@@ -120,7 +129,11 @@ pub async fn run_detail(
 ) -> Response {
     let repo_display = repo.trim_end_matches(".git").to_string();
     let repo_name = db::resolve_repo_name(&repo);
-    if quire.repo(&repo_name).is_err() || !db::is_valid_run_id(&run_id) {
+    match quire.repo(&repo_name) {
+        Ok(r) if r.exists() => {}
+        _ => return StatusCode::NOT_FOUND.into_response(),
+    };
+    if !db::is_valid_run_id(&run_id) {
         return StatusCode::NOT_FOUND.into_response();
     }
 
@@ -340,7 +353,7 @@ mod tests {
     }
 
     #[tokio::test]
-    async fn run_list_returns_empty_for_unknown_repo() {
+    async fn run_list_returns_404_for_unknown_repo() {
         let env = TestEnv::new();
         let app = env.app();
         let req = Request::builder()
@@ -348,8 +361,7 @@ mod tests {
             .body(Body::empty())
             .unwrap();
         let resp = app.oneshot(req).await.unwrap();
-        // Unknown repo still has a valid name — returns empty run list.
-        assert_eq!(resp.status(), StatusCode::OK);
+        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
     }
 
     #[tokio::test]