Files
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

309 lines
11 KiB
Rust

use crate::test_support::*;
#[tokio::test]
async fn resume_session_restores_persisted_compaction_for_provider_context() -> Result<()> {
let _env = setup_test_env()?;
let runtime_dir = short_runtime_dir(format!(
"jcode-compaction-resume-test-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&runtime_dir)?;
let socket_path = runtime_dir.join("jcode.sock");
let debug_socket_path = runtime_dir.join("jcode-debug.sock");
let provider = CapturingCompactionProvider::new();
let captured_messages = provider.captured_messages();
let provider: Arc<dyn Provider> = Arc::new(provider);
let server_instance =
server::Server::new_with_paths(provider, socket_path.clone(), debug_socket_path.clone());
let server_handle = tokio::spawn(async move { server_instance.run().await });
let result = async {
let mut session = Session::create_with_id(
"session_resume_compaction_restore_test".to_string(),
None,
Some("resume compaction restore test".to_string()),
);
session.add_message(
Role::User,
vec![ContentBlock::Text {
text: "older user turn".to_string(),
cache_control: None,
}],
);
session.add_message(
Role::Assistant,
vec![ContentBlock::Text {
text: "older assistant turn".to_string(),
cache_control: None,
}],
);
session.add_message(
Role::User,
vec![ContentBlock::Text {
text: "recent preserved turn".to_string(),
cache_control: None,
}],
);
session.compaction = Some(StoredCompactionState {
summary_text: "Worked on Gemini OAuth reload fixes.".to_string(),
openai_encrypted_content: None,
covers_up_to_turn: 2,
original_turn_count: 2,
compacted_count: 2,
});
session.save()?;
wait_for_server_ready(&socket_path, &debug_socket_path).await?;
let mut client = server::Client::connect_with_path(socket_path.clone()).await?;
let subscribe_id = client.subscribe().await?;
let _ = collect_until_done_unix(&mut client, subscribe_id).await?;
let resume_id = client.resume_session(&session.id).await?;
let _ = collect_until_history_unix(&mut client, resume_id).await?;
let message_id = client
.send_message("continue from the restored session")
.await?;
let mut seen_events = Vec::new();
let deadline = Instant::now() + Duration::from_secs(10);
while Instant::now() < deadline {
let event = timeout(Duration::from_secs(1), client.read_event()).await??;
let is_done = matches!(event, ServerEvent::Done { id } if id == message_id);
let is_error = matches!(event, ServerEvent::Error { id, .. } if id == message_id);
seen_events.push(format!("{event:?}"));
if is_done {
break;
}
if is_error {
anyhow::bail!(
"message request failed while validating compaction restore: {}",
seen_events.join(" | ")
);
}
}
let captured = captured_messages.lock().unwrap();
assert_eq!(
captured.len(),
1,
"expected exactly one provider completion call"
);
let provider_messages = &captured[0];
assert!(
provider_messages.len() >= 3,
"expected summary + preserved tail + new user message"
);
let summary_text = flatten_text_blocks(&provider_messages[0]);
assert!(summary_text.contains("Previous Conversation Summary"));
assert!(summary_text.contains("Gemini OAuth reload fixes"));
let joined = provider_messages
.iter()
.map(flatten_text_blocks)
.collect::<Vec<_>>()
.join("\n---\n");
assert!(joined.contains("recent preserved turn"));
assert!(joined.contains("continue from the restored session"));
assert!(!joined.contains("older user turn"));
assert!(!joined.contains("older assistant turn"));
Ok::<_, anyhow::Error>(())
}
.await;
abort_server_and_cleanup(&server_handle, &socket_path, &debug_socket_path);
result
}
/// Test that a simple text response works
#[tokio::test]
async fn test_simple_response() -> Result<()> {
let _env = setup_test_env()?;
let provider = MockProvider::new();
// Queue a simple response
provider.queue_response(vec![
StreamEvent::TextDelta("Hello! ".to_string()),
StreamEvent::TextDelta("How can I help?".to_string()),
StreamEvent::MessageEnd {
stop_reason: Some("end_turn".to_string()),
},
StreamEvent::SessionId("test-session-123".to_string()),
]);
let provider: Arc<dyn jcode::provider::Provider> = Arc::new(provider);
let registry = Registry::new(provider.clone()).await;
let mut agent = Agent::new(provider, registry);
let response = agent.run_once_capture("Say hello").await?;
let saved = Session::load(agent.session_id())?;
assert_eq!(response, "Hello! How can I help?");
assert!(saved.is_debug, "test sessions should be marked debug");
Ok(())
}
#[tokio::test]
async fn test_agent_clear_preserves_debug_flag() -> Result<()> {
let _env = setup_test_env()?;
let provider = MockProvider::new();
let provider: Arc<dyn jcode::provider::Provider> = Arc::new(provider);
let registry = Registry::new(provider.clone()).await;
let mut agent = Agent::new(provider, registry);
agent.set_debug(true);
let old_session_id = agent.session_id().to_string();
agent.clear();
assert_ne!(agent.session_id(), old_session_id);
assert!(agent.is_debug());
Ok(())
}
#[tokio::test]
async fn test_debug_create_session_marks_debug() -> Result<()> {
let _env = setup_test_env()?;
let runtime_dir = short_runtime_dir(format!(
"jcode-debug-test-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&runtime_dir)?;
let socket_path = runtime_dir.join("jcode.sock");
let debug_socket_path = runtime_dir.join("jcode-debug.sock");
let provider = MockProvider::new();
let provider: Arc<dyn jcode::provider::Provider> = Arc::new(provider);
let server_instance =
server::Server::new_with_paths(provider, socket_path.clone(), debug_socket_path.clone());
let server_handle = tokio::spawn(async move { server_instance.run().await });
wait_for_server_ready(&socket_path, &debug_socket_path).await?;
let session_id = debug_create_headless_session(debug_socket_path.clone()).await?;
let session = Session::load(&session_id)?;
assert!(session.is_debug);
abort_server_and_cleanup(&server_handle, &socket_path, &debug_socket_path);
Ok(())
}
#[tokio::test]
async fn test_debug_create_selfdev_session_marks_canary() -> Result<()> {
let _env = setup_test_env()?;
let runtime_dir = short_runtime_dir(format!(
"jcode-debug-selfdev-test-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&runtime_dir)?;
let socket_path = runtime_dir.join("jcode.sock");
let debug_socket_path = runtime_dir.join("jcode-debug.sock");
let provider = MockProvider::new();
let provider: Arc<dyn jcode::provider::Provider> = Arc::new(provider);
let server_instance =
server::Server::new_with_paths(provider, socket_path.clone(), debug_socket_path.clone());
let server_handle = tokio::spawn(async move { server_instance.run().await });
wait_for_server_ready(&socket_path, &debug_socket_path).await?;
let session_id = debug_create_headless_session_with_command(
debug_socket_path.clone(),
"create_session:selfdev:/tmp",
)
.await?;
let session = Session::load(&session_id)?;
assert!(session.is_debug);
assert!(session.is_canary);
abort_server_and_cleanup(&server_handle, &socket_path, &debug_socket_path);
Ok(())
}
#[tokio::test]
async fn test_clear_preserves_debug_for_resumed_debug_session() -> Result<()> {
let _env = setup_test_env()?;
let runtime_dir = short_runtime_dir(format!(
"jcode-clear-debug-test-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&runtime_dir)?;
let socket_path = runtime_dir.join("jcode.sock");
let debug_socket_path = runtime_dir.join("jcode-debug.sock");
let provider = MockProvider::new();
let provider: Arc<dyn jcode::provider::Provider> = Arc::new(provider);
let server_instance =
server::Server::new_with_paths(provider, socket_path.clone(), debug_socket_path.clone());
let server_handle = tokio::spawn(async move { server_instance.run().await });
wait_for_server_ready(&socket_path, &debug_socket_path).await?;
let debug_session_id = debug_create_headless_session(debug_socket_path.clone()).await?;
let mut client = server::Client::connect_with_path(socket_path.clone()).await?;
let resume_id = client.resume_session(&debug_session_id).await?;
// Drain resume completion so clear() events are unambiguous.
let mut saw_resume_history = false;
let resume_deadline = Instant::now() + Duration::from_secs(5);
while Instant::now() < resume_deadline {
let event = tokio::time::timeout(Duration::from_secs(1), client.read_event()).await??;
match event {
ServerEvent::Ack { .. } => continue,
ServerEvent::History { id, .. } if id == resume_id => {
saw_resume_history = true;
break;
}
ServerEvent::Error { id, message, .. } if id == resume_id => {
anyhow::bail!("resume_session failed: {}", message);
}
_ => {}
}
}
if !saw_resume_history {
anyhow::bail!("Timed out waiting for resume history event");
}
client.clear().await?;
let mut new_session_id = None;
let clear_deadline = Instant::now() + Duration::from_secs(5);
while Instant::now() < clear_deadline {
let event = tokio::time::timeout(Duration::from_secs(1), client.read_event()).await??;
match event {
ServerEvent::Ack { .. } => continue,
ServerEvent::SessionId { session_id } => {
new_session_id = Some(session_id);
}
ServerEvent::Done { .. } if new_session_id.is_some() => break,
_ => {}
}
}
let new_session_id = new_session_id
.ok_or_else(|| anyhow::anyhow!("Did not receive new session id after clear"))?;
assert_ne!(new_session_id, debug_session_id);
let session = Session::load(&new_session_id)?;
assert!(session.is_debug);
abort_server_and_cleanup(&server_handle, &socket_path, &debug_socket_path);
Ok(())
}