1use tonic::transport::Channel;
2use tonic::{Request, metadata::MetadataValue};
3use std::str::FromStr;
4use crate::api::client::chiral::chiral_client::ChiralClient;
5use crate::api::client::chiral::RequestUserCommunicate;
6
7
8pub async fn list_of_projects(client: &mut ChiralClient<Channel>, email: &str, token_auth: &str)-> Result<serde_json::Value, Box<dyn std::error::Error>>{
9 let end_point = "ListOfProjects";
10 let serialized = format!("{{\"{end_point}\": null}}");
11
12
13 let req_msg = RequestUserCommunicate{
14 serialized_request : serialized.clone(),
15 };
16 let mut request = Request::new(req_msg);
17
18 request.metadata_mut().insert("user_id", MetadataValue::from_str(email)?);
19 request.metadata_mut().insert("auth_token", MetadataValue::from_str(token_auth)?);
20
21 let response = client.user_communicate(request).await?.into_inner();
22 if !response.serialized_reply.trim().is_empty() {
23 let parsed: serde_json::Value = serde_json::from_str(&response.serialized_reply)?;
24 if let Some(result) = parsed.get(end_point) {
25 return Ok(result.clone());
26 } else {
27 return Err("Missing endpoint data in server response".into());
28 }
29 }
30
31 if !response.error.trim().is_empty() {
32 return Err(format!("Server error: {}", response.error).into());
33 }
34
35 Err("Unexpected empty response from server".into())
36}
37
38
39pub async fn list_of_example_projects(client: &mut ChiralClient<Channel>, email: &str, token_auth: &str)-> Result<serde_json::Value, Box<dyn std::error::Error>>{
40 let end_point = "ListOfExampleProjects";
41 let serialized = format!("{{\"{end_point}\": null}}");
42
43 let req_msg = RequestUserCommunicate{
44 serialized_request : serialized.clone(),
45 };
46 let mut request = Request::new(req_msg);
47
48 request.metadata_mut().insert("user_id", MetadataValue::from_str(email)?);
49 request.metadata_mut().insert("auth_token", MetadataValue::from_str(token_auth)?);
50
51 let response = client.user_communicate(request).await?.into_inner();
52 if !response.serialized_reply.trim().is_empty() {
53 let parsed: serde_json::Value = serde_json::from_str(&response.serialized_reply)?;
54 if let Some(result) = parsed.get(end_point) {
55 return Ok(result.clone());
56 } else {
57 return Err("Missing endpoint data in server response".into());
58 }
59 }
60
61 if !response.error.trim().is_empty() {
62 return Err(format!("Server error: {}", response.error).into());
63 }
64
65 Err("Unexpected empty response from server".into())
66}
67
68
69pub async fn list_of_project_files(client: &mut ChiralClient<Channel>, email: &str, token_auth: &str, project_name: &str)-> Result<serde_json::Value, Box<dyn std::error::Error>>{
70 let end_point = "ListOfProjectFiles";
71 let serialized = format!("{{\"{end_point}\": \"{project_name}\"}}");
72
73
74 let req_msg = RequestUserCommunicate{
75 serialized_request : serialized.clone(),
76 };
77 let mut request = Request::new(req_msg);
78
79 request.metadata_mut().insert("user_id", MetadataValue::from_str(email)?);
80 request.metadata_mut().insert("auth_token", MetadataValue::from_str(token_auth)?);
81
82 let response = client.user_communicate(request).await?.into_inner();
83 if !response.serialized_reply.trim().is_empty() {
84 let parsed: serde_json::Value = serde_json::from_str(&response.serialized_reply)?;
85 if let Some(result) = parsed.get(end_point) {
86 return Ok(result.clone());
87 } else {
88 return Err("Missing endpoint data in server response".into());
89 }
90 }
91
92 if !response.error.trim().is_empty() {
93 return Err(format!("Server error: {}", response.error).into());
94 }
95
96 Err("Unexpected empty response from server".into())
97}
98
99
100pub async fn import_example_project(client: &mut ChiralClient<Channel>, email: &str, token_auth: &str, project_name: &str)-> Result<serde_json::Value, Box<dyn std::error::Error>>{
101 let end_point = "ImportExampleProject";
102 let serialized = format!("{{\"{end_point}\": \"{project_name}\"}}");
103
104
105 let req_msg = RequestUserCommunicate{
106 serialized_request : serialized.clone(),
107 };
108 let mut request = Request::new(req_msg);
109
110 request.metadata_mut().insert("user_id", MetadataValue::from_str(email)?);
111 request.metadata_mut().insert("auth_token", MetadataValue::from_str(token_auth)?);
112
113 let response = client.user_communicate(request).await?.into_inner();
114 if !response.serialized_reply.trim().is_empty() {
115 let parsed: serde_json::Value = serde_json::from_str(&response.serialized_reply)?;
116 if let Some(result) = parsed.get(end_point) {
117 return Ok(result.clone());
118 } else {
119 return Err("Missing endpoint data in server response".into());
120 }
121 }
122
123 if !response.error.trim().is_empty() {
124 return Err(format!("Server error: {}", response.error).into());
125 }
126
127 Err("Unexpected empty response from server".into())
128}
129
130
131pub async fn get_project_files(client: &mut ChiralClient<Channel>, email: &str, token_auth: &str, project_name: &str, file_name: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> { let _end_point = "GetJobs";
132 let end_point = "GetProjectFile";
133 let serialized = format!("{{\"{end_point}\": [\"{project_name}\", \"{file_name}\"]}}");
134
135
136 let req_msg = RequestUserCommunicate {
137 serialized_request: serialized.clone(),
138 };
139
140 println!("Sending payload: {serialized}" );
141
142 let mut request = Request::new(req_msg);
143 request.metadata_mut().insert("user_id", MetadataValue::from_str(email)?);
144 request.metadata_mut().insert("auth_token", MetadataValue::from_str(token_auth)?);
145
146 let response = client.user_communicate(request).await?.into_inner();
147 println!("Reply JSON: {}", response.serialized_reply);
148
149
150 if !response.serialized_reply.trim().is_empty() {
151 let parsed: serde_json::Value = serde_json::from_str(&response.serialized_reply)?;
152 if let Some(result) = parsed.get(end_point) {
153 return Ok(result.clone());
154 } else {
155 return Err("Missing endpoint data in server response".into());
156 }
157 }
158
159 if !response.error.trim().is_empty() {
160 return Err(format!("Server error: {}", response.error).into());
161 }
162
163 Err("Unexpected empty response from server".into())
164
165}
166
167#[cfg(test)]
168mod tests{
169 use super::*;
170 use crate::api::create_client;
171 use dotenvy;
172 use rand::seq::SliceRandom;
173 use rand::thread_rng;
174
175 #[tokio::test]
176 async fn test_list_of_projects() {
177 dotenvy::from_filename(".env.staging").ok();
178 let url = std::env::var("CHIRAL_STAGING_API_URL").expect("Missing env").trim() .to_string();
179 let email = std::env::var("TEST_EMAIL").expect("Missing env").trim() .to_string();
180 let token_auth = std::env::var("TEST_TOKEN_AUTH").expect("Missing env").trim() .to_string();
181 let mut client = create_client(&url).await.expect("Failed to create API client.");
182
183 let projects = list_of_projects(&mut client, &email, &token_auth).await.expect("List of projects failed");
184
185 assert!(projects.is_array(),"Expected JSON array but got: {}",projects);
186
187 let projects_array = projects.as_array().expect("Projects response is not a valid JSON array");
188
189 for (i, project) in projects_array.iter().enumerate() {
190 match project {
191 serde_json::Value::Object(obj) => {
192 assert!(obj.get("id").is_some(),"Project at index {} missing 'id': {}",i,project);
193 assert!(obj.get("name").is_some(),"Project at index {} missing 'name': {}",i,project);
194 }
195 serde_json::Value::String(s) => {
196 assert!(!s.trim().is_empty(),"Project at index {} is an empty string",i);
197 }
198 _ => {
199 panic!("Project at index {} is neither object nor string: {}",i, project);
200 }
201 }
202 }
203 }
204
205 #[tokio::test]
206 async fn test_list_of_example_projects(){
207 dotenvy::from_filename(".env.staging").ok();
208 let url = std::env::var("CHIRAL_STAGING_API_URL").expect("Missing env").trim() .to_string();
209 let email = std::env::var("TEST_EMAIL").expect("Missing env").trim() .to_string();
210 let token_auth = std::env::var("TEST_TOKEN_AUTH").expect("Missing env").trim() .to_string();
211
212 let mut client = create_client(&url).await.expect("Failed to create API client.");
213
214 let projects = list_of_example_projects(&mut client, &email, &token_auth).await.expect("List of projects failed");
215
216 assert!(projects.is_array(),"Expected JSON array but got: {}",projects);
217 let projects_array = projects.as_array().expect("Projects response is not a valid JSON array");
218 for (i, project) in projects_array.iter().enumerate() {
219 match project {
220 serde_json::Value::Object(obj) => {
221 assert!(obj.get("id").is_some(),"Project at index {} missing 'id': {}",i,project);
222 assert!(obj.get("name").is_some(),"Project at index {} missing 'name': {}",i,project);
223 }
224 serde_json::Value::String(s) => {
225 assert!(!s.trim().is_empty(),"Project at index {} is an empty string",i);
226 }
227 _ => {
228 panic!("Project at index {} is neither object nor string: {}",i, project);
229 }
230 }
231 }
232
233 }
234
235 #[tokio::test]
236 async fn test_list_of_project_files(){
237 dotenvy::from_filename(".env.staging").ok();
238 let url = std::env::var("CHIRAL_STAGING_API_URL").expect("Missing env").trim() .to_string();
239 let email = std::env::var("TEST_EMAIL").expect("Missing env").trim() .to_string();
240 let token_auth = std::env::var("TEST_TOKEN_AUTH").expect("Missing env").trim() .to_string();
241
242 let mut client = create_client(&url).await.expect("Failed to create API client.");
243 let example_projects = list_of_example_projects(&mut client, &email, &token_auth).await.expect("Failed to get example projects");
244 let project_name_opt = example_projects.as_array().and_then(|arr| {
245 arr.iter().find_map(|p| match p {
246 serde_json::Value::String(name) => Some(name.clone()),
247 serde_json::Value::Object(obj) => obj.get("name").and_then(|v| v.as_str()).map(String::from),
248 _ => None,
249 })
250 });
251
252 let project_name = project_name_opt.expect("No valid project name found in example projects");
253
254 println!("Using example project: {}", project_name);
255
256 let files = list_of_project_files(&mut client, &email, &token_auth, &project_name).await.expect("Failed to get project files");
257
258 assert!(files.is_array(),"Expected project files to be a JSON array, got: {}",files);
259
260 let file_array = files.as_array().unwrap();
261 println!("Found {} file(s) in project '{}'", file_array.len(), project_name);
262
263 for (i, file) in file_array.iter().enumerate() {
264 assert!(file.is_string(),"Expected file entry at index {} to be a string, got: {}",i,file);
265 }
266 }
267
268 #[tokio::test]
269 async fn test_import_example_project() {
270 dotenvy::from_filename(".env.staging").ok();
271 let url = std::env::var("CHIRAL_STAGING_API_URL").expect("Missing env").trim() .to_string();
272 let email = std::env::var("TEST_EMAIL").expect("Missing env").trim() .to_string();
273 let token_auth = std::env::var("TEST_TOKEN_AUTH").expect("Missing env").trim() .to_string();
274
275 let mut client = create_client(&url).await.expect("Failed to create API client");
276
277 let existing_projects = list_of_projects(&mut client, &email, &token_auth).await.expect("Failed to fetch list of projects");
278
279 println!("Existing projects: {}", existing_projects);
280
281 let example_projects = list_of_example_projects(&mut client, &email, &token_auth).await.expect("Failed to fetch list of example projects");
282
283 let example_project_name = example_projects
284 .as_array()
285 .and_then(|arr| {
286 arr.iter().find_map(|p| match p {
287 serde_json::Value::String(name) => Some(name.clone()),
288 serde_json::Value::Object(obj) => obj.get("name").and_then(|v| v.as_str()).map(String::from),
289 _ => None,
290 })
291 }).expect("No valid example project name found");
292
293 println!("Trying to import: {}", example_project_name);
294
295 let already_exists = existing_projects
296 .as_array()
297 .map(|arr| {
298 arr.iter().any(|p| match p {
299 serde_json::Value::String(name) => name == &example_project_name,
300 serde_json::Value::Object(obj) => obj
301 .get("name")
302 .and_then(|v| v.as_str())
303 .map(|s| s == example_project_name)
304 .unwrap_or(false),
305 _ => false,
306 })
307 })
308 .unwrap_or(false);
309
310 if already_exists {
311 println!("Project '{}' already exists — skipping import.", example_project_name);
312 } else {
313 let result = import_example_project(&mut client, &email, &token_auth, &example_project_name).await.expect("Failed to import example project");
314
315 println!("Import result: {}", result);
316
317 assert!(result.is_object() || result.is_string(),"Expected object or string in response, got: {}",result);
318 }
319 }
320
321 #[tokio::test]
322 async fn test_get_project_files() {
323 dotenvy::from_filename(".env.staging").ok();
324 let url = std::env::var("CHIRAL_STAGING_API_URL").expect("Missing env").trim() .to_string();
325 let email = std::env::var("TEST_EMAIL").expect("Missing env").trim() .to_string();
326 let token_auth = std::env::var("TEST_TOKEN_AUTH").expect("Missing env").trim() .to_string();
327
328 let mut client = create_client(&url).await.expect("Failed to create API client");
329
330 let projects = list_of_projects(&mut client, &email, &token_auth)
331 .await
332 .expect("Failed to fetch list of projects");
333
334 let project_name = projects
335 .as_array()
336 .and_then(|arr| {
337 let mut rng = thread_rng();
338 let valid_names: Vec<String> = arr
339 .iter()
340 .filter_map(|p| match p {
341 serde_json::Value::Object(obj) => obj.get("name").and_then(|v| v.as_str()).map(String::from),
342 serde_json::Value::String(name) => Some(name.clone()),
343 _ => None,
344 })
345 .filter(|name| !name.starts_with("gromacs_bench"))
346 .collect();
347
348 valid_names.choose(&mut rng).cloned()
349 })
350 .expect("No valid project name found (after filtering broken gromacs_bench projects)");
351
352
353 println!("Using project: {project_name}");
354
355 let files = list_of_project_files(&mut client, &email, &token_auth, &project_name)
356 .await
357 .expect("Failed to list project files");
358
359 let file_array = files.as_array().expect("File list is not an array");
360
361 println!("Found {} file(s) in project.", file_array.len());
362 let mut at_least_one_success = false;
363
364 for file in file_array {
365 let file_name = match file {
366 serde_json::Value::Object(obj) => obj.get("name").and_then(|v| v.as_str()).map(String::from),
367 serde_json::Value::String(name) => Some(name.clone()),
368 _ => None,
369 };
370
371 let Some(file_name) = file_name else {
372 println!("Skipping invalid file entry: {file:?}");
373 continue;
374 };
375
376 println!("Trying file: {}", file_name);
377
378 match get_project_files(&mut client, &email, &token_auth, &project_name, &file_name).await {
379 Ok(file_data) => {
380 println!("Successfully fetched file: {}\nData: {}", file_name, file_data);
381 assert!(
382 file_data.is_string() || file_data.is_object(),
383 "Expected JSON string or object for file '{}', got: {}",
384 file_name,
385 file_data
386 );
387 at_least_one_success = true;
388 }
389 Err(e) => {
390 println!("Error fetching '{}': {}", file_name, e);
391 }
392 }
393 }
394
395 assert!(
396 at_least_one_success,
397 "No project file could be successfully fetched and validated."
398 );
399
400 }
401
402}