use std::path::{Path, PathBuf};
use tempfile::TempDir;
use trusty_mpm::daemon::managed_routes::inproject;
use trusty_mpm::project::{Project, ProjectRegistry};
use trusty_mpm::session_manager::ManagedSessionId;
use super::{ManagedWorkspace, provision_for_fallback, provision_for_launch};
struct ReposRootGuard {
prev: Option<std::ffi::OsString>,
}
impl ReposRootGuard {
fn set(path: &Path) -> Self {
let prev = std::env::var_os(inproject::REPOS_ROOT_ENV);
unsafe { std::env::set_var(inproject::REPOS_ROOT_ENV, path) };
Self { prev }
}
}
impl Drop for ReposRootGuard {
fn drop(&mut self) {
match self.prev.take() {
Some(v) => unsafe { std::env::set_var(inproject::REPOS_ROOT_ENV, v) },
None => unsafe { std::env::remove_var(inproject::REPOS_ROOT_ENV) },
}
}
}
async fn registry_with(repo_url: &str, worktree: Option<bool>) -> TempDir {
let dir = tempfile::tempdir().unwrap();
let registry = ProjectRegistry::load(dir.path()).await.unwrap();
registry
.register(Project {
name: "fixture".into(),
repo_url: repo_url.into(),
default_branch: "main".into(),
stack_hint: None,
tags: vec![],
description: None,
gh_user: None,
gh_account: None,
github: None,
commit_name: None,
commit_email: None,
worktree,
})
.await
.unwrap();
assert!(
dir.path().join("projects.json").is_file(),
"fixture precondition: projects.json must exist at {}",
dir.path().display()
);
dir
}
fn init_git_repo(path: &Path) {
std::fs::create_dir_all(path).unwrap();
let run = |args: &[&str]| {
let status = std::process::Command::new("git")
.args(args)
.current_dir(path)
.status()
.unwrap_or_else(|e| panic!("git {args:?} failed to spawn: {e}"));
assert!(
status.success(),
"git {args:?} failed in {}",
path.display()
);
};
run(&["init"]);
run(&["config", "user.email", "ci@test.invalid"]);
run(&["config", "user.name", "CI"]);
run(&["commit", "--allow-empty", "-m", "init"]);
}
fn assert_nothing_provisioned(base: &Path) {
assert!(
!base.exists(),
"#4300: base clone dir must NOT be created for an opted-out project: {}",
base.display()
);
assert!(
!base.join(".git").exists(),
"#4300: base clone must NOT be initialised: {}",
base.join(".git").display()
);
assert!(
!base.join(".worktrees").exists(),
"#4300: worktree dir must NOT be created: {}",
base.join(".worktrees").display()
);
}
fn expected_worktree(base: &Path, session_id: &ManagedSessionId) -> PathBuf {
base.join(".worktrees").join(session_id.to_string())
}
#[tokio::test]
async fn provision_for_launch_ignores_a_registered_worktree_true_project() {
let origin = "https://github.invalid/fixture-owner/isolated-repo";
let _registry = registry_with(
"https://github.invalid/fixture-owner/isolated-repo",
Some(true),
)
.await;
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.join("fixture-owner")
.join("isolated-repo");
init_git_repo(&base);
let live = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch(origin, &base, live.path(), false, &session_id)
.await
.unwrap();
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(live.path().to_path_buf()),
"#5274 row 1: a `worktree: true` project must not relocate the session"
);
assert!(
!expected_worktree(&base, &session_id).exists(),
"#5274: no per-session worktree may be created without an explicit request: {}",
expected_worktree(&base, &session_id).display()
);
}
#[tokio::test]
async fn provision_for_launch_without_a_request_uses_the_main_checkout() {
let origin = "git@github.invalid:fixture-owner/optout-repo.git";
let _registry = registry_with(
"https://github.invalid/fixture-owner/optout-repo",
Some(false),
)
.await;
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root.path().join("fixture-owner").join("optout-repo");
let live = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch(origin, &base, live.path(), false, &session_id)
.await
.expect(
"no worktree request must not attempt a clone at all — a clone error \
here means the request was never consulted",
);
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(live.path().to_path_buf()),
"#5274 row 2: a session with no worktree request runs in its own checkout"
);
assert_nothing_provisioned(&base);
assert!(
!expected_worktree(&base, &session_id).exists(),
"#4300: the per-session worktree must NOT exist"
);
}
#[tokio::test]
async fn provision_for_launch_explicit_request_creates_worktree() {
let origin = "https://github.invalid/fixture-owner/isolated-repo";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root
.path()
.join("fixture-owner")
.join("isolated-repo");
init_git_repo(&base);
let live = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch(origin, &base, live.path(), true, &session_id)
.await
.unwrap();
let expected = expected_worktree(&base, &session_id);
assert_eq!(
workspace,
ManagedWorkspace::Worktree(expected.clone()),
"#5274 row 3: an explicit `--worktree` request must provision one"
);
assert!(
expected.is_dir(),
"the worktree directory must exist at {}",
expected.display()
);
}
#[tokio::test]
async fn provision_for_launch_from_subdirectory_targets_repo_root() {
let origin = "https://github.invalid/fixture-owner/optout-repo.git";
let repos_root = tempfile::tempdir().unwrap();
let base = repos_root.path().join("fixture-owner").join("optout-repo");
let live = tempfile::tempdir().unwrap();
init_git_repo(live.path());
let subdir = live.path().join("crates").join("inner");
std::fs::create_dir_all(&subdir).unwrap();
let repo_root = live.path().canonicalize().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_launch(origin, &base, &subdir, false, &session_id)
.await
.expect("no worktree request must not attempt a clone at all");
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(repo_root.clone()),
"`tm launch` from a subdirectory must target the repo ROOT"
);
assert_ne!(
workspace.path(),
subdir.as_path(),
"the subdirectory must NEVER become the deploy target"
);
assert!(
!subdir.join(".claude").exists(),
"nothing may be provisioned inside the subdirectory: {}",
subdir.display()
);
assert_nothing_provisioned(&base);
}
#[tokio::test]
#[serial_test::serial]
async fn provision_for_fallback_opted_out_creates_no_clone_and_no_worktree() {
let origin = "https://github.invalid/fixture-owner/fallback-repo.git";
let registry = registry_with(
"https://github.invalid/fixture-owner/fallback-repo",
Some(false),
)
.await;
let repos_root = tempfile::tempdir().unwrap();
let _guard = ReposRootGuard::set(repos_root.path());
let git_root = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_fallback(registry.path(), origin, git_root.path(), &session_id)
.await
.expect(
"#4300: an opted-out project must not attempt a clone at all — a clone \
error here means the opt-out was never consulted",
);
assert_eq!(
workspace,
ManagedWorkspace::MainCheckout(git_root.path().to_path_buf()),
"#4300: the daemon-unreachable fallback must launch in the repo root"
);
assert_nothing_provisioned(
&repos_root
.path()
.join("fixture-owner")
.join("fallback-repo"),
);
assert_eq!(
std::fs::read_dir(repos_root.path()).unwrap().count(),
0,
"#4300: the managed repos root must stay empty for an opted-out project"
);
}
#[tokio::test]
#[serial_test::serial]
async fn provision_for_fallback_unset_creates_worktree_not_live_checkout() {
let origin = "https://github.invalid/fixture-owner/fallback-repo.git";
let registry = registry_with("https://github.invalid/fixture-owner/fallback-repo", None).await;
let repos_root = tempfile::tempdir().unwrap();
let _guard = ReposRootGuard::set(repos_root.path());
let base = repos_root
.path()
.join("fixture-owner")
.join("fallback-repo");
init_git_repo(&base);
let git_root = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_fallback(registry.path(), origin, git_root.path(), &session_id)
.await
.unwrap();
let expected = expected_worktree(&base, &session_id);
assert_eq!(
workspace,
ManagedWorkspace::Worktree(expected.clone()),
"an unset `worktree` key must still redirect to a protected worktree"
);
assert!(
expected.is_dir(),
"worktree must exist at {}",
expected.display()
);
assert!(
!git_root.path().join(".worktrees").exists(),
"#1724: nothing may be provisioned inside the live checkout"
);
}
#[tokio::test]
#[serial_test::serial]
async fn provision_for_fallback_other_projects_optout_does_not_leak() {
let registry = registry_with(
"https://github.invalid/fixture-owner/optout-repo",
Some(false),
)
.await;
let repos_root = tempfile::tempdir().unwrap();
let _guard = ReposRootGuard::set(repos_root.path());
let base = repos_root
.path()
.join("fixture-owner")
.join("fallback-repo");
init_git_repo(&base);
let git_root = tempfile::tempdir().unwrap();
let session_id = ManagedSessionId::new();
let workspace = provision_for_fallback(
registry.path(),
"https://github.invalid/fixture-owner/fallback-repo.git",
git_root.path(),
&session_id,
)
.await
.unwrap();
let expected = expected_worktree(&base, &session_id);
assert_eq!(
workspace,
ManagedWorkspace::Worktree(expected.clone()),
"an unrelated project's opt-out must not disable isolation here"
);
assert!(
expected.is_dir(),
"worktree must exist at {}",
expected.display()
);
}