#![allow(clippy::unwrap_used)]
#![allow(dead_code)]
use agentic_config::types::OrchestratorConfig;
use opencode_orchestrator_mcp::server::OrchestratorServer;
use opencode_orchestrator_mcp::server::OrchestratorServerHandle;
use opencode_orchestrator_mcp::server::RecoveryMode;
use serde_json::Value;
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::Request;
use wiremock::Respond;
use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;
pub async fn test_orchestrator_server(mock: &MockServer) -> Arc<OrchestratorServerHandle> {
test_orchestrator_server_with_config(mock, OrchestratorConfig::default()).await
}
pub async fn test_orchestrator_server_with_config(
mock: &MockServer,
config: OrchestratorConfig,
) -> Arc<OrchestratorServerHandle> {
Mock::given(method("GET"))
.and(path("/global/health"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"healthy": true,
"version": "test",
})))
.mount(mock)
.await;
let base_url = mock.uri().trim_end_matches('/').to_string();
let client = opencode_rs::ClientBuilder::new()
.base_url(&base_url)
.timeout_secs(5) .build()
.unwrap();
Arc::new(OrchestratorServerHandle::from_server_unshared(
OrchestratorServer::from_client_unshared_with_config(
client,
base_url,
RecoveryMode::External,
config,
),
))
}
#[derive(Clone)]
pub struct SequenceResponder {
responders: Vec<ResponseTemplate>,
calls: Arc<AtomicUsize>,
}
impl SequenceResponder {
pub fn new(responders: Vec<ResponseTemplate>) -> Self {
assert!(!responders.is_empty(), "responders must not be empty");
Self {
responders,
calls: Arc::new(AtomicUsize::new(0)),
}
}
pub fn call_counter(&self) -> CallCounter {
CallCounter {
inner: Arc::clone(&self.calls),
}
}
}
#[derive(Clone)]
pub struct CallCounter {
inner: Arc<AtomicUsize>,
}
impl CallCounter {
pub fn get(&self) -> usize {
self.inner.load(Ordering::SeqCst)
}
}
#[derive(Clone)]
pub struct SwitchAfterCallsResponder {
counter: CallCounter,
min_calls: usize,
before: ResponseTemplate,
after: ResponseTemplate,
}
impl SwitchAfterCallsResponder {
pub fn new(
counter: CallCounter,
min_calls: usize,
before: ResponseTemplate,
after: ResponseTemplate,
) -> Self {
Self {
counter,
min_calls,
before,
after,
}
}
}
impl Respond for SwitchAfterCallsResponder {
fn respond(&self, _req: &Request) -> ResponseTemplate {
if self.counter.get() >= self.min_calls {
self.after.clone()
} else {
self.before.clone()
}
}
}
impl Respond for SequenceResponder {
fn respond(&self, _req: &Request) -> ResponseTemplate {
let idx = self.calls.fetch_add(1, Ordering::SeqCst);
self.responders
.get(idx)
.cloned()
.unwrap_or_else(|| self.responders.last().cloned().expect("non-empty"))
}
}
pub fn session_fixture(session_id: &str) -> serde_json::Value {
session_fixture_with_path(session_id, None)
}
pub fn session_fixture_with_path(session_id: &str, path: Option<&str>) -> serde_json::Value {
serde_json::json!({
"id": session_id,
"slug": session_id,
"projectId": "proj1",
"directory": "/tmp",
"path": path,
"title": "Test Session",
"version": "1.0",
"time": { "created": 1_234_567_890, "updated": 1_234_567_890 }
})
}
pub fn status_v2_idle() -> serde_json::Value {
serde_json::json!({})
}
pub fn status_v2_busy(session_id: &str) -> serde_json::Value {
serde_json::json!({
session_id: { "type": "busy" }
})
}
pub fn status_v2_retry(session_id: &str, attempt: u64) -> serde_json::Value {
serde_json::json!({
session_id: {
"type": "retry",
"attempt": attempt,
"message": "retrying",
"next": 0
}
})
}
pub fn session_status_fixture(statuses: &[(&str, Value)]) -> Value {
let mut map = serde_json::Map::new();
for (session_id, status) in statuses {
map.insert((*session_id).to_string(), status.clone());
}
Value::Object(map)
}
pub fn busy_status_fixture() -> Value {
serde_json::json!({ "type": "busy" })
}
pub fn retry_status_fixture(attempt: u64, message: &str, next: u64) -> Value {
serde_json::json!({
"type": "retry",
"attempt": attempt,
"message": message,
"next": next,
})
}
pub fn permission_fixture(
id: &str,
session_id: &str,
permission: &str,
patterns: &[&str],
) -> serde_json::Value {
serde_json::json!({
"id": id,
"sessionID": session_id, "permission": permission,
"patterns": patterns,
"always": [],
"tool": null,
"metadata": null
})
}
pub fn question_fixture(
id: &str,
session_id: &str,
questions: &[serde_json::Value],
) -> serde_json::Value {
serde_json::json!({
"id": id,
"sessionID": session_id,
"questions": questions,
"tool": null,
})
}
pub fn messages_fixture(session_id: &str, assistant_text: Option<&str>) -> serde_json::Value {
let mut msgs = vec![serde_json::json!({
"info": {"id": "u1", "sessionID": session_id, "role": "user", "time": {"created": 1}},
"parts": []
})];
if let Some(text) = assistant_text {
msgs.push(serde_json::json!({
"info": {"id": "a1", "sessionID": session_id, "role": "assistant", "time": {"created": 2}},
"parts": [{"type": "text", "text": text}]
}));
}
serde_json::Value::Array(msgs)
}
pub fn message_fixture(
session_id: &str,
message_id: &str,
role: &str,
created: i64,
completed: Option<i64>,
parts: Vec<Value>,
) -> Value {
let mut time = serde_json::json!({ "created": created });
if let Some(completed) = completed {
time["completed"] = serde_json::json!(completed);
}
let parts = Value::Array(parts);
serde_json::json!({
"info": {
"id": message_id,
"sessionID": session_id,
"role": role,
"time": time,
},
"parts": parts,
})
}
pub fn tool_part_fixture(call_id: &str, tool: &str, state: Option<Value>) -> Value {
let mut part = serde_json::json!({
"type": "tool",
"callID": call_id,
"tool": tool,
"input": {},
});
if let Some(state) = state {
part["state"] = state;
}
part
}
pub fn message_history_fixture(messages: Vec<Value>) -> Value {
Value::Array(messages)
}
pub fn sessions_list_fixture(session_ids: &[&str]) -> serde_json::Value {
serde_json::json!(
session_ids
.iter()
.map(|id| session_fixture(id))
.collect::<Vec<_>>()
)
}
pub fn commands_list_fixture() -> serde_json::Value {
serde_json::json!([
{"name": "test", "description": "Run tests"},
{"name": "build", "description": "Build project"},
{"name": "lint", "description": "Run linter"}
])
}
pub async fn seed_spawned_sessions(server: &Arc<OrchestratorServerHandle>, session_ids: &[&str]) {
let srv = server
.acquire()
.await
.expect("test server should be initialized");
let mut spawned = srv.spawned_sessions().write().await;
for session_id in session_ids {
spawned.insert((*session_id).to_string());
}
}