use super::*;
use crate::daemon::managed_routes::project_registry_routes::{
RegisterProjectBody, register_project_registry_op,
};
use crate::daemon::mcp_project::project_register;
use crate::project::{AdoptionStore, SkipReason};
use crate::session_manager::worktree_git_fixture::GitWorktreeFixture;
async fn isolated_state() -> Arc<DaemonState> {
let root = tempfile::tempdir().expect("tempdir").keep();
Arc::new(DaemonState::with_root_isolated_managed(root).await)
}
fn adopted(state: &Arc<DaemonState>) -> Vec<(String, String)> {
let store = AdoptionStore::load(&crate::project::registry_data_dir_under(
state.framework_root(),
));
let mut rows: Vec<(String, String)> = store
.entries()
.iter()
.map(|e| {
(
e.project.clone(),
e.path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned(),
)
})
.collect();
rows.sort();
rows
}
fn register_body(name: &str, repo_url: &str) -> RegisterProjectBody {
RegisterProjectBody {
name: name.to_string(),
repo_url: repo_url.to_string(),
default_branch: None,
description: None,
tags: None,
stack_hint: None,
gh_user: None,
gh_account: None,
gh_config_dir: None,
worktree: None,
}
}
#[tokio::test]
async fn parity_register_adopts_the_same_worktrees_across_transports() {
let fx = GitWorktreeFixture::new();
fx.add_worktree("pre-one");
fx.add_worktree("pre-two");
let checkout = fx.repo.to_str().expect("utf8 checkout");
let via_mcp = isolated_state().await;
project_register(
&via_mcp, "gnomish", checkout, None, None, None, None, None, None,
)
.await
.expect("MCP register");
let via_cli = isolated_state().await;
register_project_registry_op(&via_cli, register_body("gnomish", checkout))
.await
.expect("CLI/HTTP register");
let mcp_rows = adopted(&via_mcp);
assert_eq!(mcp_rows.len(), 2, "the MCP transport adopts both worktrees");
assert_eq!(
adopted(&via_cli),
mcp_rows,
"both transports must write the same adoption records"
);
}
#[tokio::test]
async fn register_response_names_a_skipped_worktree() {
let fx = GitWorktreeFixture::new();
fx.add_worktree("pre-one");
let broken = fx.repo.join(".claude").join("worktrees").join("broken");
std::fs::create_dir_all(&broken).expect("broken worktree dir");
let checkout = fx.repo.to_str().expect("utf8 checkout");
let state = isolated_state().await;
let response = register_project_registry_op(&state, register_body("gnomish", checkout))
.await
.expect("register must still succeed");
assert_eq!(response.adoption.recorded, 1);
assert_eq!(response.adoption.skipped, 1);
let skipped = response
.adoption
.unrecorded
.iter()
.find(|u| u.reason == SkipReason::NoGitdirPointer)
.expect("the skipped worktree must be named");
assert!(skipped.path.ends_with("broken"), "got {:?}", skipped.path);
let body = serde_json::to_value(&response).expect("serialize the response");
assert_eq!(body["name"], "gnomish", "the record stays at the top level");
assert_eq!(body["adoption"]["recorded"], 1);
assert_eq!(
body["adoption"]["unrecorded"][0]["reason"],
"no_gitdir_pointer"
);
}
#[tokio::test]
async fn register_project_op_backfills_pre_existing_worktrees_7588() {
let fx = GitWorktreeFixture::new();
fx.add_worktree("pre-one");
fx.add_worktree("pre-two");
let state = isolated_state().await;
crate::daemon::rpc::registry::projects::register_project_op(&state, fx.repo.clone());
let rows = adopted(&state);
let trees: Vec<&str> = rows.iter().map(|(_, t)| t.as_str()).collect();
assert_eq!(
trees,
vec!["pre-one", "pre-two"],
"tm project init must record the checkout's pre-existing worktrees"
);
}
#[tokio::test]
async fn register_project_op_adopts_under_the_owning_projects_name_7588() {
let fx = GitWorktreeFixture::new();
fx.add_worktree("pre-one");
let checkout = fx.repo.to_str().expect("utf8 checkout");
let state = isolated_state().await;
register_project_registry_op(&state, register_body("gnomish", checkout))
.await
.expect("registry B registers");
crate::daemon::rpc::registry::projects::register_project_op(&state, fx.repo.clone());
let rows = adopted(&state);
assert_eq!(rows, vec![("gnomish".to_string(), "pre-one".to_string())]);
}