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
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]