From aca434fdfc9b01a96bd8d70f05a5bfcf71a52641 Mon Sep 17 00:00:00 2001 From: overtrue Date: Fri, 13 Nov 2026 10:42:36 +0800 Subject: [PATCH] test(server): pin legacy HeadBucket answers by name and the extension routes the stack leaves to the router --- rustfs/src/server/stack.rs | 9 +++++ .../tests/embedded_gateway_stack_test/main.rs | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/rustfs/src/server/stack.rs b/rustfs/src/server/stack.rs index 0b3362121b..dd228cbeef 100644 --- a/rustfs/src/server/stack.rs +++ b/rustfs/src/server/stack.rs @@ -195,6 +195,11 @@ mod tests { (Method::DELETE, "/health"), (Method::GET, "/minio/health/x"), (Method::GET, "/minio/health/live"), + // Near misses of the RustFS extension routes: a wrong method, an object level. + (Method::POST, "/bucket?replication-reset"), + (Method::GET, "/bucket/key?events"), + (Method::PUT, "/bucket?events"), + (Method::GET, "/bucket?lambdaArn=arn"), ] { assert_eq!( answered_by(&mut service, request(method.clone(), target)).await, @@ -218,6 +223,10 @@ mod tests { (Method::GET, "/profile/cpu"), (Method::GET, "/profile/memory"), (Method::GET, "/bucket?replication-metrics"), + (Method::PUT, "/bucket?replication-reset"), + (Method::GET, "/bucket?events"), + (Method::GET, "/?events="), + (Method::GET, "/bucket/key?lambdaArn=arn"), ] { assert_eq!( answered_by(&mut service, request(method.clone(), target)).await, diff --git a/rustfs/tests/embedded_gateway_stack_test/main.rs b/rustfs/tests/embedded_gateway_stack_test/main.rs index d392fd14c0..88aff0cf4b 100644 --- a/rustfs/tests/embedded_gateway_stack_test/main.rs +++ b/rustfs/tests/embedded_gateway_stack_test/main.rs @@ -324,6 +324,11 @@ fn the_refusal_matrix_holds_on_the_legacy_stack() { /// Names RustFS creates and the AWS bucket grammar refuses. An IP-shaped name such as `256.1.1.1` /// is also outside the AWS grammar, but RustFS's own create rule refuses it, so no store holds one. const RUSTFS_ONLY_NAMES: [&str; 5] = ["a-s3alias", "sthree-x", "x--ol-s3", "x--x-s3", "xn--abc"]; +/// HeadBucket statuses the legacy stack answers on `main` for names no store holds. +const HEAD_UPPER: u16 = 400; +const HEAD_DOTTED_QUAD: u16 = 404; +const HEAD_TOO_SHORT: u16 = 400; +const HEAD_TOO_LONG: u16 = 400; /// A listing names every bucket the store holds, as it is stored, with its date and the owner. async fn the_listing_names_every_stored_bucket(server: RustFSServer) { @@ -360,6 +365,41 @@ fn the_legacy_stack_lists_every_stored_bucket() { with_stack(LEGACY, the_listing_names_every_stored_bucket); } +/// HeadBucket on the legacy stack answers every name as before the use case moved onto the +/// gateway's types: a stored name the AWS grammar refuses is found, a missing one is not, and a +/// name the edge refuses is refused there. Never a server fault. The statuses were measured on +/// `main` before this change. +#[test] +fn the_legacy_stack_heads_every_bucket_name_as_before() { + with_stack(LEGACY, |server| async move { + for name in RUSTFS_ONLY_NAMES { + create_bucket_in_the_store(name).await; + } + let client = s3_client(&server.endpoint(), server.access_key(), server.secret_key()); + let too_long = "a".repeat(64); + let expected = RUSTFS_ONLY_NAMES.iter().map(|name| (*name, 200)).chain([ + ("missing-bucket", 404), + ("UPPER", HEAD_UPPER), + ("256.1.1.1", HEAD_DOTTED_QUAD), + ("ab", HEAD_TOO_SHORT), + (too_long.as_str(), HEAD_TOO_LONG), + ]); + + let mut answered = Vec::new(); + let mut wanted = Vec::new(); + for (name, status) in expected { + let observed = match client.head_bucket().bucket(name).send().await { + Ok(_) => Some(200), + Err(error) => refusal(&error).1, + }; + answered.push((name.to_owned(), observed)); + wanted.push((name.to_owned(), Some(status))); + } + assert_eq!(answered, wanted); + server.shutdown().await; + }); +} + /// The PutObject-only upload-size capability is refused on any other operation, before it is /// served, with the same answer on both stacks. The legacy stack refuses it in the admission both /// edges share; the gateway refuses the query before its handler runs.