streamdigest 0.1.0

Async file hashing and digest calculation with progress reporting
Documentation
#[cfg(test)]
mod test_hasher {
    use crate::hasher::Hasher;

    use anyhow::Result;
    use std::fs::File;
    use std::io::Write;
    use std::path::PathBuf;
    use std::sync::Arc;
    use tempfile::TempDir;

    // 2261 characters long
    const LONG_STRING: &str = "a verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long string";
    const LONG_HASH: &str = "00019aad629dbdcab103d3dfa6c15797b1edc417e4a4b811ca386d9fc815b1b5";
    // 3065 characters long
    const LONG_WITH_NEWLINE: &str = "a verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long string\n";
    const LONG_WITH_NEWLINE_HASH: &str =
        "0003ccdb94f7877c2b3143cfadfa72caaf70fcf96d41851a96402bd4671b07ab";

    const SHORT_STRING: &str = "test338";
    const SHORT_HASH: &str = "00099816175096d3c2e85e557843d8fdfdd996659df43d16c596a79bfbe82d17";
    const SHORT_WITH_NEWLINE: &str = "test7248\n";
    const SHORT_WITH_NEWLINE_HASH: &str =
        "000ffee850a842b3d595a856231f8e679bfb34327a5333a6d04b6428f8113df7";

    async fn create_temp_file(dir: &TempDir, content: &[u8]) -> Result<PathBuf> {
        let file_path = dir.path().join("test_file.txt");
        {
            let mut file = File::create(&file_path)?;
            file.write_all(content)?;
            file.flush()?;
        }
        Ok(file_path)
    }

    #[tokio::test]
    async fn test_stream_hasher_sha256_file() -> Result<()> {
        let temp_dir = TempDir::new()?;
        let hasher = Hasher::new().with_read_buffer_size(4096);

        // Test with short string without newline
        let file_path = create_temp_file(&temp_dir, SHORT_STRING.as_bytes()).await?;
        let hash = hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, SHORT_HASH);

