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 #[must_use]
451 pub fn applications_api(&self) -> &Self {
452 self
453 }
454
455 #[must_use]
458 pub fn sandbox_api(&self) -> SandboxApi<'_> {
459 SandboxApi::new(self)
460 }
461
462 #[must_use]
465 pub fn identity_api(&self) -> IdentityApi<'_> {
466 IdentityApi::new(self)
467 }
468
469 #[must_use]
472 pub fn pipeline_api(&self) -> PipelineApi {
473 PipelineApi::new(self.clone())
474 }
475
476 #[must_use]
479 pub fn policy_api(&self) -> PolicyApi<'_> {
480 PolicyApi::new(self)
481 }
482
483 #[must_use]
486 pub fn findings_api(&self) -> FindingsApi {
487 FindingsApi::new(self.clone())
488 }
489
490 #[must_use]
493 pub fn reporting_api(&self) -> reporting::ReportingApi {
494 reporting::ReportingApi::new(self.clone())
495 }
496
497 pub fn scan_api(&self) -> Result<ScanApi, VeracodeError> {
504 Ok(ScanApi::new(self.new_xml_variant()))
505 }
506
507 pub fn build_api(&self) -> Result<build::BuildApi, VeracodeError> {
514 Ok(build::BuildApi::new(self.new_xml_variant()))
515 }
516
517 #[must_use]
520 pub fn workflow(&self) -> workflow::VeracodeWorkflow {
521 workflow::VeracodeWorkflow::new(self.clone())
522 }
523}
524
525impl fmt::Display for VeracodeError {
526 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
527 match self {
528 VeracodeError::Http(e) => write!(f, "HTTP error: {e}"),
529 VeracodeError::Serialization(e) => write!(f, "Serialization error: {e}"),
530 VeracodeError::Authentication(e) => write!(f, "Authentication error: {e}"),
531 VeracodeError::InvalidResponse(e) => write!(f, "Invalid response: {e}"),
532 VeracodeError::HttpStatus {
533 status_code,
534 url,
535 message,
536 } => write!(f, "HTTP {status_code} error at {url}: {message}"),
537 VeracodeError::InvalidConfig(e) => write!(f, "Invalid configuration: {e}"),
538 VeracodeError::NotFound(e) => write!(f, "Item not found: {e}"),
539 VeracodeError::RetryExhausted(e) => write!(f, "Retry attempts exhausted: {e}"),
540 VeracodeError::RateLimited {
541 retry_after_seconds,
542 message,
543 } => match retry_after_seconds {
544 Some(seconds) => {
545 write!(f, "Rate limit exceeded: {message} (retry after {seconds}s)")
546 }
547 None => write!(f, "Rate limit exceeded: {message}"),
548 },
549 VeracodeError::Validation(e) => write!(f, "Validation error: {e}"),
550 }
551 }
552}
553
554impl std::error::Error for VeracodeError {}
555
556impl From<ReqwestError> for VeracodeError {
557 fn from(error: ReqwestError) -> Self {
558 VeracodeError::Http(error)
559 }
560}
561
562impl From<serde_json::Error> for VeracodeError {
563 fn from(error: serde_json::Error) -> Self {
564 VeracodeError::Serialization(error)
565 }
566}
567
568impl From<validation::ValidationError> for VeracodeError {
569 fn from(error: validation::ValidationError) -> Self {
570 VeracodeError::Validation(error)
571 }
572}
573
574#[derive(Clone)]
582pub struct VeracodeCredentials {
583 api_id: Arc<SecretString>,
585 api_key: Arc<SecretString>,
587}
588
589impl VeracodeCredentials {
590 #[must_use]
592 pub fn new(api_id: String, api_key: String) -> Self {
593 Self {
594 api_id: Arc::new(SecretString::new(api_id.into())),
595 api_key: Arc::new(SecretString::new(api_key.into())),
596 }
597 }
598
599 #[must_use]
606 pub fn api_id_ptr(&self) -> Arc<SecretString> {
607 Arc::clone(&self.api_id)
608 }
609
610 #[must_use]
617 pub fn api_key_ptr(&self) -> Arc<SecretString> {
618 Arc::clone(&self.api_key)
619 }
620
621 #[must_use]
626 pub fn expose_api_id(&self) -> &str {
627 self.api_id.expose_secret()
628 }
629
630 #[must_use]
635 pub fn expose_api_key(&self) -> &str {
636 self.api_key.expose_secret()
637 }
638}
639
640impl std::fmt::Debug for VeracodeCredentials {
641 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
642 f.debug_struct("VeracodeCredentials")
643 .field("api_id", &"[REDACTED]")
644 .field("api_key", &"[REDACTED]")
645 .finish()
646 }
647}
648
649#[derive(Clone)]
656pub struct VeracodeConfig {
657 pub credentials: VeracodeCredentials,
659 pub base_url: String,
661 pub rest_base_url: String,
663 pub xml_base_url: String,
665 pub region: VeracodeRegion,
667 pub validate_certificates: bool,
669 pub retry_config: RetryConfig,
671 pub connect_timeout: u64,
673 pub request_timeout: u64,
675 pub proxy_url: Option<String>,
677 pub proxy_username: Option<SecretString>,
679 pub proxy_password: Option<SecretString>,
681}
682
683impl std::fmt::Debug for VeracodeConfig {
685 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
686 let proxy_url_redacted = self.proxy_url.as_ref().map(|url| {
688 if url.contains('@') {
689 if let Some(at_pos) = url.rfind('@') {
691 if let Some(proto_end) = url.find("://") {
692 let protocol = url.get(..proto_end).unwrap_or("");
694 let host_part = url.get(at_pos.saturating_add(1)..).unwrap_or("");
695 format!("{}://[REDACTED]@{}", protocol, host_part)
696 } else {
697 "[REDACTED]".to_string()
698 }
699 } else {
700 "[REDACTED]".to_string()
701 }
702 } else {
703 url.clone()
704 }
705 });
706
707 f.debug_struct("VeracodeConfig")
708 .field("credentials", &self.credentials)
709 .field("base_url", &self.base_url)
710 .field("rest_base_url", &self.rest_base_url)
711 .field("xml_base_url", &self.xml_base_url)
712 .field("region", &self.region)
713 .field("validate_certificates", &self.validate_certificates)
714 .field("retry_config", &self.retry_config)
715 .field("connect_timeout", &self.connect_timeout)
716 .field("request_timeout", &self.request_timeout)
717 .field("proxy_url", &proxy_url_redacted)
718 .field(
719 "proxy_username",
720 &self.proxy_username.as_ref().map(|_| "[REDACTED]"),
721 )
722 .field(
723 "proxy_password",
724 &self.proxy_password.as_ref().map(|_| "[REDACTED]"),
725 )
726 .finish()
727 }
728}
729
730const COMMERCIAL_REST_URL: &str = "https://api.veracode.com";
732const COMMERCIAL_XML_URL: &str = "https://analysiscenter.veracode.com";
733const EUROPEAN_REST_URL: &str = "https://api.veracode.eu";
734const EUROPEAN_XML_URL: &str = "https://analysiscenter.veracode.eu";
735const FEDERAL_REST_URL: &str = "https://api.veracode.us";
736const FEDERAL_XML_URL: &str = "https://analysiscenter.veracode.us";
737
738#[derive(Debug, Clone, Copy, PartialEq)]
743pub enum VeracodeRegion {
744 Commercial,
746 European,
748 Federal,
750}
751
752impl VeracodeConfig {
753 #[must_use]
769 pub fn new(api_id: &str, api_key: &str) -> Self {
770 let credentials = VeracodeCredentials::new(api_id.to_string(), api_key.to_string());
771 Self {
772 credentials,
773 base_url: COMMERCIAL_REST_URL.to_string(),
774 rest_base_url: COMMERCIAL_REST_URL.to_string(),
775 xml_base_url: COMMERCIAL_XML_URL.to_string(),
776 region: VeracodeRegion::Commercial,
777 validate_certificates: true, retry_config: RetryConfig::default(),
779 connect_timeout: 30, request_timeout: 300, proxy_url: None,
782 proxy_username: None,
783 proxy_password: None,
784 }
785 }
786
787 #[must_use]
792 pub fn from_arc_credentials(api_id: Arc<SecretString>, api_key: Arc<SecretString>) -> Self {
793 let credentials = VeracodeCredentials { api_id, api_key };
794
795 Self {
796 credentials,
797 base_url: COMMERCIAL_REST_URL.to_string(),
798 rest_base_url: COMMERCIAL_REST_URL.to_string(),
799 xml_base_url: COMMERCIAL_XML_URL.to_string(),
800 region: VeracodeRegion::Commercial,
801 validate_certificates: true,
802 retry_config: RetryConfig::default(),
803 connect_timeout: 30,
804 request_timeout: 300,
805 proxy_url: None,
806 proxy_username: None,
807 proxy_password: None,
808 }
809 }
810
811 #[must_use]
824 pub fn with_region(mut self, region: VeracodeRegion) -> Self {
825 let (rest_url, xml_url) = match region {
826 VeracodeRegion::Commercial => (COMMERCIAL_REST_URL, COMMERCIAL_XML_URL),
827 VeracodeRegion::European => (EUROPEAN_REST_URL, EUROPEAN_XML_URL),
828 VeracodeRegion::Federal => (FEDERAL_REST_URL, FEDERAL_XML_URL),
829 };
830
831 self.region = region;
832 self.rest_base_url = rest_url.to_string();
833 self.xml_base_url = xml_url.to_string();
834 self.base_url = self.rest_base_url.clone(); self
836 }
837
838 #[must_use]
847 pub fn with_certificate_validation_disabled(mut self) -> Self {
848 self.validate_certificates = false;
849 self
850 }
851
852 #[must_use]
865 pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
866 self.retry_config = retry_config;
867 self
868 }
869
870 #[must_use]
879 pub fn with_retries_disabled(mut self) -> Self {
880 self.retry_config = RetryConfig::new().with_max_attempts(0);
881 self
882 }
883
884 #[must_use]
896 pub fn with_connect_timeout(mut self, timeout_seconds: u64) -> Self {
897 self.connect_timeout = timeout_seconds;
898 self
899 }
900
901 #[must_use]
914 pub fn with_request_timeout(mut self, timeout_seconds: u64) -> Self {
915 self.request_timeout = timeout_seconds;
916 self
917 }
918
919 #[must_use]
932 pub fn with_timeouts(
933 mut self,
934 connect_timeout_seconds: u64,
935 request_timeout_seconds: u64,
936 ) -> Self {
937 self.connect_timeout = connect_timeout_seconds;
938 self.request_timeout = request_timeout_seconds;
939 self
940 }
941
942 #[must_use]
944 pub fn api_id_arc(&self) -> Arc<SecretString> {
945 self.credentials.api_id_ptr()
946 }
947
948 #[must_use]
950 pub fn api_key_arc(&self) -> Arc<SecretString> {
951 self.credentials.api_key_ptr()
952 }
953
954 #[must_use]
982 pub fn with_proxy(mut self, proxy_url: impl Into<String>) -> Self {
983 self.proxy_url = Some(proxy_url.into());
984 self
985 }
986
987 #[must_use]
1012 pub fn with_proxy_auth(
1013 mut self,
1014 username: impl Into<String>,
1015 password: impl Into<String>,
1016 ) -> Self {
1017 self.proxy_username = Some(SecretString::new(username.into().into()));
1018 self.proxy_password = Some(SecretString::new(password.into().into()));
1019 self
1020 }
1021}
1022
1023#[cfg(test)]
1024#[allow(clippy::expect_used)]
1025mod tests {
1026 use super::*;
1027
1028 #[test]
1029 fn test_config_creation() {
1030 let config = VeracodeConfig::new("test_api_id", "test_api_key");
1031
1032 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
1033 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
1034 assert_eq!(config.base_url, "https://api.veracode.com");
1035 assert_eq!(config.rest_base_url, "https://api.veracode.com");
1036 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
1037 assert_eq!(config.region, VeracodeRegion::Commercial);
1038 assert!(config.validate_certificates); assert_eq!(config.retry_config.max_attempts, 5); }
1041
1042 #[test]
1043 fn test_european_region_config() {
1044 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1045 .with_region(VeracodeRegion::European);
1046
1047 assert_eq!(config.base_url, "https://api.veracode.eu");
1048 assert_eq!(config.rest_base_url, "https://api.veracode.eu");
1049 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
1050 assert_eq!(config.region, VeracodeRegion::European);
1051 }
1052
1053 #[test]
1054 fn test_federal_region_config() {
1055 let config =
1056 VeracodeConfig::new("test_api_id", "test_api_key").with_region(VeracodeRegion::Federal);
1057
1058 assert_eq!(config.base_url, "https://api.veracode.us");
1059 assert_eq!(config.rest_base_url, "https://api.veracode.us");
1060 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
1061 assert_eq!(config.region, VeracodeRegion::Federal);
1062 }
1063
1064 #[test]
1065 fn test_certificate_validation_disabled() {
1066 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1067 .with_certificate_validation_disabled();
1068
1069 assert!(!config.validate_certificates);
1070 }
1071
1072 #[test]
1073 fn test_veracode_credentials_debug_redaction() {
1074 let credentials = VeracodeCredentials::new(
1075 "test_api_id_123".to_string(),
1076 "test_api_key_456".to_string(),
1077 );
1078 let debug_output = format!("{credentials:?}");
1079
1080 assert!(debug_output.contains("VeracodeCredentials"));
1082 assert!(debug_output.contains("[REDACTED]"));
1083
1084 assert!(!debug_output.contains("test_api_id_123"));
1086 assert!(!debug_output.contains("test_api_key_456"));
1087 }
1088
1089 #[test]
1090 fn test_veracode_config_debug_redaction() {
1091 let config = VeracodeConfig::new("test_api_id_123", "test_api_key_456");
1092 let debug_output = format!("{config:?}");
1093
1094 assert!(debug_output.contains("VeracodeConfig"));
1096 assert!(debug_output.contains("credentials"));
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_credentials_access_methods() {
1106 let credentials = VeracodeCredentials::new(
1107 "test_api_id_123".to_string(),
1108 "test_api_key_456".to_string(),
1109 );
1110
1111 assert_eq!(credentials.expose_api_id(), "test_api_id_123");
1113 assert_eq!(credentials.expose_api_key(), "test_api_key_456");
1114 }
1115
1116 #[test]
1117 fn test_veracode_credentials_arc_pointers() {
1118 let credentials = VeracodeCredentials::new(
1119 "test_api_id_123".to_string(),
1120 "test_api_key_456".to_string(),
1121 );
1122
1123 let api_id_arc = credentials.api_id_ptr();
1125 let api_key_arc = credentials.api_key_ptr();
1126
1127 assert_eq!(api_id_arc.expose_secret(), "test_api_id_123");
1129 assert_eq!(api_key_arc.expose_secret(), "test_api_key_456");
1130 }
1131
1132 #[test]
1133 fn test_veracode_credentials_clone() {
1134 let credentials = VeracodeCredentials::new(
1135 "test_api_id_123".to_string(),
1136 "test_api_key_456".to_string(),
1137 );
1138 let cloned_credentials = credentials.clone();
1139
1140 assert_eq!(
1142 credentials.expose_api_id(),
1143 cloned_credentials.expose_api_id()
1144 );
1145 assert_eq!(
1146 credentials.expose_api_key(),
1147 cloned_credentials.expose_api_key()
1148 );
1149 }
1150
1151 #[test]
1152 fn test_config_with_arc_credentials() {
1153 use secrecy::SecretString;
1154 use std::sync::Arc;
1155
1156 let api_id_arc = Arc::new(SecretString::new("test_api_id".into()));
1157 let api_key_arc = Arc::new(SecretString::new("test_api_key".into()));
1158
1159 let config = VeracodeConfig::from_arc_credentials(api_id_arc, api_key_arc);
1160
1161 assert_eq!(config.credentials.expose_api_id(), "test_api_id");
1162 assert_eq!(config.credentials.expose_api_key(), "test_api_key");
1163 assert_eq!(config.region, VeracodeRegion::Commercial);
1164 }
1165
1166 #[test]
1167 fn test_error_display() {
1168 let error = VeracodeError::Authentication("Invalid API key".to_string());
1169 assert_eq!(format!("{error}"), "Authentication error: Invalid API key");
1170 }
1171
1172 #[test]
1173 fn test_error_from_reqwest() {
1174 fn _test_conversion(error: reqwest::Error) -> VeracodeError {
1179 VeracodeError::from(error)
1180 }
1181
1182 }
1185
1186 #[test]
1187 fn test_retry_config_default() {
1188 let config = RetryConfig::default();
1189 assert_eq!(config.max_attempts, 5);
1190 assert_eq!(config.initial_delay_ms, 1000);
1191 assert_eq!(config.max_delay_ms, 30000);
1192 assert_eq!(config.backoff_multiplier, 2.0);
1193 assert_eq!(config.max_total_delay_ms, 300000);
1194 assert!(config.jitter_enabled); }
1196
1197 #[test]
1198 fn test_retry_config_builder() {
1199 let config = RetryConfig::new()
1200 .with_max_attempts(5)
1201 .with_initial_delay(500)
1202 .with_max_delay(60000)
1203 .with_backoff_multiplier(1.5)
1204 .with_max_total_delay(600000);
1205
1206 assert_eq!(config.max_attempts, 5);
1207 assert_eq!(config.initial_delay_ms, 500);
1208 assert_eq!(config.max_delay_ms, 60000);
1209 assert_eq!(config.backoff_multiplier, 1.5);
1210 assert_eq!(config.max_total_delay_ms, 600000);
1211 }
1212
1213 #[test]
1214 fn test_retry_config_calculate_delay() {
1215 let config = RetryConfig::new()
1216 .with_initial_delay(1000)
1217 .with_backoff_multiplier(2.0)
1218 .with_max_delay(10000)
1219 .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); }
1229
1230 #[test]
1231 fn test_retry_config_is_retryable_error() {
1232 let config = RetryConfig::new();
1233
1234 assert!(
1236 config.is_retryable_error(&VeracodeError::InvalidResponse("temp error".to_string()))
1237 );
1238
1239 assert!(!config.is_retryable_error(&VeracodeError::Authentication("bad auth".to_string())));
1241 assert!(!config.is_retryable_error(&VeracodeError::Serialization(
1242 serde_json::from_str::<i32>("invalid").expect_err("should fail to deserialize")
1243 )));
1244 assert!(
1245 !config.is_retryable_error(&VeracodeError::InvalidConfig("bad config".to_string()))
1246 );
1247 assert!(!config.is_retryable_error(&VeracodeError::NotFound("not found".to_string())));
1248 assert!(
1249 !config.is_retryable_error(&VeracodeError::RetryExhausted("exhausted".to_string()))
1250 );
1251 }
1252
1253 #[test]
1254 fn test_veracode_config_with_retry_config() {
1255 let retry_config = RetryConfig::new().with_max_attempts(5);
1256 let config =
1257 VeracodeConfig::new("test_api_id", "test_api_key").with_retry_config(retry_config);
1258
1259 assert_eq!(config.retry_config.max_attempts, 5);
1260 }
1261
1262 #[test]
1263 fn test_veracode_config_with_retries_disabled() {
1264 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_retries_disabled();
1265
1266 assert_eq!(config.retry_config.max_attempts, 0);
1267 }
1268
1269 #[test]
1270 fn test_timeout_configuration() {
1271 let config = VeracodeConfig::new("test_api_id", "test_api_key");
1272
1273 assert_eq!(config.connect_timeout, 30);
1275 assert_eq!(config.request_timeout, 300);
1276 }
1277
1278 #[test]
1279 fn test_with_connect_timeout() {
1280 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_connect_timeout(60);
1281
1282 assert_eq!(config.connect_timeout, 60);
1283 assert_eq!(config.request_timeout, 300); }
1285
1286 #[test]
1287 fn test_with_request_timeout() {
1288 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_request_timeout(600);
1289
1290 assert_eq!(config.connect_timeout, 30); assert_eq!(config.request_timeout, 600);
1292 }
1293
1294 #[test]
1295 fn test_with_timeouts() {
1296 let config = VeracodeConfig::new("test_api_id", "test_api_key").with_timeouts(120, 1800);
1297
1298 assert_eq!(config.connect_timeout, 120);
1299 assert_eq!(config.request_timeout, 1800);
1300 }
1301
1302 #[test]
1303 fn test_timeout_configuration_chaining() {
1304 let config = VeracodeConfig::new("test_api_id", "test_api_key")
1305 .with_region(VeracodeRegion::European)
1306 .with_connect_timeout(45)
1307 .with_request_timeout(900)
1308 .with_retries_disabled();
1309
1310 assert_eq!(config.region, VeracodeRegion::European);
1311 assert_eq!(config.connect_timeout, 45);
1312 assert_eq!(config.request_timeout, 900);
1313 assert_eq!(config.retry_config.max_attempts, 0);
1314 }
1315
1316 #[test]
1317 fn test_retry_exhausted_error_display() {
1318 let error = VeracodeError::RetryExhausted("Failed after 3 attempts".to_string());
1319 assert_eq!(
1320 format!("{error}"),
1321 "Retry attempts exhausted: Failed after 3 attempts"
1322 );
1323 }
1324
1325 #[test]
1326 fn test_rate_limited_error_display_with_retry_after() {
1327 let error = VeracodeError::RateLimited {
1328 retry_after_seconds: Some(60),
1329 message: "Too Many Requests".to_string(),
1330 };
1331 assert_eq!(
1332 format!("{error}"),
1333 "Rate limit exceeded: Too Many Requests (retry after 60s)"
1334 );
1335 }
1336
1337 #[test]
1338 fn test_rate_limited_error_display_without_retry_after() {
1339 let error = VeracodeError::RateLimited {
1340 retry_after_seconds: None,
1341 message: "Too Many Requests".to_string(),
1342 };
1343 assert_eq!(format!("{error}"), "Rate limit exceeded: Too Many Requests");
1344 }
1345
1346 #[test]
1347 fn test_rate_limited_error_is_retryable() {
1348 let config = RetryConfig::new();
1349 let error = VeracodeError::RateLimited {
1350 retry_after_seconds: Some(60),
1351 message: "Rate limit exceeded".to_string(),
1352 };
1353 assert!(config.is_retryable_error(&error));
1354 }
1355
1356 #[test]
1357 fn test_calculate_rate_limit_delay_with_retry_after() {
1358 let config = RetryConfig::new();
1359 let delay = config.calculate_rate_limit_delay(Some(30));
1360 assert_eq!(delay.as_secs(), 30);
1361 }
1362
1363 #[test]
1364 #[cfg_attr(miri, ignore)] fn test_calculate_rate_limit_delay_without_retry_after() {
1366 let config = RetryConfig::new();
1367 let delay = config.calculate_rate_limit_delay(None);
1368
1369 assert!(delay.as_secs() >= 5);
1372 assert!(delay.as_secs() <= 65);
1373 }
1374
1375 #[test]
1376 fn test_rate_limit_config_defaults() {
1377 let config = RetryConfig::default();
1378 assert_eq!(config.rate_limit_buffer_seconds, 5);
1379 assert_eq!(config.rate_limit_max_attempts, 1);
1380 }
1381
1382 #[test]
1383 fn test_rate_limit_config_builders() {
1384 let config = RetryConfig::new()
1385 .with_rate_limit_buffer(10)
1386 .with_rate_limit_max_attempts(2);
1387
1388 assert_eq!(config.rate_limit_buffer_seconds, 10);
1389 assert_eq!(config.rate_limit_max_attempts, 2);
1390 }
1391
1392 #[test]
1393 #[cfg_attr(miri, ignore)] fn test_rate_limit_delay_uses_buffer() {
1395 let config = RetryConfig::new().with_rate_limit_buffer(15);
1396 let delay = config.calculate_rate_limit_delay(None);
1397
1398 assert!(delay.as_secs() >= 15);
1400 assert!(delay.as_secs() <= 75); }
1402
1403 #[test]
1404 fn test_jitter_disabled() {
1405 let config = RetryConfig::new().with_jitter_disabled();
1406 assert!(!config.jitter_enabled);
1407
1408 let delay1 = config.calculate_delay(2);
1410 let delay2 = config.calculate_delay(2);
1411 assert_eq!(delay1, delay2);
1412 }
1413
1414 #[test]
1415 fn test_jitter_enabled() {
1416 let config = RetryConfig::new(); assert!(config.jitter_enabled);
1418
1419 let base_delay = config.initial_delay_ms;
1421 let delay = config.calculate_delay(1);
1422
1423 #[allow(
1425 clippy::cast_possible_truncation,
1426 clippy::cast_sign_loss,
1427 clippy::cast_precision_loss
1428 )]
1429 let min_expected = (base_delay as f64 * 0.75) as u64;
1430 #[allow(
1431 clippy::cast_possible_truncation,
1432 clippy::cast_sign_loss,
1433 clippy::cast_precision_loss
1434 )]
1435 let max_expected = (base_delay as f64 * 1.25) as u64;
1436
1437 assert!(delay.as_millis() >= min_expected as u128);
1438 assert!(delay.as_millis() <= max_expected as u128);
1439 }
1440}
1441
1442#[cfg(test)]
1449mod security_tests {
1450 use super::*;
1451 use proptest::prelude::*;
1452
1453 proptest! {
1462 #![proptest_config(ProptestConfig {
1463 cases: if cfg!(miri) { 5 } else { 1000 },
1464 failure_persistence: None,
1465 .. ProptestConfig::default()
1466 })]
1467
1468 #[test]
1470 fn test_calculate_delay_no_overflow(
1471 attempt in 0u32..1000u32,
1472 initial_delay in 1u64..100_000u64,
1473 multiplier in 1.0f64..10.0f64,
1474 max_delay in 1u64..1_000_000u64,
1475 ) {
1476 let config = RetryConfig::new()
1477 .with_initial_delay(initial_delay)
1478 .with_backoff_multiplier(multiplier)
1479 .with_max_delay(max_delay)
1480 .with_jitter_disabled(); let delay = config.calculate_delay(attempt);
1484
1485 assert!(delay.as_millis() <= max_delay as u128);
1487
1488 assert!(delay <= Duration::from_millis(max_delay));
1491 }
1492
1493 #[test]
1495 fn test_calculate_delay_extreme_multipliers(
1496 attempt in 0u32..100u32,
1497 multiplier in 1.0f64..1000.0f64,
1498 ) {
1499 let config = RetryConfig::new()
1500 .with_initial_delay(1000)
1501 .with_backoff_multiplier(multiplier)
1502 .with_max_delay(60_000)
1503 .with_jitter_disabled();
1504
1505 let delay = config.calculate_delay(attempt);
1507
1508 assert!(delay.as_millis() <= 60_000);
1510 }
1511 }
1512
1513 proptest! {
1522 #![proptest_config(ProptestConfig {
1523 cases: if cfg!(miri) { 5 } else { 1000 },
1524 failure_persistence: None,
1525 .. ProptestConfig::default()
1526 })]
1527
1528 #[test]
1530 fn test_rate_limit_delay_with_retry_after(
1531 retry_after_seconds in 0u64..100_000u64,
1532 ) {
1533 let config = RetryConfig::new();
1534
1535 let delay = config.calculate_rate_limit_delay(Some(retry_after_seconds));
1537
1538 assert_eq!(delay.as_secs(), retry_after_seconds);
1540 }
1541
1542 #[test]
1544 fn test_rate_limit_delay_buffer_no_overflow(
1545 buffer_seconds in 0u64..10_000u64,
1546 ) {
1547 let config = RetryConfig::new()
1548 .with_rate_limit_buffer(buffer_seconds);
1549
1550 let delay = config.calculate_rate_limit_delay(None);
1552
1553 assert!(delay.as_secs() >= buffer_seconds);
1555
1556 assert!(delay.as_secs() <= 60_u64.saturating_add(buffer_seconds));
1558 }
1559 }
1560
1561 proptest! {
1570 #![proptest_config(ProptestConfig {
1571 cases: if cfg!(miri) { 5 } else { 500 },
1572 failure_persistence: None,
1573 .. ProptestConfig::default()
1574 })]
1575
1576 #[test]
1578 fn test_credentials_debug_never_exposes_secrets(
1579 api_id in "[a-zA-Z0-9]{10,256}",
1580 api_key in "[a-zA-Z0-9]{10,256}",
1581 ) {
1582 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1583 let debug_output = format!("{:?}", credentials);
1584
1585 assert!(debug_output.contains("VeracodeCredentials"));
1587
1588 assert!(debug_output.contains("[REDACTED]"));
1590
1591 assert!(!debug_output.contains(&api_id));
1593 assert!(!debug_output.contains(&api_key));
1594 }
1595
1596 #[test]
1598 fn test_credentials_arc_cloning_preserves_values(
1599 api_id in "[a-zA-Z0-9]{10,100}",
1600 api_key in "[a-zA-Z0-9]{10,100}",
1601 ) {
1602 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1603
1604 let api_id_arc1 = credentials.api_id_ptr();
1606 let api_id_arc2 = credentials.api_id_ptr();
1607 let api_key_arc1 = credentials.api_key_ptr();
1608 let api_key_arc2 = credentials.api_key_ptr();
1609
1610 assert_eq!(api_id_arc1.expose_secret(), &api_id);
1612 assert_eq!(api_id_arc2.expose_secret(), &api_id);
1613 assert_eq!(api_key_arc1.expose_secret(), &api_key);
1614 assert_eq!(api_key_arc2.expose_secret(), &api_key);
1615 }
1616
1617 #[test]
1619 fn test_credentials_expose_methods_correctness(
1620 api_id in "[a-zA-Z0-9]{10,256}",
1621 api_key in "[a-zA-Z0-9]{10,256}",
1622 ) {
1623 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
1624
1625 assert_eq!(credentials.expose_api_id(), api_id);
1627 assert_eq!(credentials.expose_api_key(), api_key);
1628 }
1629 }
1630
1631 proptest! {
1638 #![proptest_config(ProptestConfig {
1639 cases: if cfg!(miri) { 5 } else { 500 },
1640 failure_persistence: None,
1641 .. ProptestConfig::default()
1642 })]
1643
1644 #[test]
1646 fn test_config_debug_redacts_proxy_credentials(
1647 protocol in "(http|https)",
1648 username in "[a-zA-Z]{15,30}",
1650 password in "[a-zA-Z]{15,30}",
1651 host in "proxy\\d{1,3}\\.example\\.com",
1652 ) {
1653 let port = 8080u16;
1654 let proxy_url = format!("{}://{}:{}@{}:{}", protocol, username, password, host, port);
1655
1656 let config = VeracodeConfig::new("api_id", "api_key")
1657 .with_proxy(&proxy_url);
1658
1659 let debug_output = format!("{:?}", config);
1660
1661 assert!(debug_output.contains("[REDACTED]"));
1663
1664 assert!(!debug_output.contains(&username));
1668 assert!(!debug_output.contains(&password));
1669
1670 assert!(debug_output.contains(&host));
1672 }
1673
1674 #[test]
1676 fn test_config_debug_handles_utf8_safely(
1677 protocol in "(http|https)",
1679 creds in "[a-zA-Z0-9]{1,30}",
1680 host in "[a-z]{3,15}\\.[a-z]{2,5}",
1681 ) {
1682 let proxy_url = format!("{}://{}@{}", protocol, creds, host);
1684
1685 let config = VeracodeConfig::new("test", "test")
1686 .with_proxy(&proxy_url);
1687
1688 let debug_output = format!("{:?}", config);
1690
1691 assert!(debug_output.contains("VeracodeConfig"));
1693 }
1694
1695 #[test]
1697 fn test_config_debug_redacts_proxy_auth(
1698 proxy_username in "[a-zA-Z0-9]{10,50}",
1699 proxy_password in "[a-zA-Z0-9]{10,50}",
1700 ) {
1701 let config = VeracodeConfig::new("api_id", "api_key")
1702 .with_proxy("http://proxy.example.com:8080")
1703 .with_proxy_auth(proxy_username.clone(), proxy_password.clone());
1704
1705 let debug_output = format!("{:?}", config);
1706
1707 assert!(debug_output.contains("proxy_username"));
1709 assert!(debug_output.contains("proxy_password"));
1710 assert!(debug_output.contains("[REDACTED]"));
1711
1712 assert!(!debug_output.contains(&proxy_username));
1714 assert!(!debug_output.contains(&proxy_password));
1715 }
1716 }
1717
1718 proptest! {
1726 #![proptest_config(ProptestConfig {
1727 cases: if cfg!(miri) { 5 } else { 100 },
1728 failure_persistence: None,
1729 .. ProptestConfig::default()
1730 })]
1731
1732 #[test]
1734 fn test_config_region_urls_are_valid(
1735 region in prop::sample::select(vec![
1736 VeracodeRegion::Commercial,
1737 VeracodeRegion::European,
1738 VeracodeRegion::Federal,
1739 ])
1740 ) {
1741 let config = VeracodeConfig::new("test", "test")
1742 .with_region(region);
1743
1744 match region {
1746 VeracodeRegion::Commercial => {
1747 assert_eq!(config.rest_base_url, "https://api.veracode.com");
1748 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
1749 assert_eq!(config.base_url, config.rest_base_url);
1750 }
1751 VeracodeRegion::European => {
1752 assert_eq!(config.rest_base_url, "https://api.veracode.eu");
1753 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
1754 assert_eq!(config.base_url, config.rest_base_url);
1755 }
1756 VeracodeRegion::Federal => {
1757 assert_eq!(config.rest_base_url, "https://api.veracode.us");
1758 assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
1759 assert_eq!(config.base_url, config.rest_base_url);
1760 }
1761 }
1762
1763 assert!(config.rest_base_url.starts_with("https://"));
1765 assert!(config.xml_base_url.starts_with("https://"));
1766 assert!(config.base_url.starts_with("https://"));
1767 }
1768 }
1769
1770 proptest! {
1779 #![proptest_config(ProptestConfig {
1780 cases: if cfg!(miri) { 5 } else { 500 },
1781 failure_persistence: None,
1782 .. ProptestConfig::default()
1783 })]
1784
1785 #[test]
1787 fn test_config_timeout_no_overflow(
1788 connect_timeout in 1u64..100_000u64,
1789 request_timeout in 1u64..100_000u64,
1790 ) {
1791 let config = VeracodeConfig::new("test", "test")
1792 .with_timeouts(connect_timeout, request_timeout);
1793
1794 assert_eq!(config.connect_timeout, connect_timeout);
1796 assert_eq!(config.request_timeout, request_timeout);
1797
1798 assert!(config.connect_timeout > 0);
1800 assert!(config.request_timeout > 0);
1801 }
1802
1803 #[test]
1805 fn test_config_individual_timeout_setters(
1806 connect_timeout in 1u64..50_000u64,
1807 request_timeout in 1u64..50_000u64,
1808 ) {
1809 let config1 = VeracodeConfig::new("test", "test")
1810 .with_connect_timeout(connect_timeout);
1811 assert_eq!(config1.connect_timeout, connect_timeout);
1812 assert_eq!(config1.request_timeout, 300); let config2 = VeracodeConfig::new("test", "test")
1815 .with_request_timeout(request_timeout);
1816 assert_eq!(config2.connect_timeout, 30); assert_eq!(config2.request_timeout, request_timeout);
1818 }
1819 }
1820
1821 proptest! {
1830 #![proptest_config(ProptestConfig {
1831 cases: if cfg!(miri) { 5 } else { 500 },
1832 failure_persistence: None,
1833 .. ProptestConfig::default()
1834 })]
1835
1836 #[test]
1838 fn test_error_display_safe_messages(
1839 message in "[a-zA-Z0-9 ]{1,100}",
1840 ) {
1841 let errors = vec![
1842 VeracodeError::Authentication(message.clone()),
1843 VeracodeError::InvalidResponse(message.clone()),
1844 VeracodeError::InvalidConfig(message.clone()),
1845 VeracodeError::NotFound(message.clone()),
1846 VeracodeError::RetryExhausted(message.clone()),
1847 ];
1848
1849 for error in errors {
1850 let display = format!("{}", error);
1851
1852 assert!(!display.is_empty());
1854
1855 assert!(display.contains(&message));
1857 }
1858 }
1859
1860 #[test]
1862 fn test_rate_limited_error_display_safe(
1863 retry_after in prop::option::of(0u64..10_000u64),
1864 message in "[a-zA-Z0-9 ]{1,100}",
1865 ) {
1866 let error = VeracodeError::RateLimited {
1867 retry_after_seconds: retry_after,
1868 message: message.clone(),
1869 };
1870
1871 let display = format!("{}", error);
1872
1873 assert!(display.contains(&message));
1875 assert!(display.contains("Rate limit exceeded"));
1876
1877 if let Some(seconds) = retry_after {
1879 assert!(display.contains(&seconds.to_string()));
1880 }
1881 }
1882 }
1883}
1884
1885#[cfg(kani)]
1892mod kani_proofs {
1893 use super::*;
1894
1895 #[kani::proof]
1902 #[kani::unwind(10)] fn verify_calculate_delay_arithmetic_safety() {
1904 let initial_delay: u64 = kani::any();
1906 let max_delay: u64 = kani::any();
1907 let multiplier: f64 = kani::any();
1908 let attempt: u32 = kani::any();
1909
1910 kani::assume(initial_delay > 0);
1912 kani::assume(initial_delay <= 100_000);
1913 kani::assume(max_delay > 0);
1914 kani::assume(max_delay >= initial_delay);
1915 kani::assume(max_delay <= 1_000_000);
1916 kani::assume(multiplier >= 1.0);
1917 kani::assume(multiplier <= 10.0);
1918 kani::assume(multiplier.is_finite());
1919 kani::assume(attempt < 20); let config = RetryConfig::new()
1922 .with_initial_delay(initial_delay)
1923 .with_backoff_multiplier(multiplier)
1924 .with_max_delay(max_delay)
1925 .with_jitter_disabled();
1926
1927 let delay = config.calculate_delay(attempt);
1929
1930 assert!(delay.as_millis() <= max_delay as u128);
1932
1933 assert!(delay.as_secs() < u64::MAX);
1935
1936 if attempt == 0 {
1938 assert_eq!(delay.as_millis(), 0);
1939 }
1940 }
1941
1942 #[kani::proof]
1951 fn verify_rate_limit_delay_safety() {
1952 let buffer_seconds: u64 = kani::any();
1953 let retry_after_seconds: Option<u64> = kani::any();
1954
1955 kani::assume(buffer_seconds <= 10_000);
1957 if let Some(secs) = retry_after_seconds {
1958 kani::assume(secs <= 100_000);
1959 }
1960
1961 if let Some(expected_secs) = retry_after_seconds {
1963 let delay = Duration::from_secs(expected_secs);
1964 assert_eq!(delay.as_secs(), expected_secs);
1966 }
1967
1968 let current_second: u64 = kani::any();
1971 kani::assume(current_second < 60);
1972
1973 let seconds_until_next_minute = 60_u64.saturating_sub(current_second);
1975 let total_delay = seconds_until_next_minute.saturating_add(buffer_seconds);
1976
1977 assert!(total_delay >= buffer_seconds);
1979
1980 assert!(total_delay <= 60 + buffer_seconds);
1982
1983 }
1986
1987 #[kani::proof]
1995 #[kani::unwind(50)]
1996 fn verify_credentials_arc_memory_safety() {
1997 let api_id = "test_api_id".to_string();
1999 let api_key = "test_key123".to_string();
2000
2001 let credentials = VeracodeCredentials::new(api_id.clone(), api_key.clone());
2002
2003 let api_id_arc1 = credentials.api_id_ptr();
2005 let api_id_arc2 = credentials.api_id_ptr();
2006
2007 assert_eq!(
2009 Arc::as_ptr(&api_id_arc1) as *const (),
2010 Arc::as_ptr(&api_id_arc2) as *const ()
2011 );
2012
2013 assert_eq!(api_id_arc1.expose_secret(), api_id_arc2.expose_secret());
2015
2016 assert_eq!(credentials.expose_api_id(), &api_id);
2018 assert_eq!(credentials.expose_api_key(), &api_key);
2019
2020 let cloned = credentials.clone();
2022 assert_eq!(cloned.expose_api_id(), credentials.expose_api_id());
2023 assert_eq!(cloned.expose_api_key(), credentials.expose_api_key());
2024
2025 let cloned2 = cloned.clone();
2027 let _ = cloned2.expose_api_id();
2028 let _ = cloned2.expose_api_key();
2029
2030 }
2032
2033 #[kani::proof]
2041 fn verify_proxy_url_redaction_safety() {
2042 let has_at_sign: bool = kani::any();
2044
2045 let url = if has_at_sign {
2047 "u:p@h".to_string()
2048 } else {
2049 "http://h".to_string()
2050 };
2051
2052 let config = VeracodeConfig::new("t", "k").with_proxy(&url);
2054
2055 assert!(config.proxy_url.is_some());
2057
2058 let _ = config.proxy_url;
2061 }
2062
2063 #[kani::proof]
2071 #[kani::unwind(10)] fn verify_region_url_construction() {
2073 let config = VeracodeConfig::new("a", "b").with_region(VeracodeRegion::Commercial);
2079
2080 assert!(!config.rest_base_url.is_empty());
2082 assert!(!config.xml_base_url.is_empty());
2083
2084 assert!(config.rest_base_url.len() < 100);
2086 assert!(config.xml_base_url.len() < 100);
2087
2088 assert!(matches!(config.region, VeracodeRegion::Commercial));
2090 }
2091
2092 #[kani::proof]
2098 #[kani::unwind(10)]
2099 fn verify_european_region_url_construction() {
2100 let config = VeracodeConfig::new("a", "b").with_region(VeracodeRegion::European);
2101
2102 assert!(!config.rest_base_url.is_empty());
2104 assert!(!config.xml_base_url.is_empty());
2105
2106 assert!(config.rest_base_url.len() < 100);
2108 assert!(config.xml_base_url.len() < 100);
2109
2110 assert!(matches!(config.region, VeracodeRegion::European));
2112 }
2113
2114 #[kani::proof]
2120 #[kani::unwind(10)]
2121 fn verify_federal_region_url_construction() {
2122 let config = VeracodeConfig::new("a", "b").with_region(VeracodeRegion::Federal);
2123
2124 assert!(!config.rest_base_url.is_empty());
2126 assert!(!config.xml_base_url.is_empty());
2127
2128 assert!(config.rest_base_url.len() < 100);
2130 assert!(config.xml_base_url.len() < 100);
2131
2132 assert!(matches!(config.region, VeracodeRegion::Federal));
2134 }
2135}