Skip to main content

oxidize_pdf/verification/
iso_matrix.rs

1//! ISO Compliance Matrix Processing
2//!
3//! This module loads and processes the ISO compliance matrix from TOML format,
4//! providing access to requirements, verification levels, and compliance tracking.
5//!
6//! DUAL FILE SYSTEM:
7//! - ISO_COMPLIANCE_MATRIX.toml: Immutable definitions (NEVER modify)
8//! - ISO_VERIFICATION_STATUS.toml: Mutable verification state (ONLY this changes)
9
10#![allow(deprecated)]
11
12use crate::error::{PdfError, Result};
13use crate::verification::{IsoRequirement, VerificationLevel};
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::fs;
17
18/// Complete ISO compliance matrix loaded from TOML (IMMUTABLE)
19#[derive(Debug, Clone, Deserialize, Serialize)]
20pub struct IsoMatrix {
21    pub metadata: MatrixMetadata,
22    #[serde(flatten)]
23    pub sections: HashMap<String, IsoSection>,
24    pub overall_summary: OverallSummary,
25    pub validation_tools: ValidationTools,
26}
27
28/// Verification status loaded from separate TOML file (MUTABLE)
29#[derive(Debug, Clone, Deserialize, Serialize)]
30pub struct VerificationStatus {
31    pub metadata: StatusMetadata,
32    pub status: HashMap<String, RequirementStatus>,
33    pub statistics: StatusStatistics,
34}
35
36/// Combined system that reads both files
37#[derive(Debug, Clone)]
38pub struct ComplianceSystem {
39    pub matrix: IsoMatrix,          // Immutable definitions
40    pub status: VerificationStatus, // Current state
41}
42
43/// Metadata for verification status file
44#[derive(Debug, Clone, Deserialize, Serialize)]
45pub struct StatusMetadata {
46    pub last_updated: String,
47    pub matrix_version: String,
48    pub total_requirements: u32,
49    pub note: String,
50    pub warning: String,
51}
52
53/// Status of individual requirement
54#[derive(Debug, Clone, Deserialize, Serialize)]
55pub struct RequirementStatus {
56    pub level: u8,
57    pub implementation: String,
58    pub test_file: String,
59    pub verified: bool,
60    pub last_checked: String,
61    pub notes: String,
62}
63
64/// Overall statistics from status file
65#[derive(Debug, Clone, Deserialize, Serialize)]
66pub struct StatusStatistics {
67    pub level_0_count: u32,
68    pub level_1_count: u32,
69    pub level_2_count: u32,
70    pub level_3_count: u32,
71    pub level_4_count: u32,
72    pub average_level: f64,
73    pub compliance_percentage: f64,
74    pub last_calculated: String,
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize)]
78pub struct MatrixMetadata {
79    pub version: String,
80    pub total_features: u32,
81    pub specification: String,
82    pub methodology: String,
83}
84
85#[derive(Debug, Clone, Deserialize, Serialize)]
86pub struct IsoSection {
87    pub name: String,
88    pub iso_section: String,
89    pub total_requirements: u32,
90    pub summary: SectionSummary,
91    pub requirements: Vec<IsoRequirementData>,
92}
93
94#[derive(Debug, Clone, Deserialize, Serialize)]
95pub struct SectionSummary {
96    pub implemented: u32,
97    pub average_level: f64,
98    pub compliance_percentage: f64,
99}
100
101#[derive(Debug, Clone, Deserialize, Serialize)]
102pub struct IsoRequirementData {
103    pub id: String,
104    pub name: String,
105    pub description: String,
106    pub iso_reference: String,
107    pub requirement_type: String, // mandatory/optional/recommended
108    pub page: u32,
109    pub original_text: String,
110}
111
112#[derive(Debug, Clone, Deserialize, Serialize)]
113pub struct OverallSummary {
114    pub total_sections: u32,
115    pub total_requirements: u32,
116    pub total_implemented: u32,
117    pub average_level: f64,
118    pub real_compliance_percentage: f64,
119    pub level_0_count: u32,
120    pub level_1_count: u32,
121    pub level_2_count: u32,
122    pub level_3_count: u32,
123    pub level_4_count: u32,
124}
125
126#[derive(Debug, Clone, Deserialize, Serialize)]
127pub struct ValidationTools {
128    pub external_validators: Vec<String>,
129    pub internal_parser: bool,
130    pub reference_pdfs: bool,
131    pub automated_testing: bool,
132}
133
134/// Load ISO compliance matrix from TOML file
135pub fn load_matrix(path: &str) -> Result<IsoMatrix> {
136    let toml_content = fs::read_to_string(path).map_err(PdfError::Io)?;
137
138    // Try direct Serde deserialization first, with custom structure for TOML arrays
139    parse_compliance_matrix_with_serde(&toml_content)
140}
141
142/// Parse using Serde with simplified structure that handles TOML correctly
143fn parse_compliance_matrix_with_serde(toml_content: &str) -> Result<IsoMatrix> {
144    // Use direct TOML deserialization instead of complex custom deserializer
145    toml::from_str::<IsoMatrix>(toml_content)
146        .map_err(|e| PdfError::ParseError(format!("Failed to parse matrix TOML: {}", e)))
147}
148
149/// Load default matrix from project root
150pub fn load_default_matrix() -> Result<IsoMatrix> {
151    // Try multiple potential locations for the matrix file
152    let potential_paths = [
153        "../ISO_COMPLIANCE_MATRIX.toml",
154        "../../ISO_COMPLIANCE_MATRIX.toml",
155        "ISO_COMPLIANCE_MATRIX.toml",
156        "./ISO_COMPLIANCE_MATRIX.toml",
157    ];
158
159    for path in &potential_paths {
160        if std::path::Path::new(path).exists() {
161            return load_matrix(path);
162        }
163    }
164
165    Err(PdfError::Io(std::io::Error::new(
166        std::io::ErrorKind::NotFound,
167        "ISO_COMPLIANCE_MATRIX.toml not found in any expected location",
168    )))
169}
170
171/// Load verification status from TOML file
172pub fn load_verification_status(path: &str) -> Result<VerificationStatus> {
173    let toml_content = fs::read_to_string(path).map_err(PdfError::Io)?;
174    toml::from_str(&toml_content)
175        .map_err(|e| PdfError::ParseError(format!("Failed to parse verification status: {}", e)))
176}
177
178/// Load default verification status from project root
179pub fn load_default_verification_status() -> Result<VerificationStatus> {
180    let potential_paths = [
181        "../ISO_VERIFICATION_STATUS.toml",
182        "../../ISO_VERIFICATION_STATUS.toml",
183        "ISO_VERIFICATION_STATUS.toml",
184        "./ISO_VERIFICATION_STATUS.toml",
185    ];
186
187    for path in &potential_paths {
188        if std::path::Path::new(path).exists() {
189            return load_verification_status(path);
190        }
191    }
192
193    Err(PdfError::Io(std::io::Error::new(
194        std::io::ErrorKind::NotFound,
195        "ISO_VERIFICATION_STATUS.toml not found in any expected location",
196    )))
197}
198
199/// Load complete compliance system (matrix + status)
200pub fn load_compliance_system() -> Result<ComplianceSystem> {
201    let matrix = load_default_matrix()?;
202    let status = load_default_verification_status()?;
203
204    Ok(ComplianceSystem { matrix, status })
205}
206
207impl ComplianceSystem {
208    /// Get all requirements with complete info (definition + status)
209    pub fn get_all_requirements(&self) -> Vec<IsoRequirement> {
210        let mut requirements = Vec::new();
211
212        for section in self.matrix.sections.values() {
213            for req_data in &section.requirements {
214                let status = self.status.status.get(&req_data.id);
215
216                let level = status
217                    .map(|s| {
218                        VerificationLevel::from_u8(s.level)
219                            .unwrap_or(VerificationLevel::NotImplemented)
220                    })
221                    .unwrap_or(VerificationLevel::NotImplemented);
222
223                requirements.push(IsoRequirement {
224                    id: req_data.id.clone(),
225                    name: req_data.name.clone(),
226                    description: req_data.description.clone(),
227                    iso_reference: req_data.iso_reference.clone(),
228                    implementation: status.and_then(|s| {
229                        if s.implementation.is_empty() {
230                            None
231                        } else {
232                            Some(s.implementation.clone())
233                        }
234                    }),
235                    test_file: status.and_then(|s| {
236                        if s.test_file.is_empty() {
237                            None
238                        } else {
239                            Some(s.test_file.clone())
240                        }
241                    }),
242                    level,
243                    verified: status.map(|s| s.verified).unwrap_or(false),
244                    notes: status
245                        .map(|s| s.notes.clone())
246                        .unwrap_or(req_data.requirement_type.clone()),
247                });
248            }
249        }
250
251        requirements
252    }
253
254    /// Get requirement info combining matrix definition + current status
255    pub fn get_requirement_info(&self, id: &str) -> Option<RequirementInfo> {
256        // Find definition in matrix
257        for section in self.matrix.sections.values() {
258            for req_data in &section.requirements {
259                if req_data.id == id {
260                    // Find status
261                    let status = self.status.status.get(id);
262
263                    return Some(RequirementInfo {
264                        id: req_data.id.clone(),
265                        name: req_data.name.clone(),
266                        description: req_data.description.clone(),
267                        iso_reference: req_data.iso_reference.clone(),
268                        requirement_type: req_data.requirement_type.clone(),
269                        page: req_data.page,
270                        level: status.map(|s| s.level).unwrap_or(0),
271                        implementation: status
272                            .map(|s| s.implementation.clone())
273                            .unwrap_or_default(),
274                        test_file: status.map(|s| s.test_file.clone()).unwrap_or_default(),
275                        verified: status.map(|s| s.verified).unwrap_or(false),
276                        last_checked: status
277                            .map(|s| s.last_checked.clone())
278                            .unwrap_or("never".to_string()),
279                        notes: status.map(|s| s.notes.clone()).unwrap_or_default(),
280                    });
281                }
282            }
283        }
284        None
285    }
286
287    /// Get requirements for a specific section with status
288    pub fn get_section_requirements(&self, section_id: &str) -> Option<Vec<IsoRequirement>> {
289        if let Some(section) = self.matrix.sections.get(section_id) {
290            let mut requirements = Vec::new();
291
292            for req_data in &section.requirements {
293                let status = self.status.status.get(&req_data.id);
294
295                let level = status
296                    .map(|s| {
297                        VerificationLevel::from_u8(s.level)
298                            .unwrap_or(VerificationLevel::NotImplemented)
299                    })
300                    .unwrap_or(VerificationLevel::NotImplemented);
301
302                requirements.push(IsoRequirement {
303                    id: req_data.id.clone(),
304                    name: req_data.name.clone(),
305                    description: req_data.description.clone(),
306                    iso_reference: req_data.iso_reference.clone(),
307                    implementation: status.and_then(|s| {
308                        if s.implementation.is_empty() {
309                            None
310                        } else {
311                            Some(s.implementation.clone())
312                        }
313                    }),
314                    test_file: status.and_then(|s| {
315                        if s.test_file.is_empty() {
316                            None
317                        } else {
318                            Some(s.test_file.clone())
319                        }
320                    }),
321                    level,
322                    verified: status.map(|s| s.verified).unwrap_or(false),
323                    notes: status
324                        .map(|s| s.notes.clone())
325                        .unwrap_or(req_data.requirement_type.clone()),
326                });
327            }
328
329            Some(requirements)
330        } else {
331            None
332        }
333    }
334
335    /// Calculate compliance statistics from current status
336    pub fn calculate_compliance_stats(&self) -> ComplianceStats {
337        ComplianceStats {
338            total_requirements: self.status.statistics.level_0_count
339                + self.status.statistics.level_1_count
340                + self.status.statistics.level_2_count
341                + self.status.statistics.level_3_count
342                + self.status.statistics.level_4_count,
343            implemented_requirements: self.status.statistics.level_1_count
344                + self.status.statistics.level_2_count
345                + self.status.statistics.level_3_count
346                + self.status.statistics.level_4_count,
347            average_compliance_percentage: self.status.statistics.compliance_percentage,
348            level_0_count: self.status.statistics.level_0_count,
349            level_1_count: self.status.statistics.level_1_count,
350            level_2_count: self.status.statistics.level_2_count,
351            level_3_count: self.status.statistics.level_3_count,
352            level_4_count: self.status.statistics.level_4_count,
353        }
354    }
355
356    /// Get requirements that need implementation (level 0)
357    pub fn get_unimplemented_requirements(&self) -> Vec<IsoRequirement> {
358        self.get_all_requirements()
359            .into_iter()
360            .filter(|req| req.level == VerificationLevel::NotImplemented)
361            .collect()
362    }
363
364    /// Get requirements that need better verification (level 1-2)
365    pub fn get_partially_implemented_requirements(&self) -> Vec<IsoRequirement> {
366        self.get_all_requirements()
367            .into_iter()
368            .filter(|req| {
369                matches!(
370                    req.level,
371                    VerificationLevel::CodeExists | VerificationLevel::GeneratesPdf
372                )
373            })
374            .collect()
375    }
376
377    /// Get fully compliant requirements (level 4)
378    pub fn get_compliant_requirements(&self) -> Vec<IsoRequirement> {
379        self.get_all_requirements()
380            .into_iter()
381            .filter(|req| req.level == VerificationLevel::IsoCompliant)
382            .collect()
383    }
384
385    /// Update verification status for a requirement
386    pub fn update_requirement_status(
387        &mut self,
388        id: &str,
389        new_status: RequirementStatus,
390    ) -> Result<()> {
391        self.status.status.insert(id.to_string(), new_status);
392
393        // Update metadata
394        self.status.metadata.last_updated = chrono::Utc::now().to_rfc3339();
395
396        // Recalculate statistics
397        self.recalculate_statistics();
398
399        Ok(())
400    }
401
402    /// Save status file (matrix is never saved - it's immutable)
403    pub fn save_status(&self, path: &str) -> Result<()> {
404        let toml_content = toml::to_string_pretty(&self.status)
405            .map_err(|e| PdfError::ParseError(format!("Failed to serialize status: {}", e)))?;
406
407        fs::write(path, toml_content).map_err(PdfError::Io)?;
408        Ok(())
409    }
410
411    /// Recalculate statistics based on current status
412    fn recalculate_statistics(&mut self) {
413        let mut level_counts = [0u32; 5];
414        let mut total_level = 0u32;
415
416        for status in self.status.status.values() {
417            if status.level <= 4 {
418                level_counts[status.level as usize] += 1;
419                total_level += status.level as u32;
420            }
421        }
422
423        let total_requirements = self.status.status.len() as u32;
424        let average_level = if total_requirements > 0 {
425            total_level as f64 / total_requirements as f64
426        } else {
427            0.0
428        };
429
430        self.status.statistics = StatusStatistics {
431            level_0_count: level_counts[0],
432            level_1_count: level_counts[1],
433            level_2_count: level_counts[2],
434            level_3_count: level_counts[3],
435            level_4_count: level_counts[4],
436            average_level,
437            compliance_percentage: (average_level / 4.0) * 100.0,
438            last_calculated: chrono::Utc::now().to_rfc3339(),
439        };
440    }
441}
442
443/// Combined requirement information (definition + status)
444#[derive(Debug, Clone)]
445pub struct RequirementInfo {
446    pub id: String,
447    pub name: String,
448    pub description: String,
449    pub iso_reference: String,
450    pub requirement_type: String,
451    pub page: u32,
452    pub level: u8,
453    pub implementation: String,
454    pub test_file: String,
455    pub verified: bool,
456    pub last_checked: String,
457    pub notes: String,
458}
459
460impl IsoMatrix {
461    /// Get all requirement definitions (without verification status)
462    /// Note: This only returns definitions. Use ComplianceSystem::get_all_requirements() for full info.
463    pub fn get_all_requirement_definitions(&self) -> Vec<&IsoRequirementData> {
464        let mut requirements = Vec::new();
465
466        for section in self.sections.values() {
467            for req_data in &section.requirements {
468                requirements.push(req_data);
469            }
470        }
471
472        requirements
473    }
474
475    /// DEPRECATED: Use ComplianceSystem::get_all_requirements() instead
476    #[deprecated(
477        note = "Use ComplianceSystem::get_all_requirements() for complete requirement info"
478    )]
479    pub fn get_all_requirements(&self) -> Vec<IsoRequirement> {
480        let mut requirements = Vec::new();
481
482        for section in self.sections.values() {
483            for req_data in &section.requirements {
484                // Return basic requirement with no verification status
485                requirements.push(IsoRequirement {
486                    id: req_data.id.clone(),
487                    name: req_data.name.clone(),
488                    description: req_data.description.clone(),
489                    iso_reference: req_data.iso_reference.clone(),
490                    implementation: None, // No status info in matrix
491                    test_file: None,      // No status info in matrix
492                    level: VerificationLevel::NotImplemented, // Default level
493                    verified: false,      // Default verification
494                    notes: req_data.requirement_type.clone(), // Use type as notes
495                });
496            }
497        }
498
499        requirements
500    }
501
502    /// DEPRECATED: Use ComplianceSystem::get_section_requirements() instead
503    #[deprecated(
504        note = "Use ComplianceSystem::get_section_requirements() for complete requirement info"
505    )]
506    pub fn get_section_requirements(&self, section_id: &str) -> Option<Vec<IsoRequirement>> {
507        if let Some(section) = self.sections.get(section_id) {
508            let mut requirements = Vec::new();
509
510            for req_data in &section.requirements {
511                // Return basic requirement with no verification status
512                requirements.push(IsoRequirement {
513                    id: req_data.id.clone(),
514                    name: req_data.name.clone(),
515                    description: req_data.description.clone(),
516                    iso_reference: req_data.iso_reference.clone(),
517                    implementation: None, // No status info in matrix
518                    test_file: None,      // No status info in matrix
519                    level: VerificationLevel::NotImplemented, // Default level
520                    verified: false,      // Default verification
521                    notes: req_data.requirement_type.clone(), // Use type as notes
522                });
523            }
524
525            Some(requirements)
526        } else {
527            None
528        }
529    }
530
531    /// DEPRECATED: Use ComplianceSystem::get_requirement_info() instead
532    #[deprecated(
533        note = "Use ComplianceSystem::get_requirement_info() for complete requirement info"
534    )]
535    pub fn get_requirement(&self, requirement_id: &str) -> Option<IsoRequirement> {
536        for section in self.sections.values() {
537            for req_data in &section.requirements {
538                if req_data.id == requirement_id {
539                    return Some(IsoRequirement {
540                        id: req_data.id.clone(),
541                        name: req_data.name.clone(),
542                        description: req_data.description.clone(),
543                        iso_reference: req_data.iso_reference.clone(),
544                        implementation: None, // No status info in matrix
545                        test_file: None,      // No status info in matrix
546                        level: VerificationLevel::NotImplemented, // Default level
547                        verified: false,      // Default verification
548                        notes: req_data.requirement_type.clone(), // Use type as notes
549                    });
550                }
551            }
552        }
553        None
554    }
555
556    /// DEPRECATED: Use ComplianceSystem::calculate_compliance_stats() instead
557    #[deprecated(note = "Use ComplianceSystem::calculate_compliance_stats() for real statistics")]
558    pub fn calculate_compliance_stats(&self) -> ComplianceStats {
559        let all_requirements = self.get_all_requirements();
560        let total_count = all_requirements.len();
561
562        let mut level_counts = [0u32; 5]; // 0-4
563        let mut total_percentage = 0.0;
564        let mut implemented_count = 0;
565
566        for req in &all_requirements {
567            let level_index = req.level as usize;
568            level_counts[level_index] += 1;
569
570            let percentage = req.level.as_percentage();
571            total_percentage += percentage;
572
573            if req.level as u8 > 0 {
574                implemented_count += 1;
575            }
576        }
577
578        let average_percentage = if total_count > 0 {
579            total_percentage / total_count as f64
580        } else {
581            0.0
582        };
583
584        ComplianceStats {
585            total_requirements: total_count as u32,
586            implemented_requirements: implemented_count,
587            average_compliance_percentage: average_percentage,
588            level_0_count: level_counts[0],
589            level_1_count: level_counts[1],
590            level_2_count: level_counts[2],
591            level_3_count: level_counts[3],
592            level_4_count: level_counts[4],
593        }
594    }
595
596    /// DEPRECATED: Use ComplianceSystem methods instead
597    #[deprecated(note = "Use ComplianceSystem::get_unimplemented_requirements() for real status")]
598    pub fn get_unimplemented_requirements(&self) -> Vec<IsoRequirement> {
599        self.get_all_requirements()
600            .into_iter()
601            .filter(|req| req.level == VerificationLevel::NotImplemented)
602            .collect()
603    }
604
605    /// DEPRECATED: Use ComplianceSystem methods instead
606    #[deprecated(
607        note = "Use ComplianceSystem::get_partially_implemented_requirements() for real status"
608    )]
609    pub fn get_partially_implemented_requirements(&self) -> Vec<IsoRequirement> {
610        self.get_all_requirements()
611            .into_iter()
612            .filter(|req| {
613                matches!(
614                    req.level,
615                    VerificationLevel::CodeExists | VerificationLevel::GeneratesPdf
616                )
617            })
618            .collect()
619    }
620
621    /// DEPRECATED: Use ComplianceSystem methods instead  
622    #[deprecated(note = "Use ComplianceSystem::get_compliant_requirements() for real status")]
623    pub fn get_compliant_requirements(&self) -> Vec<IsoRequirement> {
624        self.get_all_requirements()
625            .into_iter()
626            .filter(|req| req.level == VerificationLevel::IsoCompliant)
627            .collect()
628    }
629}
630
631/// Compliance statistics calculated from matrix
632#[derive(Debug, Clone)]
633pub struct ComplianceStats {
634    pub total_requirements: u32,
635    pub implemented_requirements: u32,
636    pub average_compliance_percentage: f64,
637    pub level_0_count: u32,
638    pub level_1_count: u32,
639    pub level_2_count: u32,
640    pub level_3_count: u32,
641    pub level_4_count: u32,
642}
643
644impl ComplianceStats {
645    /// Get compliance percentage as a string for display
646    pub fn compliance_percentage_display(&self) -> String {
647        format!("{:.1}%", self.average_compliance_percentage)
648    }
649
650    /// Get implementation percentage (non-zero levels)
651    pub fn implementation_percentage(&self) -> f64 {
652        if self.total_requirements > 0 {
653            (self.implemented_requirements as f64 / self.total_requirements as f64) * 100.0
654        } else {
655            0.0
656        }
657    }
658}
659
660#[cfg(test)]
661mod tests {
662    use super::*;
663
664    use tempfile::NamedTempFile;
665
666    use std::io::Write;
667
668    fn create_test_matrix_toml() -> String {
669        r#"
670[metadata]
671version = "2025-08-21"
672total_features = 3
673specification = "ISO 32000-1:2008"
674methodology = "docs/ISO_TESTING_METHODOLOGY.md"
675
676[section_7_5]
677name = "Document Structure"
678iso_section = "7.5"
679total_requirements = 2
680
681[section_7_5.summary]
682implemented = 1
683average_level = 2.0
684compliance_percentage = 50.0
685
686[[section_7_5.requirements]]
687id = "7.5.2.1"
688name = "Catalog Type Entry"
689description = "Document catalog must have /Type /Catalog"
690iso_reference = "7.5.2, Table 3.25"
691requirement_type = "mandatory"
692page = 42
693original_text = "Document catalog must have /Type /Catalog entry"
694
695[[section_7_5.requirements]]
696id = "7.5.2.2"
697name = "Catalog Version Entry"
698description = "Optional /Version entry in catalog"
699iso_reference = "7.5.2, Table 3.25"
700requirement_type = "optional"
701page = 42
702original_text = "Optional /Version entry in document catalog"
703
704[overall_summary]
705total_sections = 1
706total_requirements = 2
707total_implemented = 1
708average_level = 1.5
709real_compliance_percentage = 37.5
710level_0_count = 1
711level_1_count = 0
712level_2_count = 0
713level_3_count = 1
714level_4_count = 0
715
716[validation_tools]
717external_validators = ["qpdf"]
718internal_parser = true
719reference_pdfs = false
720automated_testing = false
721"#
722        .to_string()
723    }
724
725    #[test]
726    fn test_load_matrix() {
727        let toml_content = create_test_matrix_toml();
728        let mut temp_file = NamedTempFile::new().unwrap();
729        temp_file.write_all(toml_content.as_bytes()).unwrap();
730
731        let matrix = load_matrix(temp_file.path().to_str().unwrap()).unwrap();
732
733        assert_eq!(matrix.metadata.total_features, 3);
734        assert_eq!(matrix.sections.len(), 1);
735        assert!(matrix.sections.contains_key("section_7_5"));
736
737        let section = &matrix.sections["section_7_5"];
738        assert_eq!(section.requirements.len(), 2);
739        assert_eq!(section.requirements[0].id, "7.5.2.1");
740        assert_eq!(section.requirements[0].requirement_type, "mandatory");
741    }
742
743    #[test]
744    fn test_get_all_requirements() {
745        let toml_content = create_test_matrix_toml();
746        let matrix: IsoMatrix = toml::from_str(&toml_content).unwrap();
747
748        let requirements = matrix.get_all_requirements();
749        assert_eq!(requirements.len(), 2);
750
751        let req1 = &requirements[0];
752        assert_eq!(req1.id, "7.5.2.1");
753        assert_eq!(req1.level, VerificationLevel::NotImplemented); // Default level in matrix-only
754        assert!(!req1.verified); // Default verification in matrix-only
755
756        let req2 = &requirements[1];
757        assert_eq!(req2.id, "7.5.2.2");
758        assert_eq!(req2.level, VerificationLevel::NotImplemented); // Default level in matrix-only
759        assert!(!req2.verified); // Default verification in matrix-only
760    }
761
762    #[test]
763    fn test_get_requirement_by_id() {
764        let toml_content = create_test_matrix_toml();
765        let matrix: IsoMatrix = toml::from_str(&toml_content).unwrap();
766
767        let req = matrix.get_requirement("7.5.2.1").unwrap();
768        assert_eq!(req.name, "Catalog Type Entry");
769        assert_eq!(req.level, VerificationLevel::NotImplemented); // Default level in matrix-only
770
771        assert!(matrix.get_requirement("nonexistent").is_none());
772    }
773
774    #[test]
775    fn test_calculate_compliance_stats() {
776        let toml_content = create_test_matrix_toml();
777        let matrix: IsoMatrix = toml::from_str(&toml_content).unwrap();
778
779        let stats = matrix.calculate_compliance_stats();
780        assert_eq!(stats.total_requirements, 2);
781        assert_eq!(stats.implemented_requirements, 0); // None implemented in matrix-only
782        assert_eq!(stats.level_0_count, 2); // Both at level 0 in matrix-only
783        assert_eq!(stats.level_1_count, 0);
784        assert_eq!(stats.level_2_count, 0);
785        assert_eq!(stats.level_3_count, 0);
786        assert_eq!(stats.level_4_count, 0);
787        assert_eq!(stats.average_compliance_percentage, 0.0); // All at level 0
788    }
789
790    #[test]
791    fn test_get_unimplemented_requirements() {
792        let toml_content = create_test_matrix_toml();
793        let matrix: IsoMatrix = toml::from_str(&toml_content).unwrap();
794
795        let unimplemented = matrix.get_unimplemented_requirements();
796        assert_eq!(unimplemented.len(), 2); // Both at level 0 in matrix-only
797                                            // Both requirements are at NotImplemented level by default
798        let ids: Vec<String> = unimplemented.iter().map(|r| r.id.clone()).collect();
799        assert!(ids.contains(&"7.5.2.1".to_string()));
800        assert!(ids.contains(&"7.5.2.2".to_string()));
801    }
802}