use super::*;
use std::io::Cursor;
#[test]
fn sha256_matches_known_vectors_and_round_trips_authority() {
for (bytes, hex) in [
(
b"".as_slice(),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
),
(
b"abc".as_slice(),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
),
] {
let expected: Sha256Digest = hex.parse().unwrap();
assert_eq!(Sha256Digest::compute(bytes), expected);
assert_eq!(expected.to_string(), hex);
assert_eq!(Sha256Digest::from_bytes(*expected.as_bytes()), expected);
assert_eq!(
hash_reader(bytes, bytes.len() as u64).unwrap(),
ArtifactIdentity {
bytes: bytes.len() as u64,
sha256: expected
}
);
}
assert_eq!(
"abc".parse::<Sha256Digest>(),
Err(DigestParseError::Length { actual: 3 })
);
assert_eq!(
"A".repeat(64).parse::<Sha256Digest>(),
Err(DigestParseError::Digit { offset: 0 })
);
assert!(matches!(
"é".repeat(32).parse::<Sha256Digest>(),
Err(DigestParseError::Digit { .. })
));
}
#[test]
fn streaming_hashes_multiple_buffers_and_preserves_mismatch_identity() {
let bytes = vec![42; 40_001];
let expected = Sha256Digest::compute(&bytes);
let actual = verify_reader(bytes.as_slice(), 40_001, expected).unwrap();
assert_eq!(actual.bytes, 40_001);
let wrong = Sha256Digest::compute(b"wrong");
assert!(matches!(verify_reader(bytes.as_slice(), 40_001, wrong),
Err(ArtifactError::DigestMismatch { expected, actual: observed }) if expected == wrong && observed == actual));
}
#[test]
fn byte_limits_are_inclusive_and_consume_only_one_overflow_byte() {
let mut stream = Cursor::new(vec![9; 40_000]);
assert!(matches!(
hash_reader(&mut stream, 16_385),
Err(ArtifactError::LimitExceeded { limit: 16_385 })
));
assert_eq!(stream.position(), 16_386);
let mut stream = Cursor::new(b"abc");
assert!(matches!(
hash_reader(&mut stream, 0),
Err(ArtifactError::LimitExceeded { limit: 0 })
));
assert_eq!(stream.position(), 1);
assert_eq!(hash_reader(b"".as_slice(), 0).unwrap().bytes, 0);
assert_eq!(hash_reader(b"abc".as_slice(), u64::MAX).unwrap().bytes, 3);
}
#[test]
fn interrupted_reads_resume_and_other_io_errors_stay_typed() {
struct Interrupted(bool);
impl Read for Interrupted {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
if !self.0 {
self.0 = true;
return Err(io::ErrorKind::Interrupted.into());
}
b"abc".as_slice().read(buffer)
}
}
struct Broken;
impl Read for Broken {
fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
Err(io::ErrorKind::PermissionDenied.into())
}
}
assert_eq!(
hash_reader(Interrupted(false).take(3), 3).unwrap().sha256,
Sha256Digest::compute(b"abc")
);
assert!(
matches!(hash_reader(Broken, 10), Err(ArtifactError::Io(source)) if source.kind() == io::ErrorKind::PermissionDenied)
);
}
#[test]
fn bounded_file_operations_leave_source_intact_and_reject_special_inputs() {
let path = Path::new(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/empty.wasm"
));
let original = std::fs::read(path).unwrap();
assert_eq!(read_file(path, 8).unwrap(), original);
assert_eq!(
hash_file(path, 8).unwrap().sha256,
Sha256Digest::compute(&original)
);
assert!(matches!(
read_file(path, 7),
Err(ArtifactError::LimitExceeded { limit: 7 })
));
assert!(matches!(
hash_file(path.parent().unwrap(), 100),
Err(ArtifactError::NotRegularFile)
));
assert_eq!(std::fs::read(path).unwrap(), original);
let missing = path.with_extension("missing");
assert!(
matches!(hash_file(&missing, 10), Err(ArtifactError::Io(source)) if source.kind() == io::ErrorKind::NotFound)
);
}
#[test]
fn owned_stream_reads_are_bounded_and_preserve_io_failure_types() {
struct Broken;
impl Read for Broken {
fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
Err(io::ErrorKind::PermissionDenied.into())
}
}
let mut stream = Cursor::new(vec![7; 40_000]);
assert!(matches!(
read_reader(&mut stream, 16_385),
Err(ArtifactError::LimitExceeded { limit: 16_385 })
));
assert_eq!(stream.position(), 16_386);
assert_eq!(read_reader(b"abc".as_slice(), 3).unwrap(), b"abc");
assert_eq!(read_reader(b"".as_slice(), 0).unwrap(), b"");
assert!(
matches!(read_reader(Broken, 10), Err(ArtifactError::Io(source))
if source.kind() == io::ErrorKind::PermissionDenied)
);
}