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 literal_null_version_remains_addressable() { let identity = ObjectSourceIdentity::from_parts( CanonicalUrl::parse("https://objects.example.test").expect("endpoint"), "source-bucket", "key.bin", 10, Some("null".into()), None, ) .expect("identity"); assert_eq!(identity.version_id(), Some("null")); assert!(identity.is_resumable()); } #[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() {} #[test] fn source_body_implements_streaming_port() { assert_source_body::(); }