Files
task_pilot/backend/tests/api.rs
T
CNWei 8fdc55e17d feat: 首页改版与 uv / 镜像配置收敛,精简任务数据结构
- 页头只保留品牌与操作图标,三栏改为 1:2:1、容器最宽 1320
- 任务卡片:状态标签移入标题行并缩小,隐藏任务标识,操作行贴内容底边
- uv 配置弹窗:删掉与首页重复的状态条,三段配置改成标签页
- 镜像源:弹窗去掉默认镜像汇总,首页卡片改为展示默认镜像与运行时注入的变量
- 后端:迁移合并为单一基线 0001_init,删掉未生效的 concurrency_policy / retry_*,
  运行触发只剩 schedule / manual;contracts、前端生成类型、README / PLAN 同步
2026-11-08 21:26:47 +08:00

183 lines
5.5 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_includes_files_when_requested() {
let (app, temp_dir) = test_app().await;
let tools = temp_dir.path().join("tools");
fs::create_dir_all(tools.join("bin")).expect("bin directory should be created");
fs::write(tools.join("uv.exe"), "x").expect("file should be written");
let uri = format!(
"/api/v1/system/directories?path={}&include_files=true",
encode_query_path(&tools)
);
let (status, body) = get_json(&app, &uri).await;
assert_eq!(status, StatusCode::OK);
let entries = body["entries"]
.as_array()
.expect("entries should be an array");
assert_eq!(entries.len(), 2);
assert_eq!(entries[0]["name"], "bin");
assert_eq!(entries[0]["is_dir"], true);
assert_eq!(entries[1]["name"], "uv.exe");
assert_eq!(entries[1]["is_dir"], false);
}
#[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()
);
}