1use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::path::PathBuf;
6
7pub use crate::tokenizer::{TokenCounts, TokenModel};
9
10pub type TokenizerModel = TokenModel;
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct Repository {
45 pub name: String,
47 pub path: PathBuf,
49 pub files: Vec<RepoFile>,
51 pub metadata: RepoMetadata,
53}
54
55impl Repository {
56 pub fn new(name: impl Into<String>, path: impl Into<PathBuf>) -> Self {
58 Self {
59 name: name.into(),
60 path: path.into(),
61 files: Vec::new(),
62 metadata: RepoMetadata::default(),
63 }
64 }
65
66 pub fn total_tokens(&self, model: TokenizerModel) -> u32 {
68 self.files.iter().map(|f| f.token_count.get(model)).sum()
69 }
70
71 pub fn files_by_language(&self, language: &str) -> Vec<&RepoFile> {
73 self.files
74 .iter()
75 .filter(|f| f.language.as_deref() == Some(language))
76 .collect()
77 }
78
79 #[must_use]
81 pub fn files_by_importance(&self) -> Vec<&RepoFile> {
82 let mut files: Vec<_> = self.files.iter().collect();
83 files.sort_by(|a, b| {
84 b.importance
85 .partial_cmp(&a.importance)
86 .unwrap_or(std::cmp::Ordering::Equal)
87 });
88 files
89 }
90}
91
92impl fmt::Display for Repository {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(
95 f,
96 "Repository({}: {} files, {} lines)",
97 self.name, self.metadata.total_files, self.metadata.total_lines
98 )
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct RepoFile {
105 pub path: PathBuf,
107 pub relative_path: String,
109 pub language: Option<String>,
111 pub size_bytes: u64,
113 pub token_count: TokenCounts,
115 pub symbols: Vec<Symbol>,
117 pub importance: f32,
119 pub content: Option<String>,
121}
122
123impl RepoFile {
124 pub fn new(path: impl Into<PathBuf>, relative_path: impl Into<String>) -> Self {
126 Self {
127 path: path.into(),
128 relative_path: relative_path.into(),
129 language: None,
130 size_bytes: 0,
131 token_count: TokenCounts::default(),
132 symbols: Vec::new(),
133 importance: 0.5,
134 content: None,
135 }
136 }
137
138 pub fn extension(&self) -> Option<&str> {
140 self.path.extension().and_then(|e| e.to_str())
141 }
142
143 #[must_use]
145 pub fn filename(&self) -> &str {
146 self.path.file_name().and_then(|n| n.to_str()).unwrap_or("")
147 }
148}
149
150impl fmt::Display for RepoFile {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 write!(
153 f,
154 "{} ({}, {} tokens)",
155 self.relative_path,
156 self.language.as_deref().unwrap_or("unknown"),
157 self.token_count.claude
158 )
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
164pub enum Visibility {
165 #[default]
166 Public,
167 Private,
168 Protected,
169 Internal, }
171
172impl Visibility {
173 pub fn name(&self) -> &'static str {
174 match self {
175 Self::Public => "public",
176 Self::Private => "private",
177 Self::Protected => "protected",
178 Self::Internal => "internal",
179 }
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct Symbol {
186 pub name: String,
188 pub kind: SymbolKind,
190 pub signature: Option<String>,
192 pub docstring: Option<String>,
194 pub start_line: u32,
196 pub end_line: u32,
198 pub references: u32,
200 pub importance: f32,
202 pub parent: Option<String>,
204 pub visibility: Visibility,
206 pub calls: Vec<String>,
208 pub extends: Option<String>,
210 pub implements: Vec<String>,
212}
213
214impl Symbol {
215 pub fn new(name: impl Into<String>, kind: SymbolKind) -> Self {
217 Self {
218 name: name.into(),
219 kind,
220 signature: None,
221 docstring: None,
222 start_line: 0,
223 end_line: 0,
224 references: 0,
225 importance: 0.5,
226 parent: None,
227 visibility: Visibility::default(),
228 calls: Vec::new(),
229 extends: None,
230 implements: Vec::new(),
231 }
232 }
233
234 #[must_use]
236 pub fn line_count(&self) -> u32 {
237 if self.end_line >= self.start_line {
238 self.end_line - self.start_line + 1
239 } else {
240 1
241 }
242 }
243}
244
245impl fmt::Display for Symbol {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(
248 f,
249 "{}:{} (lines {}-{})",
250 self.kind.name(),
251 self.name,
252 self.start_line,
253 self.end_line
254 )
255 }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
260pub enum SymbolKind {
261 Function,
262 Method,
263 Class,
264 Interface,
265 Struct,
266 Enum,
267 Constant,
268 Variable,
269 Import,
270 Export,
271 TypeAlias,
272 Module,
273 Trait,
274 Macro,
275}
276
277impl SymbolKind {
278 #[must_use]
280 pub fn name(&self) -> &'static str {
281 match self {
282 Self::Function => "function",
283 Self::Method => "method",
284 Self::Class => "class",
285 Self::Interface => "interface",
286 Self::Struct => "struct",
287 Self::Enum => "enum",
288 Self::Constant => "constant",
289 Self::Variable => "variable",
290 Self::Import => "import",
291 Self::Export => "export",
292 Self::TypeAlias => "type",
293 Self::Module => "module",
294 Self::Trait => "trait",
295 Self::Macro => "macro",
296 }
297 }
298
299 #[must_use]
301 #[allow(clippy::should_implement_trait)]
302 pub fn from_str(s: &str) -> Option<Self> {
303 match s.to_lowercase().as_str() {
304 "function" => Some(Self::Function),
305 "method" => Some(Self::Method),
306 "class" => Some(Self::Class),
307 "interface" => Some(Self::Interface),
308 "struct" => Some(Self::Struct),
309 "enum" => Some(Self::Enum),
310 "constant" => Some(Self::Constant),
311 "variable" => Some(Self::Variable),
312 "import" => Some(Self::Import),
313 "export" => Some(Self::Export),
314 "type" | "typealias" => Some(Self::TypeAlias),
315 "module" => Some(Self::Module),
316 "trait" => Some(Self::Trait),
317 "macro" => Some(Self::Macro),
318 _ => None,
319 }
320 }
321}
322
323impl std::str::FromStr for SymbolKind {
324 type Err = ();
325
326 fn from_str(s: &str) -> Result<Self, Self::Err> {
327 SymbolKind::from_str(s).ok_or(())
328 }
329}
330
331impl fmt::Display for SymbolKind {
332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 write!(f, "{}", self.name())
334 }
335}
336
337#[derive(Debug, Clone, Default, Serialize, Deserialize)]
339pub struct RepoMetadata {
340 pub total_files: u32,
342 pub total_lines: u64,
344 pub total_tokens: TokenCounts,
346 pub languages: Vec<LanguageStats>,
348 pub framework: Option<String>,
350 pub description: Option<String>,
352 pub branch: Option<String>,
354 pub commit: Option<String>,
356 pub directory_structure: Option<String>,
358 pub external_dependencies: Vec<String>,
360 pub git_history: Option<GitHistory>,
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct LanguageStats {
367 pub language: String,
369 pub files: u32,
371 pub lines: u64,
373 pub percentage: f32,
375}
376
377#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct GitCommitInfo {
380 pub hash: String,
382 pub short_hash: String,
384 pub author: String,
386 pub date: String,
388 pub message: String,
390}
391
392#[derive(Debug, Clone, Default, Serialize, Deserialize)]
394pub struct GitHistory {
395 pub commits: Vec<GitCommitInfo>,
397 pub changed_files: Vec<GitChangedFile>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct GitChangedFile {
404 pub path: String,
406 pub status: String,
408 #[serde(skip_serializing_if = "Option::is_none")]
410 pub diff_content: Option<String>,
411}
412
413#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
415pub enum CompressionLevel {
416 None,
418 Minimal,
420 #[default]
422 Balanced,
423 Aggressive,
425 Extreme,
427 Focused,
429 Semantic,
443}
444
445impl CompressionLevel {
446 pub fn expected_reduction(&self) -> u8 {
453 match self {
454 Self::None => 0,
455 Self::Minimal => 15,
456 Self::Balanced => 35,
457 Self::Aggressive => 60,
458 Self::Extreme => 80,
459 Self::Focused => 75,
460 Self::Semantic => 65,
463 }
464 }
465
466 pub fn description(&self) -> &'static str {
468 match self {
469 Self::None => "No compression - original content preserved",
470 Self::Minimal => "Remove empty lines, trim whitespace",
471 Self::Balanced => "Remove comments, normalize whitespace",
472 Self::Aggressive => "Remove docstrings, keep signatures only",
473 Self::Extreme => "Key symbols only - minimal context",
474 Self::Focused => "Focused symbols with small surrounding context",
475 Self::Semantic => "Semantic chunking with intelligent sampling",
476 }
477 }
478
479 #[allow(clippy::should_implement_trait)]
484 pub fn from_str(s: &str) -> Option<Self> {
485 match s.to_lowercase().as_str() {
486 "none" => Some(Self::None),
487 "minimal" => Some(Self::Minimal),
488 "balanced" => Some(Self::Balanced),
489 "aggressive" => Some(Self::Aggressive),
490 "extreme" => Some(Self::Extreme),
491 "focused" => Some(Self::Focused),
492 "semantic" => Some(Self::Semantic),
493 _ => None,
494 }
495 }
496
497 pub fn name(&self) -> &'static str {
499 match self {
500 Self::None => "none",
501 Self::Minimal => "minimal",
502 Self::Balanced => "balanced",
503 Self::Aggressive => "aggressive",
504 Self::Extreme => "extreme",
505 Self::Focused => "focused",
506 Self::Semantic => "semantic",
507 }
508 }
509
510 pub fn all() -> &'static [Self] {
512 &[
513 Self::None,
514 Self::Minimal,
515 Self::Balanced,
516 Self::Aggressive,
517 Self::Extreme,
518 Self::Focused,
519 Self::Semantic,
520 ]
521 }
522}
523
524impl std::str::FromStr for CompressionLevel {
525 type Err = ();
526
527 fn from_str(s: &str) -> Result<Self, Self::Err> {
528 CompressionLevel::from_str(s).ok_or(())
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535
536 #[test]
537 fn test_repository_new() {
538 let repo = Repository::new("test", "/tmp/test");
539 assert_eq!(repo.name, "test");
540 assert!(repo.files.is_empty());
541 }
542
543 #[test]
544 fn test_repository_total_tokens() {
545 let mut repo = Repository::new("test", "/tmp/test");
546 let mut file1 = RepoFile::new("/tmp/test/a.rs", "a.rs");
547 file1.token_count.set(TokenizerModel::Claude, 100);
548 let mut file2 = RepoFile::new("/tmp/test/b.rs", "b.rs");
549 file2.token_count.set(TokenizerModel::Claude, 200);
550 repo.files.push(file1);
551 repo.files.push(file2);
552 assert_eq!(repo.total_tokens(TokenizerModel::Claude), 300);
553 }
554
555 #[test]
556 fn test_repository_files_by_language() {
557 let mut repo = Repository::new("test", "/tmp/test");
558 let mut file1 = RepoFile::new("/tmp/test/a.rs", "a.rs");
559 file1.language = Some("rust".to_string());
560 let mut file2 = RepoFile::new("/tmp/test/b.py", "b.py");
561 file2.language = Some("python".to_string());
562 let mut file3 = RepoFile::new("/tmp/test/c.rs", "c.rs");
563 file3.language = Some("rust".to_string());
564 repo.files.push(file1);
565 repo.files.push(file2);
566 repo.files.push(file3);
567
568 let rust_files = repo.files_by_language("rust");
569 assert_eq!(rust_files.len(), 2);
570 let python_files = repo.files_by_language("python");
571 assert_eq!(python_files.len(), 1);
572 let go_files = repo.files_by_language("go");
573 assert_eq!(go_files.len(), 0);
574 }
575
576 #[test]
577 fn test_repository_files_by_importance() {
578 let mut repo = Repository::new("test", "/tmp/test");
579 let mut file1 = RepoFile::new("/tmp/test/a.rs", "a.rs");
580 file1.importance = 0.3;
581 let mut file2 = RepoFile::new("/tmp/test/b.rs", "b.rs");
582 file2.importance = 0.9;
583 let mut file3 = RepoFile::new("/tmp/test/c.rs", "c.rs");
584 file3.importance = 0.6;
585 repo.files.push(file1);
586 repo.files.push(file2);
587 repo.files.push(file3);
588
589 let sorted = repo.files_by_importance();
590 assert_eq!(sorted[0].relative_path, "b.rs");
591 assert_eq!(sorted[1].relative_path, "c.rs");
592 assert_eq!(sorted[2].relative_path, "a.rs");
593 }
594
595 #[test]
596 fn test_repository_display() {
597 let mut repo = Repository::new("my-project", "/tmp/my-project");
598 repo.metadata.total_files = 42;
599 repo.metadata.total_lines = 1000;
600 let display = format!("{}", repo);
601 assert!(display.contains("my-project"));
602 assert!(display.contains("42 files"));
603 assert!(display.contains("1000 lines"));
604 }
605
606 #[test]
607 fn test_repo_file_new() {
608 let file = RepoFile::new("/tmp/test/src/main.rs", "src/main.rs");
609 assert_eq!(file.relative_path, "src/main.rs");
610 assert!(file.language.is_none());
611 assert_eq!(file.importance, 0.5);
612 }
613
614 #[test]
615 fn test_repo_file_extension() {
616 let file = RepoFile::new("/tmp/test/main.rs", "main.rs");
617 assert_eq!(file.extension(), Some("rs"));
618
619 let file_no_ext = RepoFile::new("/tmp/test/Makefile", "Makefile");
620 assert_eq!(file_no_ext.extension(), None);
621 }
622
623 #[test]
624 fn test_repo_file_filename() {
625 let file = RepoFile::new("/tmp/test/src/main.rs", "src/main.rs");
626 assert_eq!(file.filename(), "main.rs");
627 }
628
629 #[test]
630 fn test_repo_file_display() {
631 let mut file = RepoFile::new("/tmp/test/main.rs", "main.rs");
632 file.language = Some("rust".to_string());
633 file.token_count.claude = 150;
634 let display = format!("{}", file);
635 assert!(display.contains("main.rs"));
636 assert!(display.contains("rust"));
637 assert!(display.contains("150"));
638 }
639
640 #[test]
641 fn test_repo_file_display_unknown_language() {
642 let file = RepoFile::new("/tmp/test/data.xyz", "data.xyz");
643 let display = format!("{}", file);
644 assert!(display.contains("unknown"));
645 }
646
647 #[test]
648 fn test_token_counts() {
649 let mut counts = TokenCounts::default();
650 counts.set(TokenizerModel::Claude, 100);
651 assert_eq!(counts.get(TokenizerModel::Claude), 100);
652 }
653
654 #[test]
655 fn test_symbol_new() {
656 let sym = Symbol::new("my_function", SymbolKind::Function);
657 assert_eq!(sym.name, "my_function");
658 assert_eq!(sym.kind, SymbolKind::Function);
659 assert_eq!(sym.importance, 0.5);
660 assert!(sym.signature.is_none());
661 assert!(sym.calls.is_empty());
662 }
663
664 #[test]
665 fn test_symbol_line_count() {
666 let mut sym = Symbol::new("test", SymbolKind::Function);
667 sym.start_line = 10;
668 sym.end_line = 20;
669 assert_eq!(sym.line_count(), 11);
670 }
671
672 #[test]
673 fn test_symbol_line_count_single_line() {
674 let mut sym = Symbol::new("test", SymbolKind::Variable);
675 sym.start_line = 5;
676 sym.end_line = 5;
677 assert_eq!(sym.line_count(), 1);
678 }
679
680 #[test]
681 fn test_symbol_line_count_inverted() {
682 let mut sym = Symbol::new("test", SymbolKind::Variable);
683 sym.start_line = 20;
684 sym.end_line = 10; assert_eq!(sym.line_count(), 1);
686 }
687
688 #[test]
689 fn test_symbol_display() {
690 let mut sym = Symbol::new("calculate", SymbolKind::Function);
691 sym.start_line = 10;
692 sym.end_line = 25;
693 let display = format!("{}", sym);
694 assert!(display.contains("function"));
695 assert!(display.contains("calculate"));
696 assert!(display.contains("10-25"));
697 }
698
699 #[test]
700 fn test_symbol_kind_name() {
701 assert_eq!(SymbolKind::Function.name(), "function");
702 assert_eq!(SymbolKind::Method.name(), "method");
703 assert_eq!(SymbolKind::Class.name(), "class");
704 assert_eq!(SymbolKind::Interface.name(), "interface");
705 assert_eq!(SymbolKind::Struct.name(), "struct");
706 assert_eq!(SymbolKind::Enum.name(), "enum");
707 assert_eq!(SymbolKind::Constant.name(), "constant");
708 assert_eq!(SymbolKind::Variable.name(), "variable");
709 assert_eq!(SymbolKind::Import.name(), "import");
710 assert_eq!(SymbolKind::Export.name(), "export");
711 assert_eq!(SymbolKind::TypeAlias.name(), "type");
712 assert_eq!(SymbolKind::Module.name(), "module");
713 assert_eq!(SymbolKind::Trait.name(), "trait");
714 assert_eq!(SymbolKind::Macro.name(), "macro");
715 }
716
717 #[test]
718 fn test_symbol_kind_from_str() {
719 assert_eq!(SymbolKind::from_str("function"), Some(SymbolKind::Function));
720 assert_eq!(SymbolKind::from_str("method"), Some(SymbolKind::Method));
721 assert_eq!(SymbolKind::from_str("class"), Some(SymbolKind::Class));
722 assert_eq!(SymbolKind::from_str("interface"), Some(SymbolKind::Interface));
723 assert_eq!(SymbolKind::from_str("struct"), Some(SymbolKind::Struct));
724 assert_eq!(SymbolKind::from_str("enum"), Some(SymbolKind::Enum));
725 assert_eq!(SymbolKind::from_str("constant"), Some(SymbolKind::Constant));
726 assert_eq!(SymbolKind::from_str("variable"), Some(SymbolKind::Variable));
727 assert_eq!(SymbolKind::from_str("import"), Some(SymbolKind::Import));
728 assert_eq!(SymbolKind::from_str("export"), Some(SymbolKind::Export));
729 assert_eq!(SymbolKind::from_str("type"), Some(SymbolKind::TypeAlias));
730 assert_eq!(SymbolKind::from_str("typealias"), Some(SymbolKind::TypeAlias));
731 assert_eq!(SymbolKind::from_str("module"), Some(SymbolKind::Module));
732 assert_eq!(SymbolKind::from_str("trait"), Some(SymbolKind::Trait));
733 assert_eq!(SymbolKind::from_str("macro"), Some(SymbolKind::Macro));
734 assert_eq!(SymbolKind::from_str("FUNCTION"), Some(SymbolKind::Function));
736 assert_eq!(SymbolKind::from_str("Class"), Some(SymbolKind::Class));
737 assert_eq!(SymbolKind::from_str("unknown"), None);
739 assert_eq!(SymbolKind::from_str(""), None);
740 }
741
742 #[test]
743 fn test_symbol_kind_std_from_str() {
744 use std::str::FromStr;
745 assert_eq!("function".parse::<SymbolKind>(), Ok(SymbolKind::Function));
746 assert_eq!("class".parse::<SymbolKind>(), Ok(SymbolKind::Class));
747 assert!("invalid".parse::<SymbolKind>().is_err());
748 }
749
750 #[test]
751 fn test_symbol_kind_display() {
752 assert_eq!(format!("{}", SymbolKind::Function), "function");
753 assert_eq!(format!("{}", SymbolKind::Class), "class");
754 }
755
756 #[test]
757 fn test_visibility_name() {
758 assert_eq!(Visibility::Public.name(), "public");
759 assert_eq!(Visibility::Private.name(), "private");
760 assert_eq!(Visibility::Protected.name(), "protected");
761 assert_eq!(Visibility::Internal.name(), "internal");
762 }
763
764 #[test]
765 fn test_visibility_default() {
766 let vis = Visibility::default();
767 assert_eq!(vis, Visibility::Public);
768 }
769
770 #[test]
771 fn test_language_stats() {
772 let stats = LanguageStats {
773 language: "rust".to_string(),
774 files: 10,
775 lines: 5000,
776 percentage: 45.5,
777 };
778 assert_eq!(stats.language, "rust");
779 assert_eq!(stats.files, 10);
780 assert_eq!(stats.lines, 5000);
781 assert!((stats.percentage - 45.5).abs() < f32::EPSILON);
782 }
783
784 #[test]
785 fn test_git_commit_info() {
786 let commit = GitCommitInfo {
787 hash: "abc123def456".to_string(),
788 short_hash: "abc123d".to_string(),
789 author: "Test Author".to_string(),
790 date: "2025-01-01".to_string(),
791 message: "Test commit".to_string(),
792 };
793 assert_eq!(commit.hash, "abc123def456");
794 assert_eq!(commit.short_hash, "abc123d");
795 assert_eq!(commit.author, "Test Author");
796 }
797
798 #[test]
799 fn test_git_changed_file() {
800 let changed = GitChangedFile {
801 path: "src/main.rs".to_string(),
802 status: "M".to_string(),
803 diff_content: Some("+new line".to_string()),
804 };
805 assert_eq!(changed.path, "src/main.rs");
806 assert_eq!(changed.status, "M");
807 assert!(changed.diff_content.is_some());
808 }
809
810 #[test]
811 fn test_git_history_default() {
812 let history = GitHistory::default();
813 assert!(history.commits.is_empty());
814 assert!(history.changed_files.is_empty());
815 }
816
817 #[test]
818 fn test_repo_metadata_default() {
819 let meta = RepoMetadata::default();
820 assert_eq!(meta.total_files, 0);
821 assert_eq!(meta.total_lines, 0);
822 assert!(meta.languages.is_empty());
823 assert!(meta.framework.is_none());
824 assert!(meta.branch.is_none());
825 }
826
827 #[test]
828 fn test_compression_level_from_str() {
829 assert_eq!(CompressionLevel::from_str("none"), Some(CompressionLevel::None));
830 assert_eq!(CompressionLevel::from_str("minimal"), Some(CompressionLevel::Minimal));
831 assert_eq!(CompressionLevel::from_str("balanced"), Some(CompressionLevel::Balanced));
832 assert_eq!(CompressionLevel::from_str("aggressive"), Some(CompressionLevel::Aggressive));
833 assert_eq!(CompressionLevel::from_str("extreme"), Some(CompressionLevel::Extreme));
834 assert_eq!(CompressionLevel::from_str("focused"), Some(CompressionLevel::Focused));
835 assert_eq!(CompressionLevel::from_str("semantic"), Some(CompressionLevel::Semantic));
836
837 assert_eq!(CompressionLevel::from_str("SEMANTIC"), Some(CompressionLevel::Semantic));
839 assert_eq!(CompressionLevel::from_str("Balanced"), Some(CompressionLevel::Balanced));
840
841 assert_eq!(CompressionLevel::from_str("unknown"), None);
843 assert_eq!(CompressionLevel::from_str(""), None);
844 }
845
846 #[test]
847 fn test_compression_level_std_from_str() {
848 use std::str::FromStr;
849 assert_eq!("balanced".parse::<CompressionLevel>(), Ok(CompressionLevel::Balanced));
850 assert!("invalid".parse::<CompressionLevel>().is_err());
851 }
852
853 #[test]
854 fn test_compression_level_name() {
855 assert_eq!(CompressionLevel::None.name(), "none");
856 assert_eq!(CompressionLevel::Semantic.name(), "semantic");
857 }
858
859 #[test]
860 fn test_compression_level_expected_reduction() {
861 assert_eq!(CompressionLevel::None.expected_reduction(), 0);
862 assert_eq!(CompressionLevel::Minimal.expected_reduction(), 15);
863 assert_eq!(CompressionLevel::Balanced.expected_reduction(), 35);
864 assert_eq!(CompressionLevel::Aggressive.expected_reduction(), 60);
865 assert_eq!(CompressionLevel::Extreme.expected_reduction(), 80);
866 assert_eq!(CompressionLevel::Focused.expected_reduction(), 75);
867 assert_eq!(CompressionLevel::Semantic.expected_reduction(), 65);
868 }
869
870 #[test]
871 fn test_compression_level_description() {
872 for level in CompressionLevel::all() {
874 assert!(!level.description().is_empty());
875 }
876 }
877
878 #[test]
879 fn test_compression_level_all() {
880 let all = CompressionLevel::all();
881 assert_eq!(all.len(), 7);
882 assert!(all.contains(&CompressionLevel::Semantic));
883 }
884
885 #[test]
886 fn test_compression_level_default() {
887 let level = CompressionLevel::default();
888 assert_eq!(level, CompressionLevel::Balanced);
889 }
890}