1pub mod app;
92pub mod build;
93pub mod client;
94pub mod findings;
95pub mod identity;
96pub mod pipeline;
97pub mod policy;
98pub mod sandbox;
99pub mod scan;
100pub mod workflow;
101
102use reqwest::Error as ReqwestError;
103use secrecy::{ExposeSecret, SecretString};
104use std::fmt;
105use std::sync::Arc;
106use std::time::Duration;
107
108pub use app::{
110 Application, ApplicationQuery, ApplicationsResponse, CreateApplicationRequest,
111 UpdateApplicationRequest,
112};
113pub use build::{
114 Build, BuildApi, BuildError, BuildList, CreateBuildRequest, DeleteBuildRequest,
115 DeleteBuildResult, GetBuildInfoRequest, GetBuildListRequest, UpdateBuildRequest,
116};
117pub use client::VeracodeClient;
118pub use findings::{
119 CweInfo, FindingCategory, FindingDetails, FindingStatus, FindingsApi, FindingsError,
120 FindingsQuery, FindingsResponse, RestFinding,
121};
122pub use identity::{
123 ApiCredential, BusinessUnit, CreateApiCredentialRequest, CreateTeamRequest, CreateUserRequest,
124 IdentityApi, IdentityError, Role, Team, UpdateTeamRequest, UpdateUserRequest, User, UserQuery,
125 UserType,
126};
127pub use pipeline::{
128 CreateScanRequest, DevStage, Finding, FindingsSummary, PipelineApi, PipelineError, Scan,
129 ScanConfig, ScanResults, ScanStage, ScanStatus, SecurityStandards, Severity,
130};
131pub use policy::{
132 ApiSource, PolicyApi, PolicyComplianceResult, PolicyComplianceStatus, PolicyError, PolicyRule,
133 PolicyScanRequest, PolicyScanResult, PolicyThresholds, ScanType, SecurityPolicy, SummaryReport,
134};
135pub use sandbox::{
136 ApiError, ApiErrorResponse, CreateSandboxRequest, Sandbox, SandboxApi, SandboxError,
137 SandboxListParams, SandboxScan, UpdateSandboxRequest,
138};
139pub use scan::{
140 BeginPreScanRequest, BeginScanRequest, PreScanMessage, PreScanResults, ScanApi, ScanError,
141 ScanInfo, ScanModule, UploadFileRequest, UploadLargeFileRequest, UploadProgress,
142 UploadProgressCallback, UploadedFile,
143};
144pub use workflow::{VeracodeWorkflow, WorkflowConfig, WorkflowError, WorkflowResultData};
145#[derive(Debug, Clone)]
147pub struct RetryConfig {
148 pub max_attempts: u32,
150 pub initial_delay_ms: u64,
152 pub max_delay_ms: u64,
154 pub backoff_multiplier: f64,
156 pub max_total_delay_ms: u64,
158 pub rate_limit_buffer_seconds: u64,
160 pub rate_limit_max_attempts: u32,
162 pub jitter_enabled: bool,
164}
165
166impl Default for RetryConfig {
167 fn default() -> Self {
168 Self {
169 max_attempts: 5,
170 initial_delay_ms: 1000,
171 max_delay_ms: 30000,
172 backoff_multiplier: 2.0,
173 max_total_delay_ms: 300_000, rate_limit_buffer_seconds: 5, rate_limit_max_attempts: 1, jitter_enabled: true, }
178 }
179}
180
181impl RetryConfig {
182 #[must_use]
184 pub fn new() -> Self {
185 Self::default()
186 }
187
188 #[must_use]
190 pub fn with_max_attempts(mut self, max_attempts: u32) -> Self {
191 self.max_attempts = max_attempts;
192 self
193 }
194
195 #[must_use]
197 pub fn with_initial_delay(mut self, delay_ms: u64) -> Self {
198 self.initial_delay_ms = delay_ms;
199 self
200 }
201
202 #[must_use]
204 pub fn with_initial_delay_millis(mut self, delay_ms: u64) -> Self {
205 self.initial_delay_ms = delay_ms;
206 self
207 }
208
209 #[must_use]
211 pub fn with_max_delay(mut self, delay_ms: u64) -> Self {
212 self.max_delay_ms = delay_ms;
213 self
214 }
215
216 #[must_use]
218 pub fn with_max_delay_millis(mut self, delay_ms: u64) -> Self {
219 self.max_delay_ms = delay_ms;
220 self
221 }
222
223 #[must_use]
225 pub fn with_backoff_multiplier(mut self, multiplier: f64) -> Self {
226 self.backoff_multiplier = multiplier;
227 self
228 }
229
230 #[must_use]
232 pub fn with_exponential_backoff(mut self, multiplier: f64) -> Self {
233 self.backoff_multiplier = multiplier;
234 self
235 }
236
237 #[must_use]
239 pub fn with_max_total_delay(mut self, delay_ms: u64) -> Self {
240 self.max_total_delay_ms = delay_ms;
241 self
242 }
243
244 #[must_use]
246 pub fn with_rate_limit_buffer(mut self, buffer_seconds: u64) -> Self {
247 self.rate_limit_buffer_seconds = buffer_seconds;
248 self
249 }
250
251 #[must_use]
253 pub fn with_rate_limit_max_attempts(mut self, max_attempts: u32) -> Self {
254 self.rate_limit_max_attempts = max_attempts;
255 self
256 }
257
258 #[must_use]
264 pub fn with_jitter_disabled(mut self) -> Self {
265 self.jitter_enabled = false;
266 self
267 }
268
269 #[must_use]
271 pub fn calculate_delay(&self, attempt: u32) -> Duration {
272 if attempt == 0 {
273 return Duration::from_millis(0);
274 }
275
276 let delay_ms = (self.initial_delay_ms as f64
277 * self.backoff_multiplier.powi((attempt - 1) as i32)) as u64;
278
279 let mut capped_delay = delay_ms.min(self.max_delay_ms);
280
281 if self.jitter_enabled {
283 use rand::Rng;
284 let jitter_range = (capped_delay as f64 * 0.25) as u64;
285 let min_delay = capped_delay.saturating_sub(jitter_range);
286 let max_delay = capped_delay + jitter_range;
287 capped_delay = rand::rng().random_range(min_delay..=max_delay);
288 }
289
290 Duration::from_millis(capped_delay)
291 }
292
293 #[must_use]
299 pub fn calculate_rate_limit_delay(&self, retry_after_seconds: Option<u64>) -> Duration {
300 if let Some(seconds) = retry_after_seconds {
301 Duration::from_secs(seconds)
303 } else {
304 let now = std::time::SystemTime::now()
306 .duration_since(std::time::UNIX_EPOCH)
307 .unwrap_or_default();
308
309 let current_second = now.as_secs() % 60;
310
311 let seconds_until_next_minute = 60 - current_second;
313
314 Duration::from_secs(seconds_until_next_minute + self.rate_limit_buffer_seconds)
315 }
316 }
317
318 #[must_use]
320 pub fn is_retryable_error(&self, error: &VeracodeError) -> bool {
321 match error {
322 VeracodeError::Http(reqwest_error) => {
323 if reqwest_error.is_timeout()
325 || reqwest_error.is_connect()
326 || reqwest_error.is_request()
327 {
328 return true;
329 }
330
331 if let Some(status) = reqwest_error.status() {
333 match status.as_u16() {
334 429 => true,
336 502..=504 => true,
338 500..=599 => true,
340 _ => false,
342 }
343 } else {
344 true
346 }
347 }
348 VeracodeError::Authentication(_)
350 | VeracodeError::Serialization(_)
351 | VeracodeError::InvalidConfig(_) => false,
352 VeracodeError::InvalidResponse(_) => true,
354 VeracodeError::NotFound(_) => false,
356 VeracodeError::RetryExhausted(_) => false,
358 VeracodeError::RateLimited { .. } => true,
360 }
361 }
362}
363
364#[derive(Debug)]
369pub enum VeracodeError {
370 Http(ReqwestError),
372 Serialization(serde_json::Error),
374 Authentication(String),
376 InvalidResponse(String),
378 InvalidConfig(String),
380 NotFound(String),
382 RetryExhausted(String),
384 RateLimited {
386 retry_after_seconds: Option<u64>,
388 message: String,
390 },
391}
392
393impl VeracodeClient {
394 fn new_xml_client(config: VeracodeConfig) -> Result<Self, VeracodeError> {
400 let mut xml_config = config;
401 xml_config.base_url = xml_config.xml_base_url.clone();
402 Self::new(xml_config)
403 }
404
405 #[must_use]
408 pub fn applications_api(&self) -> &Self {
409 self
410 }
411
412 #[must_use]
415 pub fn sandbox_api(&self) -> SandboxApi<'_> {
416 SandboxApi::new(self)
417 }
418
419 #[must_use]
422 pub fn identity_api(&self) -> IdentityApi<'_> {
423 IdentityApi::new(self)
424 }
425
426 #[must_use]
429 pub fn pipeline_api(&self) -> PipelineApi {
430 PipelineApi::new(self.clone())
431 }
432
433 #[must_use]
436 pub fn policy_api(&self) -> PolicyApi<'_> {
437 PolicyApi::new(self)
438 }
439
440 #[must_use]
443 pub fn findings_api(&self) -> FindingsApi {
444 FindingsApi::new(self.clone())
445 }
446
447 #[must_use]
450 pub fn scan_api(&self) -> ScanApi {
451 let xml_client = Self::new_xml_client(self.config().clone())
453 .expect("XML client creation should not fail if main client was created successfully");
454 ScanApi::new(xml_client)
455 }
456
457 #[must_use]
460 pub fn build_api(&self) -> build::BuildApi {
461 let xml_client = Self::new_xml_client(self.config().clone())
463 .expect("XML client creation should not fail if main client was created successfully");
464 build::BuildApi::new(xml_client)
465 }
466
467 #[must_use]
470 pub fn workflow(&self) -> workflow::VeracodeWorkflow {
471 workflow::VeracodeWorkflow::new(self.clone())
472 }
473}
474
475impl fmt::Display for VeracodeError {
476 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
477 match self {
478 VeracodeError::Http(e) => write!(f, "HTTP error: {e}"),
479 VeracodeError::Serialization(e) => write!(f, "Serialization error: {e}"),
480 VeracodeError::Authentication(e) => write!(f, "Authentication error: {e}"),
481 VeracodeError::InvalidResponse(e) => write!(f, "Invalid response: {e}"),
482 VeracodeError::InvalidConfig(e) => write!(f, "Invalid configuration: {e}"),
483 VeracodeError::NotFound(e) => write!(f, "Item not found: {e}"),
484 VeracodeError::RetryExhausted(e) => write!(f, "Retry attempts exhausted: {e}"),
485 VeracodeError::RateLimited {
486 retry_after_seconds,
487 message,
488 } => match retry_after_seconds {
489 Some(seconds) => {
490 write!(f, "Rate limit exceeded: {message} (retry after {seconds}s)")
491 }
492 None => write!(f, "Rate limit exceeded: {message}"),
493 },
494 }
495 }
496}
497
498impl std::error::Error for VeracodeError {}
499
500impl From<ReqwestError> for VeracodeError {
501 fn from(error: ReqwestError) -> Self {
502 VeracodeError::Http(error)
503 }
504}
505
506impl From<serde_json::Error> for VeracodeError {
507 fn from(error: serde_json::Error) -> Self {
508 VeracodeError::Serialization(error)
509 }
510}
511
512#[derive(Clone)]
520pub struct VeracodeCredentials {
521 api_id: Arc<SecretString>,
523 api_key: Arc<SecretString>,
525}
526
527impl VeracodeCredentials {
528 #[must_use]
530 pub fn new(api_id: String, api_key: String) -> Self {
531 Self {
532 api_id: Arc::new(SecretString::new(api_id.into())),
533 api_key: Arc::new(SecretString::new(api_key.into())),
534 }
535 }
536
537 #[must_use]
544 pub fn api_id_ptr(&self) -> Arc<SecretString> {
545 Arc::clone(&self.api_id)
546 }
547
548 #[must_use]
555 pub fn api_key_ptr(&self) -> Arc<SecretString> {
556 Arc::clone(&self.api_key)
557 }
558
559 #[must_use]
564 pub fn expose_api_id(&self) -> &str {
565 self.api_id.expose_secret()
566 }
567
568 #[must_use]
573 pub fn expose_api_key(&self) -> &str {
574 self.api_key.expose_secret()
575 }
576}
577
578impl std::fmt::Debug for VeracodeCredentials {
579 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580 f.debug_struct("VeracodeCredentials")
581 .field("api_id", &"[REDACTED]")
582 .field("api_key", &"[REDACTED]")
583 .finish()
584 }
585}
586
587#[derive(Clone)]
594pub struct VeracodeConfig {
595 pub credentials: VeracodeCredentials,
597 pub base_url: String,
599 pub rest_base_url: String,
601 pub xml_base_url: String,
603 pub region: VeracodeRegion,
605 pub validate_certificates: bool,
607 pub retry_config: RetryConfig,
609 pub connect_timeout: u64,
611 pub request_timeout: u64,
613 pub proxy_url: Option<String>,
615 pub proxy_username: Option<SecretString>,
617 pub proxy_password: Option<SecretString>,
619}
620
621impl std::fmt::Debug for VeracodeConfig {
623 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
624 let proxy_url_redacted = self.proxy_url.as_ref().map(|url| {
626 if url.contains('@') {
627 if let Some(at_pos) = url.rfind('@') {
629 if let Some(proto_end) = url.find("://") {
630 format!("{}://[REDACTED]@{}", &url[..proto_end], &url[at_pos + 1..])
631 } else {
632 "[REDACTED]".to_string()
633 }
634 } else {
635 "[REDACTED]".to_string()
636 }
637 } else {
638 url.clone()
639 }
640 });
641
642 f.debug_struct("VeracodeConfig")
643 .field("credentials", &self.credentials)
644 .field("base_url", &self.base_url)
645 .field("rest_base_url", &self.rest_base_url)
646 .field("xml_base_url", &self.xml_base_url)
647 .field("region", &self.region)
648 .field("validate_certificates", &self.validate_certificates)
649 .field("retry_config", &self.retry_config)
650 .field("connect_timeout", &self.connect_timeout)
651 .field("request_timeout", &self.request_timeout)
652 .field("proxy_url", &proxy_url_redacted)
653 .field(
654 "proxy_username",
655 &self.proxy_username.as_ref().map(|_| "[REDACTED]"),
656 )
657 .field(
658 "proxy_password",
659 &self.proxy_password.as_ref().map(|_| "[REDACTED]"),
660 )
661 .finish()
662 }
663}
664
665const COMMERCIAL_REST_URL: &str = "https://api.veracode.com";
667const COMMERCIAL_XML_URL: &str = "https://analysiscenter.veracode.com";
668const EUROPEAN_REST_URL: &str = "https://api.veracode.eu";
669const EUROPEAN_XML_URL: &str = "https://analysiscenter.veracode.eu";
670const FEDERAL_REST_URL: &str = "https://api.veracode.us";
671const FEDERAL_XML_URL: &str = "https://analysiscenter.veracode.us";
672
673#[derive(Debug, Clone, Copy, PartialEq)]
678pub enum VeracodeRegion {
679 Commercial,
681 European,
683 Federal,
685}
686
687impl VeracodeConfig {
688 #[must_use]
704 pub fn new(api_id: &str, api_key: &str) -> Self {
705 let credentials = VeracodeCredentials::new(api_id.to_string(), api_key.to_string());
706 Self {
707 credentials,
708 base_url: COMMERCIAL_REST_URL.to_string(),
709 rest_base_url: COMMERCIAL_REST_URL.to_string(),
710 xml_base_url: COMMERCIAL_XML_URL.to_string(),
711 region: VeracodeRegion::Commercial,
712 validate_certificates: true, retry_config: RetryConfig::default(),
714 connect_timeout: 30, request_timeout: 300, proxy_url: None,
717 proxy_username: None,
718 proxy_password: None,
719 }
720 }
721
722 #[must_use]
727 pub fn from_arc_credentials(api_id: Arc<SecretString>, api_key: Arc<SecretString>) -> Self {
728 let credentials = VeracodeCredentials { api_id, api_key };
729
730 Self {
731 credentials,
732 base_url: COMMERCIAL_REST_URL.to_string(),
733 rest_base_url: COMMERCIAL_REST_URL.to_string(),
734 xml_base_url: COMMERCIAL_XML_URL.to_string(),
735 region: VeracodeRegion::Commercial,
736 validate_certificates: true,
737 retry_config: RetryConfig::default(),
738 connect_timeout: 30,
739 request_timeout: 300,
740 proxy_url: None,
741 proxy_username: None,
742 proxy_password: None,
743 }
744 }
745
746 #[must_use]
759 pub fn with_region(mut self, region: VeracodeRegion) -> Self {
760 let (rest_url, xml_url) = match region {
761 VeracodeRegion::Commercial => (COMMERCIAL_REST_URL, COMMERCIAL_XML_URL),
762 VeracodeRegion::European => (EUROPEAN_REST_URL, EUROPEAN_XML_URL),
763 VeracodeRegion::Federal => (FEDERAL_REST_URL, FEDERAL_XML_URL),
764 };
765
766 self.region = region;
767 self.rest_base_url = rest_url.to_string();
768 self.xml_base_url = xml_url.to_string();
769 self.base_url = self.rest_base_url.clone(); self
771 }
772
773 #[must_use]
782 pub fn with_certificate_validation_disabled(mut self) -> Self {
783 self.validate_certificates = false;
784 self
785 }
786
787 #[must_use]
800 pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
801 self.retry_config = retry_config;
802 self
803 }
804
805 #[must_use]
814 pub fn with_retries_disabled(mut self) -> Self {
815 self.retry_config = RetryConfig::new().with_max_attempts(0);
816 self
817 }
818
819 #[must_use]
831 pub fn with_connect_timeout(mut self, timeout_seconds: u64) -> Self {
832 self.connect_timeout = timeout_seconds;
833 self
834 }
835
836 #[must_use]
849 pub fn with_request_timeout(mut self, timeout_seconds: u64) -> Self {
850 self.request_timeout = timeout_seconds;
851 self
852 }
853
854 #[must_use]
867 pub fn with_timeouts(
868 mut self,
869 connect_timeout_seconds: u64,
870 request_timeout_seconds: u64,
871 ) -> Self {
872 self.connect_timeout = connect_timeout_seconds;
873 self.request_timeout = request_timeout_seconds;
874 self
875 }
876
877 #[must_use]
879 pub fn api_id_arc(&self) -> Arc<SecretString> {
880 self.credentials.api_id_ptr()
881 }
882
883 #[must_use]
885 pub fn api_key_arc(&self) -> Arc<SecretString> {
886 self.credentials.api_key_ptr()
887 }
888
889 #[must_use]
917 pub fn with_proxy(mut self, proxy_url: impl Into<String>) -> Self {
918 self.proxy_url = Some(proxy_url.into());
919 self
920 }
921
922 #[must_use]
947 pub fn with_proxy_auth(
948 mut self,
949 username: impl Into<String>,
950 password: impl Into<String>,
951 ) -> Self {
952 self.proxy_username = Some(SecretString::new(username.into().into()));
953 self.proxy_password = Some(SecretString::new(password.into().into()));
954 self
955 }
956}
957
958#[cfg(test)]
959mod tests {
960 use super::*;
961
962 #[test]
963 fn test_config_creation() {
964 let config = VeracodeConfig::new("test_api_id", "test_api_key");
965
966 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
967 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
968 assert_eq!(config.base_url, "https://api.veracode.com");
969 assert_eq!(config.rest_base_url, "https://api.veracode.com");
970 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
971 assert_eq!(config.region, VeracodeRegion::Commercial);
972 assert!(config.validate_certificates); assert_eq!(config.retry_config.max_attempts, 5); }
975
976 #[test]
977 fn test_european_region_config() {
978 let config = VeracodeConfig::new("test_api_id", "test_api_key")
979 .with_region(VeracodeRegion::European);
980
981 assert_eq!(config.base_url, "https://api.veracode.eu");
982 assert_eq!(config.rest_base_url, "https://api.veracode.eu");
983 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
984 assert_eq!(config.region, VeracodeRegion::European);
985 }
986
987 #[test]
988 fn test_federal_region_config() {
989 let config =
990 VeracodeConfig::new("test_api_id", "test_api_key").with_region(VeracodeRegion::Federal);
991
992 assert_eq!(config.base_url, "https://api.veracode.us");
993 assert_eq!(config.rest_base_url, "https://api.veracode.us");
994 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
995 assert_eq!(config.region, VeracodeRegion::Federal);
996 }
997
998 #[test]
999 fn test_certificate_validation_disabled() {
1000 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1001 .with_certificate_validation_disabled();
1002
1003 assert!(!config.validate_certificates);
1004 }
1005
1006 #[test]
1007 fn test_veracode_credentials_debug_redaction() {
1008 let credentials = VeracodeCredentials::new(
1009 "test_api_id_123".to_string(),
1010 "test_api_key_456".to_string(),
1011 );
1012 let debug_output = format!("{credentials:?}");
1013
1014 assert!(debug_output.contains("VeracodeCredentials"));
1016 assert!(debug_output.contains("[REDACTED]"));
1017
1018 assert!(!debug_output.contains("test_api_id_123"));
1020 assert!(!debug_output.contains("test_api_key_456"));
1021 }
1022
1023 #[test]
1024 fn test_veracode_config_debug_redaction() {
1025 let config = VeracodeConfig::new("test_api_id_123", "test_api_key_456");
1026 let debug_output = format!("{config:?}");
1027
1028 assert!(debug_output.contains("VeracodeConfig"));
1030 assert!(debug_output.contains("credentials"));
1031 assert!(debug_output.contains("[REDACTED]"));
1032
1033 assert!(!debug_output.contains("test_api_id_123"));
1035 assert!(!debug_output.contains("test_api_key_456"));
1036 }
1037
1038 #[test]
1039 fn test_veracode_credentials_access_methods() {
1040 let credentials = VeracodeCredentials::new(
1041 "test_api_id_123".to_string(),
1042 "test_api_key_456".to_string(),
1043 );
1044
1045 assert_eq!(credentials.expose_api_id(), "test_api_id_123");
1047 assert_eq!(credentials.expose_api_key(), "test_api_key_456");
1048 }
1049
1050 #[test]
1051 fn test_veracode_credentials_arc_pointers() {
1052 let credentials = VeracodeCredentials::new(
1053 "test_api_id_123".to_string(),
1054 "test_api_key_456".to_string(),
1055 );
1056
1057 let api_id_arc = credentials.api_id_ptr();
1059 let api_key_arc = credentials.api_key_ptr();
1060
1061 assert_eq!(api_id_arc.expose_secret(), "test_api_id_123");
1063 assert_eq!(api_key_arc.expose_secret(), "test_api_key_456");
1064 }
1065
1066 #[test]
1067 fn test_veracode_credentials_clone() {
1068 let credentials = VeracodeCredentials::new(
1069 "test_api_id_123".to_string(),
1070 "test_api_key_456".to_string(),
1071 );
1072 let cloned_credentials = credentials.clone();
1073
1074 assert_eq!(
1076 credentials.expose_api_id(),
1077 cloned_credentials.expose_api_id()
1078 );
1079 assert_eq!(
1080 credentials.expose_api_key(),
1081 cloned_credentials.expose_api_key()
1082 );
1083 }
1084
1085 #[test]
1086 fn test_config_with_arc_credentials() {
1087 use secrecy::SecretString;
1088 use std::sync::Arc;
1089
1090 let api_id_arc = Arc::new(SecretString::new("test_api_id".into()));
1091 let api_key_arc = Arc::new(SecretString::new("test_api_key".into()));
1092
1093 let config = VeracodeConfig::from_arc_credentials(api_id_arc, api_key_arc);
1094
1095 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
1096 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
1097 assert_eq!(config.region, VeracodeRegion::Commercial);
1098 }
1099
1100 #[test]
1101 fn test_error_display() {
1102 let error = VeracodeError::Authentication("Invalid API key".to_string());
1103 assert_eq!(format!("{error}"), "Authentication error: Invalid API key");
1104 }
1105
1106 #[test]
1107 fn test_error_from_reqwest() {
1108 fn _test_conversion(error: reqwest::Error) -> VeracodeError {
1113 VeracodeError::from(error)
1114 }
1115
1116 }
1119
1120 #[test]
1121 fn test_retry_config_default() {
1122 let config = RetryConfig::default();
1123 assert_eq!(config.max_attempts, 5);
1124 assert_eq!(config.initial_delay_ms, 1000);
1125 assert_eq!(config.max_delay_ms, 30000);
1126 assert_eq!(config.backoff_multiplier, 2.0);
1127 assert_eq!(config.max_total_delay_ms, 300000);
1128 assert!(config.jitter_enabled); }
1130
1131 #[test]
1132 fn test_retry_config_builder() {
1133 let config = RetryConfig::new()
1134 .with_max_attempts(5)
1135 .with_initial_delay(500)
1136 .with_max_delay(60000)
1137 .with_backoff_multiplier(1.5)
1138 .with_max_total_delay(600000);
1139
1140 assert_eq!(config.max_attempts, 5);
1141 assert_eq!(config.initial_delay_ms, 500);
1142 assert_eq!(config.max_delay_ms, 60000);
1143 assert_eq!(config.backoff_multiplier, 1.5);
1144 assert_eq!(config.max_total_delay_ms, 600000);
1145 }
1146
1147 #[test]
1148 fn test_retry_config_calculate_delay() {
1149 let config = RetryConfig::new()
1150 .with_initial_delay(1000)
1151 .with_backoff_multiplier(2.0)
1152 .with_max_delay(10000)
1153 .with_jitter_disabled(); assert_eq!(config.calculate_delay(0).as_millis(), 0); assert_eq!(config.calculate_delay(1).as_millis(), 1000); assert_eq!(config.calculate_delay(2).as_millis(), 2000); assert_eq!(config.calculate_delay(3).as_millis(), 4000); assert_eq!(config.calculate_delay(4).as_millis(), 8000); assert_eq!(config.calculate_delay(5).as_millis(), 10000); }
1163
1164 #[test]
1165 fn test_retry_config_is_retryable_error() {
1166 let config = RetryConfig::new();
1167
1168 assert!(
1170 config.is_retryable_error(&VeracodeError::InvalidResponse("temp error".to_string()))
1171 );
1172
1173 assert!(!config.is_retryable_error(&VeracodeError::Authentication("bad auth".to_string())));
1175 assert!(!config.is_retryable_error(&VeracodeError::Serialization(
1176 serde_json::from_str::<i32>("invalid").unwrap_err()
1177 )));
1178 assert!(
1179 !config.is_retryable_error(&VeracodeError::InvalidConfig("bad config".to_string()))
1180 );
1181 assert!(!config.is_retryable_error(&VeracodeError::NotFound("not found".to_string())));
1182 assert!(
1183 !config.is_retryable_error(&VeracodeError::RetryExhausted("exhausted".to_string()))
1184 );
1185 }
1186
1187 #[test]
1188 fn test_veracode_config_with_retry_config() {
1189 let retry_config = RetryConfig::new().with_max_attempts(5);
1190 let config =
1191 VeracodeConfig::new("test_api_id", "test_api_key").with_retry_config(retry_config);
1192
1193 assert_eq!(config.retry_config.max_attempts, 5);
1194 }
1195
1196 #[test]
1197 fn test_veracode_config_with_retries_disabled() {
1198 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_retries_disabled();
1199
1200 assert_eq!(config.retry_config.max_attempts, 0);
1201 }
1202
1203 #[test]
1204 fn test_timeout_configuration() {
1205 let config = VeracodeConfig::new("test_api_id", "test_api_key");
1206
1207 assert_eq!(config.connect_timeout, 30);
1209 assert_eq!(config.request_timeout, 300);
1210 }
1211
1212 #[test]
1213 fn test_with_connect_timeout() {
1214 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_connect_timeout(60);
1215
1216 assert_eq!(config.connect_timeout, 60);
1217 assert_eq!(config.request_timeout, 300); }
1219
1220 #[test]
1221 fn test_with_request_timeout() {
1222 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_request_timeout(600);
1223
1224 assert_eq!(config.connect_timeout, 30); assert_eq!(config.request_timeout, 600);
1226 }
1227
1228 #[test]
1229 fn test_with_timeouts() {
1230 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_timeouts(120, 1800);
1231
1232 assert_eq!(config.connect_timeout, 120);
1233 assert_eq!(config.request_timeout, 1800);
1234 }
1235
1236 #[test]
1237 fn test_timeout_configuration_chaining() {
1238 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1239 .with_region(VeracodeRegion::European)
1240 .with_connect_timeout(45)
1241 .with_request_timeout(900)
1242 .with_retries_disabled();
1243
1244 assert_eq!(config.region, VeracodeRegion::European);
1245 assert_eq!(config.connect_timeout, 45);
1246 assert_eq!(config.request_timeout, 900);
1247 assert_eq!(config.retry_config.max_attempts, 0);
1248 }
1249
1250 #[test]
1251 fn test_retry_exhausted_error_display() {
1252 let error = VeracodeError::RetryExhausted("Failed after 3 attempts".to_string());
1253 assert_eq!(
1254 format!("{error}"),
1255 "Retry attempts exhausted: Failed after 3 attempts"
1256 );
1257 }
1258
1259 #[test]
1260 fn test_rate_limited_error_display_with_retry_after() {
1261 let error = VeracodeError::RateLimited {
1262 retry_after_seconds: Some(60),
1263 message: "Too Many Requests".to_string(),
1264 };
1265 assert_eq!(
1266 format!("{error}"),
1267 "Rate limit exceeded: Too Many Requests (retry after 60s)"
1268 );
1269 }
1270
1271 #[test]
1272 fn test_rate_limited_error_display_without_retry_after() {
1273 let error = VeracodeError::RateLimited {
1274 retry_after_seconds: None,
1275 message: "Too Many Requests".to_string(),
1276 };
1277 assert_eq!(format!("{error}"), "Rate limit exceeded: Too Many Requests");
1278 }
1279
1280 #[test]
1281 fn test_rate_limited_error_is_retryable() {
1282 let config = RetryConfig::new();
1283 let error = VeracodeError::RateLimited {
1284 retry_after_seconds: Some(60),
1285 message: "Rate limit exceeded".to_string(),
1286 };
1287 assert!(config.is_retryable_error(&error));
1288 }
1289
1290 #[test]
1291 fn test_calculate_rate_limit_delay_with_retry_after() {
1292 let config = RetryConfig::new();
1293 let delay = config.calculate_rate_limit_delay(Some(30));
1294 assert_eq!(delay.as_secs(), 30);
1295 }
1296
1297 #[test]
1298 fn test_calculate_rate_limit_delay_without_retry_after() {
1299 let config = RetryConfig::new();
1300 let delay = config.calculate_rate_limit_delay(None);
1301
1302 assert!(delay.as_secs() >= 5);
1305 assert!(delay.as_secs() <= 65);
1306 }
1307
1308 #[test]
1309 fn test_rate_limit_config_defaults() {
1310 let config = RetryConfig::default();
1311 assert_eq!(config.rate_limit_buffer_seconds, 5);
1312 assert_eq!(config.rate_limit_max_attempts, 1);
1313 }
1314
1315 #[test]
1316 fn test_rate_limit_config_builders() {
1317 let config = RetryConfig::new()
1318 .with_rate_limit_buffer(10)
1319 .with_rate_limit_max_attempts(2);
1320
1321 assert_eq!(config.rate_limit_buffer_seconds, 10);
1322 assert_eq!(config.rate_limit_max_attempts, 2);
1323 }
1324
1325 #[test]
1326 fn test_rate_limit_delay_uses_buffer() {
1327 let config = RetryConfig::new().with_rate_limit_buffer(15);
1328 let delay = config.calculate_rate_limit_delay(None);
1329
1330 assert!(delay.as_secs() >= 15);
1332 assert!(delay.as_secs() <= 75); }
1334
1335 #[test]
1336 fn test_jitter_disabled() {
1337 let config = RetryConfig::new().with_jitter_disabled();
1338 assert!(!config.jitter_enabled);
1339
1340 let delay1 = config.calculate_delay(2);
1342 let delay2 = config.calculate_delay(2);
1343 assert_eq!(delay1, delay2);
1344 }
1345
1346 #[test]
1347 fn test_jitter_enabled() {
1348 let config = RetryConfig::new(); assert!(config.jitter_enabled);
1350
1351 let base_delay = config.initial_delay_ms;
1353 let delay = config.calculate_delay(1);
1354
1355 let min_expected = (base_delay as f64 * 0.75) as u64;
1357 let max_expected = (base_delay as f64 * 1.25) as u64;
1358
1359 assert!(delay.as_millis() >= min_expected as u128);
1360 assert!(delay.as_millis() <= max_expected as u128);
1361 }
1362}