ci(e2e): stabilize full-gate tooling (#5805)

This commit is contained in:
cxymds
2026-08-07 23:04:33 +08:00
committed by GitHub
parent 3792fed827
commit ba5641237c
5 changed files with 165 additions and 133 deletions
+83 -105
View File
@@ -16,91 +16,17 @@
#[cfg(test)]
mod tests {
use crate::common::{RustFSTestEnvironment, init_logging};
use crate::common::{RustFSTestEnvironment, init_logging, signed_s3_request};
use aws_sdk_s3::error::ProvideErrorMetadata;
use aws_sdk_s3::types::{
AccelerateConfiguration, BucketAccelerateStatus, BucketLoggingStatus, IndexDocument, LoggingEnabled, Payer,
RequestPaymentConfiguration, WebsiteConfiguration,
};
use http::Method;
use http::header::CONTENT_TYPE;
use serial_test::serial;
use std::path::PathBuf;
use std::process::Command;
use tracing::info;
fn awscurl_binary_path() -> PathBuf {
std::env::var_os("AWSCURL_PATH")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("awscurl"))
}
fn awscurl_available() -> bool {
Command::new(awscurl_binary_path()).arg("--version").output().is_ok()
}
fn execute_s3_awscurl(
method: &str,
url: &str,
access_key: &str,
secret_key: &str,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let output = Command::new(awscurl_binary_path())
.args([
"--service",
"s3",
"--region",
"us-east-1",
"--access_key",
access_key,
"--secret_key",
secret_key,
"-i",
"-X",
method,
url,
])
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
return Err(format!("awscurl failed: stderr='{stderr}', stdout='{stdout}'").into());
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
fn parse_status(raw: &str) -> Option<u16> {
raw.lines()
.filter_map(|line| {
if line.starts_with("HTTP/") {
line.split_whitespace().nth(1)?.parse::<u16>().ok()
} else {
None
}
})
.next_back()
}
fn parse_body(raw: &str) -> String {
if let Some(pos) = raw.rfind("\r\n\r\n") {
return raw[pos + 4..].to_string();
}
if let Some(pos) = raw.rfind("\n\n") {
return raw[pos + 2..].to_string();
}
String::new()
}
fn parse_headers(raw: &str) -> String {
let start = raw.rfind("HTTP/").unwrap_or(0);
let tail = &raw[start..];
if let Some(pos) = tail.find("\r\n\r\n") {
return tail[..pos].to_string();
}
if let Some(pos) = tail.find("\n\n") {
return tail[..pos].to_string();
}
tail.to_string()
}
#[tokio::test]
#[serial]
async fn test_dummy_bucket_compatibility_endpoints() {
@@ -470,10 +396,6 @@ mod tests {
async fn test_dummy_bucket_endpoints_http_contracts() {
init_logging();
info!("Starting test: dummy-compat bucket API HTTP contracts");
if !awscurl_available() {
info!("Skipping test_dummy_bucket_endpoints_http_contracts: awscurl binary not found");
return;
}
let mut env = RustFSTestEnvironment::new().await.expect("Failed to create test environment");
env.start_rustfs_server(vec![]).await.expect("Failed to start RustFS");
@@ -488,56 +410,112 @@ mod tests {
.await
.expect("Failed to create bucket");
let logging_raw = execute_s3_awscurl("GET", &format!("{}/{bucket}?logging=", env.url), &env.access_key, &env.secret_key)
.expect("GetBucketLogging HTTP request failed");
assert_eq!(parse_status(&logging_raw), Some(200), "GetBucketLogging should return 200");
let logging_body = parse_body(&logging_raw);
let logging_response = signed_s3_request(
Method::GET,
&format!("{}/{bucket}?logging=", env.url),
None,
None,
&env.access_key,
&env.secret_key,
)
.await
.expect("GetBucketLogging HTTP request failed");
assert_eq!(logging_response.status(), 200, "GetBucketLogging should return 200");
let logging_body = logging_response
.text()
.await
.expect("Failed to read GetBucketLogging response body");
assert!(
logging_body.contains("<BucketLoggingStatus"),
"GetBucketLogging response should contain BucketLoggingStatus XML, got: {logging_body}"
);
let accel_raw = execute_s3_awscurl("GET", &format!("{}/{bucket}?accelerate=", env.url), &env.access_key, &env.secret_key)
.expect("GetBucketAccelerateConfiguration HTTP request failed");
assert_eq!(parse_status(&accel_raw), Some(200), "GetBucketAccelerateConfiguration should return 200");
let accel_body = parse_body(&accel_raw);
let accel_response = signed_s3_request(
Method::GET,
&format!("{}/{bucket}?accelerate=", env.url),
None,
None,
&env.access_key,
&env.secret_key,
)
.await
.expect("GetBucketAccelerateConfiguration HTTP request failed");
assert_eq!(accel_response.status(), 200, "GetBucketAccelerateConfiguration should return 200");
let accel_body = accel_response
.text()
.await
.expect("Failed to read GetBucketAccelerateConfiguration response body");
assert!(
accel_body.contains("<AccelerateConfiguration"),
"GetBucketAccelerateConfiguration response should contain AccelerateConfiguration XML, got: {accel_body}"
);
let payment_raw =
execute_s3_awscurl("GET", &format!("{}/{bucket}?requestPayment=", env.url), &env.access_key, &env.secret_key)
.expect("GetBucketRequestPayment HTTP request failed");
assert_eq!(parse_status(&payment_raw), Some(200), "GetBucketRequestPayment should return 200");
let payment_body = parse_body(&payment_raw);
let payment_response = signed_s3_request(
Method::GET,
&format!("{}/{bucket}?requestPayment=", env.url),
None,
None,
&env.access_key,
&env.secret_key,
)
.await
.expect("GetBucketRequestPayment HTTP request failed");
assert_eq!(payment_response.status(), 200, "GetBucketRequestPayment should return 200");
let payment_body = payment_response
.text()
.await
.expect("Failed to read GetBucketRequestPayment response body");
assert!(
payment_body.contains("<Payer>BucketOwner</Payer>"),
"GetBucketRequestPayment should return BucketOwner payer, got: {payment_body}"
);
let website_raw = execute_s3_awscurl("GET", &format!("{}/{bucket}?website=", env.url), &env.access_key, &env.secret_key)
.expect("GetBucketWebsite HTTP request failed");
let website_response = signed_s3_request(
Method::GET,
&format!("{}/{bucket}?website=", env.url),
None,
None,
&env.access_key,
&env.secret_key,
)
.await
.expect("GetBucketWebsite HTTP request failed");
assert_eq!(
parse_status(&website_raw),
Some(404),
website_response.status(),
404,
"GetBucketWebsite should return 404 when website config is absent"
);
let website_content_type = parse_headers(&website_raw).to_ascii_lowercase();
let website_content_type = website_response
.headers()
.get(CONTENT_TYPE)
.expect("GetBucketWebsite response should include Content-Type")
.to_str()
.expect("GetBucketWebsite Content-Type should be valid ASCII")
.to_ascii_lowercase();
assert!(
website_content_type.contains("content-type:") && website_content_type.contains("xml"),
website_content_type.contains("xml"),
"GetBucketWebsite error response should be XML, got content-type: {website_content_type}"
);
let website_body = parse_body(&website_raw);
let website_body = website_response
.text()
.await
.expect("Failed to read GetBucketWebsite response body");
assert!(
website_body.contains("<Code>NoSuchWebsiteConfiguration</Code>"),
"GetBucketWebsite should return NoSuchWebsiteConfiguration code, got: {website_body}"
);
let delete_raw =
execute_s3_awscurl("DELETE", &format!("{}/{bucket}?website=", env.url), &env.access_key, &env.secret_key)
.expect("DeleteBucketWebsite HTTP request failed");
assert_eq!(parse_status(&delete_raw), Some(204), "DeleteBucketWebsite should return 204");
let delete_response = signed_s3_request(
Method::DELETE,
&format!("{}/{bucket}?website=", env.url),
None,
None,
&env.access_key,
&env.secret_key,
)
.await
.expect("DeleteBucketWebsite HTTP request failed");
assert_eq!(delete_response.status(), 204, "DeleteBucketWebsite should return 204");
env.stop_server();
}