Skip to content

Commit 6b2a8cd

Browse files
committed
test: satisfy coverage gate
1 parent 33a2bcb commit 6b2a8cd

10 files changed

Lines changed: 1568 additions & 82 deletions

File tree

src/database.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,22 @@ mod tests {
946946
));
947947
}
948948

949+
#[tokio::test]
950+
async fn reader_open_failure_is_reported() {
951+
let result = BucketConnectionInner::connect(
952+
rusqlite::Connection::open_in_memory,
953+
Arc::new(|| Err(rusqlite::Error::InvalidQuery)),
954+
1,
955+
1,
956+
None,
957+
)
958+
.await;
959+
assert!(matches!(
960+
result,
961+
Err(SqliteError::Rusqlite(rusqlite::Error::InvalidQuery))
962+
));
963+
}
964+
949965
fn sender_without_receiver() -> SyncSender<WorkerCommand> {
950966
let (sender, receiver) = sync_channel(1);
951967
drop(receiver);
@@ -974,6 +990,10 @@ mod tests {
974990
let execute = WorkerCommand::Execute(Box::new(|_| {}));
975991
assert_eq!(format!("{execute:?}"), "Execute");
976992
assert_eq!(format!("{:?}", WorkerCommand::Close), "Close");
993+
let connection = BucketConnection {
994+
inner: Arc::new(inner_with_handles(None, Vec::new(), Vec::new())),
995+
};
996+
assert!(format!("{connection:?}").contains("BucketConnection"));
977997
assert_eq!(WorkerRole::Writer.name(), "writer");
978998
assert_eq!(WorkerRole::Reader.name(), "reader");
979999
assert_eq!(WorkerRole::BlobReader.name(), "blob reader");
@@ -1075,6 +1095,42 @@ mod tests {
10751095
Ok(())
10761096
}
10771097

1098+
#[tokio::test]
1099+
async fn blob_read_stops_when_result_receiver_is_dropped() -> SqliteResult<()> {
1100+
let mut connection = rusqlite::Connection::open_in_memory()?;
1101+
connection.execute_batch(
1102+
"
1103+
CREATE TABLE blob_source (
1104+
sequence INTEGER PRIMARY KEY,
1105+
body BLOB NOT NULL
1106+
) STRICT;
1107+
INSERT INTO blob_source (sequence, body) VALUES (1, X'010203');
1108+
",
1109+
)?;
1110+
let (result_sender, result_receiver) = oneshot::channel();
1111+
drop(result_receiver);
1112+
let (chunk_sender, mut chunk_receiver) = tokio_mpsc::channel(1);
1113+
let timed_out = AtomicBool::new(false);
1114+
1115+
BucketConnectionInner::execute_blob_read(
1116+
&mut connection,
1117+
|_| {
1118+
Ok((
1119+
(),
1120+
vec![BlobReadRequest::new("blob_source", "body", 1, 0, 3, 3)],
1121+
))
1122+
},
1123+
result_sender,
1124+
&chunk_sender,
1125+
None,
1126+
&timed_out,
1127+
&Handle::current(),
1128+
);
1129+
drop(chunk_sender);
1130+
assert!(chunk_receiver.recv().await.is_none());
1131+
Ok(())
1132+
}
1133+
10781134
#[test]
10791135
fn send_blob_chunk_waits_without_timeout_for_capacity() -> Result<(), Box<dyn std::error::Error>>
10801136
{

src/s3/bucket.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ pub(super) async fn list_buckets(
510510

511511
#[cfg(test)]
512512
mod tests {
513-
use super::is_valid_bucket_name;
513+
use super::*;
514514

515515
#[test]
516516
fn validates_dns_bucket_names() {
@@ -535,4 +535,54 @@ mod tests {
535535
assert!(!is_valid_bucket_name(bucket), "{bucket}");
536536
}
537537
}
538+
539+
#[test]
540+
fn bucket_encryption_validation_rejects_unsupported_shapes() {
541+
assert!(
542+
validate_sse_s3_bucket_encryption_configuration(&ServerSideEncryptionConfiguration {
543+
rules: Vec::new()
544+
})
545+
.is_err()
546+
);
547+
assert!(
548+
validate_sse_s3_bucket_encryption_configuration(&ServerSideEncryptionConfiguration {
549+
rules: vec![ServerSideEncryptionRule {
550+
apply_server_side_encryption_by_default: Some(ServerSideEncryptionByDefault {
551+
kms_master_key_id: None,
552+
sse_algorithm: ServerSideEncryption::from_static(
553+
ServerSideEncryption::AES256
554+
),
555+
},),
556+
bucket_key_enabled: Some(true),
557+
}],
558+
},)
559+
.is_err()
560+
);
561+
assert!(
562+
validate_sse_s3_bucket_encryption_configuration(&ServerSideEncryptionConfiguration {
563+
rules: vec![ServerSideEncryptionRule {
564+
apply_server_side_encryption_by_default: None,
565+
bucket_key_enabled: None,
566+
}],
567+
},)
568+
.is_err()
569+
);
570+
assert!(
571+
validate_sse_s3_bucket_encryption_configuration(&ServerSideEncryptionConfiguration {
572+
rules: vec![ServerSideEncryptionRule {
573+
apply_server_side_encryption_by_default: Some(ServerSideEncryptionByDefault {
574+
kms_master_key_id: Some("key".to_string()),
575+
sse_algorithm: ServerSideEncryption::from_static(
576+
ServerSideEncryption::AES256
577+
),
578+
},),
579+
bucket_key_enabled: None,
580+
}],
581+
},)
582+
.is_err()
583+
);
584+
585+
let configuration = sse_s3_bucket_encryption_configuration();
586+
assert!(validate_sse_s3_bucket_encryption_configuration(&configuration).is_ok());
587+
}
538588
}

src/s3/copy.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,10 @@ mod tests {
573573
s3::{ChecksumAlgorithmKind, FullObjectChecksum, StoredObjectLock},
574574
sqlite::KeyMetadata,
575575
};
576-
use s3s::dto::{ChecksumAlgorithm, ETagCondition, ObjectCannedACL, Timestamp};
576+
use s3s::dto::{
577+
ChecksumAlgorithm, ETagCondition, ObjectCannedACL, ObjectLockLegalHoldStatus,
578+
ObjectLockMode, Timestamp,
579+
};
577580
use time::Duration;
578581

579582
fn assert_error_code<T>(result: S3Result<T>, code: &S3ErrorCode) {
@@ -665,6 +668,18 @@ mod tests {
665668
assert!(matches!(result, Err(SqliteError::PreconditionFailed)));
666669
}
667670

671+
#[test]
672+
#[should_panic(expected = "expected")]
673+
fn assert_error_code_panics_on_success() {
674+
assert_error_code(Ok(()), &S3ErrorCode::InvalidRequest);
675+
}
676+
677+
#[test]
678+
#[should_panic(expected = "assertion failed")]
679+
fn assert_precondition_failed_panics_on_success() {
680+
assert_precondition_failed(&Ok(()));
681+
}
682+
668683
#[test]
669684
fn parses_copy_object_request_defaults() {
670685
let request = match parse_copy_object_request(copy_input()) {
@@ -693,6 +708,7 @@ mod tests {
693708
#[test]
694709
fn parses_copy_object_replace_metadata_and_conditions() {
695710
let expires = Timestamp::from(OffsetDateTime::UNIX_EPOCH);
711+
let retain_until = Timestamp::from(OffsetDateTime::now_utc() + Duration::days(1));
696712
let input = match CopyObjectInput::builder()
697713
.bucket("target-bucket".to_string())
698714
.key("target-key".to_string())
@@ -715,6 +731,13 @@ mod tests {
715731
.copy_source_if_none_match(Some(ETagCondition::ETag(ETag::Weak("other".to_string()))))
716732
.copy_source_if_modified_since(Some(Timestamp::from(OffsetDateTime::UNIX_EPOCH)))
717733
.copy_source_if_unmodified_since(Some(Timestamp::from(OffsetDateTime::UNIX_EPOCH)))
734+
.object_lock_legal_hold_status(Some(ObjectLockLegalHoldStatus::from_static(
735+
ObjectLockLegalHoldStatus::ON,
736+
)))
737+
.object_lock_mode(Some(ObjectLockMode::from_static(
738+
ObjectLockMode::GOVERNANCE,
739+
)))
740+
.object_lock_retain_until_date(Some(retain_until))
718741
.build()
719742
{
720743
Ok(input) => input,
@@ -752,6 +775,7 @@ mod tests {
752775
Some(ETagCondition::ETag(ETag::Weak(value))) if value == "other"
753776
));
754777
assert!(request.source_conditions.unmodified_since.is_some());
778+
assert!(request.object_lock_headers.has_any());
755779
}
756780

757781
#[test]
@@ -801,6 +825,21 @@ mod tests {
801825
parse_copy_object_request(grant_header),
802826
&S3ErrorCode::AccessControlListNotSupported,
803827
);
828+
829+
let unsupported_sse = match CopyObjectInput::builder()
830+
.bucket("target-bucket".to_string())
831+
.key("target-key".to_string())
832+
.copy_source(source("source-bucket", "source-key", None))
833+
.ssekms_key_id(Some("key".to_string()))
834+
.build()
835+
{
836+
Ok(input) => input,
837+
Err(err) => panic!("unexpected build error: {err:?}"),
838+
};
839+
assert_error_code(
840+
parse_copy_object_request(unsupported_sse),
841+
&S3ErrorCode::InvalidArgument,
842+
);
804843
}
805844

806845
#[test]

src/s3/cors.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,11 @@ mod tests {
871871
invalid_method.allowed_methods = vec!["PATCH".to_string()];
872872
assert!(validate_stored_cors_rules(&[invalid_method]).is_err());
873873

874+
let mut empty_origin = valid_rule();
875+
empty_origin.allowed_origins = vec![String::new()];
876+
assert!(validate_stored_cors_rules(&[empty_origin]).is_err());
877+
assert!(validate_header_pattern("*").is_ok());
878+
874879
let mut invalid_allowed_header = valid_rule();
875880
invalid_allowed_header.allowed_headers = vec!["bad header".to_string()];
876881
assert!(validate_stored_cors_rules(&[invalid_allowed_header]).is_err());
@@ -938,6 +943,61 @@ mod tests {
938943
assert!(bucket_preflight_headers(&rules, &origin, &headers).is_none());
939944
}
940945

946+
#[test]
947+
fn fallback_and_configured_cors_helpers_cover_response_shapes() {
948+
let origin = HeaderValue::from_static("https://app.example.test");
949+
let mut headers = HeaderMap::new();
950+
headers.insert(
951+
ACCESS_CONTROL_REQUEST_METHOD,
952+
HeaderValue::from_static("GET"),
953+
);
954+
headers.insert(
955+
ACCESS_CONTROL_REQUEST_HEADERS,
956+
HeaderValue::from_static("authorization"),
957+
);
958+
headers.insert(
959+
hyper::header::HeaderName::from_static(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK),
960+
HeaderValue::from_static(TRUE),
961+
);
962+
963+
let permissive_preflight = permissive_preflight_headers(&origin, &headers);
964+
assert!(permissive_preflight.allow_private_network);
965+
let permissive_actual = permissive_actual_headers(&origin);
966+
assert!(permissive_actual.allow_credentials);
967+
assert!(!permissive_actual.allow_private_network);
968+
969+
let custom = ConfiguredCors {
970+
rule: valid_rule(),
971+
allow_credentials: true,
972+
allow_private_network: true,
973+
};
974+
let custom_preflight = custom_preflight_headers(&custom, &origin, &headers);
975+
assert!(custom_preflight.is_some_and(|headers| headers.allow_private_network));
976+
assert!(
977+
custom_preflight_headers(
978+
&custom,
979+
&HeaderValue::from_static("https://other.example.test"),
980+
&headers,
981+
)
982+
.is_none()
983+
);
984+
assert!(custom_actual_headers(&custom, &origin, "PUT").is_none());
985+
let custom_actual = custom_actual_headers(&custom, &origin, "GET");
986+
assert!(custom_actual.is_some_and(|headers| headers.expose_headers.is_some()));
987+
988+
assert!(FallbackCors::Disabled.is_disabled());
989+
assert!(CorsMatchSource::select(&FallbackCors::Disabled, None).is_none());
990+
assert!(matches!(
991+
CorsMatchSource::select(&FallbackCors::Permissive, None),
992+
Some(CorsMatchSource::Permissive)
993+
));
994+
let bucket_rules = vec![valid_rule()];
995+
assert!(matches!(
996+
CorsMatchSource::select(&FallbackCors::Disabled, Some(&bucket_rules)),
997+
Some(CorsMatchSource::Bucket(_))
998+
));
999+
}
1000+
9411001
#[test]
9421002
fn wildcard_matching_supports_s3_cors_patterns() {
9431003
assert!(wildcard_match("*", "https://app.example.test"));

0 commit comments

Comments
 (0)