        // Test with short string with newline
        let file_path = create_temp_file(&temp_dir, SHORT_WITH_NEWLINE.as_bytes()).await?;
        let hash = hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, SHORT_WITH_NEWLINE_HASH);

        // Test with long string without newline
        let file_path = create_temp_file(&temp_dir, LONG_STRING.as_bytes()).await?;
        let hash = hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, LONG_HASH);

        // Test with long string with newline
        let file_path = create_temp_file(&temp_dir, LONG_WITH_NEWLINE.as_bytes()).await?;
        let hash = hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, LONG_WITH_NEWLINE_HASH);

        Ok(())
    }

    #[tokio::test]
    async fn test_stream_hasher_with_different_buffer_sizes() -> Result<()> {
        let temp_dir = TempDir::new()?;

        // Test with buffer size smaller than the content
        let small_buffer_hasher = Hasher::new().with_read_buffer_size(64);

        // Test with buffer size exactly matching the short string
        let exact_short_hasher = Hasher::new().with_read_buffer_size(SHORT_STRING.len());

        // Test with buffer size between short and long string
        let medium_buffer_hasher = Hasher::new().with_read_buffer_size(1024);

        // Test with buffer size larger than the long string
        let large_buffer_hasher = Hasher::new().with_read_buffer_size(LONG_WITH_NEWLINE.len() * 2);

        // Test short string with different buffer sizes
        let file_path = create_temp_file(&temp_dir, SHORT_STRING.as_bytes()).await?;

        let hash = small_buffer_hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, SHORT_HASH, "Failed with small buffer on short string");

        let hash = exact_short_hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, SHORT_HASH, "Failed with exact buffer on short string");

        let hash = medium_buffer_hasher.sha256_file(&file_path).await?;
        assert_eq!(
            hash, SHORT_HASH,
            "Failed with medium buffer on short string"
        );

        let hash = large_buffer_hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, SHORT_HASH, "Failed with large buffer on short string");

        // Test long string with different buffer sizes
        let file_path = create_temp_file(&temp_dir, LONG_STRING.as_bytes()).await?;

        let hash = small_buffer_hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, LONG_HASH, "Failed with small buffer on long string");

        let hash = medium_buffer_hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, LONG_HASH, "Failed with medium buffer on long string");

        let hash = large_buffer_hasher.sha256_file(&file_path).await?;
        assert_eq!(hash, LONG_HASH, "Failed with large buffer on long string");

        Ok(())
    }

    #[test]
    fn test_sync_sha256_bytes() -> Result<()> {
        // Test with short strings
        let hash = Hasher::sha256sum(SHORT_STRING);
        assert_eq!(hash, SHORT_HASH);

        let hash = Hasher::sha256sum(SHORT_WITH_NEWLINE);
        assert_eq!(hash, SHORT_WITH_NEWLINE_HASH);

        // Test with long strings
        let hash = Hasher::sha256sum(LONG_STRING);
        assert_eq!(hash, LONG_HASH);

        let hash = Hasher::sha256sum(LONG_WITH_NEWLINE);
        assert_eq!(hash, LONG_WITH_NEWLINE_HASH);

        Ok(())
    }

    #[test]
    fn test_sync_sha256_reader() -> Result<()> {
        // Test with different buffer sizes
        let tiny_buffer_hasher = Hasher::new().with_read_buffer_size(16);
        let small_buffer_hasher = Hasher::new().with_read_buffer_size(128);
        let medium_buffer_hasher = Hasher::new().with_read_buffer_size(1024);
        let large_buffer_hasher = Hasher::new().with_read_buffer_size(4096);

        // Test short string with different buffer sizes
        let hash = tiny_buffer_hasher.sha256_reader(SHORT_STRING.as_bytes())?;
        assert_eq!(hash, SHORT_HASH, "Failed with tiny buffer on short string");

        let hash = small_buffer_hasher.sha256_reader(SHORT_STRING.as_bytes())?;
        assert_eq!(hash, SHORT_HASH, "Failed with small buffer on short string");

        let hash = medium_buffer_hasher.sha256_reader(SHORT_STRING.as_bytes())?;
        assert_eq!(
            hash, SHORT_HASH,
            "Failed with medium buffer on short string"
        );

        let hash = large_buffer_hasher.sha256_reader(SHORT_STRING.as_bytes())?;
        assert_eq!(hash, SHORT_HASH, "Failed with large buffer on short string");

        // Test long string with different buffer sizes
        let hash = tiny_buffer_hasher.sha256_reader(LONG_STRING.as_bytes())?;
        assert_eq!(hash, LONG_HASH, "Failed with tiny buffer on long string");

        let hash = small_buffer_hasher.sha256_reader(LONG_STRING.as_bytes())?;
        assert_eq!(hash, LONG_HASH, "Failed with small buffer on long string");

        let hash = medium_buffer_hasher.sha256_reader(LONG_STRING.as_bytes())?;
        assert_eq!(hash, LONG_HASH, "Failed with medium buffer on long string");

        let hash = large_buffer_hasher.sha256_reader(LONG_STRING.as_bytes())?;
        assert_eq!(hash, LONG_HASH, "Failed with large buffer on long string");

        Ok(())
    }

    #[tokio::test]
    async fn test_stream_hasher_custom_stream() -> Result<()> {
        use std::pin::Pin;
        use std::task::{Context, Poll};
        use tokio::io::{AsyncRead, ReadBuf};

        struct TestStream {
            data: Vec<u8>,
            position: usize,
            chunk_size: usize, // Added to test different read sizes
        }

        impl TestStream {
            fn new(data: Vec<u8>, chunk_size: usize) -> Self {
                Self {
                    data,
                    position: 0,
                    chunk_size,
                }
            }
        }

        impl AsyncRead for TestStream {
            fn poll_read(
                mut self: Pin<&mut Self>,
                _cx: &mut Context<'_>,
                buf: &mut ReadBuf<'_>,
            ) -> Poll<std::io::Result<()>> {
                if self.position >= self.data.len() {
                    return Poll::Ready(Ok(()));
                }

                let remaining = self.data.len() - self.position;
                let to_read =
                    std::cmp::min(std::cmp::min(remaining, buf.remaining()), self.chunk_size);

                let chunk = &self.data[self.position..(self.position + to_read)];
                buf.put_slice(chunk);

                self.position += to_read;
                Poll::Ready(Ok(()))
            }
        }

        // Test with different buffer sizes and stream chunk sizes
        let hasher_configs = [
            ("small hasher buffer, small chunks", 64, 32),
            ("small hasher buffer, large chunks", 64, 1024),
            ("large hasher buffer, small chunks", 4096, 32),
            ("large hasher buffer, large chunks", 4096, 1024),
        ];

        for (config_name, buffer_size, chunk_size) in hasher_configs {
            let hasher = Hasher::new().with_read_buffer_size(buffer_size);

            // Test short string
            let stream = TestStream::new(SHORT_STRING.as_bytes().to_vec(), chunk_size);
            let hash = hasher.sha256_stream(stream).await?;
            assert_eq!(
                hash, SHORT_HASH,
                "Failed with {config_name} on short string"
            );

            // Test short string with newline
            let stream = TestStream::new(SHORT_WITH_NEWLINE.as_bytes().to_vec(), chunk_size);
            let hash = hasher.sha256_stream(stream).await?;
            assert_eq!(
                hash, SHORT_WITH_NEWLINE_HASH,
                "Failed with {config_name} on short string with newline"
            );

            // Test long string
            let stream = TestStream::new(LONG_STRING.as_bytes().to_vec(), chunk_size);
            let hash = hasher.sha256_stream(stream).await?;
            assert_eq!(hash, LONG_HASH, "Failed with {config_name} on long string");

            // Test long string with newline
            let stream = TestStream::new(LONG_WITH_NEWLINE.as_bytes().to_vec(), chunk_size);
            let hash = hasher.sha256_stream(stream).await?;
            assert_eq!(
                hash, LONG_WITH_NEWLINE_HASH,
                "Failed with {config_name} on long string with newline"
            );
        }

        Ok(())
    }

    #[tokio::test]
    async fn test_non_existent_file() -> Result<()> {
        let temp_dir = TempDir::new()?;
        let non_existent_path = temp_dir.path().join("non_existent.txt");

        let hasher = Hasher::new();
        let result = hasher.sha256_file(&non_existent_path).await;
        assert!(result.is_err());
        Ok(())
    }

    #[tokio::test]
    async fn test_stats_callback() -> Result<()> {
        use std::sync::Mutex;

        let temp_dir = TempDir::new()?;
        let file_path = create_temp_file(&temp_dir, &vec![0; 10_000]).await?;

        let stats_received = Arc::new(Mutex::new(false));
        let stats_clone = Arc::clone(&stats_received);

        let hasher = Hasher::new()
            .with_read_buffer_size(100)
            .with_throughput_update_interval(tokio::time::Duration::from_millis(1))
            .with_stats_callback(move |_| {
                *stats_clone.lock().unwrap() = true;
            });

        hasher.sha256_file(file_path).await?;

        // Check if callback was called
        assert!(*stats_received.lock().unwrap());

        Ok(())
    }

    #[tokio::test]
    async fn test_channel_buffer_size() -> Result<()> {
        let temp_dir = TempDir::new()?;
        let file_path = create_temp_file(&temp_dir, LONG_STRING.as_bytes()).await?;

        // Test with different channel buffer sizes
        let small_channel_hasher = Hasher::new()
            .with_read_buffer_size(128)
            .with_channel_buffer_size(1);

        let large_channel_hasher = Hasher::new()
            .with_read_buffer_size(128)
            .with_channel_buffer_size(10);

        let hash1 = small_channel_hasher.sha256_file(&file_path).await?;
        assert_eq!(hash1, LONG_HASH);

        let hash2 = large_channel_hasher.sha256_file(&file_path).await?;
        assert_eq!(hash2, LONG_HASH);

        Ok(())
    }

    #[tokio::test]
    async fn test_edge_cases() -> Result<()> {
        let temp_dir = TempDir::new()?;

        // Test with empty file
        let empty_file_path = create_temp_file(&temp_dir, b"").await?;
        let hasher = Hasher::new();
        let hash = hasher.sha256_file(&empty_file_path).await?;
        assert_eq!(
            hash,
            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
        );

        // Test with buffer size larger than file
        let tiny_file_path = create_temp_file(&temp_dir, b"a").await?;
        let large_buffer_hasher = Hasher::new().with_read_buffer_size(4096);
        let hash = large_buffer_hasher.sha256_file(&tiny_file_path).await?;
        assert_eq!(
            hash,
            "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"
        );

        // Test with buffer size equal to file size
        let exact_buffer_hasher = Hasher::new().with_read_buffer_size(1);
        let hash = exact_buffer_hasher.sha256_file(&tiny_file_path).await?;
        assert_eq!(
            hash,
            "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"
        );

        // Test with zero buffer size (should use default)
        let zero_buffer_hasher = Hasher::new().with_read_buffer_size(0);
        let hash = zero_buffer_hasher.sha256_file(&tiny_file_path).await?;
        assert_eq!(
            hash,
            "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"
        );

        Ok(())
    }
}