veracode_platform/
policy.rs

1//! Policy API module for Veracode Platform
2//!
3//! This module provides functionality for managing security policies, policy compliance,
4//! and policy scan operations within the Veracode platform.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use chrono::{DateTime, Utc};
9
10use crate::{VeracodeClient, VeracodeError};
11
12/// Represents a security policy in the Veracode platform
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct SecurityPolicy {
15    /// Globally unique identifier for the policy
16    pub guid: String,
17    /// Policy name
18    pub name: String,
19    /// Policy description
20    pub description: Option<String>,
21    /// Policy type (CUSTOMER, BUILTIN, STANDARD)
22    #[serde(rename = "type")]
23    pub policy_type: String,
24    /// Policy version number
25    pub version: u32,
26    /// When the policy was created
27    pub created: Option<DateTime<Utc>>,
28    /// Who modified the policy last
29    pub modified_by: Option<String>,
30    /// Organization ID this policy belongs to
31    pub organization_id: Option<u64>,
32    /// Policy category (APPLICATION, etc.)
33    pub category: String,
34    /// Whether this is a vendor policy
35    pub vendor_policy: bool,
36    /// Scan frequency rules
37    pub scan_frequency_rules: Vec<ScanFrequencyRule>,
38    /// Finding rules for the policy
39    pub finding_rules: Vec<FindingRule>,
40    /// Custom severities defined for this policy
41    pub custom_severities: Vec<serde_json::Value>,
42    /// Grace periods for different severity levels
43    pub sev5_grace_period: u32,
44    pub sev4_grace_period: u32,
45    pub sev3_grace_period: u32,
46    pub sev2_grace_period: u32,
47    pub sev1_grace_period: u32,
48    pub sev0_grace_period: u32,
49    /// Score grace period
50    pub score_grace_period: u32,
51    /// SCA blacklist grace period
52    pub sca_blacklist_grace_period: u32,
53    /// SCA grace periods (nullable)
54    pub sca_grace_periods: Option<serde_json::Value>,
55    /// Evaluation date
56    pub evaluation_date: Option<DateTime<Utc>>,
57    /// Evaluation date type
58    pub evaluation_date_type: Option<String>,
59    /// Policy capabilities
60    pub capabilities: Vec<String>,
61    /// Links for API navigation
62    #[serde(rename = "_links")]
63    pub links: Option<serde_json::Value>,
64}
65
66/// Policy compliance status
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "UPPERCASE")]
69pub enum PolicyComplianceStatus {
70    /// Application passes all policy requirements
71    Pass,
72    /// Application fails policy requirements
73    Fail,
74    /// Policy compliance check is pending
75    Pending,
76    /// Policy compliance status is not determined
77    NotDetermined,
78    /// Policy compliance check resulted in error
79    Error,
80}
81
82/// Individual policy rule
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct PolicyRule {
85    /// Rule identifier
86    pub id: String,
87    /// Rule name
88    pub name: String,
89    /// Rule description
90    pub description: Option<String>,
91    /// Rule type (e.g., severity, category)
92    pub rule_type: String,
93    /// Rule criteria
94    pub criteria: Option<serde_json::Value>,
95    /// Whether the rule is enabled
96    pub enabled: bool,
97    /// Rule severity level
98    pub severity: Option<String>,
99}
100
101/// Policy compliance thresholds
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct PolicyThresholds {
104    /// Maximum allowed Very High severity flaws
105    pub very_high: Option<u32>,
106    /// Maximum allowed High severity flaws
107    pub high: Option<u32>,
108    /// Maximum allowed Medium severity flaws
109    pub medium: Option<u32>,
110    /// Maximum allowed Low severity flaws
111    pub low: Option<u32>,
112    /// Maximum allowed Very Low severity flaws
113    pub very_low: Option<u32>,
114    /// Overall score threshold
115    pub score_threshold: Option<f64>,
116}
117
118/// Policy scan request
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct PolicyScanRequest {
121    /// Application GUID to scan
122    pub application_guid: String,
123    /// Policy GUID to apply
124    pub policy_guid: String,
125    /// Scan type (static, dynamic, sca)
126    pub scan_type: ScanType,
127    /// Optional sandbox GUID for sandbox scans
128    pub sandbox_guid: Option<String>,
129    /// Scan configuration
130    pub config: Option<PolicyScanConfig>,
131}
132
133/// Types of scans for policy evaluation
134#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "lowercase")]
136pub enum ScanType {
137    /// Static Application Security Testing
138    Static,
139    /// Dynamic Application Security Testing
140    Dynamic,
141    /// Software Composition Analysis
142    Sca,
143    /// Manual penetration testing
144    Manual,
145}
146
147/// Configuration for policy scans
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct PolicyScanConfig {
150    /// Whether to auto-submit the scan
151    pub auto_submit: Option<bool>,
152    /// Scan timeout in minutes
153    pub timeout_minutes: Option<u32>,
154    /// Include third-party components
155    pub include_third_party: Option<bool>,
156    /// Scan modules to include
157    pub modules: Option<Vec<String>>,
158}
159
160/// Policy scan result
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct PolicyScanResult {
163    /// Scan identifier
164    pub scan_id: u64,
165    /// Application GUID
166    pub application_guid: String,
167    /// Policy GUID used for evaluation
168    pub policy_guid: String,
169    /// Scan status
170    pub status: ScanStatus,
171    /// Scan type
172    pub scan_type: ScanType,
173    /// When the scan was initiated
174    pub started: DateTime<Utc>,
175    /// When the scan completed
176    pub completed: Option<DateTime<Utc>>,
177    /// Policy compliance result
178    pub compliance_result: Option<PolicyComplianceResult>,
179    /// Findings summary
180    pub findings_summary: Option<FindingsSummary>,
181    /// URL to detailed results
182    pub results_url: Option<String>,
183}
184
185/// Status of a policy scan
186#[derive(Debug, Clone, Serialize, Deserialize)]
187#[serde(rename_all = "UPPERCASE")]
188pub enum ScanStatus {
189    /// Scan is queued for processing
190    Queued,
191    /// Scan is currently running
192    Running,
193    /// Scan completed successfully
194    Completed,
195    /// Scan failed
196    Failed,
197    /// Scan was cancelled
198    Cancelled,
199    /// Scan timed out
200    Timeout,
201}
202
203/// Policy compliance evaluation result
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct PolicyComplianceResult {
206    /// Overall compliance status
207    pub status: PolicyComplianceStatus,
208    /// Compliance score (0-100)
209    pub score: Option<f64>,
210    /// Whether scan passed policy requirements
211    pub passed: bool,
212    /// Detailed compliance breakdown
213    pub breakdown: Option<ComplianceBreakdown>,
214    /// Policy violations found
215    pub violations: Option<Vec<PolicyViolation>>,
216    /// Compliance summary message
217    pub summary: Option<String>,
218}
219
220/// Detailed compliance breakdown by severity
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct ComplianceBreakdown {
223    /// Very High severity findings count
224    pub very_high: u32,
225    /// High severity findings count
226    pub high: u32,
227    /// Medium severity findings count
228    pub medium: u32,
229    /// Low severity findings count
230    pub low: u32,
231    /// Very Low severity findings count
232    pub very_low: u32,
233    /// Total findings count
234    pub total: u32,
235}
236
237/// Policy violation details
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct PolicyViolation {
240    /// Violation type
241    pub violation_type: String,
242    /// Severity of the violation
243    pub severity: String,
244    /// Description of the violation
245    pub description: String,
246    /// Count of this violation type
247    pub count: u32,
248    /// Threshold that was exceeded
249    pub threshold_exceeded: Option<u32>,
250    /// Actual value that caused the violation
251    pub actual_value: Option<u32>,
252}
253
254/// Summary of findings from a policy scan
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct FindingsSummary {
257    /// Total number of findings
258    pub total: u32,
259    /// Number of open findings
260    pub open: u32,
261    /// Number of fixed findings
262    pub fixed: u32,
263    /// Number of findings by severity
264    pub by_severity: HashMap<String, u32>,
265    /// Number of findings by category
266    pub by_category: Option<HashMap<String, u32>>,
267}
268
269/// Scan frequency rule for policies
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct ScanFrequencyRule {
272    /// Type of scan this rule applies to
273    pub scan_type: String,
274    /// How frequently scans should be performed
275    pub frequency: String,
276}
277
278/// Finding rule for policies
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct FindingRule {
281    /// Type of finding rule
282    #[serde(rename = "type")]
283    pub rule_type: String,
284    /// Scan types this rule applies to
285    pub scan_type: Vec<String>,
286    /// Rule value/threshold
287    pub value: String,
288    /// Advanced options for the rule
289    pub advanced_options: Option<serde_json::Value>,
290}
291
292/// Advanced options for finding rules
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct FindingRuleAdvancedOptions {
295    /// Override severity
296    pub override_severity: Option<bool>,
297    /// Build action (WARNING, ERROR, etc.)
298    pub build_action: Option<String>,
299    /// Component dependency type
300    pub component_dependency: Option<String>,
301    /// Vulnerable methods setting
302    pub vulnerable_methods: Option<String>,
303    /// Selected licenses
304    pub selected_licenses: Option<Vec<String>>,
305    /// Override severity level
306    pub override_severity_level: Option<String>,
307    /// Whether to allow non-OSS licenses
308    pub allowed_nonoss_licenses: Option<bool>,
309    /// Whether to allow unrecognized licenses
310    pub allowed_unrecognized_licenses: Option<bool>,
311    /// Whether all licenses must meet requirement
312    pub all_licenses_must_meet_requirement: Option<bool>,
313    /// Whether this is a blocklist
314    pub is_blocklist: Option<bool>,
315}
316
317/// Query parameters for listing policies
318#[derive(Debug, Clone, Default)]
319pub struct PolicyListParams {
320    /// Filter by policy name
321    pub name: Option<String>,
322    /// Filter by policy type
323    pub policy_type: Option<String>,
324    /// Filter by active status
325    pub is_active: Option<bool>,
326    /// Include only default policies
327    pub default_only: Option<bool>,
328    /// Page number for pagination
329    pub page: Option<u32>,
330    /// Number of items per page
331    pub size: Option<u32>,
332}
333
334impl PolicyListParams {
335    /// Convert to query parameters for HTTP requests
336    pub fn to_query_params(&self) -> Vec<(String, String)> {
337        let mut params = Vec::new();
338        
339        if let Some(name) = &self.name {
340            params.push(("name".to_string(), name.clone()));
341        }
342        if let Some(policy_type) = &self.policy_type {
343            params.push(("type".to_string(), policy_type.clone()));
344        }
345        if let Some(is_active) = self.is_active {
346            params.push(("active".to_string(), is_active.to_string()));
347        }
348        if let Some(default_only) = self.default_only {
349            params.push(("default".to_string(), default_only.to_string()));
350        }
351        if let Some(page) = self.page {
352            params.push(("page".to_string(), page.to_string()));
353        }
354        if let Some(size) = self.size {
355            params.push(("size".to_string(), size.to_string()));
356        }
357        
358        params
359    }
360}
361
362/// Response wrapper for policy list operations
363#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct PolicyListResponse {
365    #[serde(rename = "_embedded")]
366    pub embedded: Option<PolicyEmbedded>,
367    pub page: Option<PageInfo>,
368    #[serde(rename = "_links")]
369    pub links: Option<serde_json::Value>,
370}
371
372/// Embedded policies in the list response
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct PolicyEmbedded {
375    #[serde(rename = "policy_versions")]
376    pub policy_versions: Vec<SecurityPolicy>,
377}
378
379/// Page information for paginated responses
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct PageInfo {
382    pub size: u32,
383    pub number: u32,
384    pub total_elements: u32,
385    pub total_pages: u32,
386}
387
388/// Policy-specific error types
389#[derive(Debug)]
390pub enum PolicyError {
391    /// Veracode API error
392    Api(VeracodeError),
393    /// Policy not found (404)
394    NotFound,
395    /// Invalid policy configuration (400)
396    InvalidConfig(String),
397    /// Policy scan failed
398    ScanFailed(String),
399    /// Policy evaluation error
400    EvaluationError(String),
401    /// Insufficient permissions (403)
402    PermissionDenied,
403    /// Authentication required (401)
404    Unauthorized,
405    /// Internal server error (500)
406    InternalServerError,
407    /// Policy compliance check timeout
408    Timeout,
409}
410
411impl std::fmt::Display for PolicyError {
412    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413        match self {
414            PolicyError::Api(err) => write!(f, "API error: {err}"),
415            PolicyError::NotFound => write!(f, "Policy not found"),
416            PolicyError::InvalidConfig(msg) => write!(f, "Invalid policy configuration: {msg}"),
417            PolicyError::ScanFailed(msg) => write!(f, "Policy scan failed: {msg}"),
418            PolicyError::EvaluationError(msg) => write!(f, "Policy evaluation error: {msg}"),
419            PolicyError::PermissionDenied => write!(f, "Insufficient permissions for policy operation"),
420            PolicyError::Unauthorized => write!(f, "Authentication required - invalid API credentials"),
421            PolicyError::InternalServerError => write!(f, "Internal server error occurred"),
422            PolicyError::Timeout => write!(f, "Policy operation timed out"),
423        }
424    }
425}
426
427impl std::error::Error for PolicyError {}
428
429impl From<VeracodeError> for PolicyError {
430    fn from(err: VeracodeError) -> Self {
431        PolicyError::Api(err)
432    }
433}
434
435impl From<reqwest::Error> for PolicyError {
436    fn from(err: reqwest::Error) -> Self {
437        PolicyError::Api(VeracodeError::Http(err))
438    }
439}
440
441impl From<serde_json::Error> for PolicyError {
442    fn from(err: serde_json::Error) -> Self {
443        PolicyError::Api(VeracodeError::Serialization(err))
444    }
445}
446
447/// Veracode Policy API operations
448pub struct PolicyApi<'a> {
449    client: &'a VeracodeClient,
450}
451
452impl<'a> PolicyApi<'a> {
453    /// Create a new PolicyApi instance
454    pub fn new(client: &'a VeracodeClient) -> Self {
455        Self { client }
456    }
457
458    /// List all available security policies
459    ///
460    /// # Arguments
461    ///
462    /// * `params` - Optional query parameters for filtering
463    ///
464    /// # Returns
465    ///
466    /// A `Result` containing a list of policies or an error.
467    pub async fn list_policies(
468        &self,
469        params: Option<PolicyListParams>,
470    ) -> Result<Vec<SecurityPolicy>, PolicyError> {
471        let endpoint = "/appsec/v1/policies";
472        
473        let query_params = params.map(|p| p.to_query_params());
474        
475        let response = self.client.get(endpoint, query_params.as_deref()).await?;
476        
477        let status = response.status().as_u16();
478        match status {
479            200 => {
480                let policy_response: PolicyListResponse = response.json().await?;
481                let policies = policy_response.embedded
482                    .map(|e| e.policy_versions)
483                    .unwrap_or_default();
484                
485                Ok(policies)
486            }
487            400 => {
488                let error_text = response.text().await.unwrap_or_default();
489                Err(PolicyError::InvalidConfig(error_text))
490            }
491            401 => Err(PolicyError::Unauthorized),
492            403 => Err(PolicyError::PermissionDenied),
493            404 => Err(PolicyError::NotFound),
494            500 => Err(PolicyError::InternalServerError),
495            _ => {
496                let error_text = response.text().await.unwrap_or_default();
497                Err(PolicyError::Api(VeracodeError::InvalidResponse(
498                    format!("HTTP {status}: {error_text}")
499                )))
500            }
501        }
502    }
503
504    /// Get a specific policy by GUID
505    ///
506    /// # Arguments
507    ///
508    /// * `policy_guid` - The GUID of the policy
509    ///
510    /// # Returns
511    ///
512    /// A `Result` containing the policy or an error.
513    pub async fn get_policy(&self, policy_guid: &str) -> Result<SecurityPolicy, PolicyError> {
514        let endpoint = format!("/appsec/v1/policies/{policy_guid}");
515
516        let response = self.client.get(&endpoint, None).await?;
517
518        let status = response.status().as_u16();
519        match status {
520            200 => {
521                let policy: SecurityPolicy = response.json().await?;
522                Ok(policy)
523            }
524            400 => {
525                let error_text = response.text().await.unwrap_or_default();
526                Err(PolicyError::InvalidConfig(error_text))
527            }
528            401 => Err(PolicyError::Unauthorized),
529            403 => Err(PolicyError::PermissionDenied),
530            404 => Err(PolicyError::NotFound),
531            500 => Err(PolicyError::InternalServerError),
532            _ => {
533                let error_text = response.text().await.unwrap_or_default();
534                Err(PolicyError::Api(VeracodeError::InvalidResponse(
535                    format!("HTTP {status}: {error_text}")
536                )))
537            }
538        }
539    }
540
541    /// Get the default policy for the organization
542    ///
543    /// # Returns
544    ///
545    /// A `Result` containing the default policy or an error.
546    pub async fn get_default_policy(&self) -> Result<SecurityPolicy, PolicyError> {
547        let params = PolicyListParams {
548            default_only: Some(true),
549            ..Default::default()
550        };
551
552        let policies = self.list_policies(Some(params)).await?;
553        // Note: Default policy identification may need to be handled differently
554        // based on the actual API response structure
555        policies.into_iter()
556            .find(|p| p.policy_type == "CUSTOMER" && p.organization_id.is_some())
557            .ok_or(PolicyError::NotFound)
558    }
559
560    /// Evaluate policy compliance for an application
561    ///
562    /// # Arguments
563    ///
564    /// * `application_guid` - The GUID of the application
565    /// * `policy_guid` - The GUID of the policy to evaluate against
566    /// * `sandbox_guid` - Optional sandbox GUID for sandbox evaluation
567    ///
568    /// # Returns
569    ///
570    /// A `Result` containing the compliance result or an error.
571    pub async fn evaluate_policy_compliance(
572        &self,
573        application_guid: &str,
574        policy_guid: &str,
575        sandbox_guid: Option<&str>,
576    ) -> Result<PolicyComplianceResult, PolicyError> {
577        let endpoint = if let Some(sandbox) = sandbox_guid {
578            format!(
579                "/appsec/v1/applications/{application_guid}/sandboxes/{sandbox}/policy/{policy_guid}/compliance"
580            )
581        } else {
582            format!(
583                "/appsec/v1/applications/{application_guid}/policy/{policy_guid}/compliance"
584            )
585        };
586
587        let response = self.client.get(&endpoint, None).await?;
588
589        let status = response.status().as_u16();
590        match status {
591            200 => {
592                let compliance: PolicyComplianceResult = response.json().await?;
593                Ok(compliance)
594            }
595            404 => Err(PolicyError::NotFound),
596            _ => {
597                let error_text = response.text().await.unwrap_or_default();
598                Err(PolicyError::Api(VeracodeError::InvalidResponse(
599                    format!("HTTP {status}: {error_text}")
600                )))
601            }
602        }
603    }
604
605    /// Initiate a policy scan for an application
606    ///
607    /// # Arguments
608    ///
609    /// * `request` - The policy scan request
610    ///
611    /// # Returns
612    ///
613    /// A `Result` containing the scan result or an error.
614    pub async fn initiate_policy_scan(
615        &self,
616        request: PolicyScanRequest,
617    ) -> Result<PolicyScanResult, PolicyError> {
618        let endpoint = "/appsec/v1/policy-scans";
619
620        let response = self.client.post(endpoint, Some(&request)).await?;
621
622        let status = response.status().as_u16();
623        match status {
624            200 | 201 => {
625                let scan_result: PolicyScanResult = response.json().await?;
626                Ok(scan_result)
627            }
628            400 => {
629                let error_text = response.text().await.unwrap_or_default();
630                Err(PolicyError::InvalidConfig(error_text))
631            }
632            404 => Err(PolicyError::NotFound),
633            _ => {
634                let error_text = response.text().await.unwrap_or_default();
635                Err(PolicyError::Api(VeracodeError::InvalidResponse(
636                    format!("HTTP {status}: {error_text}")
637                )))
638            }
639        }
640    }
641
642    /// Get policy scan status and results
643    ///
644    /// # Arguments
645    ///
646    /// * `scan_id` - The ID of the policy scan
647    ///
648    /// # Returns
649    ///
650    /// A `Result` containing the scan result or an error.
651    pub async fn get_policy_scan_result(
652        &self,
653        scan_id: u64,
654    ) -> Result<PolicyScanResult, PolicyError> {
655        let endpoint = format!("/appsec/v1/policy-scans/{scan_id}");
656
657        let response = self.client.get(&endpoint, None).await?;
658
659        let status = response.status().as_u16();
660        match status {
661            200 => {
662                let scan_result: PolicyScanResult = response.json().await?;
663                Ok(scan_result)
664            }
665            404 => Err(PolicyError::NotFound),
666            _ => {
667                let error_text = response.text().await.unwrap_or_default();
668                Err(PolicyError::Api(VeracodeError::InvalidResponse(
669                    format!("HTTP {status}: {error_text}")
670                )))
671            }
672        }
673    }
674
675    /// Check if a policy scan is complete
676    ///
677    /// # Arguments
678    ///
679    /// * `scan_id` - The ID of the policy scan
680    ///
681    /// # Returns
682    ///
683    /// A `Result` containing a boolean indicating completion status.
684    pub async fn is_policy_scan_complete(&self, scan_id: u64) -> Result<bool, PolicyError> {
685        let scan_result = self.get_policy_scan_result(scan_id).await?;
686        Ok(matches!(scan_result.status, ScanStatus::Completed | ScanStatus::Failed | ScanStatus::Cancelled))
687    }
688
689    /// Get policy violations for an application
690    ///
691    /// # Arguments
692    ///
693    /// * `application_guid` - The GUID of the application
694    /// * `policy_guid` - The GUID of the policy
695    /// * `sandbox_guid` - Optional sandbox GUID
696    ///
697    /// # Returns
698    ///
699    /// A `Result` containing policy violations or an error.
700    pub async fn get_policy_violations(
701        &self,
702        application_guid: &str,
703        policy_guid: &str,
704        sandbox_guid: Option<&str>,
705    ) -> Result<Vec<PolicyViolation>, PolicyError> {
706        let compliance = self.evaluate_policy_compliance(application_guid, policy_guid, sandbox_guid).await?;
707        Ok(compliance.violations.unwrap_or_default())
708    }
709}
710
711/// Convenience methods for common policy operations
712impl<'a> PolicyApi<'a> {
713    /// Check if an application passes policy compliance
714    ///
715    /// # Arguments
716    ///
717    /// * `application_guid` - The GUID of the application
718    /// * `policy_guid` - The GUID of the policy
719    ///
720    /// # Returns
721    ///
722    /// A `Result` containing a boolean indicating compliance status.
723    pub async fn is_application_compliant(
724        &self,
725        application_guid: &str,
726        policy_guid: &str,
727    ) -> Result<bool, PolicyError> {
728        let compliance = self.evaluate_policy_compliance(application_guid, policy_guid, None).await?;
729        Ok(compliance.passed)
730    }
731
732    /// Get compliance score for an application
733    ///
734    /// # Arguments
735    ///
736    /// * `application_guid` - The GUID of the application
737    /// * `policy_guid` - The GUID of the policy
738    ///
739    /// # Returns
740    ///
741    /// A `Result` containing the compliance score or an error.
742    pub async fn get_compliance_score(
743        &self,
744        application_guid: &str,
745        policy_guid: &str,
746    ) -> Result<Option<f64>, PolicyError> {
747        let compliance = self.evaluate_policy_compliance(application_guid, policy_guid, None).await?;
748        Ok(compliance.score)
749    }
750
751    /// Get active policies for the organization
752    ///
753    /// # Returns
754    ///
755    /// A `Result` containing a list of active policies or an error.
756    pub async fn get_active_policies(&self) -> Result<Vec<SecurityPolicy>, PolicyError> {
757        // Note: The active/inactive concept may need to be handled differently
758        // based on the actual API response structure
759        let policies = self.list_policies(None).await?;
760        Ok(policies) // Return all policies for now
761    }
762}
763
764#[cfg(test)]
765mod tests {
766    use super::*;
767
768    #[test]
769    fn test_policy_list_params_to_query() {
770        let params = PolicyListParams {
771            name: Some("test-policy".to_string()),
772            is_active: Some(true),
773            page: Some(1),
774            size: Some(10),
775            ..Default::default()
776        };
777
778        let query_params = params.to_query_params();
779        assert_eq!(query_params.len(), 4);
780        assert!(query_params.contains(&("name".to_string(), "test-policy".to_string())));
781        assert!(query_params.contains(&("active".to_string(), "true".to_string())));
782        assert!(query_params.contains(&("page".to_string(), "1".to_string())));
783        assert!(query_params.contains(&("size".to_string(), "10".to_string())));
784    }
785
786    #[test]
787    fn test_policy_error_display() {
788        let error = PolicyError::NotFound;
789        assert_eq!(error.to_string(), "Policy not found");
790
791        let error = PolicyError::InvalidConfig("test".to_string());
792        assert_eq!(error.to_string(), "Invalid policy configuration: test");
793
794        let error = PolicyError::Timeout;
795        assert_eq!(error.to_string(), "Policy operation timed out");
796    }
797
798    #[test]
799    fn test_scan_type_serialization() {
800        let scan_type = ScanType::Static;
801        let json = serde_json::to_string(&scan_type).unwrap();
802        assert_eq!(json, "\"static\"");
803
804        let deserialized: ScanType = serde_json::from_str(&json).unwrap();
805        assert!(matches!(deserialized, ScanType::Static));
806    }
807
808    #[test]
809    fn test_policy_compliance_status_serialization() {
810        let status = PolicyComplianceStatus::Pass;
811        let json = serde_json::to_string(&status).unwrap();
812        assert_eq!(json, "\"PASS\"");
813
814        let deserialized: PolicyComplianceStatus = serde_json::from_str(&json).unwrap();
815        assert!(matches!(deserialized, PolicyComplianceStatus::Pass));
816    }
817}