use super::super::router;
use super::test_state;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use serde_json::json;
use tower::util::ServiceExt;
#[cfg(unix)]
struct RestoreMode {
path: std::path::PathBuf,
mode: u32,
}
#[cfg(unix)]
impl Drop for RestoreMode {
fn drop(&mut self) {
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&self.path, std::fs::Permissions::from_mode(self.mode));
}
}
#[cfg(unix)]
async fn create_palace(
state: &crate::AppState,
app: &axum::Router,
id: &str,
) -> std::path::PathBuf {
let resp = app
.clone()
.oneshot(
Request::builder()
.method("POST")
.uri("/api/v1/palaces")
.header("content-type", "application/json")
.body(Body::from(json!({ "name": id }).to_string()))
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK, "palace create must succeed");
state
.registry
.remove(&trusty_common::memory_core::PalaceId::new(id));
state.data_root.join(id)
}
#[cfg(unix)]
fn deny_reading_metadata(palace_dir: &std::path::Path) -> RestoreMode {
use std::os::unix::fs::PermissionsExt;
let target = palace_dir.join("palace.json");
std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o000)).unwrap();
let restore = RestoreMode {
path: target.clone(),
mode: 0o600,
};
match std::fs::read(&target) {
Ok(_) => panic!(
"cannot exercise #5549: {} is still readable at mode 000. Run this suite as a \
non-root user on a filesystem that honours POSIX permission bits.",
target.display()
),
Err(e) => assert_eq!(
e.kind(),
std::io::ErrorKind::PermissionDenied,
"expected the locked palace.json to deny reads, got {e}"
),
}
restore
}
#[cfg(unix)]
fn deny_statting_metadata(palace_dir: &std::path::Path) -> RestoreMode {
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(palace_dir, std::fs::Permissions::from_mode(0o000)).unwrap();
let restore = RestoreMode {
path: palace_dir.to_path_buf(),
mode: 0o700,
};
let target = palace_dir.join("palace.json");
match target.try_exists() {
Ok(_) => panic!(
"cannot exercise #5549: {} is still stattable with its directory at mode 000. Run \
this suite as a non-root user on a filesystem that honours POSIX permission bits.",
target.display()
),
Err(e) => assert_eq!(
e.kind(),
std::io::ErrorKind::PermissionDenied,
"expected the locked directory to deny statting palace.json, got {e}"
),
}
restore
}
#[cfg(unix)]
#[tokio::test]
async fn unreadable_palace_is_500_not_404_at_the_service_open_handle() {
let state = test_state();
let app = router().with_state(state.clone());
let dir = create_palace(&state, &app, "unreadable-svc").await;
let _restore = deny_reading_metadata(&dir);
let resp = app
.oneshot(
Request::builder()
.uri("/api/v1/palaces/unreadable-svc/drawers")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_ne!(
resp.status(),
StatusCode::NOT_FOUND,
"a palace whose metadata cannot be read was reported as absent — that is the #5549 \
coercion re-created at MemoryService::open_handle, one layer out from PR #5574"
);
assert_eq!(
resp.status(),
StatusCode::INTERNAL_SERVER_ERROR,
"an undeterminable palace open must surface as a server-side failure"
);
}
#[cfg(unix)]
#[tokio::test]
async fn unreadable_palace_is_500_not_404_at_the_web_open_handle() {
let state = test_state();
let app = router().with_state(state.clone());
let dir = create_palace(&state, &app, "unreadable-web").await;
let _restore = deny_reading_metadata(&dir);
let resp = app
.oneshot(
Request::builder()
.uri("/api/v1/kg/gaps?palace=unreadable-web")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_ne!(
resp.status(),
StatusCode::NOT_FOUND,
"a palace whose metadata cannot be read was reported as absent — that is the #5549 \
coercion re-created at web::error::open_handle"
);
assert_eq!(
resp.status(),
StatusCode::INTERNAL_SERVER_ERROR,
"an undeterminable palace open must surface as a server-side failure"
);
}
#[cfg(unix)]
#[tokio::test]
async fn unstattable_palace_is_500_not_404_at_the_service_open_handle() {
let state = test_state();
let app = router().with_state(state.clone());
let dir = create_palace(&state, &app, "unstattable-svc").await;
let _restore = deny_statting_metadata(&dir);
let resp = app
.oneshot(
Request::builder()
.uri("/api/v1/palaces/unstattable-svc/drawers")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_ne!(
resp.status(),
StatusCode::NOT_FOUND,
"a palace whose metadata could not even be statted was reported as absent — #5574 made \
that an Io error at load_palace, and MemoryService::open_handle flattened it back to 404"
);
assert_eq!(
resp.status(),
StatusCode::INTERNAL_SERVER_ERROR,
"an undeterminable palace open must surface as a server-side failure"
);
}
#[cfg(unix)]
#[tokio::test]
async fn unstattable_palace_is_500_not_404_at_the_web_open_handle() {
let state = test_state();
let app = router().with_state(state.clone());
let dir = create_palace(&state, &app, "unstattable-web").await;
let _restore = deny_statting_metadata(&dir);
let resp = app
.oneshot(
Request::builder()
.uri("/api/v1/kg/gaps?palace=unstattable-web")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_ne!(
resp.status(),
StatusCode::NOT_FOUND,
"a palace whose metadata could not even be statted was reported as absent — #5574 made \
that an Io error at load_palace, and web::error::open_handle flattened it back to 404"
);
assert_eq!(
resp.status(),
StatusCode::INTERNAL_SERVER_ERROR,
"an undeterminable palace open must surface as a server-side failure"
);
}
#[tokio::test]
async fn absent_palace_is_still_404_at_both_open_handles() {
let state = test_state();
let app = router().with_state(state);
let resp = app
.clone()
.oneshot(
Request::builder()
.uri("/api/v1/palaces/never-created/drawers")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(
resp.status(),
StatusCode::NOT_FOUND,
"an absent palace must still be 404 at MemoryService::open_handle"
);
let resp = app
.oneshot(
Request::builder()
.uri("/api/v1/kg/gaps?palace=never-created")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(
resp.status(),
StatusCode::NOT_FOUND,
"an absent palace must still be 404 at web::error::open_handle"
);
}