meta: move public crates to packages/
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I928162008cb1ba02e1aa0e7aa971e8326a6a6964
This commit is contained in:
parent
70b0113d8a
commit
00bab69598
308 changed files with 53890 additions and 53889 deletions
274
packages/pinakes-server/tests/plugin.rs
Normal file
274
packages/pinakes-server/tests/plugin.rs
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
mod common;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{body::Body, http::StatusCode};
|
||||
use common::{
|
||||
default_config,
|
||||
delete_authed,
|
||||
get,
|
||||
get_authed,
|
||||
patch_json_authed,
|
||||
post_json,
|
||||
post_json_authed,
|
||||
put_json_authed,
|
||||
response_body,
|
||||
setup_app,
|
||||
setup_app_with_auth,
|
||||
test_addr,
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
use pinakes_core::{config::PluginsConfig, plugin::PluginManager};
|
||||
use tower::ServiceExt;
|
||||
|
||||
async fn setup_app_with_plugins()
|
||||
-> (axum::Router, Arc<PluginManager>, 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::Value> = 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
|
||||
);
|
||||
}
|
||||
|
||||
// RBAC tests using common helpers with auth setup
|
||||
|
||||
#[tokio::test]
|
||||
async fn media_list_unauthenticated() {
|
||||
let app = setup_app().await;
|
||||
let resp = app.oneshot(get("/api/v1/media")).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body = response_body(resp).await;
|
||||
assert!(body.is_array());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn media_list_authenticated() {
|
||||
let (app, _, _, viewer) = setup_app_with_auth().await;
|
||||
let resp = app
|
||||
.clone()
|
||||
.oneshot(get_authed("/api/v1/media", &viewer))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn import_unauthenticated_rejected() {
|
||||
let (app, ..) = setup_app_with_auth().await;
|
||||
let resp = app
|
||||
.clone()
|
||||
.oneshot(post_json(
|
||||
"/api/v1/media/import",
|
||||
r#"{"path":"/tmp/test.txt"}"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn import_viewer_forbidden() {
|
||||
let (app, _, _, viewer) = setup_app_with_auth().await;
|
||||
let resp = app
|
||||
.clone()
|
||||
.oneshot(post_json_authed(
|
||||
"/api/v1/media/import",
|
||||
r#"{"path":"/tmp/test.txt"}"#,
|
||||
&viewer,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_media_viewer_forbidden() {
|
||||
let (app, _, _, viewer) = setup_app_with_auth().await;
|
||||
let fake_id = uuid::Uuid::now_v7();
|
||||
let resp = app
|
||||
.clone()
|
||||
.oneshot(patch_json_authed(
|
||||
&format!("/api/v1/media/{fake_id}"),
|
||||
r#"{"title":"new"}"#,
|
||||
&viewer,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_media_viewer_forbidden() {
|
||||
let (app, _, _, viewer) = setup_app_with_auth().await;
|
||||
let fake_id = uuid::Uuid::now_v7();
|
||||
let resp = app
|
||||
.clone()
|
||||
.oneshot(delete_authed(&format!("/api/v1/media/{fake_id}"), &viewer))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_sync_device_viewer_forbidden() {
|
||||
let (app, _, _, viewer) = setup_app_with_auth().await;
|
||||
let fake_id = uuid::Uuid::now_v7();
|
||||
let resp = app
|
||||
.clone()
|
||||
.oneshot(put_json_authed(
|
||||
&format!("/api/v1/sync/devices/{fake_id}"),
|
||||
r#"{"name":"renamed"}"#,
|
||||
&viewer,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue