#![allow(clippy::unwrap_used, clippy::expect_used)]
#[path = "integration/mod.rs"]
mod typed_tests;
use opencode_rs::ClientBuilder;
use opencode_rs::types::event::Event;
use opencode_rs::types::message::PromptPart;
use opencode_rs::types::message::PromptRequest;
use opencode_rs::types::session::CreateSessionRequest;
use std::time::Duration;
fn should_run() -> bool {
std::env::var("OPENCODE_INTEGRATION").is_ok()
}
fn base_url() -> String {
std::env::var("OPENCODE_BASE_URL").unwrap_or_else(|_| "http://127.0.0.1:4096".to_string())
}
fn directory() -> String {
std::env::var("OPENCODE_DIRECTORY").unwrap_or_else(|_| {
std::env::current_dir()
.map_or_else(|_| "/tmp".to_string(), |p| p.to_string_lossy().to_string())
})
}
fn build_client() -> opencode_rs::Client {
ClientBuilder::new()
.base_url(base_url())
.directory(directory())
.timeout_secs(300)
.build()
.unwrap()
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_server_health() {
if !should_run() {
return;
}
let client = build_client();
let health = client.misc().health().await.expect("Failed to get health");
assert!(health.healthy, "Server should be healthy");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_session_lifecycle() {
if !should_run() {
return;
}
let client = build_client();
let session = client
.sessions()
.create(&CreateSessionRequest {
title: Some("Integration Test Session".into()),
..Default::default()
})
.await
.expect("Failed to create session");
assert!(!session.id.is_empty());
let fetched = client
.sessions()
.get(&session.id)
.await
.expect("Failed to get session");
assert_eq!(fetched.id, session.id);
match client.sessions().list().await {
Ok(sessions) => {
println!("Found {} sessions", sessions.len());
}
Err(e) => {
println!("List sessions failed: {e:?}");
}
}
client
.sessions()
.delete(&session.id)
.await
.expect("Failed to delete session");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_session_prompt_and_stream() {
if !should_run() {
return;
}
let client = build_client();
let session = client
.sessions()
.create(&CreateSessionRequest::default())
.await
.expect("Failed to create session");
let mut subscription = client
.subscribe_session(&session.id)
.expect("Failed to subscribe");
let prompt_result = client
.messages()
.prompt(
&session.id,
&PromptRequest {
parts: vec![PromptPart::Text {
text: "Say 'hello' and nothing else.".into(),
synthetic: None,
ignored: None,
metadata: None,
}],
message_id: None,
model: None,
agent: None,
no_reply: None,
system: None,
variant: None,
},
)
.await;
if prompt_result.is_err() {
println!("Prompt failed (no provider?): {:?}", prompt_result.err());
subscription.close();
let _ = client.sessions().delete(&session.id).await;
return;
}
let timeout = Duration::from_secs(30);
let start = std::time::Instant::now();
let mut got_any_event = false;
while start.elapsed() < timeout {
tokio::select! {
event = subscription.recv() => {
match event {
Some(Event::SessionIdle { .. } | Event::SessionError { .. }) => {
got_any_event = true;
break;
}
Some(_) => {
got_any_event = true;
}
None => break,
}
}
() = tokio::time::sleep(Duration::from_millis(100)) => {}
}
}
println!(
"Streaming test: got_any_event={}, elapsed={:?}",
got_any_event,
start.elapsed()
);
subscription.close();
client
.sessions()
.delete(&session.id)
.await
.expect("Failed to delete session");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_session_abort() {
if !should_run() {
return;
}
let client = build_client();
let session = client
.sessions()
.create(&CreateSessionRequest::default())
.await
.expect("Failed to create session");
let result = client.sessions().abort(&session.id).await;
if let Err(e) = &result {
assert!(!e.is_not_found(), "Session should exist");
}
client
.sessions()
.delete(&session.id)
.await
.expect("Failed to delete session");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_permissions_list() {
if !should_run() {
return;
}
let client = build_client();
let permissions = client
.permissions()
.list()
.await
.expect("Failed to list permissions");
let _ = permissions.len();
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_files_list() {
if !should_run() {
return;
}
let client = build_client();
match client.files().list().await {
Ok(files) => {
let _ = files.len();
}
Err(e) => {
println!("Files list not available: {e:?}");
}
}
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_files_status() {
if !should_run() {
return;
}
let client = build_client();
let status = client
.files()
.status()
.await
.expect("Failed to get file status");
let _ = status.len();
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_project_list() {
if !should_run() {
return;
}
let client = build_client();
let projects = client
.project()
.list()
.await
.expect("Failed to list projects");
assert!(!projects.is_empty(), "Should have at least one project");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_project_current() {
if !should_run() {
return;
}
let client = build_client();
let project = client
.project()
.current()
.await
.expect("Failed to get current project");
assert!(!project.id.is_empty(), "Project should have an ID");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_providers_list() {
if !should_run() {
return;
}
let client = build_client();
let response = client
.providers()
.list()
.await
.expect("Failed to list providers");
for provider in &response.all {
assert!(!provider.id.is_empty(), "Provider should have an ID");
assert!(!provider.name.is_empty(), "Provider should have a name");
}
println!(
"Found {} providers, {} defaults, {} connected",
response.all.len(),
response.default.len(),
response.connected.len()
);
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_mcp_status() {
if !should_run() {
return;
}
let client = build_client();
let status = client
.mcp()
.status()
.await
.expect("Failed to get MCP status");
let _ = status.servers.len();
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_config_get() {
if !should_run() {
return;
}
let client = build_client();
let config = client.config().get().await.expect("Failed to get config");
assert!(config.extra.is_object() || config.extra.is_null());
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_agents_list() {
if !should_run() {
return;
}
let client = build_client();
let agents = client
.tools()
.agents()
.await
.expect("Failed to list agents");
assert!(!agents.is_empty(), "Should have at least one agent");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_commands_list() {
if !should_run() {
return;
}
let client = build_client();
let commands = client
.tools()
.commands()
.await
.expect("Failed to list commands");
let _ = commands.len();
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_vcs_info() {
if !should_run() {
return;
}
let client = build_client();
let vcs = client.misc().vcs().await.expect("Failed to get VCS info");
println!("VCS type: {:?}", vcs.r#type);
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_path_info() {
if !should_run() {
return;
}
let client = build_client();
let path = client.misc().path().await.expect("Failed to get path info");
assert!(!path.directory.is_empty(), "Directory should not be empty");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_openapi_doc() {
if !should_run() {
return;
}
let client = build_client();
let doc = client
.misc()
.doc()
.await
.expect("Failed to get OpenAPI doc");
assert!(doc.spec.is_object(), "Doc should be a JSON object");
assert!(
doc.spec.get("openapi").is_some() || doc.spec.get("swagger").is_some(),
"Should be an OpenAPI/Swagger document"
);
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_session_fork() {
if !should_run() {
return;
}
let client = build_client();
let parent = client
.sessions()
.create(&CreateSessionRequest {
title: Some("Parent Session".into()),
..Default::default()
})
.await
.expect("Failed to create parent session");
let forked = client
.sessions()
.fork(&parent.id)
.await
.expect("Failed to fork session");
assert_ne!(forked.id, parent.id, "Forked session should have new ID");
client
.sessions()
.delete(&forked.id)
.await
.expect("Failed to delete forked session");
client
.sessions()
.delete(&parent.id)
.await
.expect("Failed to delete parent session");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_session_children() {
if !should_run() {
return;
}
let client = build_client();
let parent = client
.sessions()
.create(&CreateSessionRequest {
title: Some("Parent Session".into()),
..Default::default()
})
.await
.expect("Failed to create parent session");
let child = client
.sessions()
.fork(&parent.id)
.await
.expect("Failed to fork session");
let children = client
.sessions()
.children(&parent.id)
.await
.expect("Failed to get children");
let _ = children.len();
client
.sessions()
.delete(&child.id)
.await
.expect("Failed to delete child session");
client
.sessions()
.delete(&parent.id)
.await
.expect("Failed to delete parent session");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_session_diff() {
if !should_run() {
return;
}
let client = build_client();
let session = client
.sessions()
.create(&CreateSessionRequest::default())
.await
.expect("Failed to create session");
match client.sessions().diff(&session.id).await {
Ok(diff) => {
let _ = diff.files.len();
}
Err(e) => {
println!("Diff returned unexpected format: {e:?}");
}
}
client
.sessions()
.delete(&session.id)
.await
.expect("Failed to delete session");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_session_todos() {
if !should_run() {
return;
}
let client = build_client();
let session = client
.sessions()
.create(&CreateSessionRequest::default())
.await
.expect("Failed to create session");
let todos = client
.sessions()
.todo(&session.id)
.await
.expect("Failed to get todos");
let _ = todos.len();
client
.sessions()
.delete(&session.id)
.await
.expect("Failed to delete session");
}
#[tokio::test]
#[ignore = "requires: opencode serve"]
async fn test_global_events() {
if !should_run() {
return;
}
let client = build_client();
let mut subscription = client
.subscribe_global()
.expect("Failed to subscribe to global events");
let timeout = Duration::from_secs(5);
let start = std::time::Instant::now();
let mut got_event = false;
while start.elapsed() < timeout {
tokio::select! {
event = subscription.recv() => {
match event {
Some(_) => {
got_event = true;
break;
}
None => break,
}
}
() = tokio::time::sleep(Duration::from_millis(100)) => {}
}
}
subscription.close();
if got_event {
println!("Received global event");
} else {
println!("No global events received within timeout (this is OK)");
}
}