1use crate::{
7 VeracodeClient, VeracodeError,
8 app::{Application, BusinessCriticality},
9 sandbox::{Sandbox, SandboxError},
10 scan::{ScanError},
11 build::{Build, BuildError}
12};
13
14pub struct VeracodeWorkflow {
16 client: VeracodeClient,
17}
18
19pub type WorkflowResult<T> = Result<T, WorkflowError>;
21
22#[derive(Debug)]
24pub enum WorkflowError {
25 Api(VeracodeError),
27 Sandbox(SandboxError),
29 Scan(ScanError),
31 Build(BuildError),
33 Workflow(String),
35 AccessDenied(String),
37 NotFound(String),
39}
40
41impl std::fmt::Display for WorkflowError {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 WorkflowError::Api(err) => write!(f, "API error: {err}"),
45 WorkflowError::Sandbox(err) => write!(f, "Sandbox error: {err}"),
46 WorkflowError::Scan(err) => write!(f, "Scan error: {err}"),
47 WorkflowError::Build(err) => write!(f, "Build error: {err}"),
48 WorkflowError::Workflow(msg) => write!(f, "Workflow error: {msg}"),
49 WorkflowError::AccessDenied(msg) => write!(f, "Access denied: {msg}"),
50 WorkflowError::NotFound(msg) => write!(f, "Not found: {msg}"),
51 }
52 }
53}
54
55impl std::error::Error for WorkflowError {}
56
57impl From<VeracodeError> for WorkflowError {
58 fn from(err: VeracodeError) -> Self {
59 WorkflowError::Api(err)
60 }
61}
62
63impl From<SandboxError> for WorkflowError {
64 fn from(err: SandboxError) -> Self {
65 WorkflowError::Sandbox(err)
66 }
67}
68
69impl From<ScanError> for WorkflowError {
70 fn from(err: ScanError) -> Self {
71 WorkflowError::Scan(err)
72 }
73}
74
75impl From<BuildError> for WorkflowError {
76 fn from(err: BuildError) -> Self {
77 WorkflowError::Build(err)
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct WorkflowConfig {
84 pub app_name: String,
86 pub sandbox_name: String,
88 pub business_criticality: BusinessCriticality,
90 pub app_description: Option<String>,
92 pub sandbox_description: Option<String>,
94 pub file_paths: Vec<String>,
96 pub auto_scan: bool,
98 pub scan_all_modules: bool,
100}
101
102impl WorkflowConfig {
103 pub fn new(app_name: String, sandbox_name: String) -> Self {
105 Self {
106 app_name,
107 sandbox_name,
108 business_criticality: BusinessCriticality::Medium,
109 app_description: None,
110 sandbox_description: None,
111 file_paths: Vec::new(),
112 auto_scan: true,
113 scan_all_modules: true,
114 }
115 }
116
117 pub fn with_business_criticality(mut self, criticality: BusinessCriticality) -> Self {
119 self.business_criticality = criticality;
120 self
121 }
122
123 pub fn with_app_description(mut self, description: String) -> Self {
125 self.app_description = Some(description);
126 self
127 }
128
129 pub fn with_sandbox_description(mut self, description: String) -> Self {
131 self.sandbox_description = Some(description);
132 self
133 }
134
135 pub fn with_file(mut self, file_path: String) -> Self {
137 self.file_paths.push(file_path);
138 self
139 }
140
141 pub fn with_files(mut self, file_paths: Vec<String>) -> Self {
143 self.file_paths.extend(file_paths);
144 self
145 }
146
147 pub fn with_auto_scan(mut self, auto_scan: bool) -> Self {
149 self.auto_scan = auto_scan;
150 self
151 }
152
153 pub fn with_scan_all_modules(mut self, scan_all: bool) -> Self {
155 self.scan_all_modules = scan_all;
156 self
157 }
158}
159
160#[derive(Debug, Clone)]
162pub struct WorkflowResultData {
163 pub application: Application,
165 pub sandbox: Sandbox,
167 pub app_id: String,
169 pub sandbox_id: String,
171 pub build_id: Option<String>,
173 pub app_created: bool,
175 pub sandbox_created: bool,
177 pub files_uploaded: usize,
179}
180
181impl VeracodeWorkflow {
182 pub fn new(client: VeracodeClient) -> Self {
184 Self { client }
185 }
186
187 pub async fn execute_complete_workflow(&self, config: WorkflowConfig) -> WorkflowResult<WorkflowResultData> {
205 println!("๐ Starting complete Veracode XML API workflow");
206 println!(" Application: {}", config.app_name);
207 println!(" Sandbox: {}", config.sandbox_name);
208 println!(" Files to upload: {}", config.file_paths.len());
209
210 println!("\n๐ฑ Step 1: Checking application existence...");
212 let (application, app_created) = match self.client.get_application_by_name(&config.app_name).await {
213 Ok(Some(app)) => {
214 println!(" โ
Application '{}' found (GUID: {})", config.app_name, app.guid);
215 (app, false)
216 }
217 Ok(None) => {
218 println!(" โ Application '{}' not found, creating...", config.app_name);
219 match self.client.create_application_if_not_exists(
220 &config.app_name,
221 config.business_criticality,
222 config.app_description.clone(),
223 ).await {
224 Ok(app) => {
225 println!(" โ
Application '{}' created successfully (GUID: {})", config.app_name, app.guid);
226 (app, true)
227 }
228 Err(VeracodeError::InvalidResponse(msg)) if msg.contains("403") || msg.contains("401") => {
229 return Err(WorkflowError::AccessDenied(format!(
230 "Access denied creating application '{}': {}", config.app_name, msg
231 )));
232 }
233 Err(e) => return Err(WorkflowError::Api(e)),
234 }
235 }
236 Err(VeracodeError::InvalidResponse(msg)) if msg.contains("403") || msg.contains("401") => {
237 return Err(WorkflowError::AccessDenied(format!(
238 "Access denied checking application '{}': {}", config.app_name, msg
239 )));
240 }
241 Err(e) => return Err(WorkflowError::Api(e)),
242 };
243
244 let app_id = self.client.get_app_id_from_guid(&application.guid).await?;
246 println!(" ๐ Application ID for XML API: {app_id}");
247
248 println!("\n๐งช Step 2: Checking sandbox existence...");
250 let sandbox_api = self.client.sandbox_api();
251 let (sandbox, sandbox_created) = match sandbox_api.get_sandbox_by_name(&application.guid, &config.sandbox_name).await {
252 Ok(Some(sandbox)) => {
253 println!(" โ
Sandbox '{}' found (GUID: {})", config.sandbox_name, sandbox.guid);
254 (sandbox, false)
255 }
256 Ok(None) => {
257 println!(" โ Sandbox '{}' not found, creating...", config.sandbox_name);
258 match sandbox_api.create_sandbox_if_not_exists(
259 &application.guid,
260 &config.sandbox_name,
261 config.sandbox_description.clone(),
262 ).await {
263 Ok(sandbox) => {
264 println!(" โ
Sandbox '{}' created successfully (GUID: {})", config.sandbox_name, sandbox.guid);
265 (sandbox, true)
266 }
267 Err(SandboxError::Api(VeracodeError::InvalidResponse(msg))) if msg.contains("403") || msg.contains("401") => {
268 return Err(WorkflowError::AccessDenied(format!(
269 "Access denied creating sandbox '{}': {}", config.sandbox_name, msg
270 )));
271 }
272 Err(e) => return Err(WorkflowError::Sandbox(e)),
273 }
274 }
275 Err(SandboxError::Api(VeracodeError::InvalidResponse(msg))) if msg.contains("403") || msg.contains("401") => {
276 return Err(WorkflowError::AccessDenied(format!(
277 "Access denied checking sandbox '{}': {}", config.sandbox_name, msg
278 )));
279 }
280 Err(e) => return Err(WorkflowError::Sandbox(e)),
281 };
282
283 let sandbox_id = sandbox_api.get_sandbox_id_from_guid(&application.guid, &sandbox.guid).await?;
285 println!(" ๐ Sandbox ID for XML API: {sandbox_id}");
286
287 println!("\n๐ค Step 3: Uploading files to sandbox...");
289 let scan_api = self.client.scan_api();
290 let mut files_uploaded = 0;
291
292 for file_path in &config.file_paths {
293 println!(" ๐ Uploading file: {file_path}");
294 match scan_api.upload_file_to_sandbox(&app_id, file_path, &sandbox_id).await {
295 Ok(uploaded_file) => {
296 println!(" โ
File uploaded successfully: {} (ID: {})", uploaded_file.file_name, uploaded_file.file_id);
297 files_uploaded += 1;
298 }
299 Err(ScanError::FileNotFound(_)) => {
300 return Err(WorkflowError::NotFound(format!("File not found: {file_path}")));
301 }
302 Err(ScanError::Unauthorized) => {
303 return Err(WorkflowError::AccessDenied(format!("Access denied uploading file: {file_path}")));
304 }
305 Err(ScanError::PermissionDenied) => {
306 return Err(WorkflowError::AccessDenied(format!("Permission denied uploading file: {file_path}")));
307 }
308 Err(e) => return Err(WorkflowError::Scan(e)),
309 }
310 }
311
312 println!(" ๐ Total files uploaded: {files_uploaded}");
313
314 let build_id = if config.auto_scan {
316 println!("\n๐ Step 4: Starting prescan and scan...");
317 match scan_api.upload_and_scan_sandbox(&app_id, &sandbox_id, &config.file_paths[0]).await {
318 Ok(build_id) => {
319 println!(" โ
Scan started successfully with build ID: {build_id}");
320 Some(build_id)
321 }
322 Err(ScanError::Unauthorized) => {
323 return Err(WorkflowError::AccessDenied("Access denied starting scan".to_string()));
324 }
325 Err(ScanError::PermissionDenied) => {
326 return Err(WorkflowError::AccessDenied("Permission denied starting scan".to_string()));
327 }
328 Err(e) => {
329 println!(" โ ๏ธ Warning: Could not start scan automatically: {e}");
330 println!(" ๐ก You may need to start the scan manually from the Veracode platform");
331 None
332 }
333 }
334 } else {
335 println!("\nโญ๏ธ Step 4: Skipping automatic scan (auto_scan = false)");
336 None
337 };
338
339 let result = WorkflowResultData {
340 application,
341 sandbox,
342 app_id,
343 sandbox_id,
344 build_id: build_id.clone(),
345 app_created,
346 sandbox_created,
347 files_uploaded,
348 };
349
350 println!("\nโ
Workflow completed successfully!");
351 println!(" ๐ Summary:");
352 println!(" - Application: {} (created: {})", config.app_name, app_created);
353 println!(" - Sandbox: {} (created: {})", config.sandbox_name, sandbox_created);
354 println!(" - Files uploaded: {files_uploaded}");
355 if let Some(build_id) = &build_id {
356 println!(" - Scan started: {} (build ID: {})", config.auto_scan, build_id);
357 } else {
358 println!(" - Scan started: {}", config.auto_scan);
359 }
360
361 Ok(result)
362 }
363
364 pub async fn ensure_app_and_sandbox(
379 &self,
380 app_name: &str,
381 sandbox_name: &str,
382 business_criticality: BusinessCriticality,
383 ) -> WorkflowResult<(Application, Sandbox, String, String)> {
384 let config = WorkflowConfig::new(app_name.to_string(), sandbox_name.to_string())
385 .with_business_criticality(business_criticality)
386 .with_auto_scan(false);
387
388 let result = self.execute_complete_workflow(config).await?;
389 Ok((result.application, result.sandbox, result.app_id, result.sandbox_id))
390 }
391
392 pub async fn get_application_by_name(&self, app_name: &str) -> WorkflowResult<Application> {
402 match self.client.get_application_by_name(app_name).await? {
403 Some(app) => Ok(app),
404 None => Err(WorkflowError::NotFound(format!("Application '{app_name}' not found"))),
405 }
406 }
407
408 pub async fn get_sandbox_by_name(&self, app_guid: &str, sandbox_name: &str) -> WorkflowResult<Sandbox> {
419 let sandbox_api = self.client.sandbox_api();
420 match sandbox_api.get_sandbox_by_name(app_guid, sandbox_name).await? {
421 Some(sandbox) => Ok(sandbox),
422 None => Err(WorkflowError::NotFound(format!("Sandbox '{sandbox_name}' not found"))),
423 }
424 }
425
426 pub async fn delete_sandbox_builds(
439 &self,
440 app_name: &str,
441 sandbox_name: &str,
442 ) -> WorkflowResult<()> {
443 println!("๐๏ธ Deleting builds from sandbox '{sandbox_name}'...");
444
445 let app = self.get_application_by_name(app_name).await?;
447 let sandbox = self.get_sandbox_by_name(&app.guid, sandbox_name).await?;
448
449 let app_id = self.client.get_app_id_from_guid(&app.guid).await?;
451 let sandbox_api = self.client.sandbox_api();
452 let sandbox_id = sandbox_api.get_sandbox_id_from_guid(&app.guid, &sandbox.guid).await?;
453
454 let scan_api = self.client.scan_api();
456 match scan_api.delete_all_sandbox_builds(&app_id, &sandbox_id).await {
457 Ok(_) => {
458 println!(" โ
Successfully deleted all builds from sandbox '{sandbox_name}'");
459 Ok(())
460 }
461 Err(ScanError::Unauthorized) => {
462 Err(WorkflowError::AccessDenied("Access denied deleting sandbox builds".to_string()))
463 }
464 Err(ScanError::PermissionDenied) => {
465 Err(WorkflowError::AccessDenied("Permission denied deleting sandbox builds".to_string()))
466 }
467 Err(ScanError::BuildNotFound) => {
468 println!(" โน๏ธ No builds found to delete in sandbox '{sandbox_name}'");
469 Ok(())
470 }
471 Err(e) => Err(WorkflowError::Scan(e)),
472 }
473 }
474
475 pub async fn delete_sandbox(
488 &self,
489 app_name: &str,
490 sandbox_name: &str,
491 ) -> WorkflowResult<()> {
492 println!("๐๏ธ Deleting sandbox '{sandbox_name}'...");
493
494 let app = self.get_application_by_name(app_name).await?;
496 let sandbox = self.get_sandbox_by_name(&app.guid, sandbox_name).await?;
497
498 let _ = self.delete_sandbox_builds(app_name, sandbox_name).await;
500
501 let sandbox_api = self.client.sandbox_api();
503 match sandbox_api.delete_sandbox(&app.guid, &sandbox.guid).await {
504 Ok(_) => {
505 println!(" โ
Successfully deleted sandbox '{sandbox_name}'");
506 Ok(())
507 }
508 Err(SandboxError::Api(VeracodeError::InvalidResponse(msg))) if msg.contains("403") || msg.contains("401") => {
509 Err(WorkflowError::AccessDenied(format!("Access denied deleting sandbox '{sandbox_name}': {msg}")))
510 }
511 Err(SandboxError::NotFound) => {
512 println!(" โน๏ธ Sandbox '{sandbox_name}' not found (may have been already deleted)");
513 Ok(())
514 }
515 Err(e) => Err(WorkflowError::Sandbox(e)),
516 }
517 }
518
519 pub async fn delete_application(
532 &self,
533 app_name: &str,
534 ) -> WorkflowResult<()> {
535 println!("๐๏ธ Deleting application '{app_name}'...");
536
537 let app = self.get_application_by_name(app_name).await?;
539
540 let sandbox_api = self.client.sandbox_api();
542 match sandbox_api.list_sandboxes(&app.guid, None).await {
543 Ok(sandboxes) => {
544 for sandbox in sandboxes {
545 println!(" ๐๏ธ Deleting sandbox: {}", sandbox.name);
546 let _ = self.delete_sandbox(app_name, &sandbox.name).await;
547 }
548 }
549 Err(e) => {
550 println!(" โ ๏ธ Warning: Could not list sandboxes for cleanup: {e}");
551 }
552 }
553
554 let app_id = self.client.get_app_id_from_guid(&app.guid).await?;
556 let scan_api = self.client.scan_api();
557 match scan_api.delete_all_app_builds(&app_id).await {
558 Ok(_) => println!(" โ
Deleted all application builds"),
559 Err(e) => println!(" โ ๏ธ Warning: Could not delete application builds: {e}"),
560 }
561
562 match self.client.delete_application(&app.guid).await {
564 Ok(_) => {
565 println!(" โ
Successfully deleted application '{app_name}'");
566 Ok(())
567 }
568 Err(VeracodeError::InvalidResponse(msg)) if msg.contains("403") || msg.contains("401") => {
569 Err(WorkflowError::AccessDenied(format!("Access denied deleting application '{app_name}': {msg}")))
570 }
571 Err(VeracodeError::NotFound(_)) => {
572 println!(" โน๏ธ Application '{app_name}' not found (may have been already deleted)");
573 Ok(())
574 }
575 Err(e) => Err(WorkflowError::Api(e)),
576 }
577 }
578
579 pub async fn complete_cleanup(
595 &self,
596 app_name: &str,
597 ) -> WorkflowResult<()> {
598 println!("๐งน Starting complete cleanup for application '{app_name}'");
599 println!(" โ ๏ธ WARNING: This will delete ALL data associated with this application");
600 println!(" This includes all sandboxes, builds, and scan results");
601
602 match self.delete_application(app_name).await {
603 Ok(_) => {
604 println!("โ
Complete cleanup finished successfully");
605 Ok(())
606 }
607 Err(WorkflowError::NotFound(_)) => {
608 println!("โน๏ธ Application '{app_name}' not found - nothing to clean up");
609 Ok(())
610 }
611 Err(e) => {
612 println!("โ Cleanup encountered errors: {e}");
613 Err(e)
614 }
615 }
616 }
617
618 pub async fn ensure_build_exists(
633 &self,
634 app_id: &str,
635 sandbox_id: Option<&str>,
636 version: Option<&str>,
637 ) -> WorkflowResult<Build> {
638 println!("๐ Checking if build exists...");
639
640 let build_api = self.client.build_api();
641
642 let get_request = crate::build::GetBuildInfoRequest {
644 app_id: app_id.to_string(),
645 build_id: None, sandbox_id: sandbox_id.map(|s| s.to_string()),
647 };
648
649 match build_api.get_build_info(get_request).await {
650 Ok(build) => {
651 println!(" โ
Build already exists: {}", build.build_id);
652 if let Some(build_version) = &build.version {
653 println!(" Version: {}", build_version);
654 }
655 Ok(build)
656 }
657 Err(crate::build::BuildError::BuildNotFound) => {
658 println!(" โ No build found, creating new build...");
659 self.create_build_for_upload(app_id, sandbox_id, version).await
660 }
661 Err(e) => {
662 println!(" โ ๏ธ Error checking build existence: {e}");
663 println!(" โ Attempting to create new build...");
665 self.create_build_for_upload(app_id, sandbox_id, version).await
666 }
667 }
668 }
669
670 async fn create_build_for_upload(
682 &self,
683 app_id: &str,
684 sandbox_id: Option<&str>,
685 version: Option<&str>,
686 ) -> WorkflowResult<Build> {
687 let build_api = self.client.build_api();
688
689 let build_version = version
690 .map(|v| v.to_string())
691 .unwrap_or_else(|| {
692 let timestamp = std::time::SystemTime::now()
694 .duration_since(std::time::UNIX_EPOCH)
695 .unwrap()
696 .as_secs();
697 format!("build-{}", timestamp)
698 });
699
700 let create_request = crate::build::CreateBuildRequest {
701 app_id: app_id.to_string(),
702 version: Some(build_version.clone()),
703 lifecycle_stage: Some("Development".to_string()),
704 launch_date: None,
705 sandbox_id: sandbox_id.map(|s| s.to_string()),
706 };
707
708 match build_api.create_build(create_request).await {
709 Ok(build) => {
710 println!(" โ
Build created successfully: {}", build.build_id);
711 println!(" Version: {}", build_version);
712 if sandbox_id.is_some() {
713 println!(" Type: Sandbox build");
714 } else {
715 println!(" Type: Application build");
716 }
717 Ok(build)
718 }
719 Err(e) => {
720 println!(" โ Build creation failed: {e}");
721 Err(WorkflowError::Build(e))
722 }
723 }
724 }
725
726 pub async fn upload_large_file_with_build_management(
743 &self,
744 app_id: &str,
745 sandbox_id: Option<&str>,
746 file_path: &str,
747 filename: Option<&str>,
748 version: Option<&str>,
749 ) -> WorkflowResult<crate::scan::UploadedFile> {
750 println!("๐ Starting large file upload with build management");
751 println!(" File: {}", file_path);
752 if let Some(sandbox_id) = sandbox_id {
753 println!(" Target: Sandbox {}", sandbox_id);
754 } else {
755 println!(" Target: Application {}", app_id);
756 }
757
758 let _build = self.ensure_build_exists(app_id, sandbox_id, version).await?;
760
761 println!("\n๐ค Uploading file using uploadlargefile.do...");
763 let scan_api = self.client.scan_api();
764
765 let upload_request = crate::scan::UploadLargeFileRequest {
766 app_id: app_id.to_string(),
767 file_path: file_path.to_string(),
768 filename: filename.map(|s| s.to_string()),
769 sandbox_id: sandbox_id.map(|s| s.to_string()),
770 };
771
772 match scan_api.upload_large_file(upload_request).await {
773 Ok(uploaded_file) => {
774 println!(" โ
Large file uploaded successfully:");
775 println!(" File ID: {}", uploaded_file.file_id);
776 println!(" File Name: {}", uploaded_file.file_name);
777 println!(" Size: {} bytes", uploaded_file.file_size);
778 Ok(uploaded_file)
779 }
780 Err(e) => {
781 println!(" โ Large file upload failed: {e}");
782 Err(WorkflowError::Scan(e))
783 }
784 }
785 }
786
787 pub async fn upload_large_file_with_progress_and_build_management<F>(
805 &self,
806 app_id: &str,
807 sandbox_id: Option<&str>,
808 file_path: &str,
809 filename: Option<&str>,
810 version: Option<&str>,
811 progress_callback: F,
812 ) -> WorkflowResult<crate::scan::UploadedFile>
813 where
814 F: Fn(u64, u64, f64) + Send + Sync,
815 {
816 println!("๐ Starting large file upload with progress tracking and build management");
817 println!(" File: {}", file_path);
818
819 let _build = self.ensure_build_exists(app_id, sandbox_id, version).await?;
821
822 println!("\n๐ค Uploading file with progress tracking...");
824 let scan_api = self.client.scan_api();
825
826 let upload_request = crate::scan::UploadLargeFileRequest {
827 app_id: app_id.to_string(),
828 file_path: file_path.to_string(),
829 filename: filename.map(|s| s.to_string()),
830 sandbox_id: sandbox_id.map(|s| s.to_string()),
831 };
832
833 match scan_api.upload_large_file_with_progress(upload_request, progress_callback).await {
834 Ok(uploaded_file) => {
835 println!(" โ
Large file uploaded successfully with progress tracking");
836 Ok(uploaded_file)
837 }
838 Err(e) => {
839 println!(" โ Large file upload with progress failed: {e}");
840 Err(WorkflowError::Scan(e))
841 }
842 }
843 }
844
845 pub async fn upload_file_with_smart_build_management(
862 &self,
863 app_id: &str,
864 sandbox_id: Option<&str>,
865 file_path: &str,
866 filename: Option<&str>,
867 version: Option<&str>,
868 ) -> WorkflowResult<crate::scan::UploadedFile> {
869 let file_metadata = std::fs::metadata(file_path)
871 .map_err(|e| WorkflowError::Workflow(format!("Cannot access file {}: {}", file_path, e)))?;
872
873 let file_size = file_metadata.len();
874 const LARGE_FILE_THRESHOLD: u64 = 100 * 1024 * 1024; println!("๐ File size: {} bytes", file_size);
877
878 if file_size > LARGE_FILE_THRESHOLD {
879 println!("๐ฆ Using large file upload (uploadlargefile.do) with build management");
880 self.upload_large_file_with_build_management(
881 app_id,
882 sandbox_id,
883 file_path,
884 filename,
885 version,
886 ).await
887 } else {
888 println!("๐ฆ Using standard file upload (uploadfile.do)");
889 let scan_api = self.client.scan_api();
890
891 let upload_request = crate::scan::UploadFileRequest {
892 app_id: app_id.to_string(),
893 file_path: file_path.to_string(),
894 save_as: filename.map(|s| s.to_string()),
895 sandbox_id: sandbox_id.map(|s| s.to_string()),
896 };
897
898 match scan_api.upload_file(upload_request).await {
899 Ok(uploaded_file) => {
900 println!(" โ
File uploaded successfully via uploadfile.do");
901 Ok(uploaded_file)
902 }
903 Err(e) => {
904 println!(" โ Standard upload failed: {e}");
905 Err(WorkflowError::Scan(e))
906 }
907 }
908 }
909 }
910
911 pub async fn get_or_create_build(
925 &self,
926 app_id: &str,
927 sandbox_id: Option<&str>,
928 version: Option<&str>,
929 ) -> WorkflowResult<Build> {
930 self.ensure_build_exists(app_id, sandbox_id, version).await
931 }
932}
933
934#[cfg(test)]
935mod tests {
936 use super::*;
937
938 #[test]
939 fn test_workflow_config_builder() {
940 let config = WorkflowConfig::new("MyApp".to_string(), "MySandbox".to_string())
941 .with_business_criticality(BusinessCriticality::High)
942 .with_app_description("Test application".to_string())
943 .with_file("test.jar".to_string())
944 .with_auto_scan(false);
945
946 assert_eq!(config.app_name, "MyApp");
947 assert_eq!(config.sandbox_name, "MySandbox");
948 assert_eq!(config.business_criticality as i32, BusinessCriticality::High as i32);
949 assert_eq!(config.app_description, Some("Test application".to_string()));
950 assert_eq!(config.file_paths, vec!["test.jar"]);
951 assert!(!config.auto_scan);
952 }
953
954 #[test]
955 fn test_workflow_error_display() {
956 let error = WorkflowError::NotFound("Application not found".to_string());
957 assert_eq!(error.to_string(), "Not found: Application not found");
958
959 let error = WorkflowError::AccessDenied("Permission denied".to_string());
960 assert_eq!(error.to_string(), "Access denied: Permission denied");
961
962 let error = WorkflowError::Workflow("Custom error".to_string());
963 assert_eq!(error.to_string(), "Workflow error: Custom error");
964 }
965}