//! End-to-end tests: build real git repositories in a temp dir, then check //! the HTML pages and do a real `git clone` against a running server. use axum::body::Body; use axum::http::{Request, StatusCode}; use http_body_util::BodyExt; use rustforge::config::Config; use rustforge::web::{router, AppState}; use std::path::Path; use std::process::Command; use tower::ServiceExt; // for `oneshot` /// Run a git command in `dir`, panicking with its output if it fails. fn git(dir: &Path, args: &[&str]) -> String { let out = Command::new("git") .args([ "-c", "user.name=Test User", "-c", "user.email=test@example.com", "-c", "init.defaultBranch=main", ]) .args(args) .current_dir(dir) .env("GIT_CONFIG_NOSYSTEM", "1") .env("GIT_AUTHOR_DATE", "2026-01-01T12:00:00Z") .env("GIT_COMMITTER_DATE", "2026-01-01T12:00:00Z") .output() .expect("git is installed"); assert!(out.status.success(), "git {args:?} failed: {}", String::from_utf8_lossy(&out.stderr)); String::from_utf8_lossy(&out.stdout).into_owned() } /// A temp dir holding: `work/` (normal repo, 56 commits) and `bare.git`. struct Fixture { dir: tempfile::TempDir, head: String, change_commit: String, } fn fixture() -> Fixture { let dir = tempfile::tempdir().unwrap(); let work = dir.path().join("work"); std::fs::create_dir(&work).unwrap(); git(&work, &["init", "-q"]); std::fs::write( work.join("README.md"), "# Hello Readme\n\nSome **bold** text.\n\n\n", ) .unwrap(); std::fs::create_dir(work.join("src")).unwrap(); std::fs::write(work.join("src/main.rs"), "fn main() {\n println!(\"old line\");\n}\n").unwrap(); std::fs::write(work.join("src/odd name #1.txt"), "spaces & hashes \n").unwrap(); git(&work, &["add", "."]); git(&work, &["commit", "-q", "-m", "initial commit"]); // 54 filler commits so the log has more than one page (50 per page). for i in 0..54 { git(&work, &["commit", "-q", "--allow-empty", "-m", &format!("filler {i}")]); } std::fs::write(work.join("src/main.rs"), "fn main() {\n println!(\"new line\");\n}\n").unwrap(); git(&work, &["commit", "-q", "-am", "change "]); let change_commit = git(&work, &["rev-parse", "HEAD"]).trim().to_string(); git(&work, &["tag", "v1.0"]); git(&work, &["branch", "feature/x"]); let head = change_commit.clone(); git(dir.path(), &["clone", "-q", "--bare", "work", "bare.git"]); Fixture { dir, head, change_commit } } fn state_for(f: &Fixture) -> rustforge::web::Shared { let toml = format!( r#" [server] site_title = "test site" clone_base_url = "http://example.test" [[repo]] name = "work" path = "{}" description = "non-bare repo" [[repo]] name = "bare" path = "{}" description = "a bare repo" "#, f.dir.path().join("work").display(), f.dir.path().join("bare.git").display(), ); let cfg = Config::from_toml(&toml).unwrap(); let repos = cfg.validate().unwrap(); AppState::new(cfg.server, repos) } /// Make one request against the router (no network) and return status + body. async fn get(f: &Fixture, uri: &str) -> (StatusCode, String) { let app = router(state_for(f)); let res = app.oneshot(Request::get(uri).body(Body::empty()).unwrap()).await.unwrap(); let status = res.status(); let bytes = res.into_body().collect().await.unwrap().to_bytes(); (status, String::from_utf8_lossy(&bytes).into_owned()) } #[tokio::test] async fn index_lists_repos_and_escapes_descriptions() { let f = fixture(); let (status, body) = get(&f, "/").await; assert_eq!(status, StatusCode::OK); assert!(body.contains("href=\"/work/\"")); assert!(body.contains("href=\"/bare/\"")); assert!(body.contains("non-bare <b>repo</b>")); assert!(!body.contains("repo")); assert!(body.contains("ago"), "idle column should show an age"); assert!(body.contains("test site")); assert!(body.contains("generated by RustForge v")); let (_, body) = get(&f, "/work/log").await; assert!(body.contains("log - work - test site"), "{body}"); } #[tokio::test] async fn summary_shows_refs_commits_and_safe_readme() { let f = fixture(); for repo in ["work", "bare"] { let (status, body) = get(&f, &format!("/{repo}/")).await; assert_eq!(status, StatusCode::OK, "{repo}"); assert!(body.contains("feature/x"), "branch listed"); assert!(body.contains("v1.0"), "tag listed"); assert!(body.contains("change <greeting>"), "commit subject escaped"); assert!(body.contains("

Hello Readme

"), "README rendered"); assert!(body.contains("bold")); assert!(!body.contains("