Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I4a6b498153eccd5407510dd541b7f4816a6a6964
212 lines
6.6 KiB
Rust
212 lines
6.6 KiB
Rust
use std::net::SocketAddr;
|
|
use std::sync::Arc;
|
|
|
|
use axum::body::Body;
|
|
use axum::extract::ConnectInfo;
|
|
use axum::http::{Request, StatusCode};
|
|
use http_body_util::BodyExt;
|
|
use tokio::sync::RwLock;
|
|
use tower::ServiceExt;
|
|
|
|
use pinakes_core::cache::CacheLayer;
|
|
use pinakes_core::config::{
|
|
AccountsConfig, Config, DirectoryConfig, JobsConfig, ScanningConfig, ServerConfig,
|
|
SqliteConfig, StorageBackendType, StorageConfig, ThumbnailConfig, UiConfig, WebhookConfig,
|
|
};
|
|
use pinakes_core::jobs::JobQueue;
|
|
use pinakes_core::storage::StorageBackend;
|
|
use pinakes_core::storage::sqlite::SqliteBackend;
|
|
|
|
/// Fake socket address for tests (governor needs ConnectInfo<SocketAddr>)
|
|
fn test_addr() -> ConnectInfo<SocketAddr> {
|
|
ConnectInfo("127.0.0.1:9999".parse().unwrap())
|
|
}
|
|
|
|
/// Build a GET request with ConnectInfo for rate limiter compatibility
|
|
fn get(uri: &str) -> Request<Body> {
|
|
let mut req = Request::builder().uri(uri).body(Body::empty()).unwrap();
|
|
req.extensions_mut().insert(test_addr());
|
|
req
|
|
}
|
|
|
|
/// Build a POST request with ConnectInfo
|
|
fn post_json(uri: &str, body: &str) -> Request<Body> {
|
|
let mut req = Request::builder()
|
|
.method("POST")
|
|
.uri(uri)
|
|
.header("content-type", "application/json")
|
|
.body(Body::from(body.to_string()))
|
|
.unwrap();
|
|
req.extensions_mut().insert(test_addr());
|
|
req
|
|
}
|
|
|
|
async fn setup_app() -> axum::Router {
|
|
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 config = Config {
|
|
storage: StorageConfig {
|
|
backend: StorageBackendType::Sqlite,
|
|
sqlite: Some(SqliteConfig {
|
|
path: ":memory:".into(),
|
|
}),
|
|
postgres: None,
|
|
},
|
|
directories: DirectoryConfig { roots: vec![] },
|
|
scanning: ScanningConfig {
|
|
watch: false,
|
|
poll_interval_secs: 300,
|
|
ignore_patterns: vec![],
|
|
import_concurrency: 8,
|
|
},
|
|
server: ServerConfig {
|
|
host: "127.0.0.1".to_string(),
|
|
port: 3000,
|
|
api_key: None,
|
|
},
|
|
ui: UiConfig::default(),
|
|
accounts: AccountsConfig::default(),
|
|
jobs: JobsConfig::default(),
|
|
thumbnails: ThumbnailConfig::default(),
|
|
webhooks: Vec::<WebhookConfig>::new(),
|
|
scheduled_tasks: vec![],
|
|
};
|
|
|
|
let job_queue = JobQueue::new(1, |_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(),
|
|
sessions: Arc::new(RwLock::new(std::collections::HashMap::new())),
|
|
job_queue,
|
|
cache: Arc::new(CacheLayer::new(60)),
|
|
scheduler: Arc::new(scheduler),
|
|
};
|
|
|
|
pinakes_server::app::create_router(state)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_list_media_empty() {
|
|
let app = setup_app().await;
|
|
|
|
let response = app.oneshot(get("/api/v1/media")).await.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
let body = response.into_body().collect().await.unwrap().to_bytes();
|
|
let items: Vec<serde_json::Value> = serde_json::from_slice(&body).unwrap();
|
|
assert_eq!(items.len(), 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_create_and_list_tags() {
|
|
let app = setup_app().await;
|
|
|
|
// Create a tag
|
|
let response = app
|
|
.clone()
|
|
.oneshot(post_json("/api/v1/tags", r#"{"name":"Music"}"#))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
// List tags
|
|
let response = app.oneshot(get("/api/v1/tags")).await.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
let body = response.into_body().collect().await.unwrap().to_bytes();
|
|
let tags: Vec<serde_json::Value> = serde_json::from_slice(&body).unwrap();
|
|
assert_eq!(tags.len(), 1);
|
|
assert_eq!(tags[0]["name"], "Music");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_search_empty() {
|
|
let app = setup_app().await;
|
|
|
|
let response = app.oneshot(get("/api/v1/search?q=test")).await.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
let body = response.into_body().collect().await.unwrap().to_bytes();
|
|
let result: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
|
assert_eq!(result["total_count"], 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_media_not_found() {
|
|
let app = setup_app().await;
|
|
|
|
let response = app
|
|
.oneshot(get("/api/v1/media/00000000-0000-0000-0000-000000000000"))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_collections_crud() {
|
|
let app = setup_app().await;
|
|
|
|
// Create collection
|
|
let response = app
|
|
.clone()
|
|
.oneshot(post_json(
|
|
"/api/v1/collections",
|
|
r#"{"name":"Favorites","kind":"manual"}"#,
|
|
))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
// List collections
|
|
let response = app.oneshot(get("/api/v1/collections")).await.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
let body = response.into_body().collect().await.unwrap().to_bytes();
|
|
let cols: Vec<serde_json::Value> = serde_json::from_slice(&body).unwrap();
|
|
assert_eq!(cols.len(), 1);
|
|
assert_eq!(cols[0]["name"], "Favorites");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_statistics_endpoint() {
|
|
let app = setup_app().await;
|
|
|
|
let response = app.oneshot(get("/api/v1/statistics")).await.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
let body = response.into_body().collect().await.unwrap().to_bytes();
|
|
let stats: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
|
assert_eq!(stats["total_media"], 0);
|
|
assert_eq!(stats["total_size_bytes"], 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_scheduled_tasks_endpoint() {
|
|
let app = setup_app().await;
|
|
|
|
let response = app.oneshot(get("/api/v1/tasks/scheduled")).await.unwrap();
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
let body = response.into_body().collect().await.unwrap().to_bytes();
|
|
let tasks: Vec<serde_json::Value> = serde_json::from_slice(&body).unwrap();
|
|
assert!(!tasks.is_empty(), "should have default scheduled tasks");
|
|
// Verify structure of first task
|
|
assert!(tasks[0]["id"].is_string());
|
|
assert!(tasks[0]["name"].is_string());
|
|
assert!(tasks[0]["schedule"].is_string());
|
|
}
|