mod common; use std::sync::Arc; use axum::{body::Body, http::StatusCode}; use common::*; use http_body_util::BodyExt; use pinakes_core::{config::PluginsConfig, plugin::PluginManager}; use tower::ServiceExt; async fn setup_app_with_plugins() -> (axum::Router, Arc, tempfile::TempDir) { use pinakes_core::{ cache::CacheLayer, config::RateLimitConfig, jobs::JobQueue, storage::{StorageBackend, sqlite::SqliteBackend}, }; use tokio::sync::RwLock; let backend = SqliteBackend::in_memory().expect("in-memory SQLite"); backend.run_migrations().await.expect("migrations"); let storage = Arc::new(backend) as pinakes_core::storage::DynStorageBackend; let temp_dir = tempfile::TempDir::new().expect("create temp dir"); let data_dir = temp_dir.path().join("data"); let cache_dir = temp_dir.path().join("cache"); std::fs::create_dir_all(&data_dir).expect("create data dir"); std::fs::create_dir_all(&cache_dir).expect("create cache dir"); let plugin_config = PluginsConfig { enabled: true, data_dir: data_dir.clone(), cache_dir: cache_dir.clone(), plugin_dirs: vec![], enable_hot_reload: false, allow_unsigned: true, max_concurrent_ops: 2, plugin_timeout_secs: 10, timeouts: pinakes_core::config::PluginTimeoutConfig::default(), max_consecutive_failures: 5, trusted_keys: vec![], }; let plugin_manager = PluginManager::new(data_dir, cache_dir, plugin_config.clone().into()) .expect("create plugin manager"); let plugin_manager = Arc::new(plugin_manager); let mut config = default_config(); config.plugins = plugin_config; let job_queue = JobQueue::new(1, 0, |_id, _kind, _cancel, _jobs| tokio::spawn(async {})); let config = Arc::new(RwLock::new(config)); let scheduler = pinakes_core::scheduler::TaskScheduler::new( job_queue.clone(), tokio_util::sync::CancellationToken::new(), config.clone(), None, ); let state = pinakes_server::state::AppState { storage, config, config_path: None, scan_progress: pinakes_core::scan::ScanProgress::new(), job_queue, cache: Arc::new(CacheLayer::new(60)), scheduler: Arc::new(scheduler), plugin_manager: Some(plugin_manager.clone()), plugin_pipeline: None, transcode_service: None, managed_storage: None, chunked_upload_manager: None, session_semaphore: Arc::new(tokio::sync::Semaphore::new(64)), webhook_dispatcher: None, }; let router = pinakes_server::app::create_router(state, &RateLimitConfig::default()); (router, plugin_manager, temp_dir) } #[tokio::test] async fn test_list_plugins_empty() { let (app, _pm, _tmp) = setup_app_with_plugins().await; let response = app.oneshot(get("/api/v1/plugins")).await.unwrap(); assert_eq!(response.status(), StatusCode::OK); let body = response.into_body().collect().await.unwrap().to_bytes(); let plugins: Vec = serde_json::from_slice(&body).unwrap(); assert_eq!(plugins.len(), 0, "should start with no plugins loaded"); } #[tokio::test] async fn test_plugin_manager_exists() { let (app, _pm, _tmp) = setup_app_with_plugins().await; let plugins = _pm.list_plugins().await; assert_eq!(plugins.len(), 0); let response = app.oneshot(get("/api/v1/plugins")).await.unwrap(); assert_eq!(response.status(), StatusCode::OK); } #[tokio::test] async fn test_plugin_not_found() { let (app, _pm, _tmp) = setup_app_with_plugins().await; let response = app .oneshot(get("/api/v1/plugins/nonexistent")) .await .unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); } #[tokio::test] async fn test_plugin_enable_disable() { let (app, pm, _tmp) = setup_app_with_plugins().await; assert!(pm.list_plugins().await.is_empty()); let mut req = axum::http::Request::builder() .method("POST") .uri("/api/v1/plugins/test-plugin/enable") .body(Body::empty()) .unwrap(); req.extensions_mut().insert(test_addr()); let response = app.clone().oneshot(req).await.unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); let mut req = axum::http::Request::builder() .method("POST") .uri("/api/v1/plugins/test-plugin/disable") .body(Body::empty()) .unwrap(); req.extensions_mut().insert(test_addr()); let response = app.oneshot(req).await.unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); } #[tokio::test] async fn test_plugin_uninstall_not_found() { let (app, _pm, _tmp) = setup_app_with_plugins().await; let mut req = axum::http::Request::builder() .method("DELETE") .uri("/api/v1/plugins/nonexistent") .body(Body::empty()) .unwrap(); req.extensions_mut().insert(test_addr()); let response = app.oneshot(req).await.unwrap(); assert!( response.status() == StatusCode::BAD_REQUEST || response.status() == StatusCode::NOT_FOUND ); }