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