test(admin): replace a source-text guard with a behavior test (#6212)

This commit is contained in:
Zhengchao An
2026-08-18 18:28:46 +08:00
committed by GitHub
parent 9852e53b4c
commit 8a3c66e655
4 changed files with 91 additions and 18 deletions
+9 -1
View File
@@ -92,6 +92,13 @@ pub struct TestECStoreEnv {
/// `init_local_disks` + `ECStore::new` on `127.0.0.1:0` (random port keeps
/// nextest's process-per-test parallelism safe).
pub ecstore: Arc<ECStore>,
/// The single-pool, single-set topology the store was built from.
///
/// The bootstrap does **not** publish it on the instance context (server
/// startup is what calls `set_endpoints`, and that write is once-only), so
/// a test that needs `get_global_endpoints` to resolve — admin server-info
/// and other topology readers — publishes this value itself.
pub endpoint_pools: EndpointServerPools,
}
impl TestECStoreEnv {
@@ -234,7 +241,7 @@ impl TestECStoreEnvBuilder {
// Port 0 keeps ECStore-backed integration binaries parallel-safe under
// nextest: no fixed peer port is ever shared between test processes.
let server_addr: std::net::SocketAddr = "127.0.0.1:0".parse().expect("parse test addr");
let ecstore = ECStore::new(server_addr, endpoint_pools, CancellationToken::new())
let ecstore = ECStore::new(server_addr, endpoint_pools.clone(), CancellationToken::new())
.await
.expect("build test ECStore");
@@ -254,6 +261,7 @@ impl TestECStoreEnvBuilder {
temp_root,
disk_paths,
ecstore,
endpoint_pools,
}
}
}
+78
View File
@@ -1541,6 +1541,84 @@ mod tests {
assert_eq!(error.code(), &S3ErrorCode::InvalidRequest);
}
/// `ServerInfoHandler` must answer an authorized admin request with the
/// per-pool erasure-set topology (rustfs/backlog#1839). That map is only
/// filled when the server-info query is issued with pools included, so a
/// handler that stopped asking for them would still return 200 with an
/// empty `pools` object instead of failing.
#[tokio::test]
#[serial_test::serial]
async fn server_info_response_carries_pool_topology() {
use crate::admin::runtime_sources::{AppContext, publish_test_app_context};
use crate::admin::storage_api::runtime::bootstrap_ctx;
use http_body_util::BodyExt as _;
use rustfs_iam::store::{Store as _, object::IAM_CONFIG_PREFIX};
use std::sync::Arc;
const ROOT_ACCESS_KEY: &str = "SERVERINFOROOTACCESSKEY";
const ROOT_SECRET_KEY: &str = "serverInfoRootSecret123";
let _ = rustfs_credentials::init_global_action_credentials(
Some(ROOT_ACCESS_KEY.to_string()),
Some(ROOT_SECRET_KEY.to_string()),
);
let env = rustfs_test_utils::TestECStoreEnv::builder()
.prefix("admin_server_info_pools")
.disk_count(1)
.init_bucket_metadata(false)
.build()
.await;
// Server startup owns this write in production; the test bootstrap
// stops short of it, and without a topology the server-info query
// returns before it ever looks at drives.
bootstrap_ctx().set_endpoints(env.endpoint_pools.clone());
rustfs_iam::store::object::ObjectStore::new(Arc::clone(&env.ecstore))
.save_iam_config(serde_json::json!({"version": 1}), format!("{}/format.json", *IAM_CONFIG_PREFIX))
.await
.expect("seed IAM format");
let iam = rustfs_iam::build_iam_sys(Arc::clone(&env.ecstore))
.await
.expect("build test IAM");
publish_test_app_context(Arc::new(AppContext::with_default_interfaces(
Arc::clone(&env.ecstore),
iam,
Arc::new(rustfs_kms::KmsServiceManager::new()),
)));
let request = S3Request {
input: Body::empty(),
method: Method::GET,
uri: Uri::from_static("/rustfs/admin/v3/info"),
headers: HeaderMap::new(),
extensions: Extensions::new(),
credentials: Some(s3s::auth::Credentials {
access_key: ROOT_ACCESS_KEY.to_string(),
secret_key: s3s::auth::SecretKey::from(ROOT_SECRET_KEY.to_string()),
}),
region: None,
service: None,
trailing_headers: None,
};
let (status, body) = super::ServerInfoHandler {}
.call(request, Params::new())
.await
.expect("root admin credentials must be served server info")
.output;
assert_eq!(status, hyper::StatusCode::OK);
let bytes = body.collect().await.expect("server info body should read").to_bytes();
let payload: serde_json::Value = serde_json::from_slice(&bytes).expect("server info must be json");
let pools = payload["info"]["pools"]
.as_object()
.expect("server info must carry a pools object");
assert!(
pools.contains_key("0"),
"server info must report the erasure-set topology of pool 0, got {pools:?}"
);
}
/// Authorization denial for this exact action is pinned to AccessDenied by
/// `crate::admin::auth::tests::non_admin_credential_is_denied`.
#[test]
@@ -1412,23 +1412,6 @@ fn test_health_routes_not_registered_when_disabled_by_env() {
});
}
#[test]
fn test_phase5_admin_info_contract() {
let system_src = include_str!("handlers/system.rs");
let server_info_impl_marker = "impl Operation for ServerInfoHandler";
let server_info_impl_start = system_src
.find(server_info_impl_marker)
.expect("Expected impl Operation for ServerInfoHandler in handlers/system.rs");
let server_info_impl_block = &system_src[server_info_impl_start..];
assert!(
server_info_impl_block.contains("default_admin_usecase()")
&& server_info_impl_block.contains("execute_query_server_info(QueryServerInfoRequest { include_pools: true })"),
"admin server info path must be served through admin runtime-source DefaultAdminUsecase::execute_query_server_info"
);
}
fn extract_block_between_markers<'a>(src: &'a str, start_marker: &str, end_marker: &str) -> &'a str {
let start = src
.find(start_marker)
+4
View File
@@ -948,6 +948,10 @@ pub(crate) mod runtime {
#[cfg(test)]
pub(crate) use super::{Endpoint, Endpoints, PoolEndpoints};
/// Test-only: the process instance context, so a handler test can publish
/// the endpoint topology that server startup normally installs.
#[cfg(test)]
pub(crate) use crate::storage::storage_api::ecstore_runtime::bootstrap_ctx;
}
pub(crate) mod s3 {