mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-10 12:05:53 +00:00
340 lines
12 KiB
Rust
340 lines
12 KiB
Rust
use crate::api_client::ApiClient;
|
|
use crate::api_testing::APITestingManager;
|
|
use crate::auth_cache::AuthCacheManager;
|
|
use crate::backend_manager::BackendManager;
|
|
use crate::debug_features::DebugFeaturesManager;
|
|
use crate::ngrok::NgrokManager;
|
|
use crate::notification_manager::NotificationManager;
|
|
use crate::permissions::PermissionsManager;
|
|
use crate::session_monitor::SessionMonitor;
|
|
use crate::terminal::TerminalManager;
|
|
use crate::terminal_integrations::TerminalIntegrationsManager;
|
|
use crate::terminal_spawn_service::TerminalSpawnService;
|
|
use crate::tty_forward::TTYForwardManager;
|
|
#[cfg(unix)]
|
|
use crate::unix_socket_server::UnixSocketServer;
|
|
use crate::updater::UpdateManager;
|
|
use crate::welcome::WelcomeManager;
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::Arc;
|
|
use tokio::sync::RwLock;
|
|
|
|
#[derive(Clone)]
|
|
pub struct AppState {
|
|
pub terminal_manager: Arc<TerminalManager>,
|
|
pub api_client: Arc<ApiClient>,
|
|
pub ngrok_manager: Arc<NgrokManager>,
|
|
pub server_monitoring: Arc<AtomicBool>,
|
|
pub server_target_port: Arc<RwLock<Option<u16>>>,
|
|
pub tty_forward_manager: Arc<TTYForwardManager>,
|
|
pub session_monitor: Arc<SessionMonitor>,
|
|
pub notification_manager: Arc<NotificationManager>,
|
|
pub welcome_manager: Arc<WelcomeManager>,
|
|
pub permissions_manager: Arc<PermissionsManager>,
|
|
pub update_manager: Arc<UpdateManager>,
|
|
pub backend_manager: Arc<BackendManager>,
|
|
pub debug_features_manager: Arc<DebugFeaturesManager>,
|
|
pub api_testing_manager: Arc<APITestingManager>,
|
|
pub auth_cache_manager: Arc<AuthCacheManager>,
|
|
pub terminal_integrations_manager: Arc<TerminalIntegrationsManager>,
|
|
pub terminal_spawn_service: Arc<TerminalSpawnService>,
|
|
#[cfg(unix)]
|
|
pub unix_socket_server: Arc<UnixSocketServer>,
|
|
}
|
|
|
|
impl Default for AppState {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl AppState {
|
|
pub fn new() -> Self {
|
|
let terminal_manager = Arc::new(TerminalManager::new());
|
|
let session_monitor = Arc::new(SessionMonitor::new(terminal_manager.clone()));
|
|
let notification_manager = Arc::new(NotificationManager::new());
|
|
let mut permissions_manager = PermissionsManager::new();
|
|
permissions_manager.set_notification_manager(notification_manager.clone());
|
|
|
|
let current_version = env!("CARGO_PKG_VERSION").to_string();
|
|
let mut update_manager = UpdateManager::new(current_version);
|
|
update_manager.set_notification_manager(notification_manager.clone());
|
|
|
|
// Get port from settings or use default
|
|
let settings = crate::settings::Settings::load().unwrap_or_default();
|
|
let port = settings.dashboard.server_port;
|
|
let backend_manager = BackendManager::new(port);
|
|
let api_client = Arc::new(ApiClient::new(port));
|
|
|
|
let mut debug_features_manager = DebugFeaturesManager::new();
|
|
debug_features_manager.set_notification_manager(notification_manager.clone());
|
|
|
|
let mut api_testing_manager = APITestingManager::new();
|
|
api_testing_manager.set_notification_manager(notification_manager.clone());
|
|
|
|
let mut auth_cache_manager = AuthCacheManager::new();
|
|
auth_cache_manager.set_notification_manager(notification_manager.clone());
|
|
|
|
let mut terminal_integrations_manager = TerminalIntegrationsManager::new();
|
|
terminal_integrations_manager.set_notification_manager(notification_manager.clone());
|
|
|
|
let terminal_integrations_manager = Arc::new(terminal_integrations_manager);
|
|
let terminal_spawn_service = Arc::new(TerminalSpawnService::new(
|
|
terminal_integrations_manager.clone(),
|
|
));
|
|
|
|
#[cfg(unix)]
|
|
let unix_socket_server = Arc::new(UnixSocketServer::new(terminal_spawn_service.clone()));
|
|
|
|
Self {
|
|
terminal_manager,
|
|
api_client,
|
|
ngrok_manager: Arc::new(NgrokManager::new()),
|
|
server_monitoring: Arc::new(AtomicBool::new(true)),
|
|
server_target_port: Arc::new(RwLock::new(None)),
|
|
tty_forward_manager: Arc::new(TTYForwardManager::new()),
|
|
session_monitor,
|
|
notification_manager,
|
|
welcome_manager: Arc::new(WelcomeManager::new()),
|
|
permissions_manager: Arc::new(permissions_manager),
|
|
update_manager: Arc::new(update_manager),
|
|
backend_manager: Arc::new(backend_manager),
|
|
debug_features_manager: Arc::new(debug_features_manager),
|
|
api_testing_manager: Arc::new(api_testing_manager),
|
|
auth_cache_manager: Arc::new(auth_cache_manager),
|
|
terminal_integrations_manager,
|
|
terminal_spawn_service,
|
|
#[cfg(unix)]
|
|
unix_socket_server,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::sync::atomic::Ordering;
|
|
|
|
#[test]
|
|
fn test_app_state_creation() {
|
|
let state = AppState::new();
|
|
|
|
// Verify all components are initialized
|
|
assert!(Arc::strong_count(&state.terminal_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.api_client) >= 1);
|
|
assert!(Arc::strong_count(&state.ngrok_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.session_monitor) >= 1);
|
|
assert!(Arc::strong_count(&state.notification_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.welcome_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.permissions_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.update_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.backend_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.debug_features_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.api_testing_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.auth_cache_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.terminal_integrations_manager) >= 1);
|
|
assert!(Arc::strong_count(&state.terminal_spawn_service) >= 1);
|
|
assert!(Arc::strong_count(&state.tty_forward_manager) >= 1);
|
|
|
|
#[cfg(unix)]
|
|
assert!(Arc::strong_count(&state.unix_socket_server) >= 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_clone_impl() {
|
|
let state1 = AppState::new();
|
|
let state2 = state1.clone();
|
|
|
|
// Verify that cloning increases reference counts
|
|
assert!(Arc::strong_count(&state1.terminal_manager) >= 2);
|
|
assert!(Arc::strong_count(&state1.api_client) >= 2);
|
|
|
|
// Verify they point to the same instances
|
|
assert!(Arc::ptr_eq(
|
|
&state1.terminal_manager,
|
|
&state2.terminal_manager
|
|
));
|
|
assert!(Arc::ptr_eq(&state1.api_client, &state2.api_client));
|
|
assert!(Arc::ptr_eq(&state1.ngrok_manager, &state2.ngrok_manager));
|
|
assert!(Arc::ptr_eq(
|
|
&state1.session_monitor,
|
|
&state2.session_monitor
|
|
));
|
|
assert!(Arc::ptr_eq(
|
|
&state1.notification_manager,
|
|
&state2.notification_manager
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_server_monitoring_default() {
|
|
let state = AppState::new();
|
|
|
|
// Server monitoring should be enabled by default
|
|
assert!(state.server_monitoring.load(Ordering::Relaxed));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_server_target_port() {
|
|
let state = AppState::new();
|
|
|
|
// Initially should be None
|
|
let port = state.server_target_port.read().await;
|
|
assert!(port.is_none());
|
|
drop(port);
|
|
|
|
// Test setting a port
|
|
{
|
|
let mut port = state.server_target_port.write().await;
|
|
*port = Some(8080);
|
|
}
|
|
|
|
// Verify the port was set
|
|
let port = state.server_target_port.read().await;
|
|
assert_eq!(*port, Some(8080));
|
|
}
|
|
|
|
#[test]
|
|
fn test_notification_manager_sharing() {
|
|
let state = AppState::new();
|
|
|
|
// All managers that need notifications should have the same notification manager
|
|
// This is verified by checking Arc pointer equality
|
|
let _notification_ptr = Arc::as_ptr(&state.notification_manager);
|
|
|
|
// We can't directly access the notification managers inside other components
|
|
// but we can verify they all exist and the reference count is high
|
|
assert!(Arc::strong_count(&state.notification_manager) >= 5); // Multiple components use it
|
|
}
|
|
|
|
#[test]
|
|
fn test_terminal_manager_sharing() {
|
|
let state = AppState::new();
|
|
|
|
// Terminal manager should be shared with session monitor
|
|
// Verify by checking reference count
|
|
assert!(Arc::strong_count(&state.terminal_manager) >= 2); // At least AppState and SessionMonitor
|
|
}
|
|
|
|
#[test]
|
|
fn test_terminal_integrations_sharing() {
|
|
let state = AppState::new();
|
|
|
|
// Terminal integrations manager should be shared with terminal spawn service
|
|
assert!(Arc::strong_count(&state.terminal_integrations_manager) >= 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_server_monitoring_toggle() {
|
|
let state = AppState::new();
|
|
|
|
// Test toggling server monitoring
|
|
state.server_monitoring.store(false, Ordering::Relaxed);
|
|
assert!(!state.server_monitoring.load(Ordering::Relaxed));
|
|
|
|
state.server_monitoring.store(true, Ordering::Relaxed);
|
|
assert!(state.server_monitoring.load(Ordering::Relaxed));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_concurrent_port_access() {
|
|
let state = AppState::new();
|
|
let state_clone = state.clone();
|
|
|
|
// Spawn a task to write
|
|
let write_handle = tokio::spawn(async move {
|
|
let mut port = state_clone.server_target_port.write().await;
|
|
*port = Some(9090);
|
|
});
|
|
|
|
// Spawn a task to read
|
|
let read_handle = tokio::spawn(async move {
|
|
// Give writer a chance to acquire lock first
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
|
let port = state.server_target_port.read().await;
|
|
port.is_some()
|
|
});
|
|
|
|
write_handle.await.unwrap();
|
|
let has_port = read_handle.await.unwrap();
|
|
assert!(has_port);
|
|
}
|
|
|
|
#[test]
|
|
fn test_api_client_port() {
|
|
// This test verifies that the API client is initialized with the correct port
|
|
let state = AppState::new();
|
|
|
|
// The port should match the one from settings (or default)
|
|
let _settings = crate::settings::Settings::load().unwrap_or_default();
|
|
// We can't directly access the port from ApiClient, but we know it should be initialized
|
|
assert!(Arc::strong_count(&state.api_client) >= 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_backend_manager_port() {
|
|
// This test verifies that the backend manager is initialized with the correct port
|
|
let state = AppState::new();
|
|
|
|
// The backend manager should be initialized with the port from settings
|
|
assert!(Arc::strong_count(&state.backend_manager) >= 1);
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn test_unix_socket_server_initialization() {
|
|
let state = AppState::new();
|
|
|
|
// Unix socket server should be initialized with terminal spawn service
|
|
assert!(Arc::strong_count(&state.unix_socket_server) >= 1);
|
|
assert!(Arc::strong_count(&state.terminal_spawn_service) >= 2); // AppState and UnixSocketServer
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_clones() {
|
|
let state1 = AppState::new();
|
|
let state2 = state1.clone();
|
|
let state3 = state2.clone();
|
|
let state4 = state1.clone();
|
|
|
|
// All clones should share the same underlying Arc instances
|
|
assert!(Arc::ptr_eq(
|
|
&state1.terminal_manager,
|
|
&state4.terminal_manager
|
|
));
|
|
assert!(Arc::ptr_eq(&state2.api_client, &state3.api_client));
|
|
|
|
// Reference count should increase with each clone
|
|
assert!(Arc::strong_count(&state1.terminal_manager) >= 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_drop_behavior() {
|
|
let state1 = AppState::new();
|
|
let initial_count = Arc::strong_count(&state1.terminal_manager);
|
|
|
|
{
|
|
let _state2 = state1.clone();
|
|
// Reference count should increase
|
|
assert_eq!(
|
|
Arc::strong_count(&state1.terminal_manager),
|
|
initial_count + 1
|
|
);
|
|
}
|
|
|
|
// After drop, reference count should decrease
|
|
assert_eq!(Arc::strong_count(&state1.terminal_manager), initial_count);
|
|
}
|
|
|
|
#[test]
|
|
fn test_version_initialization() {
|
|
let state = AppState::new();
|
|
|
|
// Update manager should be initialized with the correct version
|
|
let version = env!("CARGO_PKG_VERSION");
|
|
assert!(!version.is_empty());
|
|
|
|
// We can't directly verify the version in UpdateManager, but we know it's initialized
|
|
assert!(Arc::strong_count(&state.update_manager) >= 1);
|
|
}
|
|
}
|