Add exact resumable object source streaming
This commit is contained in:
parent
5b43b6c660
commit
0ad79c597f
1 changed files with 1482 additions and 1 deletions
122
crates/remora-object-source/tests/validation.rs
Normal file
122
crates/remora-object-source/tests/validation.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
use remora_domain::MemberPath;
|
||||
use remora_object_source::{
|
||||
DestinationResumeProof, ObjectResumeCheckpoint, ObjectSourceConfig, ObjectSourceError,
|
||||
ObjectSourceIdentity, ObjectSourceRequest, ObjectValidator,
|
||||
};
|
||||
use remora_outbound::CanonicalUrl;
|
||||
use remora_stream::{Sha256Accountant, SourceBody};
|
||||
use remora_target::{CompletedPart, MultipartIdentity};
|
||||
|
||||
fn digest(bytes: &[u8]) -> remora_stream::IntegrityDigest {
|
||||
let mut accountant = Sha256Accountant::new();
|
||||
accountant.update(bytes);
|
||||
accountant.finalize().digest().expect("digest").clone()
|
||||
}
|
||||
|
||||
fn source_identity(size: u64) -> ObjectSourceIdentity {
|
||||
ObjectSourceIdentity::from_parts(
|
||||
CanonicalUrl::parse("https://objects.example.test").expect("endpoint"),
|
||||
"source-bucket",
|
||||
"private/key.bin",
|
||||
size,
|
||||
None,
|
||||
Some(ObjectValidator::parse("\"validator\"").expect("validator")),
|
||||
)
|
||||
.expect("identity")
|
||||
}
|
||||
|
||||
fn destination(size: u64) -> DestinationResumeProof {
|
||||
DestinationResumeProof::new(
|
||||
CanonicalUrl::parse("https://target.example.test").expect("target endpoint"),
|
||||
MultipartIdentity {
|
||||
target_store_id: "target-a".to_owned(),
|
||||
bucket: "target-bucket".to_owned(),
|
||||
key: "_remora/a/attempt/o/object".to_owned(),
|
||||
upload_id: "upload-1".to_owned(),
|
||||
},
|
||||
vec![CompletedPart {
|
||||
part_number: 1,
|
||||
etag: "part-etag".to_owned(),
|
||||
size: Some(size),
|
||||
}],
|
||||
None,
|
||||
)
|
||||
.expect("destination")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checkpoint_requires_exact_source_and_destination_boundary() {
|
||||
let path = MemberPath::parse("dir/cafe\u{301}.bin").expect("path");
|
||||
let checkpoint = ObjectResumeCheckpoint::new(
|
||||
source_identity(10),
|
||||
path,
|
||||
4,
|
||||
digest(b"abcd"),
|
||||
destination(4),
|
||||
)
|
||||
.expect("checkpoint");
|
||||
assert_eq!(checkpoint.result_path().canonical(), "dir/café.bin");
|
||||
assert_eq!(checkpoint.destination().completed_bytes(), 4);
|
||||
assert_eq!(checkpoint.destination().completed_part_count(), 1);
|
||||
assert_eq!(
|
||||
ObjectResumeCheckpoint::new(
|
||||
source_identity(10),
|
||||
MemberPath::parse("member.bin").expect("path"),
|
||||
5,
|
||||
digest(b"abcde"),
|
||||
destination(4),
|
||||
),
|
||||
Err(ObjectSourceError::InvalidRequest)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restart_only_identity_cannot_form_checkpoint() {
|
||||
let identity = ObjectSourceIdentity::from_parts(
|
||||
CanonicalUrl::parse("https://objects.example.test").expect("endpoint"),
|
||||
"source-bucket",
|
||||
"key.bin",
|
||||
10,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.expect("identity");
|
||||
assert!(!identity.is_resumable());
|
||||
assert_eq!(
|
||||
ObjectResumeCheckpoint::new(
|
||||
identity,
|
||||
MemberPath::parse("member.bin").expect("path"),
|
||||
4,
|
||||
digest(b"abcd"),
|
||||
destination(4),
|
||||
),
|
||||
Err(ObjectSourceError::InvalidRequest)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_and_debug_surfaces_hide_keys_and_credentials() {
|
||||
let config = ObjectSourceConfig::new(
|
||||
"https://objects.example.test",
|
||||
"bucket",
|
||||
"region",
|
||||
"access-secret",
|
||||
"credential-secret",
|
||||
)
|
||||
.expect("config");
|
||||
let request = ObjectSourceRequest::new("private/token/object", "member.bin").expect("request");
|
||||
let combined = format!("{config:?} {request:?} {:?}", source_identity(10));
|
||||
assert!(!combined.contains("private"));
|
||||
assert!(!combined.contains("token"));
|
||||
assert!(!combined.contains("access-secret"));
|
||||
assert!(!combined.contains("credential-secret"));
|
||||
assert!(ObjectSourceRequest::new("key", "../bad").is_err());
|
||||
assert!(ObjectValidator::parse("bad\r\nvalidator").is_err());
|
||||
}
|
||||
|
||||
fn assert_source_body<T: SourceBody>() {}
|
||||
|
||||
#[test]
|
||||
fn source_body_implements_streaming_port() {
|
||||
assert_source_body::<remora_object_source::ObjectSourceBody>();
|
||||
}
|
||||
Loading…
Reference in a new issue