zoe_client/system_check/
blob_service.rs

1//! Blob service tests for system check
2//!
3//! This module contains tests that verify the client can properly upload and
4//! download files through the blob service, including data integrity
5//! verification and error handling.
6
7use super::{SystemCheckConfig, TestInfo, TestResult};
8use crate::{Client, services::BlobStore};
9use tracing::{debug, info};
10
11/// Run all blob service tests
12pub async fn run_tests(client: &Client, config: &SystemCheckConfig) -> Vec<TestInfo> {
13    let mut tests = Vec::new();
14
15    // Test blob upload and download
16    tests.push(test_blob_upload_download(client, config).await);
17
18    // Test blob integrity
19    tests.push(test_blob_integrity(client, config).await);
20
21    tests
22}
23
24/// Test basic blob upload and download functionality
25async fn test_blob_upload_download(client: &Client, config: &SystemCheckConfig) -> TestInfo {
26    let mut test = TestInfo::new("Blob Upload/Download");
27
28    debug!(
29        "Testing blob upload/download with {} bytes...",
30        config.blob_test_size
31    );
32
33    // Generate random test data
34    use rand::{RngCore, SeedableRng};
35    let mut rng = rand::rngs::StdRng::from_entropy();
36    let test_data: Vec<u8> = (0..config.blob_test_size)
37        .map(|_| rng.next_u32() as u8)
38        .collect();
39    let original_checksum = crc32fast::hash(&test_data);
40
41    // Upload the blob
42    let blob_id = match client.blob_service().upload_blob(&test_data).await {
43        Ok(id) => {
44            test.add_detail(format!(
45                "Successfully uploaded blob with ID: {}",
46                hex::encode(id)
47            ));
48            id
49        }
50        Err(e) => {
51            let error = format!("Failed to upload blob: {e}");
52            return test.with_result(TestResult::Failed { error });
53        }
54    };
55
56    // Download the blob
57    match client.blob_service().get_blob(&blob_id).await {
58        Ok(downloaded_data) => {
59            let downloaded_checksum = crc32fast::hash(&downloaded_data);
60
61            test.add_detail(format!(
62                "Successfully downloaded blob: {} bytes",
63                downloaded_data.len()
64            ));
65            test.add_detail(format!("Original checksum: {original_checksum:08x}"));
66            test.add_detail(format!("Downloaded checksum: {downloaded_checksum:08x}"));
67
68            if downloaded_data == test_data && downloaded_checksum == original_checksum {
69                info!("Blob upload/download test completed successfully");
70                test.with_result(TestResult::Passed)
71            } else {
72                let error = "Downloaded data does not match original data".to_string();
73                test.with_result(TestResult::Failed { error })
74            }
75        }
76        Err(e) => {
77            let error = format!("Failed to download blob: {e}");
78            test.with_result(TestResult::Failed { error })
79        }
80    }
81}
82
83/// Test blob data integrity with various sizes
84async fn test_blob_integrity(client: &Client, _config: &SystemCheckConfig) -> TestInfo {
85    let mut test = TestInfo::new("Blob Integrity");
86
87    debug!("Testing blob integrity...");
88
89    // Test with different data patterns to ensure integrity
90    let test_patterns = vec![
91        (vec![0u8; 100], "Zero bytes"),
92        (vec![255u8; 100], "Max bytes"),
93        ((0..100u8).collect(), "Sequential bytes"),
94    ];
95
96    let mut successful_tests = 0;
97
98    let pattern_count = test_patterns.len();
99    for (pattern_data, pattern_name) in test_patterns {
100        let original_checksum = crc32fast::hash(&pattern_data);
101
102        match client.blob_service().upload_blob(&pattern_data).await {
103            Ok(blob_id) => match client.blob_service().get_blob(&blob_id).await {
104                Ok(downloaded_data) => {
105                    let downloaded_checksum = crc32fast::hash(&downloaded_data);
106
107                    if downloaded_data == pattern_data && downloaded_checksum == original_checksum {
108                        test.add_detail(format!("{pattern_name}: ✓ Integrity verified"));
109                        successful_tests += 1;
110                    } else {
111                        test.add_detail(format!("{pattern_name}: ✗ Integrity check failed"));
112                    }
113                }
114                Err(e) => {
115                    test.add_detail(format!("{pattern_name}: ✗ Download failed: {e}"));
116                }
117            },
118            Err(e) => {
119                test.add_detail(format!("{pattern_name}: ✗ Upload failed: {e}"));
120            }
121        }
122    }
123
124    if successful_tests == pattern_count {
125        info!("Blob integrity test completed successfully");
126        test.with_result(TestResult::Passed)
127    } else {
128        let error = format!("Only {successful_tests}/{pattern_count} integrity tests passed");
129        test.with_result(TestResult::Failed { error })
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::Client;
137    use tempfile::TempDir;
138
139    async fn create_test_client() -> (Client, TempDir) {
140        let temp_dir = TempDir::new().unwrap();
141        let media_storage_path = temp_dir.path().join("blobs");
142        let db_storage_path = temp_dir.path().join("db");
143
144        let client = {
145            let mut builder = Client::builder();
146            builder.media_storage_dir_pathbuf(media_storage_path);
147            builder.db_storage_dir_pathbuf(db_storage_path);
148            builder.autoconnect(false);
149            builder.build().await.unwrap()
150        };
151
152        (client, temp_dir)
153    }
154
155    #[tokio::test]
156    async fn test_blob_service_tests_structure() {
157        let (client, _temp_dir) = create_test_client().await;
158        let config = SystemCheckConfig::default();
159
160        let results = run_tests(&client, &config).await;
161
162        // Should have 2 tests
163        assert_eq!(results.len(), 2);
164
165        let test_names: Vec<_> = results.iter().map(|t| &t.name).collect();
166        assert!(test_names.contains(&&"Blob Upload/Download".to_string()));
167        assert!(test_names.contains(&&"Blob Integrity".to_string()));
168    }
169
170    #[test]
171    fn test_data_patterns() {
172        // Test the integrity of our test patterns
173        let patterns = vec![
174            (vec![0u8; 10], "Zero bytes"),
175            (vec![255u8; 10], "Max bytes"),
176            ((0..10u8).collect(), "Sequential bytes"),
177        ];
178
179        for (data, name) in patterns {
180            let checksum = crc32fast::hash(&data);
181            // Verify checksum is consistent
182            assert_eq!(
183                checksum,
184                crc32fast::hash(&data),
185                "Checksum inconsistent for {name}"
186            );
187        }
188    }
189}