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::Rng;
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::NotFound(_) => false,
384 VeracodeError::RetryExhausted(_) => false,
386 VeracodeError::RateLimited { .. } => true,
388 }
389 }
390}
391
392#[derive(Debug)]
397#[must_use = "Need to handle all error enum types."]
398pub enum VeracodeError {
399 Http(ReqwestError),
401 Serialization(serde_json::Error),
403 Authentication(String),
405 InvalidResponse(String),
407 InvalidConfig(String),
409 NotFound(String),
411 RetryExhausted(String),
413 RateLimited {
415 retry_after_seconds: Option<u64>,
417 message: String,
419 },
420 Validation(validation::ValidationError),
422}
423
424impl VeracodeClient {
425 fn new_xml_client(config: VeracodeConfig) -> Result<Self, VeracodeError> {
431 let mut xml_config = config;
432 xml_config.base_url = xml_config.xml_base_url.clone();
433 Self::new(xml_config)
434 }
435
436 #[must_use]
439 pub fn applications_api(&self) -> &Self {
440 self
441 }
442
443 #[must_use]
446 pub fn sandbox_api(&self) -> SandboxApi<'_> {
447 SandboxApi::new(self)
448 }
449
450 #[must_use]
453 pub fn identity_api(&self) -> IdentityApi<'_> {
454 IdentityApi::new(self)
455 }
456
457 #[must_use]
460 pub fn pipeline_api(&self) -> PipelineApi {
461 PipelineApi::new(self.clone())
462 }
463
464 #[must_use]
467 pub fn policy_api(&self) -> PolicyApi<'_> {
468 PolicyApi::new(self)
469 }
470
471 #[must_use]
474 pub fn findings_api(&self) -> FindingsApi {
475 FindingsApi::new(self.clone())
476 }
477
478 #[must_use]
481 pub fn reporting_api(&self) -> reporting::ReportingApi {
482 reporting::ReportingApi::new(self.clone())
483 }
484
485 pub fn scan_api(&self) -> Result<ScanApi, VeracodeError> {
492 let xml_client = Self::new_xml_client(self.config().clone())?;
494 Ok(ScanApi::new(xml_client))
495 }
496
497 pub fn build_api(&self) -> Result<build::BuildApi, VeracodeError> {
504 let xml_client = Self::new_xml_client(self.config().clone())?;
506 Ok(build::BuildApi::new(xml_client))
507 }
508
509 #[must_use]
512 pub fn workflow(&self) -> workflow::VeracodeWorkflow {
513 workflow::VeracodeWorkflow::new(self.clone())
514 }
515}
516
517impl fmt::Display for VeracodeError {
518 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
519 match self {
520 VeracodeError::Http(e) => write!(f, "HTTP error: {e}"),
521 VeracodeError::Serialization(e) => write!(f, "Serialization error: {e}"),
522 VeracodeError::Authentication(e) => write!(f, "Authentication error: {e}"),
523 VeracodeError::InvalidResponse(e) => write!(f, "Invalid response: {e}"),
524 VeracodeError::InvalidConfig(e) => write!(f, "Invalid configuration: {e}"),
525 VeracodeError::NotFound(e) => write!(f, "Item not found: {e}"),
526 VeracodeError::RetryExhausted(e) => write!(f, "Retry attempts exhausted: {e}"),
527 VeracodeError::RateLimited {
528 retry_after_seconds,
529 message,
530 } => match retry_after_seconds {
531 Some(seconds) => {
532 write!(f, "Rate limit exceeded: {message} (retry after {seconds}s)")
533 }
534 None => write!(f, "Rate limit exceeded: {message}"),
535 },
536 VeracodeError::Validation(e) => write!(f, "Validation error: {e}"),
537 }
538 }
539}
540
541impl std::error::Error for VeracodeError {}
542
543impl From<ReqwestError> for VeracodeError {
544 fn from(error: ReqwestError) -> Self {
545 VeracodeError::Http(error)
546 }
547}
548
549impl From<serde_json::Error> for VeracodeError {
550 fn from(error: serde_json::Error) -> Self {
551 VeracodeError::Serialization(error)
552 }
553}
554
555impl From<validation::ValidationError> for VeracodeError {
556 fn from(error: validation::ValidationError) -> Self {
557 VeracodeError::Validation(error)
558 }
559}
560
561#[derive(Clone)]
569pub struct VeracodeCredentials {
570 api_id: Arc<SecretString>,
572 api_key: Arc<SecretString>,
574}
575
576impl VeracodeCredentials {
577 #[must_use]
579 pub fn new(api_id: String, api_key: String) -> Self {
580 Self {
581 api_id: Arc::new(SecretString::new(api_id.into())),
582 api_key: Arc::new(SecretString::new(api_key.into())),
583 }
584 }
585
586 #[must_use]
593 pub fn api_id_ptr(&self) -> Arc<SecretString> {
594 Arc::clone(&self.api_id)
595 }
596
597 #[must_use]
604 pub fn api_key_ptr(&self) -> Arc<SecretString> {
605 Arc::clone(&self.api_key)
606 }
607
608 #[must_use]
613 pub fn expose_api_id(&self) -> &str {
614 self.api_id.expose_secret()
615 }
616
617 #[must_use]
622 pub fn expose_api_key(&self) -> &str {
623 self.api_key.expose_secret()
624 }
625}
626
627impl std::fmt::Debug for VeracodeCredentials {
628 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
629 f.debug_struct("VeracodeCredentials")
630 .field("api_id", &"[REDACTED]")
631 .field("api_key", &"[REDACTED]")
632 .finish()
633 }
634}
635
636#[derive(Clone)]
643pub struct VeracodeConfig {
644 pub credentials: VeracodeCredentials,
646 pub base_url: String,
648 pub rest_base_url: String,
650 pub xml_base_url: String,
652 pub region: VeracodeRegion,
654 pub validate_certificates: bool,
656 pub retry_config: RetryConfig,
658 pub connect_timeout: u64,
660 pub request_timeout: u64,
662 pub proxy_url: Option<String>,
664 pub proxy_username: Option<SecretString>,
666 pub proxy_password: Option<SecretString>,
668}
669
670impl std::fmt::Debug for VeracodeConfig {
672 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
673 let proxy_url_redacted = self.proxy_url.as_ref().map(|url| {
675 if url.contains('@') {
676 if let Some(at_pos) = url.rfind('@') {
678 if let Some(proto_end) = url.find("://") {
679 let protocol = url.get(..proto_end).unwrap_or("");
681 let host_part = url.get(at_pos.saturating_add(1)..).unwrap_or("");
682 format!("{}://[REDACTED]@{}", protocol, host_part)
683 } else {
684 "[REDACTED]".to_string()
685 }
686 } else {
687 "[REDACTED]".to_string()
688 }
689 } else {
690 url.clone()
691 }
692 });
693
694 f.debug_struct("VeracodeConfig")
695 .field("credentials", &self.credentials)
696 .field("base_url", &self.base_url)
697 .field("rest_base_url", &self.rest_base_url)
698 .field("xml_base_url", &self.xml_base_url)
699 .field("region", &self.region)
700 .field("validate_certificates", &self.validate_certificates)
701 .field("retry_config", &self.retry_config)
702 .field("connect_timeout", &self.connect_timeout)
703 .field("request_timeout", &self.request_timeout)
704 .field("proxy_url", &proxy_url_redacted)
705 .field(
706 "proxy_username",
707 &self.proxy_username.as_ref().map(|_| "[REDACTED]"),
708 )
709 .field(
710 "proxy_password",
711 &self.proxy_password.as_ref().map(|_| "[REDACTED]"),
712 )
713 .finish()
714 }
715}
716
717const COMMERCIAL_REST_URL: &str = "https://api.veracode.com";
719const COMMERCIAL_XML_URL: &str = "https://analysiscenter.veracode.com";
720const EUROPEAN_REST_URL: &str = "https://api.veracode.eu";
721const EUROPEAN_XML_URL: &str = "https://analysiscenter.veracode.eu";
722const FEDERAL_REST_URL: &str = "https://api.veracode.us";
723const FEDERAL_XML_URL: &str = "https://analysiscenter.veracode.us";
724
725#[derive(Debug, Clone, Copy, PartialEq)]
730pub enum VeracodeRegion {
731 Commercial,
733 European,
735 Federal,
737}
738
739impl VeracodeConfig {
740 #[must_use]
756 pub fn new(api_id: &str, api_key: &str) -> Self {
757 let credentials = VeracodeCredentials::new(api_id.to_string(), api_key.to_string());
758 Self {
759 credentials,
760 base_url: COMMERCIAL_REST_URL.to_string(),
761 rest_base_url: COMMERCIAL_REST_URL.to_string(),
762 xml_base_url: COMMERCIAL_XML_URL.to_string(),
763 region: VeracodeRegion::Commercial,
764 validate_certificates: true, retry_config: RetryConfig::default(),
766 connect_timeout: 30, request_timeout: 300, proxy_url: None,
769 proxy_username: None,
770 proxy_password: None,
771 }
772 }
773
774 #[must_use]
779 pub fn from_arc_credentials(api_id: Arc<SecretString>, api_key: Arc<SecretString>) -> Self {
780 let credentials = VeracodeCredentials { api_id, api_key };
781
782 Self {
783 credentials,
784 base_url: COMMERCIAL_REST_URL.to_string(),
785 rest_base_url: COMMERCIAL_REST_URL.to_string(),
786 xml_base_url: COMMERCIAL_XML_URL.to_string(),
787 region: VeracodeRegion::Commercial,
788 validate_certificates: true,
789 retry_config: RetryConfig::default(),
790 connect_timeout: 30,
791 request_timeout: 300,
792 proxy_url: None,
793 proxy_username: None,
794 proxy_password: None,
795 }
796 }
797
798 #[must_use]
811 pub fn with_region(mut self, region: VeracodeRegion) -> Self {
812 let (rest_url, xml_url) = match region {
813 VeracodeRegion::Commercial => (COMMERCIAL_REST_URL, COMMERCIAL_XML_URL),
814 VeracodeRegion::European => (EUROPEAN_REST_URL, EUROPEAN_XML_URL),
815 VeracodeRegion::Federal => (FEDERAL_REST_URL, FEDERAL_XML_URL),
816 };
817
818 self.region = region;
819 self.rest_base_url = rest_url.to_string();
820 self.xml_base_url = xml_url.to_string();
821 self.base_url = self.rest_base_url.clone(); self
823 }
824
825 #[must_use]
834 pub fn with_certificate_validation_disabled(mut self) -> Self {
835 self.validate_certificates = false;
836 self
837 }
838
839 #[must_use]
852 pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
853 self.retry_config = retry_config;
854 self
855 }
856
857 #[must_use]
866 pub fn with_retries_disabled(mut self) -> Self {
867 self.retry_config = RetryConfig::new().with_max_attempts(0);
868 self
869 }
870
871 #[must_use]
883 pub fn with_connect_timeout(mut self, timeout_seconds: u64) -> Self {
884 self.connect_timeout = timeout_seconds;
885 self
886 }
887
888 #[must_use]
901 pub fn with_request_timeout(mut self, timeout_seconds: u64) -> Self {
902 self.request_timeout = timeout_seconds;
903 self
904 }
905
906 #[must_use]
919 pub fn with_timeouts(
920 mut self,
921 connect_timeout_seconds: u64,
922 request_timeout_seconds: u64,
923 ) -> Self {
924 self.connect_timeout = connect_timeout_seconds;
925 self.request_timeout = request_timeout_seconds;
926 self
927 }
928
929 #[must_use]
931 pub fn api_id_arc(&self) -> Arc<SecretString> {
932 self.credentials.api_id_ptr()
933 }
934
935 #[must_use]
937 pub fn api_key_arc(&self) -> Arc<SecretString> {
938 self.credentials.api_key_ptr()
939 }
940
941 #[must_use]
969 pub fn with_proxy(mut self, proxy_url: impl Into<String>) -> Self {
970 self.proxy_url = Some(proxy_url.into());
971 self
972 }
973
974 #[must_use]
999 pub fn with_proxy_auth(
1000 mut self,
1001 username: impl Into<String>,
1002 password: impl Into<String>,
1003 ) -> Self {
1004 self.proxy_username = Some(SecretString::new(username.into().into()));
1005 self.proxy_password = Some(SecretString::new(password.into().into()));
1006 self
1007 }
1008}
1009
1010#[cfg(test)]
1011#[allow(clippy::expect_used)]
1012mod tests {
1013 use super::*;
1014
1015 #[test]
1016 fn test_config_creation() {
1017 let config = VeracodeConfig::new("test_api_id", "test_api_key");
1018
1019 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
1020 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
1021 assert_eq!(config.base_url, "https://api.veracode.com");
1022 assert_eq!(config.rest_base_url, "https://api.veracode.com");
1023 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
1024 assert_eq!(config.region, VeracodeRegion::Commercial);
1025 assert!(config.validate_certificates); assert_eq!(config.retry_config.max_attempts, 5); }
1028
1029 #[test]
1030 fn test_european_region_config() {
1031 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1032 .with_region(VeracodeRegion::European);
1033
1034 assert_eq!(config.base_url, "https://api.veracode.eu");
1035 assert_eq!(config.rest_base_url, "https://api.veracode.eu");
1036 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
1037 assert_eq!(config.region, VeracodeRegion::European);
1038 }
1039
1040 #[test]
1041 fn test_federal_region_config() {
1042 let config =
1043 VeracodeConfig::new("test_api_id", "test_api_key").with_region(VeracodeRegion::Federal);
1044
1045 assert_eq!(config.base_url, "https://api.veracode.us");
1046 assert_eq!(config.rest_base_url, "https://api.veracode.us");
1047 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
1048 assert_eq!(config.region, VeracodeRegion::Federal);
1049 }
1050
1051 #[test]
1052 fn test_certificate_validation_disabled() {
1053 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1054 .with_certificate_validation_disabled();
1055
1056 assert!(!config.validate_certificates);
1057 }
1058
1059 #[test]
1060 fn test_veracode_credentials_debug_redaction() {
1061 let credentials = VeracodeCredentials::new(
1062 "test_api_id_123".to_string(),
1063 "test_api_key_456".to_string(),
1064 );
1065 let debug_output = format!("{credentials:?}");
1066
1067 assert!(debug_output.contains("VeracodeCredentials"));
1069 assert!(debug_output.contains("[REDACTED]"));
1070
1071 assert!(!debug_output.contains("test_api_id_123"));
1073 assert!(!debug_output.contains("test_api_key_456"));
1074 }
1075
1076 #[test]
1077 fn test_veracode_config_debug_redaction() {
1078 let config = VeracodeConfig::new("test_api_id_123", "test_api_key_456");
1079 let debug_output = format!("{config:?}");
1080
1081 assert!(debug_output.contains("VeracodeConfig"));
1083 assert!(debug_output.contains("credentials"));
1084 assert!(debug_output.contains("[REDACTED]"));
1085
1086 assert!(!debug_output.contains("test_api_id_123"));
1088 assert!(!debug_output.contains("test_api_key_456"));
1089 }
1090
1091 #[test]
1092 fn test_veracode_credentials_access_methods() {
1093 let credentials = VeracodeCredentials::new(
1094 "test_api_id_123".to_string(),
1095 "test_api_key_456".to_string(),
1096 );
1097
1098 assert_eq!(credentials.expose_api_id(), "test_api_id_123");
1100 assert_eq!(credentials.expose_api_key(), "test_api_key_456");
1101 }
1102
1103 #[test]
1104 fn test_veracode_credentials_arc_pointers() {
1105 let credentials = VeracodeCredentials::new(
1106 "test_api_id_123".to_string(),
1107 "test_api_key_456".to_string(),
1108 );
1109
1110 let api_id_arc = credentials.api_id_ptr();
1112 let api_key_arc = credentials.api_key_ptr();
1113
1114 assert_eq!(api_id_arc.expose_secret(), "test_api_id_123");
1116 assert_eq!(api_key_arc.expose_secret(), "test_api_key_456");
1117 }
1118
1119 #[test]
1120 fn test_veracode_credentials_clone() {
1121 let credentials = VeracodeCredentials::new(
1122 "test_api_id_123".to_string(),
1123 "test_api_key_456".to_string(),
1124 );
1125 let cloned_credentials = credentials.clone();
1126
1127 assert_eq!(
1129 credentials.expose_api_id(),
1130 cloned_credentials.expose_api_id()
1131 );
1132 assert_eq!(
1133 credentials.expose_api_key(),
1134 cloned_credentials.expose_api_key()
1135 );
1136 }
1137
1138 #[test]
1139 fn test_config_with_arc_credentials() {
1140 use secrecy::SecretString;
1141 use std::sync::Arc;
1142
1143 let api_id_arc = Arc::new(SecretString::new("test_api_id".into()));
1144 let api_key_arc = Arc::new(SecretString::new("test_api_key".into()));
1145
1146 let config = VeracodeConfig::from_arc_credentials(api_id_arc, api_key_arc);
1147
1148 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
1149 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
1150 assert_eq!(config.region, VeracodeRegion::Commercial);
1151 }
1152
1153 #[test]
1154 fn test_error_display() {
1155 let error = VeracodeError::Authentication("Invalid API key".to_string());
1156 assert_eq!(format!("{error}"), "Authentication error: Invalid API key");
1157 }
1158
1159 #[test]
1160 fn test_error_from_reqwest() {
1161 fn _test_conversion(error: reqwest::Error) -> VeracodeError {
1166 VeracodeError::from(error)
1167 }
1168
1169 }
1172
1173 #[test]
1174 fn test_retry_config_default() {
1175 let config = RetryConfig::default();
1176 assert_eq!(config.max_attempts, 5);
1177 assert_eq!(config.initial_delay_ms, 1000);
1178 assert_eq!(config.max_delay_ms, 30000);
1179 assert_eq!(config.backoff_multiplier, 2.0);
1180 assert_eq!(config.max_total_delay_ms, 300000);
1181 assert!(config.jitter_enabled); }
1183
1184 #[test]
1185 fn test_retry_config_builder() {
1186 let config = RetryConfig::new()
1187 .with_max_attempts(5)
1188 .with_initial_delay(500)
1189 .with_max_delay(60000)
1190 .with_backoff_multiplier(1.5)
1191 .with_max_total_delay(600000);
1192
1193 assert_eq!(config.max_attempts, 5);
1194 assert_eq!(config.initial_delay_ms, 500);
1195 assert_eq!(config.max_delay_ms, 60000);
1196 assert_eq!(config.backoff_multiplier, 1.5);
1197 assert_eq!(config.max_total_delay_ms, 600000);
1198 }
1199
1200 #[test]
1201 fn test_retry_config_calculate_delay() {
1202 let config = RetryConfig::new()
1203 .with_initial_delay(1000)
1204 .with_backoff_multiplier(2.0)
1205 .with_max_delay(10000)
1206 .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); }
1216
1217 #[test]
1218 fn test_retry_config_is_retryable_error() {
1219 let config = RetryConfig::new();
1220
1221 assert!(
1223 config.is_retryable_error(&VeracodeError::InvalidResponse("temp error".to_string()))
1224 );
1225
1226 assert!(!config.is_retryable_error(&VeracodeError::Authentication("bad auth".to_string())));
1228 assert!(!config.is_retryable_error(&VeracodeError::Serialization(
1229 serde_json::from_str::<i32>("invalid").expect_err("should fail to deserialize")
1230 )));
1231 assert!(
1232 !config.is_retryable_error(&VeracodeError::InvalidConfig("bad config".to_string()))
1233 );
1234 assert!(!config.is_retryable_error(&VeracodeError::NotFound("not found".to_string())));
1235 assert!(
1236 !config.is_retryable_error(&VeracodeError::RetryExhausted("exhausted".to_string()))
1237 );
1238 }
1239
1240 #[test]
1241 fn test_veracode_config_with_retry_config() {
1242 let retry_config = RetryConfig::new().with_max_attempts(5);
1243 let config =
1244 VeracodeConfig::new("test_api_id", "test_api_key").with_retry_config(retry_config);
1245
1246 assert_eq!(config.retry_config.max_attempts, 5);
1247 }
1248
1249 #[test]
1250 fn test_veracode_config_with_retries_disabled() {
1251 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_retries_disabled();
1252
1253 assert_eq!(config.retry_config.max_attempts, 0);
1254 }
1255
1256 #[test]
1257 fn test_timeout_configuration() {
1258 let config = VeracodeConfig::new("test_api_id", "test_api_key");
1259
1260 assert_eq!(config.connect_timeout, 30);
1262 assert_eq!(config.request_timeout, 300);
1263 }
1264
1265 #[test]
1266 fn test_with_connect_timeout() {
1267 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_connect_timeout(60);
1268
1269 assert_eq!(config.connect_timeout, 60);
1270 assert_eq!(config.request_timeout, 300); }
1272
1273 #[test]
1274 fn test_with_request_timeout() {
1275 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_request_timeout(600);
1276
1277 assert_eq!(config.connect_timeout, 30); assert_eq!(config.request_timeout, 600);
1279 }
1280
1281 #[test]
1282 fn test_with_timeouts() {
1283 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_timeouts(120, 1800);
1284
1285 assert_eq!(config.connect_timeout, 120);
1286 assert_eq!(config.request_timeout, 1800);
1287 }
1288
1289 #[test]
1290 fn test_timeout_configuration_chaining() {
1291 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1292 .with_region(VeracodeRegion::European)
1293 .with_connect_timeout(45)
1294 .with_request_timeout(900)
1295 .with_retries_disabled();
1296
1297 assert_eq!(config.region, VeracodeRegion::European);
1298 assert_eq!(config.connect_timeout, 45);
1299 assert_eq!(config.request_timeout, 900);
1300 assert_eq!(config.retry_config.max_attempts, 0);
1301 }
1302
1303 #[test]
1304 fn test_retry_exhausted_error_display() {
1305 let error = VeracodeError::RetryExhausted("Failed after 3 attempts".to_string());
1306 assert_eq!(
1307 format!("{error}"),
1308 "Retry attempts exhausted: Failed after 3 attempts"
1309 );
1310 }
1311
1312 #[test]
1313 fn test_rate_limited_error_display_with_retry_after() {
1314 let error = VeracodeError::RateLimited {
1315 retry_after_seconds: Some(60),
1316 message: "Too Many Requests".to_string(),
1317 };
1318 assert_eq!(
1319 format!("{error}"),
1320 "Rate limit exceeded: Too Many Requests (retry after 60s)"
1321 );
1322 }
1323
1324 #[test]
1325 fn test_rate_limited_error_display_without_retry_after() {
1326 let error = VeracodeError::RateLimited {
1327 retry_after_seconds: None,
1328 message: "Too Many Requests".to_string(),
1329 };
1330 assert_eq!(format!("{error}"), "Rate limit exceeded: Too Many Requests");
1331 }
1332
1333 #[test]
1334 fn test_rate_limited_error_is_retryable() {
1335 let config = RetryConfig::new();
1336 let error = VeracodeError::RateLimited {
1337 retry_after_seconds: Some(60),
1338 message: "Rate limit exceeded".to_string(),
1339 };
1340 assert!(config.is_retryable_error(&error));
1341 }
1342
1343 #[test]
1344 fn test_calculate_rate_limit_delay_with_retry_after() {
1345 let config = RetryConfig::new();
1346 let delay = config.calculate_rate_limit_delay(Some(30));
1347 assert_eq!(delay.as_secs(), 30);
1348 }
1349
1350 #[test]
1351 #[cfg_attr(miri, ignore)] fn test_calculate_rate_limit_delay_without_retry_after() {
1353 let config = RetryConfig::new();
1354 let delay = config.calculate_rate_limit_delay(None);
1355
1356 assert!(delay.as_secs() >= 5);
1359 assert!(delay.as_secs() <= 65);
1360 }
1361
1362 #[test]
1363 fn test_rate_limit_config_defaults() {
1364 let config = RetryConfig::default();
1365 assert_eq!(config.rate_limit_buffer_seconds, 5);
1366 assert_eq!(config.rate_limit_max_attempts, 1);
1367 }
1368
1369 #[test]
1370 fn test_rate_limit_config_builders() {
1371 let config = RetryConfig::new()
1372 .with_rate_limit_buffer(10)
1373 .with_rate_limit_max_attempts(2);
1374
1375 assert_eq!(config.rate_limit_buffer_seconds, 10);
1376 assert_eq!(config.rate_limit_max_attempts, 2);
1377 }
1378
1379 #[test]
1380 #[cfg_attr(miri, ignore)] fn test_rate_limit_delay_uses_buffer() {
1382 let config = RetryConfig::new().with_rate_limit_buffer(15);
1383 let delay = config.calculate_rate_limit_delay(None);
1384
1385 assert!(delay.as_secs() >= 15);
1387 assert!(delay.as_secs() <= 75); }
1389
1390 #[test]
1391 fn test_jitter_disabled() {
1392 let config = RetryConfig::new().with_jitter_disabled();
1393 assert!(!config.jitter_enabled);
1394
1395 let delay1 = config.calculate_delay(2);
1397 let delay2 = config.calculate_delay(2);
1398 assert_eq!(delay1, delay2);
1399 }
1400
1401 #[test]
1402 fn test_jitter_enabled() {
1403 let config = RetryConfig::new(); assert!(config.jitter_enabled);
1405
1406 let base_delay = config.initial_delay_ms;
1408 let delay = config.calculate_delay(1);
1409
1410 #[allow(
1412 clippy::cast_possible_truncation,
1413 clippy::cast_sign_loss,
1414 clippy::cast_precision_loss
1415 )]
1416 let min_expected = (base_delay as f64 * 0.75) as u64;
1417 #[allow(
1418 clippy::cast_possible_truncation,
1419 clippy::cast_sign_loss,
1420 clippy::cast_precision_loss
1421 )]
1422 let max_expected = (base_delay as f64 * 1.25) as u64;
1423
1424 assert!(delay.as_millis() >= min_expected as u128);
1425 assert!(delay.as_millis() <= max_expected as u128);
1426 }
1427}
1428
1429#[cfg(test)]
1436mod security_tests {
1437 use super::*;
1438 use proptest::prelude::*;
1439
1440 proptest! {
1449 #![proptest_config(ProptestConfig {
1450 cases: if cfg!(miri) { 5 } else { 1000 },
1451 failure_persistence: None,
1452 .. ProptestConfig::default()
1453 })]
1454
1455 #[test]
1457 fn test_calculate_delay_no_overflow(
1458 attempt in 0u32..1000u32,
1459 initial_delay in 1u64..100_000u64,
1460 multiplier in 1.0f64..10.0f64,
1461 max_delay in 1u64..1_000_000u64,
1462 ) {
1463 let config = RetryConfig::new()
1464 .with_initial_delay(initial_delay)
1465 .with_backoff_multiplier(multiplier)
1466 .with_max_delay(max_delay)
1467 .with_jitter_disabled(); let delay = config.calculate_delay(attempt);
1471
1472 assert!(delay.as_millis() <= max_delay as u128);
1474
1475 assert!(delay <= Duration::from_millis(max_delay));
1478 }
1479
1480 #[test]
1482 fn test_calculate_delay_extreme_multipliers(
1483 attempt in 0u32..100u32,
1484 multiplier in 1.0f64..1000.0f64,
1485 ) {
1486 let config = RetryConfig::new()
1487 .with_initial_delay(1000)
1488 .with_backoff_multiplier(multiplier)
1489 .with_max_delay(60_000)
1490 .with_jitter_disabled();
1491
1492 let delay = config.calculate_delay(attempt);
1494
1495 assert!(delay.as_millis() <= 60_000);
1497 }
1498 }
1499
1500 proptest! {
1509 #![proptest_config(ProptestConfig {
1510 cases: if cfg!(miri) { 5 } else { 1000 },
1511 failure_persistence: None,
1512 .. ProptestConfig::default()
1513 })]
1514
1515 #[test]
1517 fn test_rate_limit_delay_with_retry_after(
1518 retry_after_seconds in 0u64..100_000u64,
1519 ) {
1520 let config = RetryConfig::new();
1521
1522 let delay = config.calculate_rate_limit_delay(Some(retry_after_seconds));
1524
1525 assert_eq!(delay.as_secs(), retry_after_seconds);
1527 }
1528
1529 #[test]
1531 fn test_rate_limit_delay_buffer_no_overflow(
1532 buffer_seconds in 0u64..10_000u64,
1533 ) {
1534 let config = RetryConfig::new()
1535 .with_rate_limit_buffer(buffer_seconds);
1536
1537 let delay = config.calculate_rate_limit_delay(None);
1539
1540 assert!(delay.as_secs() >= buffer_seconds);
1542
1543 assert!(delay.as_secs() <= 60_u64.saturating_add(buffer_seconds));
1545 }
1546 }
1547
1548 proptest! {
1557 #![proptest_config(ProptestConfig {
1558 cases: if cfg!(miri) { 5 } else { 500 },
1559 failure_persistence: None,
1560 .. ProptestConfig::default()
1561 })]
1562
1563 #[test]
1565 fn test_credentials_debug_never_exposes_secrets(
1566 api_id in "[a-zA-Z0-9]{10,256}",
1567 api_key in "[a-zA-Z0-9]{10,256}",
1568 ) {
1569 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1570 let debug_output = format!("{:?}", credentials);
1571
1572 assert!(debug_output.contains("VeracodeCredentials"));
1574
1575 assert!(debug_output.contains("[REDACTED]"));
1577
1578 assert!(!debug_output.contains(&api_id));
1580 assert!(!debug_output.contains(&api_key));
1581 }
1582
1583 #[test]
1585 fn test_credentials_arc_cloning_preserves_values(
1586 api_id in "[a-zA-Z0-9]{10,100}",
1587 api_key in "[a-zA-Z0-9]{10,100}",
1588 ) {
1589 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1590
1591 let api_id_arc1 = credentials.api_id_ptr();
1593 let api_id_arc2 = credentials.api_id_ptr();
1594 let api_key_arc1 = credentials.api_key_ptr();
1595 let api_key_arc2 = credentials.api_key_ptr();
1596
1597 assert_eq!(api_id_arc1.expose_secret(), &api_id);
1599 assert_eq!(api_id_arc2.expose_secret(), &api_id);
1600 assert_eq!(api_key_arc1.expose_secret(), &api_key);
1601 assert_eq!(api_key_arc2.expose_secret(), &api_key);
1602 }
1603
1604 #[test]
1606 fn test_credentials_expose_methods_correctness(
1607 api_id in "[a-zA-Z0-9]{10,256}",
1608 api_key in "[a-zA-Z0-9]{10,256}",
1609 ) {
1610 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1611
1612 assert_eq!(credentials.expose_api_id(), api_id);
1614 assert_eq!(credentials.expose_api_key(), api_key);
1615 }
1616 }
1617
1618 proptest! {
1625 #![proptest_config(ProptestConfig {
1626 cases: if cfg!(miri) { 5 } else { 500 },
1627 failure_persistence: None,
1628 .. ProptestConfig::default()
1629 })]
1630
1631 #[test]
1633 fn test_config_debug_redacts_proxy_credentials(
1634 protocol in "(http|https)",
1635 username in "[a-zA-Z]{15,30}",
1637 password in "[a-zA-Z]{15,30}",
1638 host in "proxy\\d{1,3}\\.example\\.com",
1639 ) {
1640 let port = 8080u16;
1641 let proxy_url = format!("{}://{}:{}@{}:{}", protocol, username, password, host, port);
1642
1643 let config = VeracodeConfig::new("api_id", "api_key")
1644 .with_proxy(&proxy_url);
1645
1646 let debug_output = format!("{:?}", config);
1647
1648 assert!(debug_output.contains("[REDACTED]"));
1650
1651 assert!(!debug_output.contains(&username));
1655 assert!(!debug_output.contains(&password));
1656
1657 assert!(debug_output.contains(&host));
1659 }
1660
1661 #[test]
1663 fn test_config_debug_handles_utf8_safely(
1664 protocol in "(http|https)",
1666 creds in "[a-zA-Z0-9]{1,30}",
1667 host in "[a-z]{3,15}\\.[a-z]{2,5}",
1668 ) {
1669 let proxy_url = format!("{}://{}@{}", protocol, creds, host);
1671
1672 let config = VeracodeConfig::new("test", "test")
1673 .with_proxy(&proxy_url);
1674
1675 let debug_output = format!("{:?}", config);
1677
1678 assert!(debug_output.contains("VeracodeConfig"));
1680 }
1681
1682 #[test]
1684 fn test_config_debug_redacts_proxy_auth(
1685 proxy_username in "[a-zA-Z0-9]{10,50}",
1686 proxy_password in "[a-zA-Z0-9]{10,50}",
1687 ) {
1688 let config = VeracodeConfig::new("api_id", "api_key")
1689 .with_proxy("http://proxy.example.com:8080")
1690 .with_proxy_auth(proxy_username.clone(), proxy_password.clone());
1691
1692 let debug_output = format!("{:?}", config);
1693
1694 assert!(debug_output.contains("proxy_username"));
1696 assert!(debug_output.contains("proxy_password"));
1697 assert!(debug_output.contains("[REDACTED]"));
1698
1699 assert!(!debug_output.contains(&proxy_username));
1701 assert!(!debug_output.contains(&proxy_password));
1702 }
1703 }
1704
1705 proptest! {
1713 #![proptest_config(ProptestConfig {
1714 cases: if cfg!(miri) { 5 } else { 100 },
1715 failure_persistence: None,
1716 .. ProptestConfig::default()
1717 })]
1718
1719 #[test]
1721 fn test_config_region_urls_are_valid(
1722 region in prop::sample::select(vec![
1723 VeracodeRegion::Commercial,
1724 VeracodeRegion::European,
1725 VeracodeRegion::Federal,
1726 ])
1727 ) {
1728 let config = VeracodeConfig::new("test", "test")
1729 .with_region(region);
1730
1731 match region {
1733 VeracodeRegion::Commercial => {
1734 assert_eq!(config.rest_base_url, "https://api.veracode.com");
1735 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
1736 assert_eq!(config.base_url, config.rest_base_url);
1737 }
1738 VeracodeRegion::European => {
1739 assert_eq!(config.rest_base_url, "https://api.veracode.eu");
1740 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
1741 assert_eq!(config.base_url, config.rest_base_url);
1742 }
1743 VeracodeRegion::Federal => {
1744 assert_eq!(config.rest_base_url, "https://api.veracode.us");
1745 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
1746 assert_eq!(config.base_url, config.rest_base_url);
1747 }
1748 }
1749
1750 assert!(config.rest_base_url.starts_with("https://"));
1752 assert!(config.xml_base_url.starts_with("https://"));
1753 assert!(config.base_url.starts_with("https://"));
1754 }
1755 }
1756
1757 proptest! {
1766 #![proptest_config(ProptestConfig {
1767 cases: if cfg!(miri) { 5 } else { 500 },
1768 failure_persistence: None,
1769 .. ProptestConfig::default()
1770 })]
1771
1772 #[test]
1774 fn test_config_timeout_no_overflow(
1775 connect_timeout in 1u64..100_000u64,
1776 request_timeout in 1u64..100_000u64,
1777 ) {
1778 let config = VeracodeConfig::new("test", "test")
1779 .with_timeouts(connect_timeout, request_timeout);
1780
1781 assert_eq!(config.connect_timeout, connect_timeout);
1783 assert_eq!(config.request_timeout, request_timeout);
1784
1785 assert!(config.connect_timeout > 0);
1787 assert!(config.request_timeout > 0);
1788 }
1789
1790 #[test]
1792 fn test_config_individual_timeout_setters(
1793 connect_timeout in 1u64..50_000u64,
1794 request_timeout in 1u64..50_000u64,
1795 ) {
1796 let config1 = VeracodeConfig::new("test", "test")
1797 .with_connect_timeout(connect_timeout);
1798 assert_eq!(config1.connect_timeout, connect_timeout);
1799 assert_eq!(config1.request_timeout, 300); let config2 = VeracodeConfig::new("test", "test")
1802 .with_request_timeout(request_timeout);
1803 assert_eq!(config2.connect_timeout, 30); assert_eq!(config2.request_timeout, request_timeout);
1805 }
1806 }
1807
1808 proptest! {
1817 #![proptest_config(ProptestConfig {
1818 cases: if cfg!(miri) { 5 } else { 500 },
1819 failure_persistence: None,
1820 .. ProptestConfig::default()
1821 })]
1822
1823 #[test]
1825 fn test_error_display_safe_messages(
1826 message in "[a-zA-Z0-9 ]{1,100}",
1827 ) {
1828 let errors = vec![
1829 VeracodeError::Authentication(message.clone()),
1830 VeracodeError::InvalidResponse(message.clone()),
1831 VeracodeError::InvalidConfig(message.clone()),
1832 VeracodeError::NotFound(message.clone()),
1833 VeracodeError::RetryExhausted(message.clone()),
1834 ];
1835
1836 for error in errors {
1837 let display = format!("{}", error);
1838
1839 assert!(!display.is_empty());
1841
1842 assert!(display.contains(&message));
1844 }
1845 }
1846
1847 #[test]
1849 fn test_rate_limited_error_display_safe(
1850 retry_after in prop::option::of(0u64..10_000u64),
1851 message in "[a-zA-Z0-9 ]{1,100}",
1852 ) {
1853 let error = VeracodeError::RateLimited {
1854 retry_after_seconds: retry_after,
1855 message: message.clone(),
1856 };
1857
1858 let display = format!("{}", error);
1859
1860 assert!(display.contains(&message));
1862 assert!(display.contains("Rate limit exceeded"));
1863
1864 if let Some(seconds) = retry_after {
1866 assert!(display.contains(&seconds.to_string()));
1867 }
1868 }
1869 }
1870}
1871
1872#[cfg(kani)]
1879mod kani_proofs {
1880 use super::*;
1881
1882 #[kani::proof]
1889 #[kani::unwind(10)] fn verify_calculate_delay_arithmetic_safety() {
1891 let initial_delay: u64 = kani::any();
1893 let max_delay: u64 = kani::any();
1894 let multiplier: f64 = kani::any();
1895 let attempt: u32 = kani::any();
1896
1897 kani::assume(initial_delay > 0);
1899 kani::assume(initial_delay <= 100_000);
1900 kani::assume(max_delay > 0);
1901 kani::assume(max_delay >= initial_delay);
1902 kani::assume(max_delay <= 1_000_000);
1903 kani::assume(multiplier >= 1.0);
1904 kani::assume(multiplier <= 10.0);
1905 kani::assume(multiplier.is_finite());
1906 kani::assume(attempt < 20); let config = RetryConfig::new()
1909 .with_initial_delay(initial_delay)
1910 .with_backoff_multiplier(multiplier)
1911 .with_max_delay(max_delay)
1912 .with_jitter_disabled();
1913
1914 let delay = config.calculate_delay(attempt);
1916
1917 assert!(delay.as_millis() <= max_delay as u128);
1919
1920 assert!(delay.as_secs() < u64::MAX);
1922
1923 if attempt == 0 {
1925 assert_eq!(delay.as_millis(), 0);
1926 }
1927 }
1928
1929 #[kani::proof]
1938 fn verify_rate_limit_delay_safety() {
1939 let buffer_seconds: u64 = kani::any();
1940 let retry_after_seconds: Option<u64> = kani::any();
1941
1942 kani::assume(buffer_seconds <= 10_000);
1944 if let Some(secs) = retry_after_seconds {
1945 kani::assume(secs <= 100_000);
1946 }
1947
1948 if let Some(expected_secs) = retry_after_seconds {
1950 let delay = Duration::from_secs(expected_secs);
1951 assert_eq!(delay.as_secs(), expected_secs);
1953 }
1954
1955 let current_second: u64 = kani::any();
1958 kani::assume(current_second < 60);
1959
1960 let seconds_until_next_minute = 60_u64.saturating_sub(current_second);
1962 let total_delay = seconds_until_next_minute.saturating_add(buffer_seconds);
1963
1964 assert!(total_delay >= buffer_seconds);
1966
1967 assert!(total_delay <= 60 + buffer_seconds);
1969
1970 }
1973
1974 #[kani::proof]
1982 #[kani::unwind(50)]
1983 fn verify_credentials_arc_memory_safety() {
1984 let api_id = "test_api_id".to_string();
1986 let api_key = "test_key123".to_string();
1987
1988 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1989
1990 let api_id_arc1 = credentials.api_id_ptr();
1992 let api_id_arc2 = credentials.api_id_ptr();
1993
1994 assert_eq!(
1996 Arc::as_ptr(&api_id_arc1) as *const (),
1997 Arc::as_ptr(&api_id_arc2) as *const ()
1998 );
1999
2000 assert_eq!(api_id_arc1.expose_secret(), api_id_arc2.expose_secret());
2002
2003 assert_eq!(credentials.expose_api_id(), &api_id);
2005 assert_eq!(credentials.expose_api_key(), &api_key);
2006
2007 let cloned = credentials.clone();
2009 assert_eq!(cloned.expose_api_id(), credentials.expose_api_id());
2010 assert_eq!(cloned.expose_api_key(), credentials.expose_api_key());
2011
2012 let cloned2 = cloned.clone();
2014 let _ = cloned2.expose_api_id();
2015 let _ = cloned2.expose_api_key();
2016
2017 }
2019
2020 #[kani::proof]
2028 fn verify_proxy_url_redaction_safety() {
2029 let has_at_sign: bool = kani::any();
2031
2032 let url = if has_at_sign {
2034 "u:p@h".to_string()
2035 } else {
2036 "http://h".to_string()
2037 };
2038
2039 let config = VeracodeConfig::new("t", "k").with_proxy(&url);
2041
2042 assert!(config.proxy_url.is_some());
2044
2045 let _ = config.proxy_url;
2048 }
2049
2050 #[kani::proof]
2058 #[kani::unwind(10)] fn verify_region_url_construction() {
2060 let config = VeracodeConfig::new("a", "b").with_region(VeracodeRegion::Commercial);
2066
2067 assert!(!config.rest_base_url.is_empty());
2069 assert!(!config.xml_base_url.is_empty());
2070
2071 assert!(config.rest_base_url.len() < 100);
2073 assert!(config.xml_base_url.len() < 100);
2074
2075 assert!(matches!(config.region, VeracodeRegion::Commercial));
2077 }
2078
2079 #[kani::proof]
2085 #[kani::unwind(10)]
2086 fn verify_european_region_url_construction() {
2087 let config = VeracodeConfig::new("a", "b").with_region(VeracodeRegion::European);
2088
2089 assert!(!config.rest_base_url.is_empty());
2091 assert!(!config.xml_base_url.is_empty());
2092
2093 assert!(config.rest_base_url.len() < 100);
2095 assert!(config.xml_base_url.len() < 100);
2096
2097 assert!(matches!(config.region, VeracodeRegion::European));
2099 }
2100
2101 #[kani::proof]
2107 #[kani::unwind(10)]
2108 fn verify_federal_region_url_construction() {
2109 let config = VeracodeConfig::new("a", "b").with_region(VeracodeRegion::Federal);
2110
2111 assert!(!config.rest_base_url.is_empty());
2113 assert!(!config.xml_base_url.is_empty());
2114
2115 assert!(config.rest_base_url.len() < 100);
2117 assert!(config.xml_base_url.len() < 100);
2118
2119 assert!(matches!(config.region, VeracodeRegion::Federal));
2121 }
2122}