zoe_client/system_check/
blob_service.rs1use super::{SystemCheckConfig, TestInfo, TestResult};
8use crate::{Client, services::BlobStore};
9use tracing::{debug, info};
10
11pub async fn run_tests(client: &Client, config: &SystemCheckConfig) -> Vec<TestInfo> {
13 let mut tests = Vec::new();
14
15 tests.push(test_blob_upload_download(client, config).await);
17
18 tests.push(test_blob_integrity(client, config).await);
20
21 tests
22}
23
24async 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 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 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 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
83async 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 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 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 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 assert_eq!(
183 checksum,
184 crc32fast::hash(&data),
185 "Checksum inconsistent for {name}"
186 );
187 }
188 }
189}