pinakes-server: consolidate helpers for the tests
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ifbc07ced09014391bc264a36be27dc8c6a6a6964
This commit is contained in:
parent
e40c66e399
commit
84288c9660
3 changed files with 343 additions and 413 deletions
323
crates/pinakes-server/tests/common/mod.rs
Normal file
323
crates/pinakes-server/tests/common/mod.rs
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::ConnectInfo,
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
use pinakes_core::{
|
||||
cache::CacheLayer,
|
||||
config::{
|
||||
AccountsConfig,
|
||||
AnalyticsConfig,
|
||||
CloudConfig,
|
||||
Config,
|
||||
DirectoryConfig,
|
||||
EnrichmentConfig,
|
||||
JobsConfig,
|
||||
ManagedStorageConfig,
|
||||
PhotoConfig,
|
||||
PluginsConfig,
|
||||
RateLimitConfig,
|
||||
ScanningConfig,
|
||||
ServerConfig,
|
||||
SharingConfig,
|
||||
SqliteConfig,
|
||||
StorageBackendType,
|
||||
StorageConfig,
|
||||
SyncConfig,
|
||||
ThumbnailConfig,
|
||||
TlsConfig,
|
||||
TranscodingConfig,
|
||||
TrashConfig,
|
||||
UiConfig,
|
||||
UserAccount,
|
||||
UserRole,
|
||||
WebhookConfig,
|
||||
},
|
||||
jobs::JobQueue,
|
||||
storage::{StorageBackend, sqlite::SqliteBackend},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tower::ServiceExt;
|
||||
|
||||
/// Fake socket address for tests (governor needs
|
||||
/// `ConnectInfo<SocketAddr>`)
|
||||
pub fn test_addr() -> ConnectInfo<SocketAddr> {
|
||||
ConnectInfo("127.0.0.1:9999".parse().unwrap())
|
||||
}
|
||||
|
||||
/// Build a GET request with `ConnectInfo` for rate limiter
|
||||
/// compatibility
|
||||
pub 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`
|
||||
pub 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
|
||||
}
|
||||
|
||||
/// Build a GET request with Bearer auth
|
||||
pub fn get_authed(uri: &str, token: &str) -> Request<Body> {
|
||||
let mut req = Request::builder()
|
||||
.uri(uri)
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(test_addr());
|
||||
req
|
||||
}
|
||||
|
||||
/// Build a POST JSON request with Bearer auth
|
||||
pub fn post_json_authed(uri: &str, body: &str, token: &str) -> Request<Body> {
|
||||
let mut req = Request::builder()
|
||||
.method("POST")
|
||||
.uri(uri)
|
||||
.header("content-type", "application/json")
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.body(Body::from(body.to_string()))
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(test_addr());
|
||||
req
|
||||
}
|
||||
|
||||
/// Build a PUT JSON request with Bearer auth
|
||||
pub fn put_json_authed(uri: &str, body: &str, token: &str) -> Request<Body> {
|
||||
let mut req = Request::builder()
|
||||
.method("PUT")
|
||||
.uri(uri)
|
||||
.header("content-type", "application/json")
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.body(Body::from(body.to_string()))
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(test_addr());
|
||||
req
|
||||
}
|
||||
|
||||
/// Build a DELETE request with Bearer auth
|
||||
pub fn delete_authed(uri: &str, token: &str) -> Request<Body> {
|
||||
let mut req = Request::builder()
|
||||
.method("DELETE")
|
||||
.uri(uri)
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(test_addr());
|
||||
req
|
||||
}
|
||||
|
||||
/// Build a PATCH JSON request with Bearer auth
|
||||
pub fn patch_json_authed(uri: &str, body: &str, token: &str) -> Request<Body> {
|
||||
let mut req = Request::builder()
|
||||
.method("PATCH")
|
||||
.uri(uri)
|
||||
.header("content-type", "application/json")
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.body(Body::from(body.to_string()))
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(test_addr());
|
||||
req
|
||||
}
|
||||
|
||||
pub fn default_config() -> 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,
|
||||
tls: TlsConfig::default(),
|
||||
authentication_disabled: true,
|
||||
cors_enabled: false,
|
||||
cors_origins: vec![],
|
||||
},
|
||||
rate_limits: RateLimitConfig::default(),
|
||||
ui: UiConfig::default(),
|
||||
accounts: AccountsConfig::default(),
|
||||
jobs: JobsConfig::default(),
|
||||
thumbnails: ThumbnailConfig::default(),
|
||||
webhooks: Vec::<WebhookConfig>::new(),
|
||||
scheduled_tasks: vec![],
|
||||
plugins: PluginsConfig::default(),
|
||||
transcoding: TranscodingConfig::default(),
|
||||
enrichment: EnrichmentConfig::default(),
|
||||
cloud: CloudConfig::default(),
|
||||
analytics: AnalyticsConfig::default(),
|
||||
photos: PhotoConfig::default(),
|
||||
managed_storage: ManagedStorageConfig::default(),
|
||||
sync: SyncConfig::default(),
|
||||
sharing: SharingConfig::default(),
|
||||
trash: TrashConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub 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 = default_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: None,
|
||||
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,
|
||||
};
|
||||
|
||||
pinakes_server::app::create_router(state, &RateLimitConfig::default())
|
||||
}
|
||||
|
||||
/// Hash a password for test user accounts
|
||||
pub fn hash_password(password: &str) -> String {
|
||||
pinakes_core::users::auth::hash_password(password).unwrap()
|
||||
}
|
||||
|
||||
/// Set up an app with accounts enabled and three pre-seeded users.
|
||||
/// Returns (Router, `admin_token`, `editor_token`, `viewer_token`).
|
||||
pub async fn setup_app_with_auth() -> (axum::Router, String, String, String) {
|
||||
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 users_to_create = vec![
|
||||
("admin", "adminpass", UserRole::Admin),
|
||||
("editor", "editorpass", UserRole::Editor),
|
||||
("viewer", "viewerpass", UserRole::Viewer),
|
||||
];
|
||||
for (username, password, role) in &users_to_create {
|
||||
let password_hash = hash_password(password);
|
||||
storage
|
||||
.create_user(username, &password_hash, *role, None)
|
||||
.await
|
||||
.expect("create user");
|
||||
}
|
||||
|
||||
let mut config = default_config();
|
||||
config.server.authentication_disabled = false;
|
||||
config.accounts.enabled = true;
|
||||
config.accounts.users = vec![
|
||||
UserAccount {
|
||||
username: "admin".to_string(),
|
||||
password_hash: hash_password("adminpass"),
|
||||
role: UserRole::Admin,
|
||||
},
|
||||
UserAccount {
|
||||
username: "editor".to_string(),
|
||||
password_hash: hash_password("editorpass"),
|
||||
role: UserRole::Editor,
|
||||
},
|
||||
UserAccount {
|
||||
username: "viewer".to_string(),
|
||||
password_hash: hash_password("viewerpass"),
|
||||
role: UserRole::Viewer,
|
||||
},
|
||||
];
|
||||
|
||||
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: None,
|
||||
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 app =
|
||||
pinakes_server::app::create_router(state, &RateLimitConfig::default());
|
||||
|
||||
let admin_token = login_user(app.clone(), "admin", "adminpass").await;
|
||||
let editor_token = login_user(app.clone(), "editor", "editorpass").await;
|
||||
let viewer_token = login_user(app.clone(), "viewer", "viewerpass").await;
|
||||
|
||||
(app, admin_token, editor_token, viewer_token)
|
||||
}
|
||||
|
||||
pub async fn login_user(
|
||||
app: axum::Router,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> String {
|
||||
let body = format!(r#"{{"username":"{username}","password":"{password}"}}"#);
|
||||
let response = app
|
||||
.oneshot(post_json("/api/v1/auth/login", &body))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.status(),
|
||||
StatusCode::OK,
|
||||
"login failed for user {username}"
|
||||
);
|
||||
let body = response.into_body().collect().await.unwrap().to_bytes();
|
||||
let result: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
result["token"].as_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
pub async fn response_body(
|
||||
response: axum::response::Response,
|
||||
) -> serde_json::Value {
|
||||
let body = response.into_body().collect().await.unwrap().to_bytes();
|
||||
serde_json::from_slice(&body).unwrap_or(serde_json::Value::Null)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue