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}
614
615impl std::fmt::Debug for VeracodeConfig {
617 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618 f.debug_struct("VeracodeConfig")
619 .field("credentials", &self.credentials)
620 .field("base_url", &self.base_url)
621 .field("rest_base_url", &self.rest_base_url)
622 .field("xml_base_url", &self.xml_base_url)
623 .field("region", &self.region)
624 .field("validate_certificates", &self.validate_certificates)
625 .field("retry_config", &self.retry_config)
626 .field("connect_timeout", &self.connect_timeout)
627 .field("request_timeout", &self.request_timeout)
628 .finish()
629 }
630}
631
632const COMMERCIAL_REST_URL: &str = "https://api.veracode.com";
634const COMMERCIAL_XML_URL: &str = "https://analysiscenter.veracode.com";
635const EUROPEAN_REST_URL: &str = "https://api.veracode.eu";
636const EUROPEAN_XML_URL: &str = "https://analysiscenter.veracode.eu";
637const FEDERAL_REST_URL: &str = "https://api.veracode.us";
638const FEDERAL_XML_URL: &str = "https://analysiscenter.veracode.us";
639
640#[derive(Debug, Clone, Copy, PartialEq)]
645pub enum VeracodeRegion {
646 Commercial,
648 European,
650 Federal,
652}
653
654impl VeracodeConfig {
655 #[must_use]
671 pub fn new(api_id: &str, api_key: &str) -> Self {
672 let credentials = VeracodeCredentials::new(api_id.to_string(), api_key.to_string());
673 Self {
674 credentials,
675 base_url: COMMERCIAL_REST_URL.to_string(),
676 rest_base_url: COMMERCIAL_REST_URL.to_string(),
677 xml_base_url: COMMERCIAL_XML_URL.to_string(),
678 region: VeracodeRegion::Commercial,
679 validate_certificates: true, retry_config: RetryConfig::default(),
681 connect_timeout: 30, request_timeout: 300, }
684 }
685
686 #[must_use]
691 pub fn from_arc_credentials(api_id: Arc<SecretString>, api_key: Arc<SecretString>) -> Self {
692 let credentials = VeracodeCredentials { api_id, api_key };
693
694 Self {
695 credentials,
696 base_url: COMMERCIAL_REST_URL.to_string(),
697 rest_base_url: COMMERCIAL_REST_URL.to_string(),
698 xml_base_url: COMMERCIAL_XML_URL.to_string(),
699 region: VeracodeRegion::Commercial,
700 validate_certificates: true,
701 retry_config: RetryConfig::default(),
702 connect_timeout: 30,
703 request_timeout: 300,
704 }
705 }
706
707 #[must_use]
720 pub fn with_region(mut self, region: VeracodeRegion) -> Self {
721 let (rest_url, xml_url) = match region {
722 VeracodeRegion::Commercial => (COMMERCIAL_REST_URL, COMMERCIAL_XML_URL),
723 VeracodeRegion::European => (EUROPEAN_REST_URL, EUROPEAN_XML_URL),
724 VeracodeRegion::Federal => (FEDERAL_REST_URL, FEDERAL_XML_URL),
725 };
726
727 self.region = region;
728 self.rest_base_url = rest_url.to_string();
729 self.xml_base_url = xml_url.to_string();
730 self.base_url = self.rest_base_url.clone(); self
732 }
733
734 #[must_use]
743 pub fn with_certificate_validation_disabled(mut self) -> Self {
744 self.validate_certificates = false;
745 self
746 }
747
748 #[must_use]
761 pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
762 self.retry_config = retry_config;
763 self
764 }
765
766 #[must_use]
775 pub fn with_retries_disabled(mut self) -> Self {
776 self.retry_config = RetryConfig::new().with_max_attempts(0);
777 self
778 }
779
780 #[must_use]
792 pub fn with_connect_timeout(mut self, timeout_seconds: u64) -> Self {
793 self.connect_timeout = timeout_seconds;
794 self
795 }
796
797 #[must_use]
810 pub fn with_request_timeout(mut self, timeout_seconds: u64) -> Self {
811 self.request_timeout = timeout_seconds;
812 self
813 }
814
815 #[must_use]
828 pub fn with_timeouts(
829 mut self,
830 connect_timeout_seconds: u64,
831 request_timeout_seconds: u64,
832 ) -> Self {
833 self.connect_timeout = connect_timeout_seconds;
834 self.request_timeout = request_timeout_seconds;
835 self
836 }
837
838 #[must_use]
840 pub fn api_id_arc(&self) -> Arc<SecretString> {
841 self.credentials.api_id_ptr()
842 }
843
844 #[must_use]
846 pub fn api_key_arc(&self) -> Arc<SecretString> {
847 self.credentials.api_key_ptr()
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854
855 #[test]
856 fn test_config_creation() {
857 let config = VeracodeConfig::new("test_api_id", "test_api_key");
858
859 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
860 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
861 assert_eq!(config.base_url, "https://api.veracode.com");
862 assert_eq!(config.rest_base_url, "https://api.veracode.com");
863 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
864 assert_eq!(config.region, VeracodeRegion::Commercial);
865 assert!(config.validate_certificates); assert_eq!(config.retry_config.max_attempts, 5); }
868
869 #[test]
870 fn test_european_region_config() {
871 let config = VeracodeConfig::new("test_api_id", "test_api_key")
872 .with_region(VeracodeRegion::European);
873
874 assert_eq!(config.base_url, "https://api.veracode.eu");
875 assert_eq!(config.rest_base_url, "https://api.veracode.eu");
876 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
877 assert_eq!(config.region, VeracodeRegion::European);
878 }
879
880 #[test]
881 fn test_federal_region_config() {
882 let config =
883 VeracodeConfig::new("test_api_id", "test_api_key").with_region(VeracodeRegion::Federal);
884
885 assert_eq!(config.base_url, "https://api.veracode.us");
886 assert_eq!(config.rest_base_url, "https://api.veracode.us");
887 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
888 assert_eq!(config.region, VeracodeRegion::Federal);
889 }
890
891 #[test]
892 fn test_certificate_validation_disabled() {
893 let config = VeracodeConfig::new("test_api_id", "test_api_key")
894 .with_certificate_validation_disabled();
895
896 assert!(!config.validate_certificates);
897 }
898
899 #[test]
900 fn test_veracode_credentials_debug_redaction() {
901 let credentials = VeracodeCredentials::new(
902 "test_api_id_123".to_string(),
903 "test_api_key_456".to_string(),
904 );
905 let debug_output = format!("{credentials:?}");
906
907 assert!(debug_output.contains("VeracodeCredentials"));
909 assert!(debug_output.contains("[REDACTED]"));
910
911 assert!(!debug_output.contains("test_api_id_123"));
913 assert!(!debug_output.contains("test_api_key_456"));
914 }
915
916 #[test]
917 fn test_veracode_config_debug_redaction() {
918 let config = VeracodeConfig::new("test_api_id_123", "test_api_key_456");
919 let debug_output = format!("{config:?}");
920
921 assert!(debug_output.contains("VeracodeConfig"));
923 assert!(debug_output.contains("credentials"));
924 assert!(debug_output.contains("[REDACTED]"));
925
926 assert!(!debug_output.contains("test_api_id_123"));
928 assert!(!debug_output.contains("test_api_key_456"));
929 }
930
931 #[test]
932 fn test_veracode_credentials_access_methods() {
933 let credentials = VeracodeCredentials::new(
934 "test_api_id_123".to_string(),
935 "test_api_key_456".to_string(),
936 );
937
938 assert_eq!(credentials.expose_api_id(), "test_api_id_123");
940 assert_eq!(credentials.expose_api_key(), "test_api_key_456");
941 }
942
943 #[test]
944 fn test_veracode_credentials_arc_pointers() {
945 let credentials = VeracodeCredentials::new(
946 "test_api_id_123".to_string(),
947 "test_api_key_456".to_string(),
948 );
949
950 let api_id_arc = credentials.api_id_ptr();
952 let api_key_arc = credentials.api_key_ptr();
953
954 assert_eq!(api_id_arc.expose_secret(), "test_api_id_123");
956 assert_eq!(api_key_arc.expose_secret(), "test_api_key_456");
957 }
958
959 #[test]
960 fn test_veracode_credentials_clone() {
961 let credentials = VeracodeCredentials::new(
962 "test_api_id_123".to_string(),
963 "test_api_key_456".to_string(),
964 );
965 let cloned_credentials = credentials.clone();
966
967 assert_eq!(
969 credentials.expose_api_id(),
970 cloned_credentials.expose_api_id()
971 );
972 assert_eq!(
973 credentials.expose_api_key(),
974 cloned_credentials.expose_api_key()
975 );
976 }
977
978 #[test]
979 fn test_config_with_arc_credentials() {
980 use secrecy::SecretString;
981 use std::sync::Arc;
982
983 let api_id_arc = Arc::new(SecretString::new("test_api_id".into()));
984 let api_key_arc = Arc::new(SecretString::new("test_api_key".into()));
985
986 let config = VeracodeConfig::from_arc_credentials(api_id_arc, api_key_arc);
987
988 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
989 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
990 assert_eq!(config.region, VeracodeRegion::Commercial);
991 }
992
993 #[test]
994 fn test_error_display() {
995 let error = VeracodeError::Authentication("Invalid API key".to_string());
996 assert_eq!(format!("{error}"), "Authentication error: Invalid API key");
997 }
998
999 #[test]
1000 fn test_error_from_reqwest() {
1001 fn _test_conversion(error: reqwest::Error) -> VeracodeError {
1006 VeracodeError::from(error)
1007 }
1008
1009 }
1012
1013 #[test]
1014 fn test_retry_config_default() {
1015 let config = RetryConfig::default();
1016 assert_eq!(config.max_attempts, 5);
1017 assert_eq!(config.initial_delay_ms, 1000);
1018 assert_eq!(config.max_delay_ms, 30000);
1019 assert_eq!(config.backoff_multiplier, 2.0);
1020 assert_eq!(config.max_total_delay_ms, 300000);
1021 assert!(config.jitter_enabled); }
1023
1024 #[test]
1025 fn test_retry_config_builder() {
1026 let config = RetryConfig::new()
1027 .with_max_attempts(5)
1028 .with_initial_delay(500)
1029 .with_max_delay(60000)
1030 .with_backoff_multiplier(1.5)
1031 .with_max_total_delay(600000);
1032
1033 assert_eq!(config.max_attempts, 5);
1034 assert_eq!(config.initial_delay_ms, 500);
1035 assert_eq!(config.max_delay_ms, 60000);
1036 assert_eq!(config.backoff_multiplier, 1.5);
1037 assert_eq!(config.max_total_delay_ms, 600000);
1038 }
1039
1040 #[test]
1041 fn test_retry_config_calculate_delay() {
1042 let config = RetryConfig::new()
1043 .with_initial_delay(1000)
1044 .with_backoff_multiplier(2.0)
1045 .with_max_delay(10000)
1046 .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); }
1056
1057 #[test]
1058 fn test_retry_config_is_retryable_error() {
1059 let config = RetryConfig::new();
1060
1061 assert!(
1063 config.is_retryable_error(&VeracodeError::InvalidResponse("temp error".to_string()))
1064 );
1065
1066 assert!(!config.is_retryable_error(&VeracodeError::Authentication("bad auth".to_string())));
1068 assert!(!config.is_retryable_error(&VeracodeError::Serialization(
1069 serde_json::from_str::<i32>("invalid").unwrap_err()
1070 )));
1071 assert!(
1072 !config.is_retryable_error(&VeracodeError::InvalidConfig("bad config".to_string()))
1073 );
1074 assert!(!config.is_retryable_error(&VeracodeError::NotFound("not found".to_string())));
1075 assert!(
1076 !config.is_retryable_error(&VeracodeError::RetryExhausted("exhausted".to_string()))
1077 );
1078 }
1079
1080 #[test]
1081 fn test_veracode_config_with_retry_config() {
1082 let retry_config = RetryConfig::new().with_max_attempts(5);
1083 let config =
1084 VeracodeConfig::new("test_api_id", "test_api_key").with_retry_config(retry_config);
1085
1086 assert_eq!(config.retry_config.max_attempts, 5);
1087 }
1088
1089 #[test]
1090 fn test_veracode_config_with_retries_disabled() {
1091 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_retries_disabled();
1092
1093 assert_eq!(config.retry_config.max_attempts, 0);
1094 }
1095
1096 #[test]
1097 fn test_timeout_configuration() {
1098 let config = VeracodeConfig::new("test_api_id", "test_api_key");
1099
1100 assert_eq!(config.connect_timeout, 30);
1102 assert_eq!(config.request_timeout, 300);
1103 }
1104
1105 #[test]
1106 fn test_with_connect_timeout() {
1107 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_connect_timeout(60);
1108
1109 assert_eq!(config.connect_timeout, 60);
1110 assert_eq!(config.request_timeout, 300); }
1112
1113 #[test]
1114 fn test_with_request_timeout() {
1115 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_request_timeout(600);
1116
1117 assert_eq!(config.connect_timeout, 30); assert_eq!(config.request_timeout, 600);
1119 }
1120
1121 #[test]
1122 fn test_with_timeouts() {
1123 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_timeouts(120, 1800);
1124
1125 assert_eq!(config.connect_timeout, 120);
1126 assert_eq!(config.request_timeout, 1800);
1127 }
1128
1129 #[test]
1130 fn test_timeout_configuration_chaining() {
1131 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1132 .with_region(VeracodeRegion::European)
1133 .with_connect_timeout(45)
1134 .with_request_timeout(900)
1135 .with_retries_disabled();
1136
1137 assert_eq!(config.region, VeracodeRegion::European);
1138 assert_eq!(config.connect_timeout, 45);
1139 assert_eq!(config.request_timeout, 900);
1140 assert_eq!(config.retry_config.max_attempts, 0);
1141 }
1142
1143 #[test]
1144 fn test_retry_exhausted_error_display() {
1145 let error = VeracodeError::RetryExhausted("Failed after 3 attempts".to_string());
1146 assert_eq!(
1147 format!("{error}"),
1148 "Retry attempts exhausted: Failed after 3 attempts"
1149 );
1150 }
1151
1152 #[test]
1153 fn test_rate_limited_error_display_with_retry_after() {
1154 let error = VeracodeError::RateLimited {
1155 retry_after_seconds: Some(60),
1156 message: "Too Many Requests".to_string(),
1157 };
1158 assert_eq!(
1159 format!("{error}"),
1160 "Rate limit exceeded: Too Many Requests (retry after 60s)"
1161 );
1162 }
1163
1164 #[test]
1165 fn test_rate_limited_error_display_without_retry_after() {
1166 let error = VeracodeError::RateLimited {
1167 retry_after_seconds: None,
1168 message: "Too Many Requests".to_string(),
1169 };
1170 assert_eq!(format!("{error}"), "Rate limit exceeded: Too Many Requests");
1171 }
1172
1173 #[test]
1174 fn test_rate_limited_error_is_retryable() {
1175 let config = RetryConfig::new();
1176 let error = VeracodeError::RateLimited {
1177 retry_after_seconds: Some(60),
1178 message: "Rate limit exceeded".to_string(),
1179 };
1180 assert!(config.is_retryable_error(&error));
1181 }
1182
1183 #[test]
1184 fn test_calculate_rate_limit_delay_with_retry_after() {
1185 let config = RetryConfig::new();
1186 let delay = config.calculate_rate_limit_delay(Some(30));
1187 assert_eq!(delay.as_secs(), 30);
1188 }
1189
1190 #[test]
1191 fn test_calculate_rate_limit_delay_without_retry_after() {
1192 let config = RetryConfig::new();
1193 let delay = config.calculate_rate_limit_delay(None);
1194
1195 assert!(delay.as_secs() >= 5);
1198 assert!(delay.as_secs() <= 65);
1199 }
1200
1201 #[test]
1202 fn test_rate_limit_config_defaults() {
1203 let config = RetryConfig::default();
1204 assert_eq!(config.rate_limit_buffer_seconds, 5);
1205 assert_eq!(config.rate_limit_max_attempts, 1);
1206 }
1207
1208 #[test]
1209 fn test_rate_limit_config_builders() {
1210 let config = RetryConfig::new()
1211 .with_rate_limit_buffer(10)
1212 .with_rate_limit_max_attempts(2);
1213
1214 assert_eq!(config.rate_limit_buffer_seconds, 10);
1215 assert_eq!(config.rate_limit_max_attempts, 2);
1216 }
1217
1218 #[test]
1219 fn test_rate_limit_delay_uses_buffer() {
1220 let config = RetryConfig::new().with_rate_limit_buffer(15);
1221 let delay = config.calculate_rate_limit_delay(None);
1222
1223 assert!(delay.as_secs() >= 15);
1225 assert!(delay.as_secs() <= 75); }
1227
1228 #[test]
1229 fn test_jitter_disabled() {
1230 let config = RetryConfig::new().with_jitter_disabled();
1231 assert!(!config.jitter_enabled);
1232
1233 let delay1 = config.calculate_delay(2);
1235 let delay2 = config.calculate_delay(2);
1236 assert_eq!(delay1, delay2);
1237 }
1238
1239 #[test]
1240 fn test_jitter_enabled() {
1241 let config = RetryConfig::new(); assert!(config.jitter_enabled);
1243
1244 let base_delay = config.initial_delay_ms;
1246 let delay = config.calculate_delay(1);
1247
1248 let min_expected = (base_delay as f64 * 0.75) as u64;
1250 let max_expected = (base_delay as f64 * 1.25) as u64;
1251
1252 assert!(delay.as_millis() >= min_expected as u128);
1253 assert!(delay.as_millis() <= max_expected as u128);
1254 }
1255}