mod common;
use bucketwarden_lock::ObjectLock;
use bucketwarden_s3::{
ListObjectVersionsRequest, ObjectMetadata, PutObjectRequest, ReplicationRule,
};
use bucketwarden_server::RuntimeError;
use common::*;
use std::collections::BTreeMap;
#[test]
fn replication_preserves_version_order_delete_markers_and_source_completion_status() {
let mut runtime = runtime();
runtime
.create_bucket("alice", "archive-001")
.expect("source");
runtime
.create_bucket("alice", "archive-002")
.expect("destination");
runtime
.put_bucket_replication(
"alice",
"archive-001",
Some("arn:aws:iam::123456789012:role/bucketwarden-replication".to_string()),
vec![ReplicationRule {
id: "records".to_string(),
status: "Enabled".to_string(),
destination_bucket: "arn:aws:s3:::archive-002".to_string(),
prefix: Some("records/".to_string()),
tag_filter: BTreeMap::new(),
delete_marker_replication: false,
existing_object_replication: false,
replicate_encrypted_objects: true,
}],
)
.expect("replication config");
let v1 = runtime
.put_object(
"alice",
PutObjectRequest {
bucket: "archive-001".to_string(),
key: "records/a.json".to_string(),
body: br#"{"version":1}"#.to_vec(),
metadata: ObjectMetadata::default(),
},
ObjectLock::none(),
)
.expect("v1")
.version_id;
let v2 = runtime
.put_object(
"alice",
PutObjectRequest {
bucket: "archive-001".to_string(),
key: "records/a.json".to_string(),
body: br#"{"version":2}"#.to_vec(),
metadata: ObjectMetadata::default(),
},
ObjectLock::none(),
)
.expect("v2")
.version_id;
let first_run = runtime
.run_bucket_replication("alice", "archive-001")
.expect("replicate");
assert_eq!(first_run.replicated_object_versions, 2);
assert_eq!(
runtime
.get_object_version("alice", "archive-001", "records/a.json", &v1)
.expect("source v1")
.replication_status
.as_deref(),
Some("COMPLETED")
);
assert_eq!(
runtime
.get_object_version("alice", "archive-001", "records/a.json", &v2)
.expect("source v2")
.replication_status
.as_deref(),
Some("COMPLETED")
);
let _delete_marker = runtime
.delete_object("alice", "archive-001", "records/a.json", false)
.expect("delete marker")
.delete_marker_version_id;
let second_run = runtime
.run_bucket_replication("alice", "archive-001")
.expect("replicate delete marker");
assert_eq!(second_run.replicated_delete_markers, 0);
let destination_versions = runtime
.list_object_versions(
"alice",
ListObjectVersionsRequest {
bucket: "archive-002".to_string(),
prefix: Some("records/".to_string()),
..ListObjectVersionsRequest::default()
},
)
.expect("list versions");
assert_eq!(destination_versions.versions.len(), 2);
assert!(destination_versions.delete_markers.is_empty());
runtime
.delete_object_version("alice", "archive-001", "records/a.json", &v2, false)
.expect("delete source version");
let deletion_run = runtime
.run_bucket_replication("alice", "archive-001")
.expect("replicate deleted version");
assert_eq!(deletion_run.replicated_deleted_versions, 1);
assert!(matches!(
runtime.get_object_version("alice", "archive-002", "records/a.json", &v2),
Err(RuntimeError::NoSuchVersion { .. })
));
}
#[test]
fn replication_respects_existing_object_replication_setting() {
let mut runtime = runtime();
runtime.set_clock_epoch_seconds(10);
runtime
.create_bucket("alice", "archive-001")
.expect("source");
runtime
.create_bucket("alice", "archive-002")
.expect("destination");
let existing = runtime
.put_object(
"alice",
PutObjectRequest {
bucket: "archive-001".to_string(),
key: "records/existing.json".to_string(),
body: br#"{"existing":true}"#.to_vec(),
metadata: ObjectMetadata::default(),
},
ObjectLock::none(),
)
.expect("existing source");
runtime.set_clock_epoch_seconds(20);
runtime
.put_bucket_replication(
"alice",
"archive-001",
Some("arn:aws:iam::123456789012:role/bucketwarden-replication".to_string()),
vec![ReplicationRule {
id: "records".to_string(),
status: "Enabled".to_string(),
destination_bucket: "arn:aws:s3:::archive-002".to_string(),
prefix: Some("records/".to_string()),
tag_filter: BTreeMap::new(),
delete_marker_replication: false,
existing_object_replication: false,
replicate_encrypted_objects: true,
}],
)
.expect("replication config");
runtime.set_clock_epoch_seconds(30);
let current = runtime
.put_object(
"alice",
PutObjectRequest {
bucket: "archive-001".to_string(),
key: "records/current.json".to_string(),
body: br#"{"current":true}"#.to_vec(),
metadata: ObjectMetadata::default(),
},
ObjectLock::none(),
)
.expect("current source");
let run = runtime
.run_bucket_replication("alice", "archive-001")
.expect("replicate");
assert_eq!(run.replicated_object_versions, 1);
assert!(matches!(
runtime.get_object_version(
"alice",
"archive-002",
"records/existing.json",
&existing.version_id
),
Err(RuntimeError::NoSuchKey(_)) | Err(RuntimeError::NoSuchVersion { .. })
));
assert_eq!(
runtime
.get_object_version(
"alice",
"archive-002",
"records/current.json",
¤t.version_id
)
.expect("replicated current")
.body,
br#"{"current":true}"#
);
}