159 lines
4.6 KiB
Rust
159 lines
4.6 KiB
Rust
use std::fs;
|
|
|
|
use axum::{
|
|
body::Body,
|
|
http::{Request, StatusCode},
|
|
};
|
|
use http_body_util::BodyExt;
|
|
use serde_json::Value;
|
|
use tempfile::TempDir;
|
|
use tower::ServiceExt;
|
|
|
|
async fn test_app() -> (axum::Router, TempDir) {
|
|
let temp_dir = TempDir::new().expect("temporary directory should be created");
|
|
let pool = task_pilot_backend::database::connect("sqlite::memory:")
|
|
.await
|
|
.expect("test database should initialize");
|
|
let app = task_pilot_backend::app(pool, temp_dir.path().join("logs"))
|
|
.await
|
|
.expect("test app should start");
|
|
(app, temp_dir)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn health_endpoint_returns_service_metadata() {
|
|
let (app, _temp_dir) = test_app().await;
|
|
let response = app
|
|
.oneshot(
|
|
Request::builder()
|
|
.uri("/api/v1/health")
|
|
.body(Body::empty())
|
|
.expect("request should build"),
|
|
)
|
|
.await
|
|
.expect("router should respond");
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
let body = response
|
|
.into_body()
|
|
.collect()
|
|
.await
|
|
.expect("response body should be readable")
|
|
.to_bytes();
|
|
let json: Value = serde_json::from_slice(&body).expect("response should be JSON");
|
|
|
|
assert_eq!(json["status"], "ok");
|
|
assert_eq!(json["service"], "task-pilot-backend");
|
|
assert_eq!(json["version"], env!("CARGO_PKG_VERSION"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn openapi_endpoint_describes_health_api() {
|
|
let (app, _temp_dir) = test_app().await;
|
|
let response = app
|
|
.oneshot(
|
|
Request::builder()
|
|
.uri("/api/v1/openapi.json")
|
|
.body(Body::empty())
|
|
.expect("request should build"),
|
|
)
|
|
.await
|
|
.expect("router should respond");
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
let body = response
|
|
.into_body()
|
|
.collect()
|
|
.await
|
|
.expect("response body should be readable")
|
|
.to_bytes();
|
|
let json: Value = serde_json::from_slice(&body).expect("response should be JSON");
|
|
|
|
assert_eq!(json["info"]["title"], "TaskPilot API");
|
|
assert!(json["paths"]["/api/v1/health"]["get"].is_object());
|
|
assert!(json["paths"]["/api/v1/system/directories"]["get"].is_object());
|
|
}
|
|
|
|
fn encode_query_path(path: &std::path::Path) -> String {
|
|
path.to_string_lossy()
|
|
.replace('%', "%25")
|
|
.replace(':', "%3A")
|
|
.replace('\\', "%5C")
|
|
}
|
|
|
|
async fn get_json(app: &axum::Router, uri: &str) -> (StatusCode, Value) {
|
|
let response = app
|
|
.clone()
|
|
.oneshot(
|
|
Request::builder()
|
|
.uri(uri)
|
|
.body(Body::empty())
|
|
.expect("request should build"),
|
|
)
|
|
.await
|
|
.expect("router should respond");
|
|
let status = response.status();
|
|
let body = response
|
|
.into_body()
|
|
.collect()
|
|
.await
|
|
.expect("response body should be readable")
|
|
.to_bytes();
|
|
|
|
(status, serde_json::from_slice(&body).unwrap_or(Value::Null))
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn directory_listing_endpoint_returns_subdirectories() {
|
|
let (app, temp_dir) = test_app().await;
|
|
let project = temp_dir.path().join("project");
|
|
fs::create_dir_all(project.join("nested")).expect("nested directory should be created");
|
|
fs::write(project.join("file.txt"), "x").expect("file should be written");
|
|
|
|
let uri = format!(
|
|
"/api/v1/system/directories?path={}",
|
|
encode_query_path(&project)
|
|
);
|
|
let (status, body) = get_json(&app, &uri).await;
|
|
|
|
assert_eq!(status, StatusCode::OK);
|
|
assert_eq!(body["path"], project.to_string_lossy().into_owned());
|
|
let entries = body["entries"]
|
|
.as_array()
|
|
.expect("entries should be an array");
|
|
assert_eq!(entries.len(), 1);
|
|
assert_eq!(entries[0]["name"], "nested");
|
|
assert_eq!(
|
|
entries[0]["path"],
|
|
project.join("nested").to_string_lossy().into_owned()
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn directory_listing_endpoint_rejects_relative_paths() {
|
|
let (app, _temp_dir) = test_app().await;
|
|
|
|
let (status, body) = get_json(&app, "/api/v1/system/directories?path=relative").await;
|
|
|
|
assert_eq!(status, StatusCode::BAD_REQUEST);
|
|
assert_eq!(body["code"], "DIRECTORY_PATH_INVALID");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn directory_listing_endpoint_returns_drives_without_path() {
|
|
let (app, _temp_dir) = test_app().await;
|
|
|
|
let (status, body) = get_json(&app, "/api/v1/system/directories").await;
|
|
|
|
assert_eq!(status, StatusCode::OK);
|
|
assert!(body["path"].is_null());
|
|
assert!(
|
|
!body["entries"]
|
|
.as_array()
|
|
.expect("entries should be an array")
|
|
.is_empty()
|
|
);
|
|
}
|