Skip to main content

batuta/
types.rs

1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4/// Workflow phase in the 5-phase Batuta pipeline
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
6pub enum WorkflowPhase {
7    Analysis,
8    Transpilation,
9    Optimization,
10    Validation,
11    Deployment,
12}
13
14impl WorkflowPhase {
15    /// Get the next phase in the workflow
16    pub fn next(&self) -> Option<WorkflowPhase> {
17        match self {
18            WorkflowPhase::Analysis => Some(WorkflowPhase::Transpilation),
19            WorkflowPhase::Transpilation => Some(WorkflowPhase::Optimization),
20            WorkflowPhase::Optimization => Some(WorkflowPhase::Validation),
21            WorkflowPhase::Validation => Some(WorkflowPhase::Deployment),
22            WorkflowPhase::Deployment => None,
23        }
24    }
25
26    /// Get all phases in order
27    pub fn all() -> Vec<WorkflowPhase> {
28        vec![
29            WorkflowPhase::Analysis,
30            WorkflowPhase::Transpilation,
31            WorkflowPhase::Optimization,
32            WorkflowPhase::Validation,
33            WorkflowPhase::Deployment,
34        ]
35    }
36}
37
38impl std::fmt::Display for WorkflowPhase {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            WorkflowPhase::Analysis => write!(f, "Analysis"),
42            WorkflowPhase::Transpilation => write!(f, "Transpilation"),
43            WorkflowPhase::Optimization => write!(f, "Optimization"),
44            WorkflowPhase::Validation => write!(f, "Validation"),
45            WorkflowPhase::Deployment => write!(f, "Deployment"),
46        }
47    }
48}
49
50/// Status of a workflow phase
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52pub enum PhaseStatus {
53    NotStarted,
54    InProgress,
55    Completed,
56    Failed,
57}
58
59impl std::fmt::Display for PhaseStatus {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            PhaseStatus::NotStarted => write!(f, "Not Started"),
63            PhaseStatus::InProgress => write!(f, "In Progress"),
64            PhaseStatus::Completed => write!(f, "Completed"),
65            PhaseStatus::Failed => write!(f, "Failed"),
66        }
67    }
68}
69
70/// Information about a single phase
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct PhaseInfo {
73    pub phase: WorkflowPhase,
74    pub status: PhaseStatus,
75    pub started_at: Option<chrono::DateTime<chrono::Utc>>,
76    pub completed_at: Option<chrono::DateTime<chrono::Utc>>,
77    pub error: Option<String>,
78}
79
80impl PhaseInfo {
81    pub fn new(phase: WorkflowPhase) -> Self {
82        Self {
83            phase,
84            status: PhaseStatus::NotStarted,
85            started_at: None,
86            completed_at: None,
87            error: None,
88        }
89    }
90
91    pub fn start(&mut self) {
92        self.status = PhaseStatus::InProgress;
93        self.started_at = Some(chrono::Utc::now());
94    }
95
96    pub fn complete(&mut self) {
97        self.status = PhaseStatus::Completed;
98        self.completed_at = Some(chrono::Utc::now());
99    }
100
101    pub fn fail(&mut self, error: String) {
102        self.status = PhaseStatus::Failed;
103        self.error = Some(error);
104        self.completed_at = Some(chrono::Utc::now());
105    }
106}
107
108/// Complete workflow state tracking
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct WorkflowState {
111    pub current_phase: Option<WorkflowPhase>,
112    pub phases: std::collections::HashMap<WorkflowPhase, PhaseInfo>,
113}
114
115impl Default for WorkflowState {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121impl WorkflowState {
122    pub fn new() -> Self {
123        let mut phases = std::collections::HashMap::new();
124        for phase in WorkflowPhase::all() {
125            phases.insert(phase, PhaseInfo::new(phase));
126        }
127
128        Self { current_phase: None, phases }
129    }
130
131    /// Load workflow state from file
132    pub fn load(path: &std::path::Path) -> anyhow::Result<Self> {
133        if !path.exists() {
134            return Ok(Self::new());
135        }
136
137        let content = std::fs::read_to_string(path)?;
138        let state = serde_json::from_str(&content)?;
139        Ok(state)
140    }
141
142    /// Save workflow state to file
143    pub fn save(&self, path: &std::path::Path) -> anyhow::Result<()> {
144        let content = serde_json::to_string_pretty(self)?;
145        std::fs::write(path, content)?;
146        Ok(())
147    }
148
149    /// Start a phase
150    pub fn start_phase(&mut self, phase: WorkflowPhase) {
151        self.current_phase = Some(phase);
152        if let Some(info) = self.phases.get_mut(&phase) {
153            info.start();
154        }
155    }
156
157    /// Complete the current phase
158    pub fn complete_phase(&mut self, phase: WorkflowPhase) {
159        if let Some(info) = self.phases.get_mut(&phase) {
160            info.complete();
161        }
162
163        // Move to next phase if available
164        if let Some(next) = phase.next() {
165            self.current_phase = Some(next);
166        } else {
167            self.current_phase = None;
168        }
169    }
170
171    /// Fail the current phase
172    pub fn fail_phase(&mut self, phase: WorkflowPhase, error: String) {
173        if let Some(info) = self.phases.get_mut(&phase) {
174            info.fail(error);
175        }
176        self.current_phase = Some(phase);
177    }
178
179    /// Get status of a specific phase
180    pub fn get_phase_status(&self, phase: WorkflowPhase) -> PhaseStatus {
181        self.phases.get(&phase).map(|info| info.status).unwrap_or(PhaseStatus::NotStarted)
182    }
183
184    /// Check if a phase is completed
185    pub fn is_phase_completed(&self, phase: WorkflowPhase) -> bool {
186        self.get_phase_status(phase) == PhaseStatus::Completed
187    }
188
189    /// Get overall progress percentage
190    pub fn progress_percentage(&self) -> f64 {
191        let total = WorkflowPhase::all().len() as f64;
192        let completed =
193            self.phases.values().filter(|info| info.status == PhaseStatus::Completed).count()
194                as f64;
195
196        (completed / total) * 100.0
197    }
198}
199
200/// Programming language detected in the project
201#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
202pub enum Language {
203    Python,
204    C,
205    Cpp,
206    Rust,
207    Shell,
208    JavaScript,
209    TypeScript,
210    Go,
211    Java,
212    Other(String),
213}
214
215impl std::fmt::Display for Language {
216    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217        match self {
218            Language::Python => write!(f, "Python"),
219            Language::C => write!(f, "C"),
220            Language::Cpp => write!(f, "C++"),
221            Language::Rust => write!(f, "Rust"),
222            Language::Shell => write!(f, "Shell"),
223            Language::JavaScript => write!(f, "JavaScript"),
224            Language::TypeScript => write!(f, "TypeScript"),
225            Language::Go => write!(f, "Go"),
226            Language::Java => write!(f, "Java"),
227            Language::Other(name) => write!(f, "{}", name),
228        }
229    }
230}
231
232impl std::str::FromStr for Language {
233    type Err = std::convert::Infallible;
234
235    fn from_str(s: &str) -> Result<Self, Self::Err> {
236        Ok(match s.to_lowercase().as_str() {
237            "python" => Language::Python,
238            "c" => Language::C,
239            "c++" | "cpp" => Language::Cpp,
240            "rust" => Language::Rust,
241            "shell" | "bash" | "sh" => Language::Shell,
242            "javascript" | "js" => Language::JavaScript,
243            "typescript" | "ts" => Language::TypeScript,
244            "go" | "golang" => Language::Go,
245            "java" => Language::Java,
246            other => Language::Other(other.to_string()),
247        })
248    }
249}
250
251/// Dependency manager type
252#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253pub enum DependencyManager {
254    Pip,    // requirements.txt
255    Pipenv, // Pipfile
256    Poetry, // pyproject.toml
257    Conda,  // environment.yml
258    Cargo,  // Cargo.toml
259    Npm,    // package.json
260    Yarn,   // yarn.lock
261    GoMod,  // go.mod
262    Maven,  // pom.xml
263    Gradle, // build.gradle
264    Make,   // Makefile
265}
266
267impl std::fmt::Display for DependencyManager {
268    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        match self {
270            DependencyManager::Pip => write!(f, "pip (requirements.txt)"),
271            DependencyManager::Pipenv => write!(f, "Pipenv"),
272            DependencyManager::Poetry => write!(f, "Poetry"),
273            DependencyManager::Conda => write!(f, "Conda"),
274            DependencyManager::Cargo => write!(f, "Cargo"),
275            DependencyManager::Npm => write!(f, "npm"),
276            DependencyManager::Yarn => write!(f, "Yarn"),
277            DependencyManager::GoMod => write!(f, "Go modules"),
278            DependencyManager::Maven => write!(f, "Maven"),
279            DependencyManager::Gradle => write!(f, "Gradle"),
280            DependencyManager::Make => write!(f, "Make"),
281        }
282    }
283}
284
285/// Information about detected dependencies
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct DependencyInfo {
288    pub manager: DependencyManager,
289    pub file_path: PathBuf,
290    pub count: Option<usize>,
291}
292
293/// Language statistics
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct LanguageStats {
296    pub language: Language,
297    pub file_count: usize,
298    pub line_count: usize,
299    pub percentage: f64,
300}
301
302/// Complete project analysis results
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct ProjectAnalysis {
305    pub root_path: PathBuf,
306    pub languages: Vec<LanguageStats>,
307    pub primary_language: Option<Language>,
308    pub dependencies: Vec<DependencyInfo>,
309    pub total_files: usize,
310    pub total_lines: usize,
311    pub tdg_score: Option<f64>,
312}
313
314impl ProjectAnalysis {
315    pub fn new(root_path: PathBuf) -> Self {
316        Self {
317            root_path,
318            languages: Vec::new(),
319            primary_language: None,
320            dependencies: Vec::new(),
321            total_files: 0,
322            total_lines: 0,
323            tdg_score: None,
324        }
325    }
326
327    pub fn recommend_transpiler(&self) -> Option<&'static str> {
328        contract_pre_transpile!(self);
329        match self.primary_language.as_ref()? {
330            Language::Python => Some("Depyler (Python → Rust)"),
331            Language::C | Language::Cpp => Some("Decy (C/C++ → Rust)"),
332            Language::Shell => Some("Bashrs (Shell → Rust)"),
333            Language::Rust => Some("Already Rust! Consider Ruchy for gradual typing."),
334            _ => None,
335        }
336    }
337
338    pub fn has_ml_dependencies(&self) -> bool {
339        // Check if project uses ML frameworks
340        self.dependencies.iter().any(|dep| {
341            matches!(
342                dep.manager,
343                DependencyManager::Pip | DependencyManager::Conda | DependencyManager::Poetry
344            )
345        })
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    // =========================================================================
354    // WorkflowPhase Tests
355    // =========================================================================
356
357    #[test]
358    fn test_workflow_phase_next() {
359        assert_eq!(WorkflowPhase::Analysis.next(), Some(WorkflowPhase::Transpilation));
360        assert_eq!(WorkflowPhase::Transpilation.next(), Some(WorkflowPhase::Optimization));
361        assert_eq!(WorkflowPhase::Optimization.next(), Some(WorkflowPhase::Validation));
362        assert_eq!(WorkflowPhase::Validation.next(), Some(WorkflowPhase::Deployment));
363        assert_eq!(WorkflowPhase::Deployment.next(), None);
364    }
365
366    #[test]
367    fn test_workflow_phase_all() {
368        let phases = WorkflowPhase::all();
369        assert_eq!(phases.len(), 5);
370        assert_eq!(phases[0], WorkflowPhase::Analysis);
371        assert_eq!(phases[1], WorkflowPhase::Transpilation);
372        assert_eq!(phases[2], WorkflowPhase::Optimization);
373        assert_eq!(phases[3], WorkflowPhase::Validation);
374        assert_eq!(phases[4], WorkflowPhase::Deployment);
375    }
376
377    #[test]
378    fn test_workflow_phase_display() {
379        assert_eq!(WorkflowPhase::Analysis.to_string(), "Analysis");
380        assert_eq!(WorkflowPhase::Transpilation.to_string(), "Transpilation");
381        assert_eq!(WorkflowPhase::Optimization.to_string(), "Optimization");
382        assert_eq!(WorkflowPhase::Validation.to_string(), "Validation");
383        assert_eq!(WorkflowPhase::Deployment.to_string(), "Deployment");
384    }
385
386    #[test]
387    fn test_workflow_phase_serialization() {
388        let phase = WorkflowPhase::Analysis;
389        let json = serde_json::to_string(&phase).expect("json serialize failed");
390        let deserialized: WorkflowPhase =
391            serde_json::from_str(&json).expect("json deserialize failed");
392        assert_eq!(phase, deserialized);
393    }
394
395    // =========================================================================
396    // PhaseStatus Tests
397    // =========================================================================
398
399    #[test]
400    fn test_phase_status_display() {
401        assert_eq!(PhaseStatus::NotStarted.to_string(), "Not Started");
402        assert_eq!(PhaseStatus::InProgress.to_string(), "In Progress");
403        assert_eq!(PhaseStatus::Completed.to_string(), "Completed");
404        assert_eq!(PhaseStatus::Failed.to_string(), "Failed");
405    }
406
407    #[test]
408    fn test_phase_status_equality() {
409        assert_eq!(PhaseStatus::NotStarted, PhaseStatus::NotStarted);
410        assert_eq!(PhaseStatus::Completed, PhaseStatus::Completed);
411        assert_ne!(PhaseStatus::NotStarted, PhaseStatus::Completed);
412        assert_ne!(PhaseStatus::InProgress, PhaseStatus::Failed);
413    }
414
415    #[test]
416    fn test_phase_status_serialization() {
417        let status = PhaseStatus::Completed;
418        let json = serde_json::to_string(&status).expect("json serialize failed");
419        let deserialized: PhaseStatus =
420            serde_json::from_str(&json).expect("json deserialize failed");
421        assert_eq!(status, deserialized);
422    }
423
424    // =========================================================================
425    // PhaseInfo Tests
426    // =========================================================================
427
428    #[test]
429    fn test_phase_info_new() {
430        let info = PhaseInfo::new(WorkflowPhase::Analysis);
431        assert_eq!(info.phase, WorkflowPhase::Analysis);
432        assert_eq!(info.status, PhaseStatus::NotStarted);
433        assert!(info.started_at.is_none());
434        assert!(info.completed_at.is_none());
435        assert!(info.error.is_none());
436    }
437
438    #[test]
439    fn test_phase_info_start() {
440        let mut info = PhaseInfo::new(WorkflowPhase::Analysis);
441        info.start();
442        assert_eq!(info.status, PhaseStatus::InProgress);
443        assert!(info.started_at.is_some());
444        assert!(info.completed_at.is_none());
445    }
446
447    #[test]
448    fn test_phase_info_complete() {
449        let mut info = PhaseInfo::new(WorkflowPhase::Analysis);
450        info.start();
451        info.complete();
452        assert_eq!(info.status, PhaseStatus::Completed);
453        assert!(info.started_at.is_some());
454        assert!(info.completed_at.is_some());
455        assert!(info.error.is_none());
456    }
457
458    #[test]
459    fn test_phase_info_fail() {
460        let mut info = PhaseInfo::new(WorkflowPhase::Analysis);
461        info.start();
462        info.fail("Test error".to_string());
463        assert_eq!(info.status, PhaseStatus::Failed);
464        assert_eq!(info.error.as_deref(), Some("Test error"));
465        assert!(info.completed_at.is_some());
466    }
467
468    #[test]
469    fn test_phase_info_serialization() {
470        let info = PhaseInfo::new(WorkflowPhase::Transpilation);
471        let json = serde_json::to_string(&info).expect("json serialize failed");
472        let deserialized: PhaseInfo = serde_json::from_str(&json).expect("json deserialize failed");
473        assert_eq!(info.phase, deserialized.phase);
474        assert_eq!(info.status, deserialized.status);
475    }
476
477    // =========================================================================
478    // WorkflowState Tests
479    // =========================================================================
480
481    #[test]
482    fn test_workflow_state_new() {
483        let state = WorkflowState::new();
484        assert!(state.current_phase.is_none());
485        assert_eq!(state.phases.len(), 5);
486
487        for phase in WorkflowPhase::all() {
488            assert!(state.phases.contains_key(&phase));
489            assert_eq!(state.get_phase_status(phase), PhaseStatus::NotStarted);
490        }
491    }
492
493    #[test]
494    fn test_workflow_state_default() {
495        let state = WorkflowState::default();
496        assert!(state.current_phase.is_none());
497        assert_eq!(state.phases.len(), 5);
498    }
499
500    #[test]
501    fn test_workflow_state_start_phase() {
502        let mut state = WorkflowState::new();
503        state.start_phase(WorkflowPhase::Analysis);
504
505        assert_eq!(state.current_phase, Some(WorkflowPhase::Analysis));
506        assert_eq!(state.get_phase_status(WorkflowPhase::Analysis), PhaseStatus::InProgress);
507    }
508
509    #[test]
510    fn test_workflow_state_complete_phase() {
511        let mut state = WorkflowState::new();
512        state.start_phase(WorkflowPhase::Analysis);
513        state.complete_phase(WorkflowPhase::Analysis);
514
515        assert_eq!(state.get_phase_status(WorkflowPhase::Analysis), PhaseStatus::Completed);
516        // Should automatically move to next phase
517        assert_eq!(state.current_phase, Some(WorkflowPhase::Transpilation));
518    }
519
520    #[test]
521    fn test_workflow_state_complete_final_phase() {
522        let mut state = WorkflowState::new();
523        state.start_phase(WorkflowPhase::Deployment);
524        state.complete_phase(WorkflowPhase::Deployment);
525
526        assert_eq!(state.get_phase_status(WorkflowPhase::Deployment), PhaseStatus::Completed);
527        // No next phase after Deployment
528        assert!(state.current_phase.is_none());
529    }
530
531    #[test]
532    fn test_workflow_state_fail_phase() {
533        let mut state = WorkflowState::new();
534        state.start_phase(WorkflowPhase::Analysis);
535        state.fail_phase(WorkflowPhase::Analysis, "Analysis failed".to_string());
536
537        assert_eq!(state.get_phase_status(WorkflowPhase::Analysis), PhaseStatus::Failed);
538        assert_eq!(state.current_phase, Some(WorkflowPhase::Analysis));
539
540        let phase_info = state.phases.get(&WorkflowPhase::Analysis).expect("key not found");
541        assert_eq!(phase_info.error.as_deref(), Some("Analysis failed"));
542    }
543
544    #[test]
545    fn test_workflow_state_is_phase_completed() {
546        let mut state = WorkflowState::new();
547        assert!(!state.is_phase_completed(WorkflowPhase::Analysis));
548
549        state.start_phase(WorkflowPhase::Analysis);
550        assert!(!state.is_phase_completed(WorkflowPhase::Analysis));
551
552        state.complete_phase(WorkflowPhase::Analysis);
553        assert!(state.is_phase_completed(WorkflowPhase::Analysis));
554    }
555
556    #[test]
557    fn test_workflow_state_progress_percentage() {
558        let mut state = WorkflowState::new();
559        assert_eq!(state.progress_percentage(), 0.0);
560
561        state.start_phase(WorkflowPhase::Analysis);
562        state.complete_phase(WorkflowPhase::Analysis);
563        assert_eq!(state.progress_percentage(), 20.0); // 1/5 = 20%
564
565        state.start_phase(WorkflowPhase::Transpilation);
566        state.complete_phase(WorkflowPhase::Transpilation);
567        assert_eq!(state.progress_percentage(), 40.0); // 2/5 = 40%
568    }
569
570    #[test]
571    fn test_workflow_state_save_and_load() {
572        use tempfile::TempDir;
573
574        let temp_dir = TempDir::new().expect("tempdir creation failed");
575        let state_path = temp_dir.path().join("workflow-state.json");
576
577        let mut state = WorkflowState::new();
578        state.start_phase(WorkflowPhase::Analysis);
579        state.complete_phase(WorkflowPhase::Analysis);
580
581        state.save(&state_path).expect("save failed");
582        assert!(state_path.exists());
583
584        let loaded_state = WorkflowState::load(&state_path).expect("unexpected failure");
585        assert_eq!(loaded_state.current_phase, state.current_phase);
586        assert_eq!(loaded_state.get_phase_status(WorkflowPhase::Analysis), PhaseStatus::Completed);
587    }
588
589    #[test]
590    fn test_workflow_state_load_nonexistent() {
591        use tempfile::TempDir;
592
593        let temp_dir = TempDir::new().expect("tempdir creation failed");
594        let state_path = temp_dir.path().join("nonexistent.json");
595
596        let state = WorkflowState::load(&state_path).expect("unexpected failure");
597        assert!(state.current_phase.is_none());
598        assert_eq!(state.progress_percentage(), 0.0);
599    }
600
601    // =========================================================================
602    // Language Tests
603    // =========================================================================
604
605    #[test]
606    fn test_language_display() {
607        assert_eq!(Language::Python.to_string(), "Python");
608        assert_eq!(Language::C.to_string(), "C");
609        assert_eq!(Language::Cpp.to_string(), "C++");
610        assert_eq!(Language::Rust.to_string(), "Rust");
611        assert_eq!(Language::Shell.to_string(), "Shell");
612        assert_eq!(Language::JavaScript.to_string(), "JavaScript");
613        assert_eq!(Language::TypeScript.to_string(), "TypeScript");
614        assert_eq!(Language::Go.to_string(), "Go");
615        assert_eq!(Language::Java.to_string(), "Java");
616        assert_eq!(Language::Other("Ruby".to_string()).to_string(), "Ruby");
617    }
618
619    #[test]
620    fn test_language_equality() {
621        assert_eq!(Language::Python, Language::Python);
622        assert_ne!(Language::Python, Language::Rust);
623        assert_eq!(Language::Other("Kotlin".to_string()), Language::Other("Kotlin".to_string()));
624    }
625
626    #[test]
627    fn test_language_serialization() {
628        let lang = Language::Python;
629        let json = serde_json::to_string(&lang).expect("json serialize failed");
630        let deserialized: Language = serde_json::from_str(&json).expect("json deserialize failed");
631        assert_eq!(lang, deserialized);
632
633        let other_lang = Language::Other("Haskell".to_string());
634        let json2 = serde_json::to_string(&other_lang).expect("json serialize failed");
635        let deserialized2: Language =
636            serde_json::from_str(&json2).expect("json deserialize failed");
637        assert_eq!(other_lang, deserialized2);
638    }
639
640    // =========================================================================
641    // DependencyManager Tests
642    // =========================================================================
643
644    #[test]
645    fn test_dependency_manager_display() {
646        assert_eq!(DependencyManager::Pip.to_string(), "pip (requirements.txt)");
647        assert_eq!(DependencyManager::Pipenv.to_string(), "Pipenv");
648        assert_eq!(DependencyManager::Poetry.to_string(), "Poetry");
649        assert_eq!(DependencyManager::Conda.to_string(), "Conda");
650        assert_eq!(DependencyManager::Cargo.to_string(), "Cargo");
651        assert_eq!(DependencyManager::Npm.to_string(), "npm");
652        assert_eq!(DependencyManager::Yarn.to_string(), "Yarn");
653        assert_eq!(DependencyManager::GoMod.to_string(), "Go modules");
654        assert_eq!(DependencyManager::Maven.to_string(), "Maven");
655        assert_eq!(DependencyManager::Gradle.to_string(), "Gradle");
656        assert_eq!(DependencyManager::Make.to_string(), "Make");
657    }
658
659    #[test]
660    fn test_dependency_manager_equality() {
661        assert_eq!(DependencyManager::Pip, DependencyManager::Pip);
662        assert_ne!(DependencyManager::Pip, DependencyManager::Cargo);
663    }
664
665    #[test]
666    fn test_dependency_manager_serialization() {
667        let manager = DependencyManager::Cargo;
668        let json = serde_json::to_string(&manager).expect("json serialize failed");
669        let deserialized: DependencyManager =
670            serde_json::from_str(&json).expect("json deserialize failed");
671        assert_eq!(manager, deserialized);
672    }
673
674    // =========================================================================
675    // DependencyInfo Tests
676    // =========================================================================
677
678    #[test]
679    fn test_dependency_info_creation() {
680        let dep_info = DependencyInfo {
681            manager: DependencyManager::Pip,
682            file_path: PathBuf::from("requirements.txt"),
683            count: Some(10),
684        };
685
686        assert_eq!(dep_info.manager, DependencyManager::Pip);
687        assert_eq!(dep_info.file_path, PathBuf::from("requirements.txt"));
688        assert_eq!(dep_info.count, Some(10));
689    }
690
691    #[test]
692    fn test_dependency_info_serialization() {
693        let dep_info = DependencyInfo {
694            manager: DependencyManager::Cargo,
695            file_path: PathBuf::from("Cargo.toml"),
696            count: Some(5),
697        };
698
699        let json = serde_json::to_string(&dep_info).expect("json serialize failed");
700        let deserialized: DependencyInfo =
701            serde_json::from_str(&json).expect("json deserialize failed");
702
703        assert_eq!(dep_info.manager, deserialized.manager);
704        assert_eq!(dep_info.file_path, deserialized.file_path);
705        assert_eq!(dep_info.count, deserialized.count);
706    }
707
708    // =========================================================================
709    // LanguageStats Tests
710    // =========================================================================
711
712    #[test]
713    fn test_language_stats_creation() {
714        let stats = LanguageStats {
715            language: Language::Python,
716            file_count: 50,
717            line_count: 10000,
718            percentage: 75.5,
719        };
720
721        assert_eq!(stats.language, Language::Python);
722        assert_eq!(stats.file_count, 50);
723        assert_eq!(stats.line_count, 10000);
724        assert_eq!(stats.percentage, 75.5);
725    }
726
727    #[test]
728    fn test_language_stats_serialization() {
729        let stats = LanguageStats {
730            language: Language::Rust,
731            file_count: 30,
732            line_count: 5000,
733            percentage: 24.5,
734        };
735
736        let json = serde_json::to_string(&stats).expect("json serialize failed");
737        let deserialized: LanguageStats =
738            serde_json::from_str(&json).expect("json deserialize failed");
739
740        assert_eq!(stats.language, deserialized.language);
741        assert_eq!(stats.file_count, deserialized.file_count);
742        assert_eq!(stats.line_count, deserialized.line_count);
743        assert_eq!(stats.percentage, deserialized.percentage);
744    }
745
746    // =========================================================================
747    // ProjectAnalysis Tests
748    // =========================================================================
749
750    #[test]
751    fn test_project_analysis_new() {
752        let analysis = ProjectAnalysis::new(PathBuf::from("/test/project"));
753
754        assert_eq!(analysis.root_path, PathBuf::from("/test/project"));
755        assert_eq!(analysis.languages.len(), 0);
756        assert!(analysis.primary_language.is_none());
757        assert_eq!(analysis.dependencies.len(), 0);
758        assert_eq!(analysis.total_files, 0);
759        assert_eq!(analysis.total_lines, 0);
760        assert!(analysis.tdg_score.is_none());
761    }
762
763    #[test]
764    fn test_project_analysis_recommend_transpiler_python() {
765        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
766        analysis.primary_language = Some(Language::Python);
767
768        assert_eq!(analysis.recommend_transpiler(), Some("Depyler (Python → Rust)"));
769    }
770
771    #[test]
772    fn test_project_analysis_recommend_transpiler_c() {
773        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
774        analysis.primary_language = Some(Language::C);
775
776        assert_eq!(analysis.recommend_transpiler(), Some("Decy (C/C++ → Rust)"));
777    }
778
779    #[test]
780    fn test_project_analysis_recommend_transpiler_cpp() {
781        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
782        analysis.primary_language = Some(Language::Cpp);
783
784        assert_eq!(analysis.recommend_transpiler(), Some("Decy (C/C++ → Rust)"));
785    }
786
787    #[test]
788    fn test_project_analysis_recommend_transpiler_shell() {
789        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
790        analysis.primary_language = Some(Language::Shell);
791
792        assert_eq!(analysis.recommend_transpiler(), Some("Bashrs (Shell → Rust)"));
793    }
794
795    #[test]
796    fn test_project_analysis_recommend_transpiler_rust() {
797        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
798        analysis.primary_language = Some(Language::Rust);
799
800        assert_eq!(
801            analysis.recommend_transpiler(),
802            Some("Already Rust! Consider Ruchy for gradual typing.")
803        );
804    }
805
806    #[test]
807    fn test_project_analysis_recommend_transpiler_other() {
808        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
809        analysis.primary_language = Some(Language::Java);
810
811        assert_eq!(analysis.recommend_transpiler(), None);
812    }
813
814    #[test]
815    fn test_project_analysis_recommend_transpiler_none() {
816        let analysis = ProjectAnalysis::new(PathBuf::from("/test"));
817        assert_eq!(analysis.recommend_transpiler(), None);
818    }
819
820    #[test]
821    fn test_project_analysis_has_ml_dependencies_pip() {
822        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
823        analysis.dependencies.push(DependencyInfo {
824            manager: DependencyManager::Pip,
825            file_path: PathBuf::from("requirements.txt"),
826            count: Some(10),
827        });
828
829        assert!(analysis.has_ml_dependencies());
830    }
831
832    #[test]
833    fn test_project_analysis_has_ml_dependencies_conda() {
834        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
835        analysis.dependencies.push(DependencyInfo {
836            manager: DependencyManager::Conda,
837            file_path: PathBuf::from("environment.yml"),
838            count: Some(5),
839        });
840
841        assert!(analysis.has_ml_dependencies());
842    }
843
844    #[test]
845    fn test_project_analysis_has_ml_dependencies_poetry() {
846        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
847        analysis.dependencies.push(DependencyInfo {
848            manager: DependencyManager::Poetry,
849            file_path: PathBuf::from("pyproject.toml"),
850            count: Some(8),
851        });
852
853        assert!(analysis.has_ml_dependencies());
854    }
855
856    #[test]
857    fn test_project_analysis_has_ml_dependencies_false() {
858        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
859        analysis.dependencies.push(DependencyInfo {
860            manager: DependencyManager::Cargo,
861            file_path: PathBuf::from("Cargo.toml"),
862            count: Some(5),
863        });
864
865        assert!(!analysis.has_ml_dependencies());
866    }
867
868    #[test]
869    fn test_project_analysis_has_ml_dependencies_empty() {
870        let analysis = ProjectAnalysis::new(PathBuf::from("/test"));
871        assert!(!analysis.has_ml_dependencies());
872    }
873
874    #[test]
875    fn test_project_analysis_serialization() {
876        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
877        analysis.primary_language = Some(Language::Python);
878        analysis.total_files = 10;
879        analysis.total_lines = 1000;
880        analysis.tdg_score = Some(85.5);
881
882        let json = serde_json::to_string(&analysis).expect("json serialize failed");
883        let deserialized: ProjectAnalysis =
884            serde_json::from_str(&json).expect("json deserialize failed");
885
886        assert_eq!(analysis.root_path, deserialized.root_path);
887        assert_eq!(analysis.primary_language, deserialized.primary_language);
888        assert_eq!(analysis.total_files, deserialized.total_files);
889        assert_eq!(analysis.total_lines, deserialized.total_lines);
890        assert_eq!(analysis.tdg_score, deserialized.tdg_score);
891    }
892}