use super::*;
use std::fs;
use std::path::PathBuf;
fn scratch_dir(tag: &str) -> PathBuf {
let pid = std::process::id();
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let p = std::env::temp_dir().join(format!("trusty-search-index-{tag}-{pid}-{nanos}"));
let _ = fs::remove_dir_all(&p);
p
}
#[test]
fn ensure_project_indexed_withholds_id_when_nothing_was_registered() {
let _guard = crate::data_dir::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let data_dir = scratch_dir("data");
fs::create_dir_all(&data_dir).unwrap();
unsafe {
std::env::set_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV, &data_dir);
}
let project = scratch_dir("git");
fs::create_dir_all(project.join(".git")).unwrap();
let nested = project.join("crates/inner");
fs::create_dir_all(&nested).unwrap();
let report = ensure_project_indexed_reporting(
&nested,
IndexOptions::default().with_allow_sensitive_path(true),
);
let pinnable = ensure_project_indexed(&nested, true);
let expected = crate::derive_index_id(&project);
unsafe {
std::env::remove_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV);
}
let _ = fs::remove_dir_all(&project);
let _ = fs::remove_dir_all(&data_dir);
assert_eq!(
report.index_id,
Some(expected),
"id is the git-root basename"
);
assert_ne!(
report.registration,
IndexRegistration::Confirmed,
"no daemon was contacted, so nothing can be confirmed"
);
assert_eq!(
pinnable, None,
"an unregistered index must not come back as a pinnable id (#5091)"
);
}
#[test]
fn reporting_says_skipped_under_test_harness() {
let _guard = crate::data_dir::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let project = scratch_dir("report-skip");
fs::create_dir_all(project.join(".git")).unwrap();
let report = ensure_project_indexed_reporting(&project, IndexOptions::default());
let _ = fs::remove_dir_all(&project);
assert_eq!(
report.registration,
IndexRegistration::SkippedUnderTest,
"a test process suppresses the write (#4255) and must say so"
);
assert!(
report.index_id.is_some(),
"the id is still returned — the fail-open contract is unchanged"
);
}
#[test]
fn reporting_says_daemon_unreachable_when_no_daemon_is_discoverable() {
let _guard = crate::data_dir::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let data_dir = scratch_dir("report-nodaemon-data");
fs::create_dir_all(&data_dir).unwrap();
let project = scratch_dir("report-nodaemon");
fs::create_dir_all(project.join(".git")).unwrap();
unsafe {
std::env::set_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV, &data_dir);
std::env::set_var(crate::test_harness::ALLOW_PRODUCTION_ENV, "1");
}
let report = ensure_project_indexed_reporting(&project, IndexOptions::default());
unsafe {
std::env::remove_var(crate::test_harness::ALLOW_PRODUCTION_ENV);
std::env::remove_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV);
}
let _ = fs::remove_dir_all(&project);
let _ = fs::remove_dir_all(&data_dir);
assert_eq!(
report.registration,
IndexRegistration::DaemonUnreachable,
"no address file means nothing was sent — that is not a registration"
);
assert!(report.index_id.is_some(), "the id is still returned");
}
#[test]
fn ensure_project_indexed_none_for_root() {
assert_eq!(ensure_project_indexed(Path::new("/"), true), None);
assert_eq!(ensure_project_indexed(Path::new("/"), false), None);
assert_eq!(
ensure_project_indexed_reporting(Path::new("/"), IndexOptions::default()).registration,
IndexRegistration::RefusedUnindexableRoot(crate::IndexRootRefusal::FilesystemRoot)
);
}
#[test]
fn ensure_project_indexed_refuses_the_real_home_directory() {
let Some(home) = dirs::home_dir() else {
panic!("this test needs a resolvable home directory");
};
assert_eq!(
crate::resolve_project_root(&home),
home,
"no ancestor of $HOME may be a git repository for this case to exist"
);
let report = ensure_project_indexed_reporting(&home, IndexOptions::default());
assert_eq!(
report.registration,
IndexRegistration::RefusedUnindexableRoot(crate::IndexRootRefusal::HomeDirectory)
);
assert_eq!(
report.index_id, None,
"the home directory's basename is the wrong id and must not be handed back"
);
assert_eq!(ensure_project_indexed(&home, false), None);
assert_eq!(ensure_project_indexed(&home, true), None);
}
#[test]
fn index_files_inner_refuses_the_real_home_directory() {
let Some(home) = dirs::home_dir() else {
panic!("this test needs a resolvable home directory");
};
index_files_inner(&home, &[PathBuf::from("some/file.rs")]);
}
#[test]
fn index_files_inner_is_noop_for_empty_paths() {
index_files_inner(Path::new("/"), &[]);
}
#[test]
fn index_files_inner_skips_when_index_id_empty() {
index_files_inner(Path::new("/"), &[PathBuf::from("some/file.rs")]);
}
#[test]
fn index_files_inner_skips_gracefully_when_daemon_down() {
let _guard = crate::data_dir::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let data_dir = scratch_dir("data-incr");
fs::create_dir_all(&data_dir).unwrap();
unsafe {
std::env::set_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV, &data_dir);
}
let project = scratch_dir("git-incr");
fs::create_dir_all(project.join(".git")).unwrap();
fs::write(project.join("main.rs"), "fn main() {}\n").unwrap();
index_files_inner(&project, &[PathBuf::from("main.rs")]);
unsafe {
std::env::remove_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV);
}
let _ = fs::remove_dir_all(&project);
let _ = fs::remove_dir_all(&data_dir);
}
#[test]
fn index_files_best_effort_drops_the_batch_when_the_shared_pool_is_saturated() {
use crate::index_dispatch::{INDEX_QUEUE_CAPACITY, MAX_INDEX_WORKERS, global};
use std::sync::mpsc::channel;
use std::time::Duration;
let wait = Duration::from_secs(30);
let (started_tx, started_rx) = channel();
let mut releases = Vec::with_capacity(MAX_INDEX_WORKERS);
for _ in 0..MAX_INDEX_WORKERS {
let (release_tx, release_rx) = channel::<()>();
releases.push(release_tx);
let started = started_tx.clone();
assert!(
global().try_submit(Box::new(move || {
let _ = started.send(());
let _ = release_rx.recv_timeout(wait);
})),
"the shared pool refused a job before every worker was even busy"
);
}
for i in 0..MAX_INDEX_WORKERS {
started_rx
.recv_timeout(wait)
.unwrap_or_else(|e| panic!("blocker {i} never started: {e}"));
}
let mut filled = 0usize;
while global().try_submit(Box::new(|| {})) {
filled += 1;
assert!(
filled <= INDEX_QUEUE_CAPACITY,
"the queue accepted {filled} jobs, more than its {INDEX_QUEUE_CAPACITY}-slot capacity"
);
}
let before = global().rejected();
index_files_best_effort(Path::new("/nonexistent-2798"), &[PathBuf::from("main.rs")]);
let after = global().rejected();
let stats = index_drop_stats();
for release in &releases {
let _ = release.send(());
}
assert_eq!(
after,
before + 1,
"a batch submitted to a saturated pool must be dropped and counted"
);
assert_eq!(
stats.dropped_batches, after,
"the public stats must read the same counter the pool increments"
);
assert!(
stats
.seconds_since_last_drop
.is_some_and(|since| since <= 60),
"a drop that just happened must be reported as recent, got {:?}",
stats.seconds_since_last_drop
);
}
#[test]
fn batch_budget_is_exhausted_at_and_past_the_cap() {
use std::time::Duration;
assert!(!batch_budget_exhausted(Duration::from_secs(0)));
assert!(!batch_budget_exhausted(
BATCH_INDEX_BUDGET - Duration::from_millis(1)
));
assert!(batch_budget_exhausted(BATCH_INDEX_BUDGET));
assert!(batch_budget_exhausted(
BATCH_INDEX_BUDGET + Duration::from_secs(600)
));
}
#[test]
fn a_truncated_batch_is_counted_separately_from_a_dropped_one() {
let before = index_drop_stats().truncated_batches;
assert!(
stop_batch_for_budget(
crate::index_dispatch::global(),
BATCH_INDEX_BUDGET,
"idx",
3,
10
),
"a batch that has spent its budget must be stopped"
);
let after = index_drop_stats();
assert_eq!(
after.truncated_batches,
before + 1,
"stopping on the budget must be counted, not only logged"
);
assert!(
after
.seconds_since_last_truncation
.is_some_and(|since| since <= 60),
"a truncation that just happened must be reported as recent, got {:?}",
after.seconds_since_last_truncation
);
}
#[test]
fn an_unexhausted_budget_records_no_truncation() {
use crate::index_dispatch::BoundedDispatcher;
use std::time::Duration;
let pool = BoundedDispatcher::new(1, 1);
assert!(
!stop_batch_for_budget(&pool, Duration::from_secs(0), "idx", 0, 10),
"a batch that has spent none of its budget must not be stopped"
);
assert!(
!stop_batch_for_budget(
&pool,
BATCH_INDEX_BUDGET - Duration::from_millis(1),
"idx",
9,
10
),
"a batch one millisecond inside its budget must not be stopped"
);
assert_eq!(
pool.truncated(),
0,
"a batch that was never stopped must not be counted as truncated"
);
assert_eq!(
pool.last_truncation_unix_secs(),
None,
"with no truncation the stamp must stay unset, never a misleading epoch"
);
}
#[test]
fn relative_index_path_strips_root_prefix() {
let root = Path::new("/Users/dev/my-project");
let abs = root.join("src/main.rs");
assert_eq!(relative_index_path(root, &abs), "src/main.rs");
}
#[test]
fn relative_index_path_falls_back_for_paths_outside_root() {
let root = Path::new("/Users/dev/my-project");
let elsewhere = Path::new("/somewhere/else/file.py");
assert_eq!(
relative_index_path(root, elsewhere),
"/somewhere/else/file.py"
);
}
#[test]
fn index_file_request_body_targets_relative_path_and_content() {
let body = index_file_request_body("src/main.rs", "fn main() {}\n");
assert_eq!(
body.get("path").and_then(serde_json::Value::as_str),
Some("src/main.rs")
);
assert_eq!(
body.get("content").and_then(serde_json::Value::as_str),
Some("fn main() {}\n")
);
assert!(
body.get("allow_sensitive_path").is_none(),
"the per-file endpoint does not re-check the denylist, so no bypass \
flag should be sent: {body:?}"
);
}
#[test]
fn create_index_request_body_respects_allow_sensitive_path_param() {
for root in [
Path::new("/Users/dev/projects/my-repo"),
Path::new("/private/var/folders/xx/scratch-project"),
] {
for allow in [true, false] {
let body = create_index_request_body(
"my-index",
root,
IndexOptions {
allow_sensitive_path: allow,
..IndexOptions::default()
},
);
assert_eq!(
body.get("allow_sensitive_path"),
Some(&serde_json::Value::Bool(allow)),
"request body for root {root:?} must set allow_sensitive_path: {allow}"
);
assert_eq!(
body.get("id").and_then(serde_json::Value::as_str),
Some("my-index")
);
}
}
}
#[test]
fn create_index_request_body_sets_skip_vector() {
let root = Path::new("/Users/dev/projects/my-repo/.worktrees/feat-x");
for allow in [true, false] {
for skip_vector in [true, false] {
let body = create_index_request_body(
"feat-x",
root,
IndexOptions {
allow_sensitive_path: allow,
skip_vector,
},
);
assert_eq!(
body.get("skip_vector"),
Some(&serde_json::Value::Bool(skip_vector)),
"body must set skip_vector: {skip_vector} (allow={allow})"
);
assert_eq!(
body.get("allow_sensitive_path"),
Some(&serde_json::Value::Bool(allow)),
"skip_vector must not disturb allow_sensitive_path"
);
}
}
}
#[test]
fn index_options_default_matches_legacy_ensure_call() {
let root = Path::new("/Users/dev/projects/my-repo");
assert_eq!(
create_index_request_body("my-repo", root, IndexOptions::default()),
create_index_request_body(
"my-repo",
root,
IndexOptions {
allow_sensitive_path: false,
skip_vector: false,
}
)
);
}
#[test]
fn ensure_project_indexed_sends_allow_sensitive_path_through_to_create_body() {
for allow in [true, false] {
let _guard = crate::data_dir::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let data_dir = scratch_dir(&format!("wire-{allow}"));
fs::create_dir_all(&data_dir).unwrap();
unsafe {
std::env::set_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV, &data_dir);
std::env::set_var(crate::test_harness::ALLOW_PRODUCTION_ENV, "1");
}
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let (tx, rx) = std::sync::mpsc::channel();
let server = std::thread::spawn(move || {
use std::io::{Read, Write};
let (mut stream, _) = listener.accept().unwrap();
drop(listener);
let mut buf = [0u8; 4096];
let n = stream.read(&mut buf).unwrap();
let request = String::from_utf8_lossy(&buf[..n]).to_string();
let _ = stream
.write_all(b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\nconnection: close\r\n\r\n");
let _ = stream.flush();
let body = request.split("\r\n\r\n").nth(1).unwrap_or("").to_string();
let _ = tx.send(body);
});
let search_data_dir = data_dir.join("trusty-search");
fs::create_dir_all(&search_data_dir).unwrap();
fs::write(search_data_dir.join("http_addr"), addr.to_string()).unwrap();
let project = scratch_dir(&format!("wire-project-{allow}"));
fs::create_dir_all(project.join(".git")).unwrap();
let _ = ensure_project_indexed(&project, allow);
let body_json: serde_json::Value = serde_json::from_str(
&rx.recv_timeout(std::time::Duration::from_secs(5))
.expect("fake daemon must have received the create-index POST"),
)
.expect("captured body must be valid JSON");
let _ = server.join();
unsafe {
std::env::remove_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV);
std::env::remove_var(crate::test_harness::ALLOW_PRODUCTION_ENV);
}
let _ = fs::remove_dir_all(&project);
let _ = fs::remove_dir_all(&data_dir);
assert_eq!(
body_json.get("allow_sensitive_path"),
Some(&serde_json::Value::Bool(allow)),
"POST /indexes body must carry allow_sensitive_path={allow} \
all the way from ensure_project_indexed's parameter; got {body_json:?}"
);
}
}
#[test]
fn index_is_fresh_true_when_recently_indexed_with_chunks() {
let now = chrono::Utc::now();
let status = serde_json::json!({
"chunk_count": 42,
"last_indexed": now.to_rfc3339(),
});
assert!(index_is_fresh(&status));
}
#[test]
fn index_is_fresh_false_when_no_chunks() {
let now = chrono::Utc::now();
let status = serde_json::json!({
"chunk_count": 0,
"last_indexed": now.to_rfc3339(),
});
assert!(!index_is_fresh(&status));
}
#[test]
fn index_is_fresh_false_when_stale() {
let stale = chrono::Utc::now() - chrono::Duration::hours(2);
let status = serde_json::json!({
"chunk_count": 10,
"last_indexed": stale.to_rfc3339(),
});
assert!(!index_is_fresh(&status));
}
#[test]
fn index_is_fresh_false_when_last_indexed_missing_or_malformed() {
assert!(!index_is_fresh(&serde_json::json!({ "chunk_count": 10 })));
assert!(!index_is_fresh(&serde_json::json!({
"chunk_count": 10,
"last_indexed": "not-a-timestamp",
})));
assert!(!index_is_fresh(&serde_json::json!({})));
}
#[test]
fn retry_backoff_is_bounded_and_increasing() {
use std::time::Duration;
assert_eq!(retry_backoff(1), Duration::from_millis(50));
assert_eq!(retry_backoff(2), Duration::from_millis(150));
assert_eq!(retry_backoff(3), Duration::from_millis(450));
assert!(retry_backoff(2) > retry_backoff(1));
assert!(retry_backoff(3) > retry_backoff(2));
assert_eq!(retry_backoff(100), Duration::from_millis(1000));
}
fn drive_retry_test(
server_fn: impl FnOnce(std::net::TcpListener, std::sync::mpsc::Sender<usize>) + Send + 'static,
) -> (IndexOutcome, usize) {
use std::net::TcpListener;
use std::sync::mpsc;
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let (tx, rx) = mpsc::channel();
let server = std::thread::spawn(move || server_fn(listener, tx));
let client = build_index_client().unwrap();
let url = format!("http://{addr}/indexes/test-index/index-file");
let body = index_file_request_body("src/main.rs", "fn main() {}\n");
let outcome = post_index_file_with_retries(&client, &url, &body);
let accepted = rx
.recv_timeout(std::time::Duration::from_secs(5))
.expect("server thread should have reported an accepted-connection count");
let _ = server.join();
(outcome, accepted)
}
#[test]
fn post_index_file_retries_transient_send_failure() {
use std::io::{Read, Write};
let (outcome, accepted) = drive_retry_test(|listener, tx| {
let mut accepted = 0usize;
for stream in listener.incoming() {
let Ok(mut stream) = stream else { break };
accepted += 1;
if accepted == 1 {
drop(stream);
continue;
}
let mut buf = [0u8; 4096];
let _ = stream.read(&mut buf);
let _ = stream
.write_all(b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\nconnection: close\r\n\r\n");
let _ = stream.flush();
let _ = tx.send(accepted);
break;
}
});
assert_eq!(outcome, IndexOutcome::Indexed);
assert_eq!(accepted, 2);
}
#[test]
fn post_index_file_exhausts_retries_and_returns_send_failed() {
let (outcome, accepted) = drive_retry_test(|listener, tx| {
let mut accepted = 0usize;
for stream in listener.incoming() {
let Ok(stream) = stream else { break };
accepted += 1;
drop(stream);
if accepted >= MAX_INDEX_ATTEMPTS as usize {
let _ = tx.send(accepted);
break;
}
}
});
assert_eq!(outcome, IndexOutcome::SendFailed);
assert_eq!(accepted, MAX_INDEX_ATTEMPTS as usize);
}
fn daemon_was_contacted_during(body: impl FnOnce()) -> bool {
use crate::data_dir::{DATA_DIR_OVERRIDE_ENV, ENV_LOCK};
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind stand-in daemon");
let addr = listener.local_addr().expect("stand-in daemon local_addr");
listener
.set_nonblocking(true)
.expect("stand-in daemon set_nonblocking");
let guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let data_dir = scratch_dir("4255-daemon");
fs::create_dir_all(&data_dir).expect("create isolated data dir");
let previous = std::env::var(DATA_DIR_OVERRIDE_ENV).ok();
unsafe { std::env::set_var(DATA_DIR_OVERRIDE_ENV, &data_dir) };
crate::write_daemon_addr("trusty-search", &addr.to_string()).expect("publish daemon addr");
assert_eq!(
crate::resolve_daemon_base_url("trusty-search"),
Some(format!("http://{addr}")),
"the stand-in daemon must be discoverable, or this test proves nothing"
);
body();
match previous {
Some(p) => unsafe { std::env::set_var(DATA_DIR_OVERRIDE_ENV, p) },
None => unsafe { std::env::remove_var(DATA_DIR_OVERRIDE_ENV) },
}
drop(guard);
let _ = fs::remove_dir_all(&data_dir);
std::thread::sleep(std::time::Duration::from_millis(250));
!matches!(
listener.accept(),
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock
)
}
#[test]
fn ensure_project_indexed_never_writes_to_a_daemon_under_test() {
let root = scratch_dir("4255-ensure");
fs::create_dir_all(&root).expect("create fixture root");
let mut id = None;
let contacted = daemon_was_contacted_during(|| {
id = ensure_project_indexed(&root, true);
});
assert!(
!contacted,
"ensure_project_indexed contacted a live trusty-search daemon from a test \
process — that is the issue #4255 registry leak"
);
assert!(
id.is_none(),
"the guard suppressed the write, so no index was registered — handing \
back a pinnable id anyway is the #5091 fail-open shape"
);
let _ = fs::remove_dir_all(&root);
}
#[test]
fn index_files_inner_never_writes_to_a_daemon_under_test() {
let root = scratch_dir("4255-incremental");
fs::create_dir_all(&root).expect("create fixture root");
let file = root.join("fixture.rs");
fs::write(&file, "fn fixture() {}\n").expect("write fixture file");
let contacted = daemon_was_contacted_during(|| {
index_files_inner(&root, std::slice::from_ref(&file));
});
assert!(
!contacted,
"index_files_inner pushed fixture content to a live trusty-search daemon \
from a test process (issue #4255)"
);
let _ = fs::remove_dir_all(&root);
}
#[test]
fn index_options_builders_match_field_construction() {
assert_eq!(
IndexOptions::default().with_skip_vector(true),
IndexOptions {
allow_sensitive_path: false,
skip_vector: true,
}
);
assert_eq!(
IndexOptions::default().with_allow_sensitive_path(true),
IndexOptions {
allow_sensitive_path: true,
skip_vector: false,
}
);
assert_eq!(
IndexOptions::default()
.with_skip_vector(true)
.with_allow_sensitive_path(true),
IndexOptions {
allow_sensitive_path: true,
skip_vector: true,
}
);
}
fn one_shot_daemon(
status_line: &'static str,
) -> (std::net::SocketAddr, std::thread::JoinHandle<()>) {
one_shot_daemon_with_body(status_line, String::new())
}
fn one_shot_daemon_with_body(
status_line: &'static str,
body: String,
) -> (std::net::SocketAddr, std::thread::JoinHandle<()>) {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind fake daemon");
let addr = listener.local_addr().expect("fake daemon local_addr");
let handle = std::thread::spawn(move || {
use std::io::{Read, Write};
let Ok((mut stream, _)) = listener.accept() else {
return;
};
drop(listener);
let mut buf = [0u8; 4096];
let _ = stream.read(&mut buf);
let _ = stream.write_all(
format!(
"{status_line}\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}",
body.len()
)
.as_bytes(),
);
let _ = stream.flush();
});
(addr, handle)
}
fn publish_daemon_addr(data_dir: &Path, addr: std::net::SocketAddr) {
let search_data_dir = data_dir.join("trusty-search");
fs::create_dir_all(&search_data_dir).unwrap();
fs::write(search_data_dir.join("http_addr"), addr.to_string()).unwrap();
}
fn with_refusing_daemon<T>(
tag: &str,
status_line: &'static str,
body: impl FnOnce(&Path) -> T,
) -> T {
let _guard = crate::data_dir::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let data_dir = scratch_dir(&format!("5091-data-{tag}"));
fs::create_dir_all(&data_dir).unwrap();
unsafe {
std::env::set_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV, &data_dir);
std::env::set_var(crate::test_harness::ALLOW_PRODUCTION_ENV, "1");
}
let (addr, server) = one_shot_daemon(status_line);
publish_daemon_addr(&data_dir, addr);
let project = scratch_dir(&format!("5091-project-{tag}"));
fs::create_dir_all(project.join(".git")).unwrap();
let out = body(&project);
let _ = server.join();
unsafe {
std::env::remove_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV);
std::env::remove_var(crate::test_harness::ALLOW_PRODUCTION_ENV);
}
let _ = fs::remove_dir_all(&project);
let _ = fs::remove_dir_all(&data_dir);
out
}
#[test]
fn create_rejected_by_the_daemon_withholds_the_pinnable_id() {
let refused = "HTTP/1.1 500 Internal Server Error";
let id = with_refusing_daemon("ensure", refused, |project| {
ensure_project_indexed(project, false)
});
assert_eq!(
id, None,
"ensure_project_indexed returned a pinnable id after the daemon REFUSED \
the create (HTTP 500) — pinning it makes every later search 404 (#5091)"
);
let id = with_refusing_daemon("ensure-with", refused, |project| {
ensure_project_indexed_with(project, IndexOptions::default().with_skip_vector(true))
});
assert_eq!(
id, None,
"ensure_project_indexed_with returned a pinnable id after the daemon \
REFUSED the create (HTTP 500) — #5091"
);
let (report, expected) = with_refusing_daemon("report", refused, |project| {
(
ensure_project_indexed_reporting(project, IndexOptions::default()),
crate::derive_index_id(project),
)
});
assert_eq!(
report.registration,
IndexRegistration::NotConfirmed,
"a 500 on the create is not a registration"
);
assert_eq!(
report.index_id,
Some(expected),
"the derived id stays available for logging and GC — it is the PIN that \
is withheld, not the id"
);
}
#[test]
fn registered_root_from_response_reads_the_already_exists_root() {
let body = r#"{"id":"api","created":false,"reason":"already exists","root_path":"/srv/other"}"#;
assert_eq!(
registered_root_from_response(body),
Some("/srv/other".to_string())
);
}
#[test]
fn registered_root_from_response_ignores_a_fresh_create() {
let body = r#"{"id":"api","created":true,"root_path":"/srv/api"}"#;
assert_eq!(registered_root_from_response(body), None);
}
#[test]
fn registered_root_from_response_tolerates_a_daemon_that_omits_it() {
assert_eq!(
registered_root_from_response(r#"{"id":"api","created":false}"#),
None
);
assert_eq!(registered_root_from_response("not json at all"), None);
assert_eq!(registered_root_from_response(""), None);
assert_eq!(
registered_root_from_response(r#"{"created":false,"root_path":42}"#),
None,
"a non-string root_path must yield None, not a panic"
);
}
#[test]
fn create_index_response_for_a_different_tree_reports_a_conflict() {
let requested = scratch_dir("mismatch-requested");
let registered = scratch_dir("mismatch-registered");
fs::create_dir_all(&requested).unwrap();
fs::create_dir_all(®istered).unwrap();
let (addr, server) = one_shot_daemon_with_body(
"HTTP/1.1 200 OK",
format!(
r#"{{"id":"api","created":false,"reason":"already exists","root_path":"{}"}}"#,
registered.display()
),
);
let outcome = best_effort_create_index(
&format!("http://{addr}"),
"api",
&requested,
IndexOptions::default(),
);
let _ = server.join();
assert_eq!(
outcome,
CreateOutcome::Conflict { existing_id: None },
"a 200 naming a different tree must not confirm the registration"
);
let _ = fs::remove_dir_all(&requested);
let _ = fs::remove_dir_all(®istered);
}
#[test]
fn create_index_response_for_the_same_tree_is_confirmed() {
let root = scratch_dir("mismatch-same-tree");
fs::create_dir_all(&root).unwrap();
let (addr, server) = one_shot_daemon_with_body(
"HTTP/1.1 200 OK",
format!(
r#"{{"id":"api","created":false,"reason":"already exists","root_path":"{}"}}"#,
root.display()
),
);
let outcome = best_effort_create_index(
&format!("http://{addr}"),
"api",
&root,
IndexOptions::default(),
);
let _ = server.join();
assert_eq!(outcome, CreateOutcome::Confirmed);
let _ = fs::remove_dir_all(&root);
}
fn scripted_daemon(
responses: Vec<(&'static str, String)>,
) -> (std::net::SocketAddr, std::thread::JoinHandle<()>) {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind fake daemon");
let addr = listener.local_addr().expect("fake daemon local_addr");
let handle = std::thread::spawn(move || {
use std::io::{Read, Write};
for (status_line, body) in responses {
let Ok((mut stream, _)) = listener.accept() else {
return;
};
let mut buf = [0u8; 4096];
let _ = stream.read(&mut buf);
let _ = stream.write_all(
format!(
"{status_line}\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}",
body.len()
)
.as_bytes(),
);
let _ = stream.flush();
}
drop(listener);
});
(addr, handle)
}
fn root_mismatch_409(index_id: &str, registered: &Path, requested: &Path) -> String {
format!(
r#"{{"error":"index '{index_id}' is registered elsewhere","index_id":"{index_id}",
"registered_root_path":"{}","requested_root_path":"{}"}}"#,
registered.display(),
requested.display()
)
}
fn with_scripted_daemon<T>(
tag: &str,
project_name: &str,
script: impl FnOnce(&Path) -> Vec<(&'static str, String)>,
body: impl FnOnce(&Path) -> T,
) -> T {
let _guard = crate::data_dir::ENV_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
let data_dir = scratch_dir(&format!("6864-data-{tag}"));
fs::create_dir_all(&data_dir).unwrap();
unsafe {
std::env::set_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV, &data_dir);
std::env::set_var(crate::test_harness::ALLOW_PRODUCTION_ENV, "1");
}
let workspace = scratch_dir(&format!("6864-ws-{tag}"));
let project = workspace.join(project_name);
fs::create_dir_all(project.join(".git")).unwrap();
let (addr, server) = scripted_daemon(script(&project));
publish_daemon_addr(&data_dir, addr);
let out = body(&project);
let _ = server.join();
unsafe {
std::env::remove_var(crate::data_dir::DATA_DIR_OVERRIDE_ENV);
std::env::remove_var(crate::test_harness::ALLOW_PRODUCTION_ENV);
}
let _ = fs::remove_dir_all(&workspace);
let _ = fs::remove_dir_all(&data_dir);
out
}
#[test]
fn registration_matches_an_existing_index_by_root_path() {
let other = scratch_dir("6864-other-checkout");
fs::create_dir_all(&other).unwrap();
let report = with_scripted_daemon(
"match",
"trusty-tools",
|project| {
vec![
(
"HTTP/1.1 409 Conflict",
root_mismatch_409("trusty-tools", &other, project),
),
(
"HTTP/1.1 200 OK",
format!(
r#"{{"indexes":[{{"id":"trusty-tools","root_path":"{}"}},
{{"id":"trusty-tools-checkout","root_path":"{}"}}]}}"#,
other.display(),
project.display()
),
),
]
},
|project| ensure_project_indexed_reporting(project, IndexOptions::default()),
);
assert_eq!(
report.registration,
IndexRegistration::Confirmed,
"an index registered at this root IS a confirmed registration (#6864)"
);
assert_eq!(
report.index_id,
Some("trusty-tools-checkout".to_string()),
"the report must carry the id that serves this tree, not the colliding \
basename the daemon refused (#6864)"
);
let _ = fs::remove_dir_all(&other);
}
#[test]
fn registration_falls_back_to_a_collision_resistant_id() {
let other = scratch_dir("6864-fallback-other");
fs::create_dir_all(&other).unwrap();
let (report, expected) = with_scripted_daemon(
"fallback",
"trusty-tools",
|project| {
vec![
(
"HTTP/1.1 409 Conflict",
root_mismatch_409("trusty-tools", &other, project),
),
(
"HTTP/1.1 200 OK",
format!(
r#"{{"indexes":[{{"id":"trusty-tools","root_path":"{}"}}]}}"#,
other.display()
),
),
(
"HTTP/1.1 200 OK",
r#"{"id":"trusty-tools-abcdef12","created":true}"#.to_string(),
),
]
},
|project| {
(
ensure_project_indexed_reporting(project, IndexOptions::default()),
crate::derive_checkout_index_id(project),
)
},
);
assert_eq!(
report.registration,
IndexRegistration::Confirmed,
"the fallback create landed, so the registration is confirmed (#6864)"
);
assert_eq!(
report.index_id, expected,
"the id must be the shared checkout derivation, not a scheme invented here \
(#6149 / #6864)"
);
let _ = fs::remove_dir_all(&other);
}