751 lines
24 KiB
Rust
751 lines
24 KiB
Rust
#![cfg(feature = "server")]
|
|
//! Tests for CoreRuntime operations
|
|
//!
|
|
//! T072: CoreRuntime::create_connector
|
|
//! T073: CoreRuntime::start_connector
|
|
//! T074: CoreRuntime::stop_connector
|
|
//! T075: CoreRuntime::send_command
|
|
|
|
use dirigent_core::connectors::{Connector, ConnectorCommand};
|
|
use dirigent_core::types::{ConnectorKind, ConnectorState};
|
|
use dirigent_core::{ConnectorConfig, CoreConfig, CoreError, CoreRuntime};
|
|
use serde_json::json;
|
|
|
|
/// Helper to create a test runtime
|
|
fn create_test_runtime() -> CoreRuntime {
|
|
CoreRuntime::new(CoreConfig::default(), None)
|
|
}
|
|
|
|
/// Helper to create an OpenCode connector config
|
|
fn create_opencode_config(id: Option<String>, title: &str) -> ConnectorConfig {
|
|
ConnectorConfig {
|
|
id,
|
|
kind: ConnectorKind::OpenCode,
|
|
owner: None,
|
|
title: Some(title.to_string()),
|
|
working_directory: None,
|
|
params: json!({
|
|
"base_url": "http://localhost:12225",
|
|
"title": title,
|
|
"initial_session": null
|
|
}),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// T072: CoreRuntime::create_connector
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_t072_connector_id_auto_generated_if_not_provided() {
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(None, "Auto ID Test");
|
|
|
|
let result = runtime.create_connector(uuid::Uuid::nil(), cfg).await;
|
|
|
|
assert!(result.is_ok(), "Should create connector successfully");
|
|
|
|
let connector_id = result.unwrap();
|
|
assert!(!connector_id.is_empty(), "Generated ID should not be empty");
|
|
|
|
// Verify it's a valid UUID format (36 chars with hyphens)
|
|
assert_eq!(connector_id.len(), 36, "Should be UUID format");
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_t072_connector_uses_provided_id() {
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(Some("my-custom-id".to_string()), "Custom ID Test");
|
|
|
|
let result = runtime.create_connector(uuid::Uuid::nil(), cfg).await;
|
|
|
|
assert!(result.is_ok(), "Should create connector successfully");
|
|
assert_eq!(result.unwrap(), "my-custom-id");
|
|
|
|
// Clean up
|
|
runtime
|
|
.remove_connector(&"my-custom-id".to_string())
|
|
.await
|
|
.ok();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_t072_already_exists_error_if_id_conflicts() {
|
|
let runtime = create_test_runtime();
|
|
let cfg1 = create_opencode_config(Some("duplicate-id".to_string()), "First");
|
|
|
|
// Create first connector
|
|
let result1 = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg1.clone())
|
|
.await;
|
|
assert!(result1.is_ok(), "First creation should succeed");
|
|
|
|
// Try to create another with the same ID
|
|
let result2 = runtime.create_connector(uuid::Uuid::nil(), cfg1).await;
|
|
|
|
assert!(result2.is_err(), "Second creation should fail");
|
|
assert_eq!(result2.unwrap_err(), CoreError::AlreadyExists);
|
|
|
|
// Clean up
|
|
runtime
|
|
.remove_connector(&"duplicate-id".to_string())
|
|
.await
|
|
.ok();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_t072_connector_appears_in_list_after_creation() {
|
|
let runtime = create_test_runtime();
|
|
|
|
// Initially empty
|
|
let list_before = runtime.list_connectors(None).await;
|
|
let initial_count = list_before.len();
|
|
|
|
// Create a connector
|
|
let cfg = create_opencode_config(Some("test-conn-1".to_string()), "Test 1");
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
// List should now contain it
|
|
let list_after = runtime.list_connectors(None).await;
|
|
assert_eq!(list_after.len(), initial_count + 1);
|
|
|
|
let found = list_after.iter().find(|c| c.id == connector_id);
|
|
assert!(found.is_some(), "Created connector should be in list");
|
|
|
|
let connector_summary = found.unwrap();
|
|
assert_eq!(connector_summary.id, "test-conn-1");
|
|
assert_eq!(connector_summary.title, "Test 1");
|
|
assert_eq!(connector_summary.owner, uuid::Uuid::nil());
|
|
assert_eq!(connector_summary.kind, ConnectorKind::OpenCode);
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_t072_invalid_config_returns_error() {
|
|
let runtime = create_test_runtime();
|
|
|
|
let cfg = ConnectorConfig {
|
|
id: None,
|
|
kind: ConnectorKind::OpenCode,
|
|
owner: None,
|
|
title: Some("Invalid".to_string()),
|
|
working_directory: None,
|
|
params: json!({
|
|
"invalid": "config"
|
|
// Missing required fields like base_url
|
|
}),
|
|
..Default::default()
|
|
};
|
|
|
|
let result = runtime.create_connector(uuid::Uuid::nil(), cfg).await;
|
|
|
|
assert!(result.is_err(), "Should fail with invalid config");
|
|
assert_eq!(result.unwrap_err(), CoreError::InvalidConfig);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_t072_mock_connector_not_allowed() {
|
|
let runtime = create_test_runtime();
|
|
|
|
let cfg = ConnectorConfig {
|
|
id: None,
|
|
kind: ConnectorKind::Mock,
|
|
owner: None,
|
|
title: Some("Mock".to_string()),
|
|
working_directory: None,
|
|
params: json!({}),
|
|
..Default::default()
|
|
};
|
|
|
|
let result = runtime.create_connector(uuid::Uuid::nil(), cfg).await;
|
|
|
|
assert!(
|
|
result.is_err(),
|
|
"Mock connectors should not be creatable via API"
|
|
);
|
|
assert_eq!(result.unwrap_err(), CoreError::InvalidConfig);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_t072_owner_override() {
|
|
let runtime = create_test_runtime();
|
|
|
|
let mut cfg = create_opencode_config(None, "Owner Test");
|
|
// Try to set owner in config
|
|
cfg.owner = Some(uuid::Uuid::from_u128(99));
|
|
|
|
// Create with different owner
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::from_u128(42), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify owner was overridden
|
|
let list = runtime.list_connectors(None).await;
|
|
let connector = list.iter().find(|c| c.id == connector_id).unwrap();
|
|
assert_eq!(
|
|
connector.owner,
|
|
uuid::Uuid::from_u128(42),
|
|
"Owner should be from parameter, not config"
|
|
);
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
// ============================================================================
|
|
// T073: CoreRuntime::start_connector
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_t073_start_connector_not_found() {
|
|
let runtime = create_test_runtime();
|
|
|
|
let result = runtime.start_connector(&"nonexistent".to_string()).await;
|
|
|
|
assert!(result.is_err(), "Should fail for nonexistent connector");
|
|
assert_eq!(result.unwrap_err(), CoreError::NotFound);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_t073_start_connector_not_yet_implemented() {
|
|
// Note: As per the runtime.rs code, starting an existing connector
|
|
// is not yet fully implemented. This test documents the current behavior.
|
|
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(Some("start-test".to_string()), "Start Test");
|
|
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Try to start it
|
|
let result = runtime.start_connector(&connector_id).await;
|
|
|
|
// Currently returns an error indicating not implemented
|
|
assert!(
|
|
result.is_err(),
|
|
"Starting existing connector not yet supported"
|
|
);
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
// ============================================================================
|
|
// T074: CoreRuntime::stop_connector
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_t074_stop_connector_not_found() {
|
|
let runtime = create_test_runtime();
|
|
|
|
let result = runtime.stop_connector(&"nonexistent".to_string()).await;
|
|
|
|
assert!(result.is_err(), "Should fail for nonexistent connector");
|
|
assert_eq!(result.unwrap_err(), CoreError::NotFound);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_t074_stop_connector_changes_state_to_stopped() {
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(Some("stop-test".to_string()), "Stop Test");
|
|
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Get the connector and verify initial state
|
|
let connector = runtime.get_connector(&connector_id).await.unwrap();
|
|
let initial_state = connector.state();
|
|
assert_eq!(initial_state, ConnectorState::Initializing);
|
|
|
|
// Stop it
|
|
let result = runtime.stop_connector(&connector_id).await;
|
|
assert!(result.is_ok(), "Stop should succeed");
|
|
|
|
// Verify state changed to Stopped
|
|
let connector = runtime.get_connector(&connector_id).await.unwrap();
|
|
let final_state = connector.state();
|
|
assert_eq!(final_state, ConnectorState::Stopped);
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_t074_stop_connector_idempotent() {
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(Some("stop-twice".to_string()), "Stop Twice");
|
|
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Stop once
|
|
let result1 = runtime.stop_connector(&connector_id).await;
|
|
assert!(result1.is_ok(), "First stop should succeed");
|
|
|
|
// Stop again
|
|
let result2 = runtime.stop_connector(&connector_id).await;
|
|
assert!(result2.is_ok(), "Second stop should also succeed");
|
|
|
|
// State should still be Stopped
|
|
let connector = runtime.get_connector(&connector_id).await.unwrap();
|
|
assert_eq!(connector.state(), ConnectorState::Stopped);
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
// ============================================================================
|
|
// T075: CoreRuntime::send_command
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_t075_send_command_not_found() {
|
|
let runtime = create_test_runtime();
|
|
|
|
let result = runtime
|
|
.send_command(&"nonexistent".to_string(), ConnectorCommand::ListSessions)
|
|
.await;
|
|
|
|
assert!(result.is_err(), "Should fail for nonexistent connector");
|
|
assert_eq!(result.unwrap_err(), CoreError::NotFound);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // TODO: Fix - cmd_rx is dropped when connector is dropped in create_connector
|
|
async fn test_t075_send_command_to_connector() {
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(Some("cmd-test".to_string()), "Command Test");
|
|
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Get the connector and subscribe to events
|
|
let connector = runtime.get_connector(&connector_id).await.unwrap();
|
|
let _events = connector.subscribe();
|
|
|
|
// Send a command via runtime
|
|
let result = runtime
|
|
.send_command(&connector_id, ConnectorCommand::ListSessions)
|
|
.await;
|
|
|
|
assert!(result.is_ok(), "Send command should succeed");
|
|
|
|
// Note: The command won't be processed until the connector is started,
|
|
// but we've verified that the command was accepted
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
#[ignore] // TODO: Fix - cmd_rx is dropped when connector is dropped in create_connector
|
|
#[tokio::test]
|
|
async fn test_t075_send_all_command_types() {
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(Some("all-cmds".to_string()), "All Commands");
|
|
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Send ListSessions
|
|
let result = runtime
|
|
.send_command(&connector_id, ConnectorCommand::ListSessions)
|
|
.await;
|
|
assert!(result.is_ok());
|
|
|
|
// Send ListMessages
|
|
let result = runtime
|
|
.send_command(
|
|
&connector_id,
|
|
ConnectorCommand::ListMessages {
|
|
session_id: "test-session".to_string(),
|
|
},
|
|
)
|
|
.await;
|
|
assert!(result.is_ok());
|
|
|
|
// Send SendMessage
|
|
let result = runtime
|
|
.send_command(
|
|
&connector_id,
|
|
ConnectorCommand::SendMessage {
|
|
session_id: "test-session".to_string(),
|
|
text: "Hello".to_string(),
|
|
},
|
|
)
|
|
.await;
|
|
assert!(result.is_ok());
|
|
|
|
// Send Reconnect
|
|
let result = runtime
|
|
.send_command(&connector_id, ConnectorCommand::Reconnect)
|
|
.await;
|
|
assert!(result.is_ok());
|
|
|
|
// Send Shutdown
|
|
let result = runtime
|
|
.send_command(&connector_id, ConnectorCommand::Shutdown)
|
|
.await;
|
|
assert!(result.is_ok());
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // TODO: Fix - cmd_rx is dropped when connector is dropped in create_connector
|
|
async fn test_t075_send_command_channel_capacity() {
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(Some("capacity-test".to_string()), "Capacity Test");
|
|
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Send many commands (the channel has capacity 100)
|
|
for i in 0..50 {
|
|
let result = runtime
|
|
.send_command(&connector_id, ConnectorCommand::ListSessions)
|
|
.await;
|
|
assert!(result.is_ok(), "Command {} should be accepted", i);
|
|
}
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Additional Runtime Tests
|
|
// ============================================================================
|
|
|
|
#[tokio::test]
|
|
async fn test_list_connectors_filters_by_owner() {
|
|
let runtime = create_test_runtime();
|
|
|
|
// Create connectors for different users
|
|
let cfg1 = create_opencode_config(Some("user1-conn1".to_string()), "User 1 Conn 1");
|
|
let cfg2 = create_opencode_config(Some("user1-conn2".to_string()), "User 1 Conn 2");
|
|
let cfg3 = create_opencode_config(Some("user2-conn1".to_string()), "User 2 Conn 1");
|
|
|
|
runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg1)
|
|
.await
|
|
.unwrap();
|
|
runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg2)
|
|
.await
|
|
.unwrap();
|
|
runtime
|
|
.create_connector(uuid::Uuid::from_u128(2), cfg3)
|
|
.await
|
|
.unwrap();
|
|
|
|
// List all
|
|
let all = runtime.list_connectors(None).await;
|
|
assert!(all.len() >= 3, "Should have at least 3 connectors");
|
|
|
|
// List for user-1
|
|
let user1_list = runtime.list_connectors(Some(uuid::Uuid::nil())).await;
|
|
assert_eq!(user1_list.len(), 2, "User 1 should have 2 connectors");
|
|
|
|
// List for user-2
|
|
let user2_list = runtime
|
|
.list_connectors(Some(uuid::Uuid::from_u128(2)))
|
|
.await;
|
|
assert_eq!(user2_list.len(), 1, "User 2 should have 1 connector");
|
|
|
|
// Clean up
|
|
runtime
|
|
.remove_connector(&"user1-conn1".to_string())
|
|
.await
|
|
.ok();
|
|
runtime
|
|
.remove_connector(&"user1-conn2".to_string())
|
|
.await
|
|
.ok();
|
|
runtime
|
|
.remove_connector(&"user2-conn1".to_string())
|
|
.await
|
|
.ok();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_connector_returns_some_if_exists() {
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(Some("get-test".to_string()), "Get Test");
|
|
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
let result = runtime.get_connector(&connector_id).await;
|
|
assert!(result.is_some(), "Should find connector");
|
|
|
|
let connector = result.unwrap();
|
|
assert_eq!(connector.id(), &connector_id);
|
|
assert_eq!(connector.title(), "Get Test");
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&connector_id).await.ok();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_connector_returns_none_if_not_exists() {
|
|
let runtime = create_test_runtime();
|
|
|
|
let result = runtime.get_connector(&"nonexistent".to_string()).await;
|
|
assert!(result.is_none(), "Should not find nonexistent connector");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_remove_connector_success() {
|
|
let runtime = create_test_runtime();
|
|
let cfg = create_opencode_config(Some("remove-test".to_string()), "Remove Test");
|
|
|
|
let connector_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Verify it exists
|
|
assert!(runtime.get_connector(&connector_id).await.is_some());
|
|
|
|
// Remove it
|
|
let result = runtime.remove_connector(&connector_id).await;
|
|
assert!(result.is_ok(), "Remove should succeed");
|
|
|
|
// Verify it's gone
|
|
assert!(runtime.get_connector(&connector_id).await.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_sharing_bus_subscribe_all() {
|
|
let runtime = create_test_runtime();
|
|
|
|
// Subscribe to every event on the SharingBus — this is the
|
|
// replacement for the retired `subscribe_global()` API.
|
|
let rx1 = runtime.sharing_bus().subscribe_all().await;
|
|
let rx2 = runtime.sharing_bus().subscribe_all().await;
|
|
|
|
// Verify both subscriptions are valid
|
|
drop(rx1);
|
|
drop(rx2);
|
|
// If this compiles and runs, subscriptions work
|
|
}
|
|
|
|
// ============================================================================
|
|
// Issue 2: Zero-Connector State (Regression Test)
|
|
// ============================================================================
|
|
|
|
/// Test that new connectors can be added after removing all existing connectors
|
|
///
|
|
/// This test verifies the fix for Issue 2: "Removing All Connectors Breaks New Connection"
|
|
///
|
|
/// Regression test ensures:
|
|
/// - Creating first connector works (0 -> 1 transition)
|
|
/// - Removing all connectors works (1 -> 0 transition)
|
|
/// - Creating connector after zero state works (0 -> 1 again)
|
|
/// - No state corruption or race conditions
|
|
/// - Config persistence handles empty state correctly
|
|
#[tokio::test]
|
|
async fn test_issue_2_create_connector_after_removing_all() {
|
|
let runtime = create_test_runtime();
|
|
|
|
// Verify starting state is empty
|
|
let initial_connectors = runtime.list_connectors(None).await;
|
|
assert_eq!(
|
|
initial_connectors.len(),
|
|
0,
|
|
"Should start with zero connectors"
|
|
);
|
|
|
|
// Create first connector (0 -> 1 transition)
|
|
let cfg1 = create_opencode_config(Some("test-connector-1".to_string()), "First Connector");
|
|
let id1 = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg1)
|
|
.await
|
|
.expect("Should create first connector");
|
|
|
|
let connectors_after_create = runtime.list_connectors(None).await;
|
|
assert_eq!(
|
|
connectors_after_create.len(),
|
|
1,
|
|
"Should have 1 connector after creation"
|
|
);
|
|
|
|
// Verify connector is in the list
|
|
let connector1 = runtime.get_connector(&id1).await;
|
|
assert!(connector1.is_some(), "First connector should exist");
|
|
|
|
// Remove the connector (1 -> 0 transition)
|
|
runtime
|
|
.remove_connector(&id1)
|
|
.await
|
|
.expect("Should remove connector successfully");
|
|
|
|
let connectors_after_remove = runtime.list_connectors(None).await;
|
|
assert_eq!(
|
|
connectors_after_remove.len(),
|
|
0,
|
|
"Should have zero connectors after removal"
|
|
);
|
|
|
|
// Verify connector is gone
|
|
let connector1_after_remove = runtime.get_connector(&id1).await;
|
|
assert!(
|
|
connector1_after_remove.is_none(),
|
|
"First connector should not exist after removal"
|
|
);
|
|
|
|
// Create new connector after reaching zero state (0 -> 1 again)
|
|
let cfg2 = create_opencode_config(Some("test-connector-2".to_string()), "Second Connector");
|
|
let id2 = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg2)
|
|
.await
|
|
.expect("Should create connector after removing all");
|
|
|
|
let connectors_final = runtime.list_connectors(None).await;
|
|
assert_eq!(
|
|
connectors_final.len(),
|
|
1,
|
|
"Should have 1 connector after recreation"
|
|
);
|
|
|
|
// Verify new connector is in the list
|
|
let connector2 = runtime.get_connector(&id2).await;
|
|
assert!(connector2.is_some(), "Second connector should exist");
|
|
|
|
// Verify IDs are different
|
|
assert_ne!(id1, id2, "New connector should have different ID");
|
|
|
|
// Verify new connector is functional (state check)
|
|
let connector2_handle = connector2.unwrap();
|
|
let state = connector2_handle.state();
|
|
assert_eq!(
|
|
state,
|
|
ConnectorState::Initializing,
|
|
"New connector should be initializing"
|
|
);
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&id2).await.ok();
|
|
}
|
|
|
|
/// Test rapid remove-create cycles to check for race conditions
|
|
///
|
|
/// This test verifies that repeated transitions between empty and non-empty
|
|
/// connector states don't cause issues with config persistence or internal state.
|
|
#[tokio::test]
|
|
async fn test_issue_2_rapid_remove_create_cycles() {
|
|
let runtime = create_test_runtime();
|
|
|
|
// Perform 5 cycles of create -> remove
|
|
for i in 0..5 {
|
|
let cfg = create_opencode_config(
|
|
Some(format!("cycle-connector-{}", i)),
|
|
&format!("Cycle Test {}", i),
|
|
);
|
|
|
|
// Create connector
|
|
let id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.expect(&format!("Should create connector in cycle {}", i));
|
|
|
|
// Verify it exists
|
|
let connectors = runtime.list_connectors(None).await;
|
|
assert_eq!(
|
|
connectors.len(),
|
|
1,
|
|
"Should have 1 connector after creation"
|
|
);
|
|
|
|
// Remove connector
|
|
runtime
|
|
.remove_connector(&id)
|
|
.await
|
|
.expect(&format!("Should remove connector in cycle {}", i));
|
|
|
|
// Verify empty state
|
|
let connectors = runtime.list_connectors(None).await;
|
|
assert_eq!(
|
|
connectors.len(),
|
|
0,
|
|
"Should have 0 connectors after removal"
|
|
);
|
|
}
|
|
|
|
// Final verification: Create one more connector to ensure state is still valid
|
|
let final_cfg = create_opencode_config(Some("final-connector".to_string()), "Final Test");
|
|
let final_id = runtime
|
|
.create_connector(uuid::Uuid::nil(), final_cfg)
|
|
.await
|
|
.expect("Should create connector after rapid cycles");
|
|
|
|
let final_connectors = runtime.list_connectors(None).await;
|
|
assert_eq!(final_connectors.len(), 1, "Should have 1 connector at end");
|
|
|
|
// Clean up
|
|
runtime.remove_connector(&final_id).await.ok();
|
|
}
|
|
|
|
/// Test that SharingBus subscriptions work across zero-connector transitions.
|
|
///
|
|
/// Ensures SSE subscriptions remain valid when all connectors are removed
|
|
/// and new connectors are added.
|
|
#[tokio::test]
|
|
async fn test_issue_2_global_events_survive_zero_connectors() {
|
|
let runtime = create_test_runtime();
|
|
|
|
// Subscribe to events before any connectors exist.
|
|
let _rx = runtime.sharing_bus().subscribe_all().await;
|
|
|
|
// Create connector
|
|
let cfg = create_opencode_config(Some("event-test".to_string()), "Event Test");
|
|
let id = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg)
|
|
.await
|
|
.expect("Should create connector");
|
|
|
|
// Remove connector (transition to zero)
|
|
runtime
|
|
.remove_connector(&id)
|
|
.await
|
|
.expect("Should remove connector");
|
|
|
|
// Subscribe again after zero state
|
|
let _rx2 = runtime.sharing_bus().subscribe_all().await;
|
|
|
|
// Create another connector
|
|
let cfg2 = create_opencode_config(Some("event-test-2".to_string()), "Event Test 2");
|
|
let id2 = runtime
|
|
.create_connector(uuid::Uuid::nil(), cfg2)
|
|
.await
|
|
.expect("Should create connector after zero state");
|
|
|
|
// If we get here, subscriptions survived the zero-connector transition
|
|
// Clean up
|
|
runtime.remove_connector(&id2).await.ok();
|
|
}
|