From 9e48c05493ad682f793b5e0e1b4fa9af0d5fea0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=B0=8F=E9=B8=AD?= Date: Sat, 15 Aug 2026 09:15:50 +0800 Subject: [PATCH] test(ecstore): pin madmin-compatible ARN partition contract Red-light evidence for backlog#1675 P1-7: madmin-go's ParseARN hard-rejects any ARN that does not start with 'arn:minio:', while RustFS generates and only accepts 'arn:rustfs:'. mc/madmin tooling therefore cannot decode RustFS remote-target listings, and MinIO-era replication configs are rejected as StaleTarget when re-registered. The new tests pin the target contract (generate arn:minio:, parse both partitions, reject unknown partitions) and fail against the current single-partition gate. --- crates/ecstore/src/bucket/target/arn.rs | 40 +++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/crates/ecstore/src/bucket/target/arn.rs b/crates/ecstore/src/bucket/target/arn.rs index 0bf0f175a..6419d99bf 100644 --- a/crates/ecstore/src/bucket/target/arn.rs +++ b/crates/ecstore/src/bucket/target/arn.rs @@ -101,14 +101,50 @@ mod tests { } /// RustFS commonly generates ARNs with an empty region: - /// `arn:rustfs:replication:::`. + /// `arn:minio:replication:::`. #[test] fn from_str_handles_empty_region_segment() { - let parsed = ARN::from_str("arn:rustfs:replication::depl-123:bucket-a").expect("valid ARN must parse"); + let parsed = ARN::from_str("arn:minio:replication::depl-123:bucket-a").expect("valid ARN must parse"); assert_eq!(parsed.arn_type, BucketTargetType::ReplicationService); assert_eq!(parsed.region, "", "region segment is empty in this form"); assert_eq!(parsed.id, "depl-123"); assert_eq!(parsed.bucket, "bucket-a"); } + + /// madmin-go's `ParseARN` hard-rejects anything that does not start with + /// `arn:minio:`, so generated ARNs must use the `minio` partition or the + /// native mc/madmin tooling cannot decode remote-target listings. + #[test] + fn display_emits_minio_partition() { + let arn = ARN::new( + BucketTargetType::ReplicationService, + "depl-123".to_string(), + String::new(), + "bucket-a".to_string(), + ); + + assert_eq!(arn.to_string(), "arn:minio:replication::depl-123:bucket-a"); + } + + /// Persisted bucket-targets.json files from older RustFS releases carry + /// `arn:rustfs:` ARNs; the legacy partition must stay parseable forever. + #[test] + fn from_str_accepts_legacy_rustfs_partition() { + let parsed = ARN::from_str("arn:rustfs:replication:us-east-1:depl-123:bucket-a").expect("legacy ARN must parse"); + + assert_eq!(parsed.arn_type, BucketTargetType::ReplicationService); + assert_eq!(parsed.region, "us-east-1"); + assert_eq!(parsed.id, "depl-123"); + assert_eq!(parsed.bucket, "bucket-a"); + } + + /// The partition whitelist is the only structural gate: `BucketTargetType:: + /// from_str(...).unwrap_or_default()` never fails, so any 6-segment string + /// would otherwise parse as `type=None`. + #[test] + fn from_str_rejects_unknown_partition() { + assert!(ARN::from_str("arn:aws:replication::depl-123:bucket-a").is_err()); + assert!(ARN::from_str("not-an-arn").is_err()); + } }