1#![cfg_attr(test, allow(clippy::expect_used))]
7pub mod app;
94pub mod build;
95pub mod client;
96pub mod findings;
97pub mod identity;
98pub mod json_validator;
99pub mod pipeline;
100pub mod policy;
101pub mod reporting;
102pub mod sandbox;
103pub mod scan;
104pub mod validation;
105pub mod workflow;
106
107use reqwest::Error as ReqwestError;
108use secrecy::{ExposeSecret, SecretString};
109use std::fmt;
110use std::sync::Arc;
111use std::time::Duration;
112
113pub use app::{
115 Application, ApplicationQuery, ApplicationsResponse, CreateApplicationRequest,
116 UpdateApplicationRequest,
117};
118pub use build::{
119 Build, BuildApi, BuildError, BuildList, CreateBuildRequest, DeleteBuildRequest,
120 DeleteBuildResult, GetBuildInfoRequest, GetBuildListRequest, UpdateBuildRequest,
121};
122pub use client::VeracodeClient;
123pub use findings::{
124 CweInfo, FindingCategory, FindingDetails, FindingStatus, FindingsApi, FindingsError,
125 FindingsQuery, FindingsResponse, RestFinding,
126};
127pub use identity::{
128 ApiCredential, BusinessUnit, CreateApiCredentialRequest, CreateTeamRequest, CreateUserRequest,
129 IdentityApi, IdentityError, Role, Team, UpdateTeamRequest, UpdateUserRequest, User, UserQuery,
130 UserType,
131};
132pub use json_validator::{MAX_JSON_DEPTH, validate_json_depth};
133pub use pipeline::{
134 CreateScanRequest, DevStage, Finding, FindingsSummary, PipelineApi, PipelineError, Scan,
135 ScanConfig, ScanResults, ScanStage, ScanStatus, SecurityStandards, Severity,
136};
137pub use policy::{
138 ApiSource, PolicyApi, PolicyComplianceResult, PolicyComplianceStatus, PolicyError, PolicyRule,
139 PolicyScanRequest, PolicyScanResult, PolicyThresholds, ScanType, SecurityPolicy, SummaryReport,
140};
141pub use reporting::{AuditReportRequest, GenerateReportResponse, ReportingApi, ReportingError};
142pub use sandbox::{
143 ApiError, ApiErrorResponse, CreateSandboxRequest, Sandbox, SandboxApi, SandboxError,
144 SandboxListParams, SandboxScan, UpdateSandboxRequest,
145};
146pub use scan::{
147 BeginPreScanRequest, BeginScanRequest, PreScanMessage, PreScanResults, ScanApi, ScanError,
148 ScanInfo, ScanModule, UploadFileRequest, UploadLargeFileRequest, UploadProgress,
149 UploadProgressCallback, UploadedFile,
150};
151pub use validation::{
152 AppGuid, AppName, DEFAULT_PAGE_SIZE, Description, MAX_APP_NAME_LEN, MAX_DESCRIPTION_LEN,
153 MAX_GUID_LEN, MAX_PAGE_NUMBER, MAX_PAGE_SIZE, ValidationError, validate_url_segment,
154};
155pub use workflow::{VeracodeWorkflow, WorkflowConfig, WorkflowError, WorkflowResultData};
156#[derive(Debug, Clone)]
158pub struct RetryConfig {
159 pub max_attempts: u32,
161 pub initial_delay_ms: u64,
163 pub max_delay_ms: u64,
165 pub backoff_multiplier: f64,
167 pub max_total_delay_ms: u64,
169 pub rate_limit_buffer_seconds: u64,
171 pub rate_limit_max_attempts: u32,
173 pub jitter_enabled: bool,
175}
176
177impl Default for RetryConfig {
178 fn default() -> Self {
179 Self {
180 max_attempts: 5,
181 initial_delay_ms: 1000,
182 max_delay_ms: 30000,
183 backoff_multiplier: 2.0,
184 max_total_delay_ms: 300_000, rate_limit_buffer_seconds: 5, rate_limit_max_attempts: 1, jitter_enabled: true, }
189 }
190}
191
192impl RetryConfig {
193 #[must_use]
195 pub fn new() -> Self {
196 Self::default()
197 }
198
199 #[must_use]
201 pub fn with_max_attempts(mut self, max_attempts: u32) -> Self {
202 self.max_attempts = max_attempts;
203 self
204 }
205
206 #[must_use]
208 pub fn with_initial_delay(mut self, delay_ms: u64) -> Self {
209 self.initial_delay_ms = delay_ms;
210 self
211 }
212
213 #[must_use]
215 pub fn with_initial_delay_millis(mut self, delay_ms: u64) -> Self {
216 self.initial_delay_ms = delay_ms;
217 self
218 }
219
220 #[must_use]
222 pub fn with_max_delay(mut self, delay_ms: u64) -> Self {
223 self.max_delay_ms = delay_ms;
224 self
225 }
226
227 #[must_use]
229 pub fn with_max_delay_millis(mut self, delay_ms: u64) -> Self {
230 self.max_delay_ms = delay_ms;
231 self
232 }
233
234 #[must_use]
236 pub fn with_backoff_multiplier(mut self, multiplier: f64) -> Self {
237 self.backoff_multiplier = multiplier;
238 self
239 }
240
241 #[must_use]
243 pub fn with_exponential_backoff(mut self, multiplier: f64) -> Self {
244 self.backoff_multiplier = multiplier;
245 self
246 }
247
248 #[must_use]
250 pub fn with_max_total_delay(mut self, delay_ms: u64) -> Self {
251 self.max_total_delay_ms = delay_ms;
252 self
253 }
254
255 #[must_use]
257 pub fn with_rate_limit_buffer(mut self, buffer_seconds: u64) -> Self {
258 self.rate_limit_buffer_seconds = buffer_seconds;
259 self
260 }
261
262 #[must_use]
264 pub fn with_rate_limit_max_attempts(mut self, max_attempts: u32) -> Self {
265 self.rate_limit_max_attempts = max_attempts;
266 self
267 }
268
269 #[must_use]
275 pub fn with_jitter_disabled(mut self) -> Self {
276 self.jitter_enabled = false;
277 self
278 }
279
280 #[must_use]
282 pub fn calculate_delay(&self, attempt: u32) -> Duration {
283 if attempt == 0 {
284 return Duration::from_millis(0);
285 }
286
287 #[allow(
288 clippy::cast_possible_truncation,
289 clippy::cast_sign_loss,
290 clippy::cast_precision_loss,
291 clippy::cast_possible_wrap
292 )]
293 let delay_ms = (self.initial_delay_ms as f64
294 * self
295 .backoff_multiplier
296 .powi(attempt.saturating_sub(1) as i32))
297 .round() as u64;
298
299 let mut capped_delay = delay_ms.min(self.max_delay_ms);
300
301 if self.jitter_enabled {
303 use rand::RngExt;
304 #[allow(
305 clippy::cast_possible_truncation,
306 clippy::cast_sign_loss,
307 clippy::cast_precision_loss
308 )]
309 let jitter_range = (capped_delay as f64 * 0.25).round() as u64;
310 let min_delay = capped_delay.saturating_sub(jitter_range);
311 let max_delay = capped_delay.saturating_add(jitter_range);
312 capped_delay = rand::rng().random_range(min_delay..=max_delay);
313 }
314
315 Duration::from_millis(capped_delay)
316 }
317
318 #[must_use]
324 pub fn calculate_rate_limit_delay(&self, retry_after_seconds: Option<u64>) -> Duration {
325 if let Some(seconds) = retry_after_seconds {
326 Duration::from_secs(seconds)
328 } else {
329 let now = std::time::SystemTime::now()
331 .duration_since(std::time::UNIX_EPOCH)
332 .unwrap_or_default();
333
334 let current_second = now.as_secs() % 60;
335
336 let seconds_until_next_minute = 60_u64.saturating_sub(current_second);
338
339 Duration::from_secs(
340 seconds_until_next_minute.saturating_add(self.rate_limit_buffer_seconds),
341 )
342 }
343 }
344
345 #[must_use]
347 pub fn is_retryable_error(&self, error: &VeracodeError) -> bool {
348 match error {
349 VeracodeError::Http(reqwest_error) => {
350 if reqwest_error.is_timeout()
352 || reqwest_error.is_connect()
353 || reqwest_error.is_request()
354 {
355 return true;
356 }
357
358 if let Some(status) = reqwest_error.status() {
360 match status.as_u16() {
361 429 => true,
363 502..=504 => true,
365 500..=599 => true,
367 _ => false,
369 }
370 } else {
371 true
373 }
374 }
375 VeracodeError::Authentication(_)
377 | VeracodeError::Serialization(_)
378 | VeracodeError::Validation(_)
379 | VeracodeError::InvalidConfig(_) => false,
380 VeracodeError::InvalidResponse(_) => true,
382 VeracodeError::HttpStatus { status_code, .. } => match status_code {
384 429 => true,
386 502..=504 => true,
388 500..=599 => true,
390 400..=499 => false,
393 _ => false,
395 },
396 VeracodeError::NotFound(_) => false,
398 VeracodeError::RetryExhausted(_) => false,
400 VeracodeError::RateLimited { .. } => true,
402 }
403 }
404}
405
406#[derive(Debug)]
411#[must_use = "Need to handle all error enum types."]
412pub enum VeracodeError {
413 Http(ReqwestError),
415 Serialization(serde_json::Error),
417 Authentication(String),
419 InvalidResponse(String),
421 HttpStatus {
423 status_code: u16,
425 url: String,
427 message: String,
429 },
430 InvalidConfig(String),
432 NotFound(String),
434 RetryExhausted(String),
436 RateLimited {
438 retry_after_seconds: Option<u64>,
440 message: String,
442 },
443 Validation(validation::ValidationError),
445}
446
447impl VeracodeClient {
448 fn new_xml_client(config: VeracodeConfig) -> Result<Self, VeracodeError> {
454 let mut xml_config = config;
455 xml_config.base_url = xml_config.xml_base_url.clone();
456 Self::new(xml_config)
457 }
458
459 #[must_use]
462 pub fn applications_api(&self) -> &Self {
463 self
464 }
465
466 #[must_use]
469 pub fn sandbox_api(&self) -> SandboxApi<'_> {
470 SandboxApi::new(self)
471 }
472
473 #[must_use]
476 pub fn identity_api(&self) -> IdentityApi<'_> {
477 IdentityApi::new(self)
478 }
479
480 #[must_use]
483 pub fn pipeline_api(&self) -> PipelineApi {
484 PipelineApi::new(self.clone())
485 }
486
487 #[must_use]
490 pub fn policy_api(&self) -> PolicyApi<'_> {
491 PolicyApi::new(self)
492 }
493
494 #[must_use]
497 pub fn findings_api(&self) -> FindingsApi {
498 FindingsApi::new(self.clone())
499 }
500
501 #[must_use]
504 pub fn reporting_api(&self) -> reporting::ReportingApi {
505 reporting::ReportingApi::new(self.clone())
506 }
507
508 pub fn scan_api(&self) -> Result<ScanApi, VeracodeError> {
515 let xml_client = Self::new_xml_client(self.config().clone())?;
517 Ok(ScanApi::new(xml_client))
518 }
519
520 pub fn build_api(&self) -> Result<build::BuildApi, VeracodeError> {
527 let xml_client = Self::new_xml_client(self.config().clone())?;
529 Ok(build::BuildApi::new(xml_client))
530 }
531
532 #[must_use]
535 pub fn workflow(&self) -> workflow::VeracodeWorkflow {
536 workflow::VeracodeWorkflow::new(self.clone())
537 }
538}
539
540impl fmt::Display for VeracodeError {
541 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
542 match self {
543 VeracodeError::Http(e) => write!(f, "HTTP error: {e}"),
544 VeracodeError::Serialization(e) => write!(f, "Serialization error: {e}"),
545 VeracodeError::Authentication(e) => write!(f, "Authentication error: {e}"),
546 VeracodeError::InvalidResponse(e) => write!(f, "Invalid response: {e}"),
547 VeracodeError::HttpStatus {
548 status_code,
549 url,
550 message,
551 } => write!(f, "HTTP {status_code} error at {url}: {message}"),
552 VeracodeError::InvalidConfig(e) => write!(f, "Invalid configuration: {e}"),
553 VeracodeError::NotFound(e) => write!(f, "Item not found: {e}"),
554 VeracodeError::RetryExhausted(e) => write!(f, "Retry attempts exhausted: {e}"),
555 VeracodeError::RateLimited {
556 retry_after_seconds,
557 message,
558 } => match retry_after_seconds {
559 Some(seconds) => {
560 write!(f, "Rate limit exceeded: {message} (retry after {seconds}s)")
561 }
562 None => write!(f, "Rate limit exceeded: {message}"),
563 },
564 VeracodeError::Validation(e) => write!(f, "Validation error: {e}"),
565 }
566 }
567}
568
569impl std::error::Error for VeracodeError {}
570
571impl From<ReqwestError> for VeracodeError {
572 fn from(error: ReqwestError) -> Self {
573 VeracodeError::Http(error)
574 }
575}
576
577impl From<serde_json::Error> for VeracodeError {
578 fn from(error: serde_json::Error) -> Self {
579 VeracodeError::Serialization(error)
580 }
581}
582
583impl From<validation::ValidationError> for VeracodeError {
584 fn from(error: validation::ValidationError) -> Self {
585 VeracodeError::Validation(error)
586 }
587}
588
589#[derive(Clone)]
597pub struct VeracodeCredentials {
598 api_id: Arc<SecretString>,
600 api_key: Arc<SecretString>,
602}
603
604impl VeracodeCredentials {
605 #[must_use]
607 pub fn new(api_id: String, api_key: String) -> Self {
608 Self {
609 api_id: Arc::new(SecretString::new(api_id.into())),
610 api_key: Arc::new(SecretString::new(api_key.into())),
611 }
612 }
613
614 #[must_use]
621 pub fn api_id_ptr(&self) -> Arc<SecretString> {
622 Arc::clone(&self.api_id)
623 }
624
625 #[must_use]
632 pub fn api_key_ptr(&self) -> Arc<SecretString> {
633 Arc::clone(&self.api_key)
634 }
635
636 #[must_use]
641 pub fn expose_api_id(&self) -> &str {
642 self.api_id.expose_secret()
643 }
644
645 #[must_use]
650 pub fn expose_api_key(&self) -> &str {
651 self.api_key.expose_secret()
652 }
653}
654
655impl std::fmt::Debug for VeracodeCredentials {
656 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
657 f.debug_struct("VeracodeCredentials")
658 .field("api_id", &"[REDACTED]")
659 .field("api_key", &"[REDACTED]")
660 .finish()
661 }
662}
663
664#[derive(Clone)]
671pub struct VeracodeConfig {
672 pub credentials: VeracodeCredentials,
674 pub base_url: String,
676 pub rest_base_url: String,
678 pub xml_base_url: String,
680 pub region: VeracodeRegion,
682 pub validate_certificates: bool,
684 pub retry_config: RetryConfig,
686 pub connect_timeout: u64,
688 pub request_timeout: u64,
690 pub proxy_url: Option<String>,
692 pub proxy_username: Option<SecretString>,
694 pub proxy_password: Option<SecretString>,
696}
697
698impl std::fmt::Debug for VeracodeConfig {
700 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
701 let proxy_url_redacted = self.proxy_url.as_ref().map(|url| {
703 if url.contains('@') {
704 if let Some(at_pos) = url.rfind('@') {
706 if let Some(proto_end) = url.find("://") {
707 let protocol = url.get(..proto_end).unwrap_or("");
709 let host_part = url.get(at_pos.saturating_add(1)..).unwrap_or("");
710 format!("{}://[REDACTED]@{}", protocol, host_part)
711 } else {
712 "[REDACTED]".to_string()
713 }
714 } else {
715 "[REDACTED]".to_string()
716 }
717 } else {
718 url.clone()
719 }
720 });
721
722 f.debug_struct("VeracodeConfig")
723 .field("credentials", &self.credentials)
724 .field("base_url", &self.base_url)
725 .field("rest_base_url", &self.rest_base_url)
726 .field("xml_base_url", &self.xml_base_url)
727 .field("region", &self.region)
728 .field("validate_certificates", &self.validate_certificates)
729 .field("retry_config", &self.retry_config)
730 .field("connect_timeout", &self.connect_timeout)
731 .field("request_timeout", &self.request_timeout)
732 .field("proxy_url", &proxy_url_redacted)
733 .field(
734 "proxy_username",
735 &self.proxy_username.as_ref().map(|_| "[REDACTED]"),
736 )
737 .field(
738 "proxy_password",
739 &self.proxy_password.as_ref().map(|_| "[REDACTED]"),
740 )
741 .finish()
742 }
743}
744
745const COMMERCIAL_REST_URL: &str = "https://api.veracode.com";
747const COMMERCIAL_XML_URL: &str = "https://analysiscenter.veracode.com";
748const EUROPEAN_REST_URL: &str = "https://api.veracode.eu";
749const EUROPEAN_XML_URL: &str = "https://analysiscenter.veracode.eu";
750const FEDERAL_REST_URL: &str = "https://api.veracode.us";
751const FEDERAL_XML_URL: &str = "https://analysiscenter.veracode.us";
752
753#[derive(Debug, Clone, Copy, PartialEq)]
758pub enum VeracodeRegion {
759 Commercial,
761 European,
763 Federal,
765}
766
767impl VeracodeConfig {
768 #[must_use]
784 pub fn new(api_id: &str, api_key: &str) -> Self {
785 let credentials = VeracodeCredentials::new(api_id.to_string(), api_key.to_string());
786 Self {
787 credentials,
788 base_url: COMMERCIAL_REST_URL.to_string(),
789 rest_base_url: COMMERCIAL_REST_URL.to_string(),
790 xml_base_url: COMMERCIAL_XML_URL.to_string(),
791 region: VeracodeRegion::Commercial,
792 validate_certificates: true, retry_config: RetryConfig::default(),
794 connect_timeout: 30, request_timeout: 300, proxy_url: None,
797 proxy_username: None,
798 proxy_password: None,
799 }
800 }
801
802 #[must_use]
807 pub fn from_arc_credentials(api_id: Arc<SecretString>, api_key: Arc<SecretString>) -> Self {
808 let credentials = VeracodeCredentials { api_id, api_key };
809
810 Self {
811 credentials,
812 base_url: COMMERCIAL_REST_URL.to_string(),
813 rest_base_url: COMMERCIAL_REST_URL.to_string(),
814 xml_base_url: COMMERCIAL_XML_URL.to_string(),
815 region: VeracodeRegion::Commercial,
816 validate_certificates: true,
817 retry_config: RetryConfig::default(),
818 connect_timeout: 30,
819 request_timeout: 300,
820 proxy_url: None,
821 proxy_username: None,
822 proxy_password: None,
823 }
824 }
825
826 #[must_use]
839 pub fn with_region(mut self, region: VeracodeRegion) -> Self {
840 let (rest_url, xml_url) = match region {
841 VeracodeRegion::Commercial => (COMMERCIAL_REST_URL, COMMERCIAL_XML_URL),
842 VeracodeRegion::European => (EUROPEAN_REST_URL, EUROPEAN_XML_URL),
843 VeracodeRegion::Federal => (FEDERAL_REST_URL, FEDERAL_XML_URL),
844 };
845
846 self.region = region;
847 self.rest_base_url = rest_url.to_string();
848 self.xml_base_url = xml_url.to_string();
849 self.base_url = self.rest_base_url.clone(); self
851 }
852
853 #[must_use]
862 pub fn with_certificate_validation_disabled(mut self) -> Self {
863 self.validate_certificates = false;
864 self
865 }
866
867 #[must_use]
880 pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
881 self.retry_config = retry_config;
882 self
883 }
884
885 #[must_use]
894 pub fn with_retries_disabled(mut self) -> Self {
895 self.retry_config = RetryConfig::new().with_max_attempts(0);
896 self
897 }
898
899 #[must_use]
911 pub fn with_connect_timeout(mut self, timeout_seconds: u64) -> Self {
912 self.connect_timeout = timeout_seconds;
913 self
914 }
915
916 #[must_use]
929 pub fn with_request_timeout(mut self, timeout_seconds: u64) -> Self {
930 self.request_timeout = timeout_seconds;
931 self
932 }
933
934 #[must_use]
947 pub fn with_timeouts(
948 mut self,
949 connect_timeout_seconds: u64,
950 request_timeout_seconds: u64,
951 ) -> Self {
952 self.connect_timeout = connect_timeout_seconds;
953 self.request_timeout = request_timeout_seconds;
954 self
955 }
956
957 #[must_use]
959 pub fn api_id_arc(&self) -> Arc<SecretString> {
960 self.credentials.api_id_ptr()
961 }
962
963 #[must_use]
965 pub fn api_key_arc(&self) -> Arc<SecretString> {
966 self.credentials.api_key_ptr()
967 }
968
969 #[must_use]
997 pub fn with_proxy(mut self, proxy_url: impl Into<String>) -> Self {
998 self.proxy_url = Some(proxy_url.into());
999 self
1000 }
1001
1002 #[must_use]
1027 pub fn with_proxy_auth(
1028 mut self,
1029 username: impl Into<String>,
1030 password: impl Into<String>,
1031 ) -> Self {
1032 self.proxy_username = Some(SecretString::new(username.into().into()));
1033 self.proxy_password = Some(SecretString::new(password.into().into()));
1034 self
1035 }
1036}
1037
1038#[cfg(test)]
1039#[allow(clippy::expect_used)]
1040mod tests {
1041 use super::*;
1042
1043 #[test]
1044 fn test_config_creation() {
1045 let config = VeracodeConfig::new("test_api_id", "test_api_key");
1046
1047 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
1048 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
1049 assert_eq!(config.base_url, "https://api.veracode.com");
1050 assert_eq!(config.rest_base_url, "https://api.veracode.com");
1051 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
1052 assert_eq!(config.region, VeracodeRegion::Commercial);
1053 assert!(config.validate_certificates); assert_eq!(config.retry_config.max_attempts, 5); }
1056
1057 #[test]
1058 fn test_european_region_config() {
1059 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1060 .with_region(VeracodeRegion::European);
1061
1062 assert_eq!(config.base_url, "https://api.veracode.eu");
1063 assert_eq!(config.rest_base_url, "https://api.veracode.eu");
1064 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
1065 assert_eq!(config.region, VeracodeRegion::European);
1066 }
1067
1068 #[test]
1069 fn test_federal_region_config() {
1070 let config =
1071 VeracodeConfig::new("test_api_id", "test_api_key").with_region(VeracodeRegion::Federal);
1072
1073 assert_eq!(config.base_url, "https://api.veracode.us");
1074 assert_eq!(config.rest_base_url, "https://api.veracode.us");
1075 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
1076 assert_eq!(config.region, VeracodeRegion::Federal);
1077 }
1078
1079 #[test]
1080 fn test_certificate_validation_disabled() {
1081 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1082 .with_certificate_validation_disabled();
1083
1084 assert!(!config.validate_certificates);
1085 }
1086
1087 #[test]
1088 fn test_veracode_credentials_debug_redaction() {
1089 let credentials = VeracodeCredentials::new(
1090 "test_api_id_123".to_string(),
1091 "test_api_key_456".to_string(),
1092 );
1093 let debug_output = format!("{credentials:?}");
1094
1095 assert!(debug_output.contains("VeracodeCredentials"));
1097 assert!(debug_output.contains("[REDACTED]"));
1098
1099 assert!(!debug_output.contains("test_api_id_123"));
1101 assert!(!debug_output.contains("test_api_key_456"));
1102 }
1103
1104 #[test]
1105 fn test_veracode_config_debug_redaction() {
1106 let config = VeracodeConfig::new("test_api_id_123", "test_api_key_456");
1107 let debug_output = format!("{config:?}");
1108
1109 assert!(debug_output.contains("VeracodeConfig"));
1111 assert!(debug_output.contains("credentials"));
1112 assert!(debug_output.contains("[REDACTED]"));
1113
1114 assert!(!debug_output.contains("test_api_id_123"));
1116 assert!(!debug_output.contains("test_api_key_456"));
1117 }
1118
1119 #[test]
1120 fn test_veracode_credentials_access_methods() {
1121 let credentials = VeracodeCredentials::new(
1122 "test_api_id_123".to_string(),
1123 "test_api_key_456".to_string(),
1124 );
1125
1126 assert_eq!(credentials.expose_api_id(), "test_api_id_123");
1128 assert_eq!(credentials.expose_api_key(), "test_api_key_456");
1129 }
1130
1131 #[test]
1132 fn test_veracode_credentials_arc_pointers() {
1133 let credentials = VeracodeCredentials::new(
1134 "test_api_id_123".to_string(),
1135 "test_api_key_456".to_string(),
1136 );
1137
1138 let api_id_arc = credentials.api_id_ptr();
1140 let api_key_arc = credentials.api_key_ptr();
1141
1142 assert_eq!(api_id_arc.expose_secret(), "test_api_id_123");
1144 assert_eq!(api_key_arc.expose_secret(), "test_api_key_456");
1145 }
1146
1147 #[test]
1148 fn test_veracode_credentials_clone() {
1149 let credentials = VeracodeCredentials::new(
1150 "test_api_id_123".to_string(),
1151 "test_api_key_456".to_string(),
1152 );
1153 let cloned_credentials = credentials.clone();
1154
1155 assert_eq!(
1157 credentials.expose_api_id(),
1158 cloned_credentials.expose_api_id()
1159 );
1160 assert_eq!(
1161 credentials.expose_api_key(),
1162 cloned_credentials.expose_api_key()
1163 );
1164 }
1165
1166 #[test]
1167 fn test_config_with_arc_credentials() {
1168 use secrecy::SecretString;
1169 use std::sync::Arc;
1170
1171 let api_id_arc = Arc::new(SecretString::new("test_api_id".into()));
1172 let api_key_arc = Arc::new(SecretString::new("test_api_key".into()));
1173
1174 let config = VeracodeConfig::from_arc_credentials(api_id_arc, api_key_arc);
1175
1176 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
1177 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
1178 assert_eq!(config.region, VeracodeRegion::Commercial);
1179 }
1180
1181 #[test]
1182 fn test_error_display() {
1183 let error = VeracodeError::Authentication("Invalid API key".to_string());
1184 assert_eq!(format!("{error}"), "Authentication error: Invalid API key");
1185 }
1186
1187 #[test]
1188 fn test_error_from_reqwest() {
1189 fn _test_conversion(error: reqwest::Error) -> VeracodeError {
1194 VeracodeError::from(error)
1195 }
1196
1197 }
1200
1201 #[test]
1202 fn test_retry_config_default() {
1203 let config = RetryConfig::default();
1204 assert_eq!(config.max_attempts, 5);
1205 assert_eq!(config.initial_delay_ms, 1000);
1206 assert_eq!(config.max_delay_ms, 30000);
1207 assert_eq!(config.backoff_multiplier, 2.0);
1208 assert_eq!(config.max_total_delay_ms, 300000);
1209 assert!(config.jitter_enabled); }
1211
1212 #[test]
1213 fn test_retry_config_builder() {
1214 let config = RetryConfig::new()
1215 .with_max_attempts(5)
1216 .with_initial_delay(500)
1217 .with_max_delay(60000)
1218 .with_backoff_multiplier(1.5)
1219 .with_max_total_delay(600000);
1220
1221 assert_eq!(config.max_attempts, 5);
1222 assert_eq!(config.initial_delay_ms, 500);
1223 assert_eq!(config.max_delay_ms, 60000);
1224 assert_eq!(config.backoff_multiplier, 1.5);
1225 assert_eq!(config.max_total_delay_ms, 600000);
1226 }
1227
1228 #[test]
1229 fn test_retry_config_calculate_delay() {
1230 let config = RetryConfig::new()
1231 .with_initial_delay(1000)
1232 .with_backoff_multiplier(2.0)
1233 .with_max_delay(10000)
1234 .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); }
1244
1245 #[test]
1246 fn test_retry_config_is_retryable_error() {
1247 let config = RetryConfig::new();
1248
1249 assert!(
1251 config.is_retryable_error(&VeracodeError::InvalidResponse("temp error".to_string()))
1252 );
1253
1254 assert!(!config.is_retryable_error(&VeracodeError::Authentication("bad auth".to_string())));
1256 assert!(!config.is_retryable_error(&VeracodeError::Serialization(
1257 serde_json::from_str::<i32>("invalid").expect_err("should fail to deserialize")
1258 )));
1259 assert!(
1260 !config.is_retryable_error(&VeracodeError::InvalidConfig("bad config".to_string()))
1261 );
1262 assert!(!config.is_retryable_error(&VeracodeError::NotFound("not found".to_string())));
1263 assert!(
1264 !config.is_retryable_error(&VeracodeError::RetryExhausted("exhausted".to_string()))
1265 );
1266 }
1267
1268 #[test]
1269 fn test_veracode_config_with_retry_config() {
1270 let retry_config = RetryConfig::new().with_max_attempts(5);
1271 let config =
1272 VeracodeConfig::new("test_api_id", "test_api_key").with_retry_config(retry_config);
1273
1274 assert_eq!(config.retry_config.max_attempts, 5);
1275 }
1276
1277 #[test]
1278 fn test_veracode_config_with_retries_disabled() {
1279 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_retries_disabled();
1280
1281 assert_eq!(config.retry_config.max_attempts, 0);
1282 }
1283
1284 #[test]
1285 fn test_timeout_configuration() {
1286 let config = VeracodeConfig::new("test_api_id", "test_api_key");
1287
1288 assert_eq!(config.connect_timeout, 30);
1290 assert_eq!(config.request_timeout, 300);
1291 }
1292
1293 #[test]
1294 fn test_with_connect_timeout() {
1295 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_connect_timeout(60);
1296
1297 assert_eq!(config.connect_timeout, 60);
1298 assert_eq!(config.request_timeout, 300); }
1300
1301 #[test]
1302 fn test_with_request_timeout() {
1303 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_request_timeout(600);
1304
1305 assert_eq!(config.connect_timeout, 30); assert_eq!(config.request_timeout, 600);
1307 }
1308
1309 #[test]
1310 fn test_with_timeouts() {
1311 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_timeouts(120, 1800);
1312
1313 assert_eq!(config.connect_timeout, 120);
1314 assert_eq!(config.request_timeout, 1800);
1315 }
1316
1317 #[test]
1318 fn test_timeout_configuration_chaining() {
1319 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1320 .with_region(VeracodeRegion::European)
1321 .with_connect_timeout(45)
1322 .with_request_timeout(900)
1323 .with_retries_disabled();
1324
1325 assert_eq!(config.region, VeracodeRegion::European);
1326 assert_eq!(config.connect_timeout, 45);
1327 assert_eq!(config.request_timeout, 900);
1328 assert_eq!(config.retry_config.max_attempts, 0);
1329 }
1330
1331 #[test]
1332 fn test_retry_exhausted_error_display() {
1333 let error = VeracodeError::RetryExhausted("Failed after 3 attempts".to_string());
1334 assert_eq!(
1335 format!("{error}"),
1336 "Retry attempts exhausted: Failed after 3 attempts"
1337 );
1338 }
1339
1340 #[test]
1341 fn test_rate_limited_error_display_with_retry_after() {
1342 let error = VeracodeError::RateLimited {
1343 retry_after_seconds: Some(60),
1344 message: "Too Many Requests".to_string(),
1345 };
1346 assert_eq!(
1347 format!("{error}"),
1348 "Rate limit exceeded: Too Many Requests (retry after 60s)"
1349 );
1350 }
1351
1352 #[test]
1353 fn test_rate_limited_error_display_without_retry_after() {
1354 let error = VeracodeError::RateLimited {
1355 retry_after_seconds: None,
1356 message: "Too Many Requests".to_string(),
1357 };
1358 assert_eq!(format!("{error}"), "Rate limit exceeded: Too Many Requests");
1359 }
1360
1361 #[test]
1362 fn test_rate_limited_error_is_retryable() {
1363 let config = RetryConfig::new();
1364 let error = VeracodeError::RateLimited {
1365 retry_after_seconds: Some(60),
1366 message: "Rate limit exceeded".to_string(),
1367 };
1368 assert!(config.is_retryable_error(&error));
1369 }
1370
1371 #[test]
1372 fn test_calculate_rate_limit_delay_with_retry_after() {
1373 let config = RetryConfig::new();
1374 let delay = config.calculate_rate_limit_delay(Some(30));
1375 assert_eq!(delay.as_secs(), 30);
1376 }
1377
1378 #[test]
1379 #[cfg_attr(miri, ignore)] fn test_calculate_rate_limit_delay_without_retry_after() {
1381 let config = RetryConfig::new();
1382 let delay = config.calculate_rate_limit_delay(None);
1383
1384 assert!(delay.as_secs() >= 5);
1387 assert!(delay.as_secs() <= 65);
1388 }
1389
1390 #[test]
1391 fn test_rate_limit_config_defaults() {
1392 let config = RetryConfig::default();
1393 assert_eq!(config.rate_limit_buffer_seconds, 5);
1394 assert_eq!(config.rate_limit_max_attempts, 1);
1395 }
1396
1397 #[test]
1398 fn test_rate_limit_config_builders() {
1399 let config = RetryConfig::new()
1400 .with_rate_limit_buffer(10)
1401 .with_rate_limit_max_attempts(2);
1402
1403 assert_eq!(config.rate_limit_buffer_seconds, 10);
1404 assert_eq!(config.rate_limit_max_attempts, 2);
1405 }
1406
1407 #[test]
1408 #[cfg_attr(miri, ignore)] fn test_rate_limit_delay_uses_buffer() {
1410 let config = RetryConfig::new().with_rate_limit_buffer(15);
1411 let delay = config.calculate_rate_limit_delay(None);
1412
1413 assert!(delay.as_secs() >= 15);
1415 assert!(delay.as_secs() <= 75); }
1417
1418 #[test]
1419 fn test_jitter_disabled() {
1420 let config = RetryConfig::new().with_jitter_disabled();
1421 assert!(!config.jitter_enabled);
1422
1423 let delay1 = config.calculate_delay(2);
1425 let delay2 = config.calculate_delay(2);
1426 assert_eq!(delay1, delay2);
1427 }
1428
1429 #[test]
1430 fn test_jitter_enabled() {
1431 let config = RetryConfig::new(); assert!(config.jitter_enabled);
1433
1434 let base_delay = config.initial_delay_ms;
1436 let delay = config.calculate_delay(1);
1437
1438 #[allow(
1440 clippy::cast_possible_truncation,
1441 clippy::cast_sign_loss,
1442 clippy::cast_precision_loss
1443 )]
1444 let min_expected = (base_delay as f64 * 0.75) as u64;
1445 #[allow(
1446 clippy::cast_possible_truncation,
1447 clippy::cast_sign_loss,
1448 clippy::cast_precision_loss
1449 )]
1450 let max_expected = (base_delay as f64 * 1.25) as u64;
1451
1452 assert!(delay.as_millis() >= min_expected as u128);
1453 assert!(delay.as_millis() <= max_expected as u128);
1454 }
1455}
1456
1457#[cfg(test)]
1464mod security_tests {
1465 use super::*;
1466 use proptest::prelude::*;
1467
1468 proptest! {
1477 #![proptest_config(ProptestConfig {
1478 cases: if cfg!(miri) { 5 } else { 1000 },
1479 failure_persistence: None,
1480 .. ProptestConfig::default()
1481 })]
1482
1483 #[test]
1485 fn test_calculate_delay_no_overflow(
1486 attempt in 0u32..1000u32,
1487 initial_delay in 1u64..100_000u64,
1488 multiplier in 1.0f64..10.0f64,
1489 max_delay in 1u64..1_000_000u64,
1490 ) {
1491 let config = RetryConfig::new()
1492 .with_initial_delay(initial_delay)
1493 .with_backoff_multiplier(multiplier)
1494 .with_max_delay(max_delay)
1495 .with_jitter_disabled(); let delay = config.calculate_delay(attempt);
1499
1500 assert!(delay.as_millis() <= max_delay as u128);
1502
1503 assert!(delay <= Duration::from_millis(max_delay));
1506 }
1507
1508 #[test]
1510 fn test_calculate_delay_extreme_multipliers(
1511 attempt in 0u32..100u32,
1512 multiplier in 1.0f64..1000.0f64,
1513 ) {
1514 let config = RetryConfig::new()
1515 .with_initial_delay(1000)
1516 .with_backoff_multiplier(multiplier)
1517 .with_max_delay(60_000)
1518 .with_jitter_disabled();
1519
1520 let delay = config.calculate_delay(attempt);
1522
1523 assert!(delay.as_millis() <= 60_000);
1525 }
1526 }
1527
1528 proptest! {
1537 #![proptest_config(ProptestConfig {
1538 cases: if cfg!(miri) { 5 } else { 1000 },
1539 failure_persistence: None,
1540 .. ProptestConfig::default()
1541 })]
1542
1543 #[test]
1545 fn test_rate_limit_delay_with_retry_after(
1546 retry_after_seconds in 0u64..100_000u64,
1547 ) {
1548 let config = RetryConfig::new();
1549
1550 let delay = config.calculate_rate_limit_delay(Some(retry_after_seconds));
1552
1553 assert_eq!(delay.as_secs(), retry_after_seconds);
1555 }
1556
1557 #[test]
1559 fn test_rate_limit_delay_buffer_no_overflow(
1560 buffer_seconds in 0u64..10_000u64,
1561 ) {
1562 let config = RetryConfig::new()
1563 .with_rate_limit_buffer(buffer_seconds);
1564
1565 let delay = config.calculate_rate_limit_delay(None);
1567
1568 assert!(delay.as_secs() >= buffer_seconds);
1570
1571 assert!(delay.as_secs() <= 60_u64.saturating_add(buffer_seconds));
1573 }
1574 }
1575
1576 proptest! {
1585 #![proptest_config(ProptestConfig {
1586 cases: if cfg!(miri) { 5 } else { 500 },
1587 failure_persistence: None,
1588 .. ProptestConfig::default()
1589 })]
1590
1591 #[test]
1593 fn test_credentials_debug_never_exposes_secrets(
1594 api_id in "[a-zA-Z0-9]{10,256}",
1595 api_key in "[a-zA-Z0-9]{10,256}",
1596 ) {
1597 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1598 let debug_output = format!("{:?}", credentials);
1599
1600 assert!(debug_output.contains("VeracodeCredentials"));
1602
1603 assert!(debug_output.contains("[REDACTED]"));
1605
1606 assert!(!debug_output.contains(&api_id));
1608 assert!(!debug_output.contains(&api_key));
1609 }
1610
1611 #[test]
1613 fn test_credentials_arc_cloning_preserves_values(
1614 api_id in "[a-zA-Z0-9]{10,100}",
1615 api_key in "[a-zA-Z0-9]{10,100}",
1616 ) {
1617 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1618
1619 let api_id_arc1 = credentials.api_id_ptr();
1621 let api_id_arc2 = credentials.api_id_ptr();
1622 let api_key_arc1 = credentials.api_key_ptr();
1623 let api_key_arc2 = credentials.api_key_ptr();
1624
1625 assert_eq!(api_id_arc1.expose_secret(), &api_id);
1627 assert_eq!(api_id_arc2.expose_secret(), &api_id);
1628 assert_eq!(api_key_arc1.expose_secret(), &api_key);
1629 assert_eq!(api_key_arc2.expose_secret(), &api_key);
1630 }
1631
1632 #[test]
1634 fn test_credentials_expose_methods_correctness(
1635 api_id in "[a-zA-Z0-9]{10,256}",
1636 api_key in "[a-zA-Z0-9]{10,256}",
1637 ) {
1638 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1639
1640 assert_eq!(credentials.expose_api_id(), api_id);
1642 assert_eq!(credentials.expose_api_key(), api_key);
1643 }
1644 }
1645
1646 proptest! {
1653 #![proptest_config(ProptestConfig {
1654 cases: if cfg!(miri) { 5 } else { 500 },
1655 failure_persistence: None,
1656 .. ProptestConfig::default()
1657 })]
1658
1659 #[test]
1661 fn test_config_debug_redacts_proxy_credentials(
1662 protocol in "(http|https)",
1663 username in "[a-zA-Z]{15,30}",
1665 password in "[a-zA-Z]{15,30}",
1666 host in "proxy\\d{1,3}\\.example\\.com",
1667 ) {
1668 let port = 8080u16;
1669 let proxy_url = format!("{}://{}:{}@{}:{}", protocol, username, password, host, port);
1670
1671 let config = VeracodeConfig::new("api_id", "api_key")
1672 .with_proxy(&proxy_url);
1673
1674 let debug_output = format!("{:?}", config);
1675
1676 assert!(debug_output.contains("[REDACTED]"));
1678
1679 assert!(!debug_output.contains(&username));
1683 assert!(!debug_output.contains(&password));
1684
1685 assert!(debug_output.contains(&host));
1687 }
1688
1689 #[test]
1691 fn test_config_debug_handles_utf8_safely(
1692 protocol in "(http|https)",
1694 creds in "[a-zA-Z0-9]{1,30}",
1695 host in "[a-z]{3,15}\\.[a-z]{2,5}",
1696 ) {
1697 let proxy_url = format!("{}://{}@{}", protocol, creds, host);
1699
1700 let config = VeracodeConfig::new("test", "test")
1701 .with_proxy(&proxy_url);
1702
1703 let debug_output = format!("{:?}", config);
1705
1706 assert!(debug_output.contains("VeracodeConfig"));
1708 }
1709
1710 #[test]
1712 fn test_config_debug_redacts_proxy_auth(
1713 proxy_username in "[a-zA-Z0-9]{10,50}",
1714 proxy_password in "[a-zA-Z0-9]{10,50}",
1715 ) {
1716 let config = VeracodeConfig::new("api_id", "api_key")
1717 .with_proxy("http://proxy.example.com:8080")
1718 .with_proxy_auth(proxy_username.clone(), proxy_password.clone());
1719
1720 let debug_output = format!("{:?}", config);
1721
1722 assert!(debug_output.contains("proxy_username"));
1724 assert!(debug_output.contains("proxy_password"));
1725 assert!(debug_output.contains("[REDACTED]"));
1726
1727 assert!(!debug_output.contains(&proxy_username));
1729 assert!(!debug_output.contains(&proxy_password));
1730 }
1731 }
1732
1733 proptest! {
1741 #![proptest_config(ProptestConfig {
1742 cases: if cfg!(miri) { 5 } else { 100 },
1743 failure_persistence: None,
1744 .. ProptestConfig::default()
1745 })]
1746
1747 #[test]
1749 fn test_config_region_urls_are_valid(
1750 region in prop::sample::select(vec![
1751 VeracodeRegion::Commercial,
1752 VeracodeRegion::European,
1753 VeracodeRegion::Federal,
1754 ])
1755 ) {
1756 let config = VeracodeConfig::new("test", "test")
1757 .with_region(region);
1758
1759 match region {
1761 VeracodeRegion::Commercial => {
1762 assert_eq!(config.rest_base_url, "https://api.veracode.com");
1763 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
1764 assert_eq!(config.base_url, config.rest_base_url);
1765 }
1766 VeracodeRegion::European => {
1767 assert_eq!(config.rest_base_url, "https://api.veracode.eu");
1768 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
1769 assert_eq!(config.base_url, config.rest_base_url);
1770 }
1771 VeracodeRegion::Federal => {
1772 assert_eq!(config.rest_base_url, "https://api.veracode.us");
1773 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
1774 assert_eq!(config.base_url, config.rest_base_url);
1775 }
1776 }
1777
1778 assert!(config.rest_base_url.starts_with("https://"));
1780 assert!(config.xml_base_url.starts_with("https://"));
1781 assert!(config.base_url.starts_with("https://"));
1782 }
1783 }
1784
1785 proptest! {
1794 #![proptest_config(ProptestConfig {
1795 cases: if cfg!(miri) { 5 } else { 500 },
1796 failure_persistence: None,
1797 .. ProptestConfig::default()
1798 })]
1799
1800 #[test]
1802 fn test_config_timeout_no_overflow(
1803 connect_timeout in 1u64..100_000u64,
1804 request_timeout in 1u64..100_000u64,
1805 ) {
1806 let config = VeracodeConfig::new("test", "test")
1807 .with_timeouts(connect_timeout, request_timeout);
1808
1809 assert_eq!(config.connect_timeout, connect_timeout);
1811 assert_eq!(config.request_timeout, request_timeout);
1812
1813 assert!(config.connect_timeout > 0);
1815 assert!(config.request_timeout > 0);
1816 }
1817
1818 #[test]
1820 fn test_config_individual_timeout_setters(
1821 connect_timeout in 1u64..50_000u64,
1822 request_timeout in 1u64..50_000u64,
1823 ) {
1824 let config1 = VeracodeConfig::new("test", "test")
1825 .with_connect_timeout(connect_timeout);
1826 assert_eq!(config1.connect_timeout, connect_timeout);
1827 assert_eq!(config1.request_timeout, 300); let config2 = VeracodeConfig::new("test", "test")
1830 .with_request_timeout(request_timeout);
1831 assert_eq!(config2.connect_timeout, 30); assert_eq!(config2.request_timeout, request_timeout);
1833 }
1834 }
1835
1836 proptest! {
1845 #![proptest_config(ProptestConfig {
1846 cases: if cfg!(miri) { 5 } else { 500 },
1847 failure_persistence: None,
1848 .. ProptestConfig::default()
1849 })]
1850
1851 #[test]
1853 fn test_error_display_safe_messages(
1854 message in "[a-zA-Z0-9 ]{1,100}",
1855 ) {
1856 let errors = vec![
1857 VeracodeError::Authentication(message.clone()),
1858 VeracodeError::InvalidResponse(message.clone()),
1859 VeracodeError::InvalidConfig(message.clone()),
1860 VeracodeError::NotFound(message.clone()),
1861 VeracodeError::RetryExhausted(message.clone()),
1862 ];
1863
1864 for error in errors {
1865 let display = format!("{}", error);
1866
1867 assert!(!display.is_empty());
1869
1870 assert!(display.contains(&message));
1872 }
1873 }
1874
1875 #[test]
1877 fn test_rate_limited_error_display_safe(
1878 retry_after in prop::option::of(0u64..10_000u64),
1879 message in "[a-zA-Z0-9 ]{1,100}",
1880 ) {
1881 let error = VeracodeError::RateLimited {
1882 retry_after_seconds: retry_after,
1883 message: message.clone(),
1884 };
1885
1886 let display = format!("{}", error);
1887
1888 assert!(display.contains(&message));
1890 assert!(display.contains("Rate limit exceeded"));
1891
1892 if let Some(seconds) = retry_after {
1894 assert!(display.contains(&seconds.to_string()));
1895 }
1896 }
1897 }
1898}
1899
1900#[cfg(kani)]
1907mod kani_proofs {
1908 use super::*;
1909
1910 #[kani::proof]
1917 #[kani::unwind(10)] fn verify_calculate_delay_arithmetic_safety() {
1919 let initial_delay: u64 = kani::any();
1921 let max_delay: u64 = kani::any();
1922 let multiplier: f64 = kani::any();
1923 let attempt: u32 = kani::any();
1924
1925 kani::assume(initial_delay > 0);
1927 kani::assume(initial_delay <= 100_000);
1928 kani::assume(max_delay > 0);
1929 kani::assume(max_delay >= initial_delay);
1930 kani::assume(max_delay <= 1_000_000);
1931 kani::assume(multiplier >= 1.0);
1932 kani::assume(multiplier <= 10.0);
1933 kani::assume(multiplier.is_finite());
1934 kani::assume(attempt < 20); let config = RetryConfig::new()
1937 .with_initial_delay(initial_delay)
1938 .with_backoff_multiplier(multiplier)
1939 .with_max_delay(max_delay)
1940 .with_jitter_disabled();
1941
1942 let delay = config.calculate_delay(attempt);
1944
1945 assert!(delay.as_millis() <= max_delay as u128);
1947
1948 assert!(delay.as_secs() < u64::MAX);
1950
1951 if attempt == 0 {
1953 assert_eq!(delay.as_millis(), 0);
1954 }
1955 }
1956
1957 #[kani::proof]
1966 fn verify_rate_limit_delay_safety() {
1967 let buffer_seconds: u64 = kani::any();
1968 let retry_after_seconds: Option<u64> = kani::any();
1969
1970 kani::assume(buffer_seconds <= 10_000);
1972 if let Some(secs) = retry_after_seconds {
1973 kani::assume(secs <= 100_000);
1974 }
1975
1976 if let Some(expected_secs) = retry_after_seconds {
1978 let delay = Duration::from_secs(expected_secs);
1979 assert_eq!(delay.as_secs(), expected_secs);
1981 }
1982
1983 let current_second: u64 = kani::any();
1986 kani::assume(current_second < 60);
1987
1988 let seconds_until_next_minute = 60_u64.saturating_sub(current_second);
1990 let total_delay = seconds_until_next_minute.saturating_add(buffer_seconds);
1991
1992 assert!(total_delay >= buffer_seconds);
1994
1995 assert!(total_delay <= 60 + buffer_seconds);
1997
1998 }
2001
2002 #[kani::proof]
2010 #[kani::unwind(50)]
2011 fn verify_credentials_arc_memory_safety() {
2012 let api_id = "test_api_id".to_string();
2014 let api_key = "test_key123".to_string();
2015
2016 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
2017
2018 let api_id_arc1 = credentials.api_id_ptr();
2020 let api_id_arc2 = credentials.api_id_ptr();
2021
2022 assert_eq!(
2024 Arc::as_ptr(&api_id_arc1) as *const (),
2025 Arc::as_ptr(&api_id_arc2) as *const ()
2026 );
2027
2028 assert_eq!(api_id_arc1.expose_secret(), api_id_arc2.expose_secret());
2030
2031 assert_eq!(credentials.expose_api_id(), &api_id);
2033 assert_eq!(credentials.expose_api_key(), &api_key);
2034
2035 let cloned = credentials.clone();
2037 assert_eq!(cloned.expose_api_id(), credentials.expose_api_id());
2038 assert_eq!(cloned.expose_api_key(), credentials.expose_api_key());
2039
2040 let cloned2 = cloned.clone();
2042 let _ = cloned2.expose_api_id();
2043 let _ = cloned2.expose_api_key();
2044
2045 }
2047
2048 #[kani::proof]
2056 fn verify_proxy_url_redaction_safety() {
2057 let has_at_sign: bool = kani::any();
2059
2060 let url = if has_at_sign {
2062 "u:p@h".to_string()
2063 } else {
2064 "http://h".to_string()
2065 };
2066
2067 let config = VeracodeConfig::new("t", "k").with_proxy(&url);
2069
2070 assert!(config.proxy_url.is_some());
2072
2073 let _ = config.proxy_url;
2076 }
2077
2078 #[kani::proof]
2086 #[kani::unwind(10)] fn verify_region_url_construction() {
2088 let config = VeracodeConfig::new("a", "b").with_region(VeracodeRegion::Commercial);
2094
2095 assert!(!config.rest_base_url.is_empty());
2097 assert!(!config.xml_base_url.is_empty());
2098
2099 assert!(config.rest_base_url.len() < 100);
2101 assert!(config.xml_base_url.len() < 100);
2102
2103 assert!(matches!(config.region, VeracodeRegion::Commercial));
2105 }
2106
2107 #[kani::proof]
2113 #[kani::unwind(10)]
2114 fn verify_european_region_url_construction() {
2115 let config = VeracodeConfig::new("a", "b").with_region(VeracodeRegion::European);
2116
2117 assert!(!config.rest_base_url.is_empty());
2119 assert!(!config.xml_base_url.is_empty());
2120
2121 assert!(config.rest_base_url.len() < 100);
2123 assert!(config.xml_base_url.len() < 100);
2124
2125 assert!(matches!(config.region, VeracodeRegion::European));
2127 }
2128
2129 #[kani::proof]
2135 #[kani::unwind(10)]
2136 fn verify_federal_region_url_construction() {
2137 let config = VeracodeConfig::new("a", "b").with_region(VeracodeRegion::Federal);
2138
2139 assert!(!config.rest_base_url.is_empty());
2141 assert!(!config.xml_base_url.is_empty());
2142
2143 assert!(config.rest_base_url.len() < 100);
2145 assert!(config.xml_base_url.len() < 100);
2146
2147 assert!(matches!(config.region, VeracodeRegion::Federal));
2149 }
2150}