use arcbox_core::{Config, Runtime, VmLifecycleConfig};
use arcbox_docker::api::{create_router, strip_api_version_prefix};
use arcbox_docker::proxy::VsockConnector;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempDir;
use tower::ServiceExt;
fn router() -> (axum::Router, TempDir) {
let tmp = TempDir::new().unwrap();
let config = Config {
data_dir: tmp.path().to_path_buf(),
..Default::default()
};
let vm_lifecycle_config = VmLifecycleConfig {
skip_vm_check: true,
..Default::default()
};
let runtime = Arc::new(
Runtime::with_vm_lifecycle_config(config, vm_lifecycle_config).expect("create runtime"),
);
let connector = Arc::new(VsockConnector::new(Arc::clone(&runtime)));
(create_router(runtime, connector), tmp)
}
async fn status_of(method: &str, uri: &str, strip_version: bool) -> Option<StatusCode> {
let (app, _tmp) = router();
let mut request = Request::builder()
.method(method)
.uri(uri)
.body(Body::empty())
.unwrap();
if strip_version {
request = strip_api_version_prefix(request);
}
tokio::time::timeout(Duration::from_secs(3), app.oneshot(request))
.await
.ok()
.map(|response| response.unwrap().status())
}
async fn assert_reaches_backend(method: &str, uri: &str) {
if let Some(status) = status_of(method, uri, false).await {
assert_ne!(
status,
StatusCode::METHOD_NOT_ALLOWED,
"{method} {uri} was 405 — shadowed by a local route instead of proxied"
);
}
}
#[tokio::test]
async fn proxied_surface_is_not_shadowed() {
assert_reaches_backend("GET", "/containers/json").await;
assert_reaches_backend("POST", "/containers/prune").await;
assert_reaches_backend("GET", "/images/json").await;
assert_reaches_backend("GET", "/networks").await;
assert_reaches_backend("GET", "/volumes").await;
assert_reaches_backend("POST", "/networks/create").await;
assert_reaches_backend("POST", "/networks/prune").await;
assert_reaches_backend("GET", "/networks/abc").await;
assert_reaches_backend("DELETE", "/networks/abc").await;
assert_reaches_backend("POST", "/networks/abc/connect").await;
assert_reaches_backend("POST", "/networks/abc/disconnect").await;
assert_reaches_backend("GET", "/_ping").await;
assert_reaches_backend("HEAD", "/_ping").await;
assert_reaches_backend("GET", "/version").await;
assert_reaches_backend("GET", "/info").await;
assert_reaches_backend("GET", "/events").await;
assert_reaches_backend("GET", "/containers/abc/json").await;
assert_reaches_backend("GET", "/containers/abc/logs").await;
}
#[tokio::test]
async fn delete_on_collection_named_paths_is_not_shadowed() {
assert_reaches_backend("DELETE", "/containers/json").await;
assert_reaches_backend("DELETE", "/containers/prune").await;
}
#[tokio::test]
async fn versioned_paths_route_after_prefix_strip() {
assert_eq!(
status_of("GET", "/v1.43/containers/json", true).await,
status_of("GET", "/containers/json", false).await,
);
}
#[tokio::test]
async fn wrong_method_on_lifecycle_route_stays_405() {
assert_eq!(
status_of("GET", "/containers/abc/start", false).await,
Some(StatusCode::METHOD_NOT_ALLOWED),
);
assert_eq!(
status_of("PUT", "/containers/abc/stop", false).await,
Some(StatusCode::METHOD_NOT_ALLOWED),
);
}