Files
1jehuang--jcode/src/cli/dispatch_tests.rs
T
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

275 lines
9.3 KiB
Rust

use super::*;
use crate::transport::Listener;
#[test]
fn auth_doctor_provider_focus_uses_global_provider_when_positional_is_absent() {
assert_eq!(
auth_doctor_provider_arg(None, &ProviderChoice::Cerebras),
Some("cerebras")
);
assert_eq!(
auth_doctor_provider_arg(None, &ProviderChoice::Auto),
None,
"auto should keep the default doctor behavior of checking configured providers"
);
}
#[test]
fn auth_doctor_positional_provider_wins_over_global_provider() {
assert_eq!(
auth_doctor_provider_arg(Some("openai"), &ProviderChoice::Cerebras),
Some("openai"),
"`jcode --provider cerebras auth doctor openai` should diagnose the explicit positional provider"
);
}
struct ReloadTestEnv {
prev_socket: Option<std::ffi::OsString>,
prev_runtime: Option<std::ffi::OsString>,
socket_path: std::path::PathBuf,
}
impl ReloadTestEnv {
fn new() -> Self {
let temp = tempfile::tempdir().expect("tempdir");
let socket_path = temp.path().join("jcode.sock");
let prev_socket = std::env::var_os("JCODE_SOCKET");
let prev_runtime = std::env::var_os("JCODE_RUNTIME_DIR");
crate::server::set_socket_path(socket_path.to_str().expect("utf8 socket path"));
crate::env::set_var("JCODE_RUNTIME_DIR", temp.path());
// Keep tempdir alive for the duration of the test helper.
let _ = temp.keep();
Self {
prev_socket,
prev_runtime,
socket_path,
}
}
}
impl Drop for ReloadTestEnv {
fn drop(&mut self) {
crate::server::clear_reload_marker();
let _ = std::fs::remove_file(&self.socket_path);
if let Some(prev_socket) = &self.prev_socket {
crate::env::set_var("JCODE_SOCKET", prev_socket);
} else {
crate::env::remove_var("JCODE_SOCKET");
}
if let Some(prev_runtime) = &self.prev_runtime {
crate::env::set_var("JCODE_RUNTIME_DIR", prev_runtime);
} else {
crate::env::remove_var("JCODE_RUNTIME_DIR");
}
}
}
#[cfg(unix)]
#[test]
fn spawn_lock_serializes_shared_server_bootstrap() {
let temp = tempfile::tempdir().expect("tempdir");
let socket_path = temp.path().join("jcode.sock");
let lock_path = spawn_lock_path(&socket_path);
let first = try_acquire_spawn_lock(&lock_path)
.expect("acquire first lock")
.expect("first lock should succeed");
let second = try_acquire_spawn_lock(&lock_path).expect("acquire second lock");
assert!(
second.is_none(),
"second lock should be held by first guard"
);
drop(first);
let third = try_acquire_spawn_lock(&lock_path)
.expect("acquire third lock")
.expect("third lock should succeed after release");
drop(third);
assert!(
!lock_path.exists(),
"lock file should be cleaned up when the guard drops"
);
}
#[test]
fn resolve_resume_id_imports_raw_codex_session_ids() {
let _guard = crate::storage::lock_test_env();
let temp = tempfile::tempdir().expect("tempdir");
crate::env::set_var("JCODE_HOME", temp.path());
let codex_dir = temp.path().join("external/.codex/sessions/2026/04/16");
std::fs::create_dir_all(&codex_dir).expect("create codex dir");
std::fs::write(
codex_dir.join("rollout.jsonl"),
concat!(
"{\"timestamp\":\"2026-04-16T10:00:00Z\",\"type\":\"session_meta\",\"payload\":{\"id\":\"codex-cli-resume-test\",\"timestamp\":\"2026-04-16T09:59:00Z\",\"cwd\":\"/tmp/codex-cli-resume\"}}\n",
"{\"timestamp\":\"2026-04-16T10:00:01Z\",\"type\":\"response_item\",\"payload\":{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Resume this Codex session\"}]}}\n",
"{\"timestamp\":\"2026-04-16T10:00:02Z\",\"type\":\"response_item\",\"payload\":{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Imported\"}]}}\n"
),
)
.expect("write codex transcript");
let resolved = resolve_resume_id("codex-cli-resume-test").expect("resolve codex id");
let imported_id = crate::import::imported_codex_session_id("codex-cli-resume-test");
assert_eq!(resolved, imported_id);
let session = crate::session::Session::load(&resolved).expect("load imported session");
assert_eq!(session.messages.len(), 2);
crate::env::remove_var("JCODE_HOME");
}
#[test]
fn resume_failure_defers_to_server_during_reload_handoff() {
// Issue #328: when `--resume <id>` cannot be resolved locally but a reload/
// update/restart handoff is in progress (JCODE_RESUMING set), we must defer
// to the server instead of exiting with "No session found matching ...".
let with_resuming = |key: &str| {
if key == "JCODE_RESUMING" {
Some(std::ffi::OsString::from("1"))
} else {
None
}
};
assert_eq!(
resume_resolution_failure_action("ses_1780655839703_174710856", with_resuming),
ResumeResolutionFailureAction::DeferToServer,
);
}
#[test]
fn resume_failure_exits_when_no_handoff_in_progress() {
// Without a reload handoff, an unresolved id is a genuine user error and
// should still surface the actionable "use --resume to list" message + exit.
let no_env = |_key: &str| Option::<std::ffi::OsString>::None;
assert_eq!(
resume_resolution_failure_action("totally-bogus-id", no_env),
ResumeResolutionFailureAction::Exit,
);
}
#[tokio::test]
async fn wait_for_existing_reload_server_uses_reloading_server_instead_of_spawning() {
let _guard = crate::storage::lock_test_env();
let env = ReloadTestEnv::new();
crate::server::write_reload_state(
"reload-test",
"hash",
crate::server::ReloadPhase::Starting,
None,
);
let bind_path = env.socket_path.clone();
let (release_tx, release_rx) = tokio::sync::oneshot::channel();
let bind_task = tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let listener = Listener::bind(&bind_path).expect("bind replacement listener");
crate::server::write_reload_state(
"reload-test",
"hash",
crate::server::ReloadPhase::SocketReady,
None,
);
let _listener = listener;
let _ = release_rx.await;
});
let result = tokio::time::timeout(
std::time::Duration::from_secs(2),
wait_for_existing_reload_server("test"),
)
.await
.expect("reload wait should not hang");
let _ = release_tx.send(());
bind_task.await.expect("bind task");
assert!(result);
}
#[tokio::test]
async fn wait_for_existing_reload_server_returns_false_for_failed_reload() {
let _guard = crate::storage::lock_test_env();
let _env = ReloadTestEnv::new();
crate::server::write_reload_state(
"reload-test",
"hash",
crate::server::ReloadPhase::Failed,
Some("boom".to_string()),
);
assert!(!wait_for_existing_reload_server("test").await);
}
#[tokio::test]
async fn wait_for_resuming_server_detects_delayed_listener_without_marker() {
let _guard = crate::storage::lock_test_env();
let env = ReloadTestEnv::new();
let bind_path = env.socket_path.clone();
let (release_tx, release_rx) = tokio::sync::oneshot::channel();
let bind_task = tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let listener = Listener::bind(&bind_path).expect("bind delayed listener");
let _listener = listener;
let _ = release_rx.await;
});
let result = tokio::time::timeout(
std::time::Duration::from_secs(2),
wait_for_resuming_server("test", std::time::Duration::from_secs(1)),
)
.await
.expect("resume wait should not hang");
let _ = release_tx.send(());
bind_task.await.expect("bind task");
assert!(
result,
"resume wait should detect a delayed server without requiring a reload marker"
);
}
#[tokio::test]
async fn wait_for_reloading_server_returns_false_when_idle() {
let _guard = crate::storage::lock_test_env();
let _env = ReloadTestEnv::new();
assert!(!wait_for_reloading_server().await);
}
#[tokio::test]
async fn wait_for_reloading_server_returns_false_when_reload_failed() {
let _guard = crate::storage::lock_test_env();
let _env = ReloadTestEnv::new();
crate::server::write_reload_state(
"reload-test",
"hash",
crate::server::ReloadPhase::Failed,
Some("boom".to_string()),
);
assert!(!wait_for_reloading_server().await);
}
#[tokio::test]
async fn wait_for_reloading_server_returns_true_for_live_listener() {
let _guard = crate::storage::lock_test_env();
let env = ReloadTestEnv::new();
let _listener = Listener::bind(&env.socket_path).expect("bind listener");
assert!(wait_for_reloading_server().await);
}
#[tokio::test]
async fn server_is_running_at_treats_live_listener_as_running_without_pong() {
let temp = tempfile::tempdir().expect("tempdir");
let socket_path = temp.path().join("jcode.sock");
let _listener = Listener::bind(&socket_path).expect("bind listener");
assert!(
server_is_running_at(&socket_path).await,
"a live listener should prevent duplicate server spawns even if ping is slow or absent"
);
}