pub const OBJECT_LOCK_LEGAL_HOLD: &str = "x-amz-object-lock-legal-hold";
pub const OBJECT_LOCK_MODE: &str = "x-amz-object-lock-mode";
pub const OBJECT_LOCK_RETAIN_UNTIL_DATE: &str = "x-amz-object-lock-retain-until-date";
pub const RESTORE: &str = "x-amz-restore";
pub const RESTORE_EXPIRY_DAYS: &str = "X-Amz-Restore-Expiry-Days";
pub const RESTORE_REQUEST_DATE: &str = "X-Amz-Restore-Request-Date";
pub const SERVER_SIDE_ENCRYPTION: &str = "x-amz-server-side-encryption";
pub const STORAGE_CLASS: &str = "x-amz-storage-class";
pub const REPLICATION_STATUS: &str = "X-Amz-Replication-Status";
#[cfg(test)]
mod tests {
use super::*;
use crate::test_data::create_pre_metadata_keys_xlmeta;
use crate::{FileMeta, FileMetaVersion, MetaObject, is_restored_object_on_disk};
use std::collections::HashMap;
const PINNED: [(&str, &str); 9] = [
(OBJECT_LOCK_LEGAL_HOLD, "x-amz-object-lock-legal-hold"),
(OBJECT_LOCK_MODE, "x-amz-object-lock-mode"),
(OBJECT_LOCK_RETAIN_UNTIL_DATE, "x-amz-object-lock-retain-until-date"),
(RESTORE, "x-amz-restore"),
(RESTORE_EXPIRY_DAYS, "X-Amz-Restore-Expiry-Days"),
(RESTORE_REQUEST_DATE, "X-Amz-Restore-Request-Date"),
(SERVER_SIDE_ENCRYPTION, "x-amz-server-side-encryption"),
(STORAGE_CLASS, "x-amz-storage-class"),
(REPLICATION_STATUS, "X-Amz-Replication-Status"),
];
const FIXTURE_VALUES: [(&str, &str); 9] = [
("x-amz-object-lock-legal-hold", "ON"),
("x-amz-object-lock-mode", "COMPLIANCE"),
("x-amz-object-lock-retain-until-date", "2099-01-01T00:00:00Z"),
("x-amz-restore", "ongoing-request=\"false\", expiry-date=\"9999-01-01T00:00:00Z\""),
("X-Amz-Restore-Expiry-Days", "7"),
("X-Amz-Restore-Request-Date", "Thu, 16 Jul 2026 00:00:00 GMT"),
("x-amz-server-side-encryption", "AES256"),
("x-amz-storage-class", "GLACIER"),
("X-Amz-Replication-Status", "COMPLETED"),
];
const FIXTURE_VERSION_ID: &str = "0b1e5a3a-1735-4a3a-8000-00000000a3a0";
fn fixture_object() -> MetaObject {
let fm = FileMeta::load(&create_pre_metadata_keys_xlmeta().expect("decode fixture hex")).expect("load fixture xl.meta");
assert_eq!(fm.versions.len(), 1);
FileMetaVersion::try_from(fm.versions[0].meta.as_slice())
.expect("decode fixture version")
.object
.expect("fixture version is an object")
}
fn fixture_metadata() -> HashMap<String, String> {
let fm = FileMeta::load(&create_pre_metadata_keys_xlmeta().expect("decode fixture hex")).expect("load fixture xl.meta");
fm.into_fileinfo("bucket", "object", FIXTURE_VERSION_ID, false, false, false)
.expect("fixture version to FileInfo")
.metadata
}
fn mutate_at(key: &str, idx: usize) -> String {
let mut bytes = key.as_bytes().to_vec();
let b = bytes[idx];
bytes[idx] = if b.is_ascii_lowercase() {
b.to_ascii_uppercase()
} else if b.is_ascii_uppercase() {
b.to_ascii_lowercase()
} else {
b'_'
};
String::from_utf8(bytes).expect("ascii mutation stays utf-8")
}
#[test]
fn persisted_metadata_keys_are_pinned_byte_for_byte() {
for (key, literal) in PINNED {
assert_eq!(key.as_bytes(), literal.as_bytes(), "persisted key {literal:?} drifted");
}
let fixture_keys: Vec<&str> = FIXTURE_VALUES.iter().map(|(k, _)| *k).collect();
let pinned_keys: Vec<&str> = PINNED.iter().map(|(_, l)| *l).collect();
assert_eq!(fixture_keys, pinned_keys, "fixture table must cover every pinned key");
}
#[test]
fn persisted_metadata_keys_match_their_historical_sources() {
use rustfs_utils::http::AMZ_BUCKET_REPLICATION_STATUS;
use rustfs_utils::http::headers::{
AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER, AMZ_OBJECT_LOCK_MODE_LOWER, AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE_LOWER, AMZ_RESTORE,
AMZ_RESTORE_EXPIRY_DAYS, AMZ_RESTORE_REQUEST_DATE, AMZ_STORAGE_CLASS,
};
assert_eq!(OBJECT_LOCK_LEGAL_HOLD, AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER);
assert_eq!(OBJECT_LOCK_MODE, AMZ_OBJECT_LOCK_MODE_LOWER);
assert_eq!(OBJECT_LOCK_RETAIN_UNTIL_DATE, AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE_LOWER);
assert_eq!(RESTORE, AMZ_RESTORE);
assert_eq!(RESTORE_EXPIRY_DAYS, AMZ_RESTORE_EXPIRY_DAYS);
assert_eq!(RESTORE_REQUEST_DATE, AMZ_RESTORE_REQUEST_DATE);
assert_eq!(STORAGE_CLASS, AMZ_STORAGE_CLASS);
assert_eq!(REPLICATION_STATUS, AMZ_BUCKET_REPLICATION_STATUS);
}
#[test]
fn pre_module_xlmeta_reads_back_every_persisted_key() {
let raw = create_pre_metadata_keys_xlmeta().expect("decode fixture hex");
for (key, _) in PINNED {
assert!(
raw.windows(key.len()).any(|w| w == key.as_bytes()),
"fixture bytes must contain persisted key {key:?} verbatim"
);
}
let obj = fixture_object();
let metadata = fixture_metadata();
for ((key, _), (fixture_key, value)) in PINNED.iter().zip(FIXTURE_VALUES) {
assert_eq!(*key, fixture_key);
assert_eq!(obj.meta_user.get(*key).map(String::as_str), Some(value), "meta_user[{key:?}]");
assert_eq!(metadata.get(*key).map(String::as_str), Some(value), "FileInfo.metadata[{key:?}]");
}
assert!(is_restored_object_on_disk(&metadata), "restore marker must read back as on disk");
assert!(obj.uses_data_dir(), "restored object's data dir must stay in use");
}
#[test]
fn remove_restore_hdrs_strips_pre_module_restore_keys() {
let mut obj = fixture_object();
obj.remove_restore_hdrs();
for key in [RESTORE, RESTORE_EXPIRY_DAYS, RESTORE_REQUEST_DATE] {
assert!(!obj.meta_user.contains_key(key), "{key:?} must be removed");
}
for key in [
OBJECT_LOCK_LEGAL_HOLD,
OBJECT_LOCK_MODE,
OBJECT_LOCK_RETAIN_UNTIL_DATE,
SERVER_SIDE_ENCRYPTION,
STORAGE_CLASS,
REPLICATION_STATUS,
] {
assert!(obj.meta_user.contains_key(key), "{key:?} must survive restore cleanup");
}
assert!(!is_restored_object_on_disk(&obj.meta_user));
assert!(!obj.uses_data_dir());
}
#[test]
fn new_writes_persist_the_same_keys_as_pre_module_bytes() {
let mut fi = FileMeta::load(&create_pre_metadata_keys_xlmeta().expect("decode fixture hex"))
.expect("load fixture xl.meta")
.into_fileinfo("bucket", "object", FIXTURE_VERSION_ID, false, false, false)
.expect("fixture version to FileInfo");
fi.metadata = PINNED
.iter()
.zip(FIXTURE_VALUES)
.map(|((key, _), (_, value))| (key.to_string(), value.to_string()))
.collect();
fi.metadata
.insert("etag".to_string(), "d41d8cd98f00b204e9800998ecf8427e".to_string());
let mut fm = FileMeta::new();
fm.add_version(fi).expect("add version");
let rewritten = FileMeta::load(&fm.marshal_msg().expect("marshal")).expect("reload");
let rewritten = FileMetaVersion::try_from(rewritten.versions[0].meta.as_slice())
.expect("decode rewritten version")
.object
.expect("rewritten version is an object");
assert_eq!(rewritten.meta_user, fixture_object().meta_user);
}
#[test]
fn single_character_key_mutation_misses_pre_module_data() {
let metadata = fixture_metadata();
let mut mutations = 0usize;
for (key, _) in PINNED {
for idx in 0..key.len() {
let mutated = mutate_at(key, idx);
assert_ne!(mutated, key);
assert!(
!metadata.contains_key(&mutated),
"mutation {mutated:?} of {key:?} must not match pre-module data"
);
mutations += 1;
}
}
assert_eq!(mutations, PINNED.iter().map(|(k, _)| k.len()).sum::<usize>());
let marker = metadata.get(RESTORE).expect("fixture restore marker").clone();
for idx in 0..RESTORE.len() {
let meta = HashMap::from([(mutate_at(RESTORE, idx), marker.clone())]);
assert!(
!is_restored_object_on_disk(&meta),
"restore marker under {:?} must not count as on disk",
mutate_at(RESTORE, idx)
);
}
}
}