This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/crates/maestro-daemon/tests/branches.rs
T

143 lines
4.0 KiB
Rust

//! Integration tests for `GET /api/branches` (#55).
mod common;
use std::net::{Ipv4Addr, SocketAddr};
use std::path::Path;
use std::process::Command;
use std::sync::Arc;
use common::DaemonFixture;
use maestro_daemon::local_api;
use tokio::net::TcpListener;
use tokio::sync::oneshot;
struct Harness {
addr: SocketAddr,
_shutdown: oneshot::Sender<()>,
_fx: DaemonFixture,
}
async fn spawn() -> Harness {
let fx = DaemonFixture::new();
let presets = maestro_config::SpawnSection::default().resolve().unwrap();
let router = local_api::router(local_api::state_for(
fx.manager.clone(),
"naps-laptop",
"/home/test",
presets,
Arc::new(maestro_daemon::projects::ProjectScanner::disabled()),
std::sync::Arc::new(fx.retained_store()),
));
let listener = TcpListener::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
.await
.unwrap();
let addr = listener.local_addr().unwrap();
let (tx, rx) = oneshot::channel::<()>();
tokio::spawn(async move {
let _ = axum::serve(listener, router)
.with_graceful_shutdown(async move {
let _ = rx.await;
})
.await;
});
Harness {
addr,
_shutdown: tx,
_fx: fx,
}
}
fn git(repo: &Path, args: &[&str]) {
let out = Command::new("git")
.arg("-C")
.arg(repo)
.args(args)
.output()
.unwrap();
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
}
/// A real repo on `main` with a second branch `feature`, and a linked
/// worktree at `<tmp>/wt-feature` holding `feature`.
fn repo_with_occupied_branch() -> tempfile::TempDir {
let tmp = tempfile::tempdir().unwrap();
let repo = tmp.path();
git(repo, &["init", "-q", "-b", "main"]);
git(
repo,
&[
"-c",
"user.email=t@t",
"-c",
"user.name=t",
"commit",
"-qm",
"init",
"--allow-empty",
],
);
git(repo, &["branch", "feature"]);
let wt = tmp.path().join("wt-feature");
git(repo, &["worktree", "add", &wt.to_string_lossy(), "feature"]);
tmp
}
async fn get_branches(addr: SocketAddr, path: &Path) -> Vec<maestro_protocol::BranchInfo> {
reqwest::get(format!(
"http://{addr}/api/branches?path={}",
urlencoding::encode(&path.to_string_lossy())
))
.await
.unwrap()
.json()
.await
.unwrap()
}
/// #55: every local branch is listed; the main checkout's HEAD reports
/// `is_head`, and a branch held by a linked worktree reports that worktree's
/// path so the client can explain why the entry is disabled.
#[tokio::test]
async fn branches_lists_local_branches_and_worktree_occupancy() {
let tmp = repo_with_occupied_branch();
let h = spawn().await;
let mut branches = get_branches(h.addr, tmp.path()).await;
branches.sort_by(|a, b| a.name.cmp(&b.name));
assert_eq!(
branches.iter().map(|b| b.name.as_str()).collect::<Vec<_>>(),
["feature", "main"]
);
let main = branches.iter().find(|b| b.name == "main").unwrap();
assert!(main.is_head, "main is the main checkout's HEAD");
assert_eq!(main.worktree, None, "HEAD occupancy rides `is_head`");
let feature = branches.iter().find(|b| b.name == "feature").unwrap();
assert!(!feature.is_head);
assert_eq!(
feature.worktree.as_deref(),
Some(tmp.path().join("wt-feature").as_path()),
"the linked worktree holding the branch must be reported"
);
}
/// #55: a path outside any repository is not an error — an empty list,
/// matching how `/api/gitroot` reports "not a repository".
#[tokio::test]
async fn branches_returns_empty_for_a_path_outside_a_repository() {
let h = spawn().await;
let tmp = tempfile::tempdir().unwrap();
let plain = tmp.path().join("plain");
std::fs::create_dir_all(&plain).unwrap();
let branches = get_branches(h.addr, &plain).await;
assert!(branches.is_empty());
}