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