ricecoder_modes/
code_mode.rs

1//! Code Mode implementation for code generation and execution
2
3use async_trait::async_trait;
4use std::path::{Path, PathBuf};
5use std::time::Instant;
6
7use crate::error::Result;
8use crate::mode::Mode;
9use crate::models::{
10    Capability, ChangeSummary, ComplexityLevel, ModeAction, ModeConfig, ModeConstraints,
11    ModeContext, ModeResponse, Operation,
12};
13
14/// Code Mode for focused code generation and modification
15///
16/// Code Mode provides full capabilities for:
17/// - Code generation from specifications
18/// - File creation and modification
19/// - Test execution
20/// - Quality validation
21/// - Change summarization
22#[derive(Debug, Clone)]
23pub struct CodeMode {
24    config: ModeConfig,
25}
26
27impl CodeMode {
28    /// Create a new Code Mode instance
29    pub fn new() -> Self {
30        Self {
31            config: ModeConfig {
32                temperature: 0.7,
33                max_tokens: 4096,
34                system_prompt: "You are a code generation assistant. Focus on writing clean, \
35                    well-documented code that follows best practices and workspace standards."
36                    .to_string(),
37                capabilities: vec![
38                    Capability::CodeGeneration,
39                    Capability::CodeModification,
40                    Capability::FileOperations,
41                    Capability::CommandExecution,
42                    Capability::TestExecution,
43                    Capability::QualityValidation,
44                ],
45                constraints: ModeConstraints {
46                    allow_file_operations: true,
47                    allow_command_execution: true,
48                    allow_code_generation: true,
49                    require_specs: false,
50                    auto_think_more_threshold: Some(ComplexityLevel::Complex),
51                },
52            },
53        }
54    }
55
56    /// Create a Code Mode with custom configuration
57    pub fn with_config(config: ModeConfig) -> Self {
58        Self { config }
59    }
60
61    /// Generate code from a specification
62    ///
63    /// This method generates code based on the provided specification.
64    /// The generated code is tracked for later file operations.
65    pub fn generate_code(&self, spec: &str) -> Result<String> {
66        // For now, return the spec as a placeholder
67        // In a real implementation, this would call an LLM
68        Ok(format!(
69            "// Generated from spec:\n// {}\n\n// TODO: Implement based on spec",
70            spec
71        ))
72    }
73
74    /// Create a file with the given content
75    ///
76    /// This method creates a new file at the specified path with the given content.
77    pub async fn create_file(&self, path: &Path, content: &str) -> Result<()> {
78        // Validate that file operations are allowed
79        if !self.config.constraints.allow_file_operations {
80            return Err(crate::error::ModeError::OperationNotAllowed {
81                mode: self.id().to_string(),
82                operation: Operation::ModifyFile.to_string(),
83            });
84        }
85
86        // Create parent directories if they don't exist
87        if let Some(parent) = path.parent() {
88            if !parent.as_os_str().is_empty() {
89                std::fs::create_dir_all(parent).map_err(|e| {
90                    crate::error::ModeError::ProcessingFailed(format!(
91                        "Failed to create directory: {}",
92                        e
93                    ))
94                })?;
95            }
96        }
97
98        // Write the file
99        std::fs::write(path, content).map_err(|e| {
100            crate::error::ModeError::ProcessingFailed(format!("Failed to write file: {}", e))
101        })?;
102
103        Ok(())
104    }
105
106    /// Modify a file with the given diff
107    ///
108    /// This method applies a diff to an existing file.
109    pub async fn modify_file(&self, path: &Path, diff: &str) -> Result<()> {
110        // Validate that file operations are allowed
111        if !self.config.constraints.allow_file_operations {
112            return Err(crate::error::ModeError::OperationNotAllowed {
113                mode: self.id().to_string(),
114                operation: Operation::ModifyFile.to_string(),
115            });
116        }
117
118        // Read the current file content
119        let current_content = std::fs::read_to_string(path).map_err(|e| {
120            crate::error::ModeError::ProcessingFailed(format!("Failed to read file: {}", e))
121        })?;
122
123        // For now, just append the diff as a comment
124        // In a real implementation, this would apply a proper diff
125        let new_content = format!("{}\n\n// Applied diff:\n{}", current_content, diff);
126
127        // Write the modified content
128        std::fs::write(path, new_content).map_err(|e| {
129            crate::error::ModeError::ProcessingFailed(format!("Failed to write file: {}", e))
130        })?;
131
132        Ok(())
133    }
134
135    /// Track file changes and return a summary
136    ///
137    /// This method creates a summary of file operations performed.
138    pub fn track_changes(&self, files_created: usize, files_modified: usize) -> ChangeSummary {
139        ChangeSummary {
140            files_created,
141            files_modified,
142            tests_run: 0,
143            tests_passed: 0,
144            quality_issues: Vec::new(),
145        }
146    }
147
148    /// Run tests for the given paths
149    ///
150    /// This method executes tests and captures the results.
151    pub async fn run_tests(&self, paths: &[PathBuf]) -> Result<(usize, usize, Vec<String>)> {
152        // Validate that test execution is allowed
153        if !self
154            .config
155            .capabilities
156            .contains(&Capability::TestExecution)
157        {
158            return Err(crate::error::ModeError::OperationNotAllowed {
159                mode: self.id().to_string(),
160                operation: Operation::RunTests.to_string(),
161            });
162        }
163
164        let mut tests_run = 0;
165        let mut tests_passed = 0;
166        let mut failures = Vec::new();
167
168        // For each path, check if it exists and count it as a test
169        for path in paths {
170            if path.exists() {
171                tests_run += 1;
172                // Assume test passes if file exists (simplified for now)
173                tests_passed += 1;
174            } else {
175                tests_run += 1;
176                failures.push(format!("Test file not found: {}", path.display()));
177            }
178        }
179
180        Ok((tests_run, tests_passed, failures))
181    }
182
183    /// Capture test results and update summary
184    ///
185    /// This method updates a change summary with test results.
186    pub fn capture_test_results(
187        &self,
188        mut summary: ChangeSummary,
189        tests_run: usize,
190        tests_passed: usize,
191        failures: Vec<String>,
192    ) -> ChangeSummary {
193        summary.tests_run = tests_run;
194        summary.tests_passed = tests_passed;
195        summary.quality_issues.extend(failures);
196        summary
197    }
198
199    /// Report test failures
200    ///
201    /// This method formats test failures for reporting.
202    pub fn report_test_failures(&self, failures: &[String]) -> String {
203        if failures.is_empty() {
204            "All tests passed!".to_string()
205        } else {
206            format!(
207                "Test failures:\n{}",
208                failures
209                    .iter()
210                    .map(|f| format!("  - {}", f))
211                    .collect::<Vec<_>>()
212                    .join("\n")
213            )
214        }
215    }
216
217    /// Validate code quality against workspace standards
218    ///
219    /// This method checks code for quality issues.
220    pub async fn validate_quality(&self, paths: &[PathBuf]) -> Result<Vec<String>> {
221        // Validate that quality validation is allowed
222        if !self
223            .config
224            .capabilities
225            .contains(&Capability::QualityValidation)
226        {
227            return Err(crate::error::ModeError::OperationNotAllowed {
228                mode: self.id().to_string(),
229                operation: Operation::ValidateQuality.to_string(),
230            });
231        }
232
233        let mut issues = Vec::new();
234
235        // Check each file for quality issues
236        for path in paths {
237            if !path.exists() {
238                issues.push(format!("File not found: {}", path.display()));
239                continue;
240            }
241
242            // Read file content
243            match std::fs::read_to_string(path) {
244                Ok(content) => {
245                    // Check for common quality issues
246                    if content.is_empty() {
247                        issues.push(format!("Empty file: {}", path.display()));
248                    }
249
250                    // Check for TODO comments
251                    if content.contains("TODO") {
252                        issues.push(format!("TODO found in: {}", path.display()));
253                    }
254
255                    // Check for FIXME comments
256                    if content.contains("FIXME") {
257                        issues.push(format!("FIXME found in: {}", path.display()));
258                    }
259
260                    // Check for unwrap() calls
261                    if content.contains(".unwrap()") {
262                        issues.push(format!("Unwrap found in: {}", path.display()));
263                    }
264                }
265                Err(e) => {
266                    issues.push(format!("Failed to read file {}: {}", path.display(), e));
267                }
268            }
269        }
270
271        Ok(issues)
272    }
273
274    /// Report quality issues
275    ///
276    /// This method formats quality issues for reporting.
277    pub fn report_quality_issues(&self, issues: &[String]) -> String {
278        if issues.is_empty() {
279            "No quality issues found!".to_string()
280        } else {
281            format!(
282                "Quality issues:\n{}",
283                issues
284                    .iter()
285                    .map(|i| format!("  - {}", i))
286                    .collect::<Vec<_>>()
287                    .join("\n")
288            )
289        }
290    }
291
292    /// Suggest improvements based on quality issues
293    ///
294    /// This method generates suggestions for code improvement.
295    pub fn suggest_improvements(&self, issues: &[String]) -> Vec<String> {
296        let mut suggestions = Vec::new();
297
298        for issue in issues {
299            if issue.contains("TODO") {
300                suggestions.push("Complete all TODO items before committing".to_string());
301            }
302            if issue.contains("FIXME") {
303                suggestions.push("Fix all FIXME items before committing".to_string());
304            }
305            if issue.contains("Unwrap") {
306                suggestions.push("Replace unwrap() with proper error handling".to_string());
307            }
308            if issue.contains("Empty file") {
309                suggestions.push("Remove empty files or add content".to_string());
310            }
311        }
312
313        // Remove duplicates
314        suggestions.sort();
315        suggestions.dedup();
316        suggestions
317    }
318
319    /// Generate a summary of all changes made
320    ///
321    /// This method creates a comprehensive summary including file counts, test results,
322    /// and quality issues.
323    pub fn generate_change_summary(
324        &self,
325        files_created: usize,
326        files_modified: usize,
327        tests_run: usize,
328        tests_passed: usize,
329        quality_issues: Vec<String>,
330    ) -> ChangeSummary {
331        ChangeSummary {
332            files_created,
333            files_modified,
334            tests_run,
335            tests_passed,
336            quality_issues,
337        }
338    }
339
340    /// Format a change summary for display
341    ///
342    /// This method creates a human-readable summary of changes.
343    pub fn format_change_summary(&self, summary: &ChangeSummary) -> String {
344        let mut output = String::new();
345        output.push_str("=== Change Summary ===\n");
346        output.push_str(&format!("Files created: {}\n", summary.files_created));
347        output.push_str(&format!("Files modified: {}\n", summary.files_modified));
348        output.push_str(&format!("Tests run: {}\n", summary.tests_run));
349        output.push_str(&format!("Tests passed: {}\n", summary.tests_passed));
350
351        if summary.tests_run > 0 {
352            let pass_rate = (summary.tests_passed as f64 / summary.tests_run as f64) * 100.0;
353            output.push_str(&format!("Pass rate: {:.1}%\n", pass_rate));
354        }
355
356        if !summary.quality_issues.is_empty() {
357            output.push_str(&format!(
358                "Quality issues: {}\n",
359                summary.quality_issues.len()
360            ));
361            for issue in &summary.quality_issues {
362                output.push_str(&format!("  - {}\n", issue));
363            }
364        } else {
365            output.push_str("Quality issues: None\n");
366        }
367
368        output
369    }
370
371    /// Provide actionable feedback based on summary
372    ///
373    /// This method generates feedback and recommendations.
374    pub fn provide_feedback(&self, summary: &ChangeSummary) -> Vec<String> {
375        let mut feedback = Vec::new();
376
377        // Feedback on files
378        if summary.files_created > 0 {
379            feedback.push(format!(
380                "Successfully created {} file(s)",
381                summary.files_created
382            ));
383        }
384        if summary.files_modified > 0 {
385            feedback.push(format!(
386                "Successfully modified {} file(s)",
387                summary.files_modified
388            ));
389        }
390
391        // Feedback on tests
392        if summary.tests_run > 0 {
393            if summary.tests_passed == summary.tests_run {
394                feedback.push("All tests passed! ✓".to_string());
395            } else {
396                let failed = summary.tests_run - summary.tests_passed;
397                feedback.push(format!("{} test(s) failed. Please review and fix.", failed));
398            }
399        }
400
401        // Feedback on quality
402        if !summary.quality_issues.is_empty() {
403            feedback.push(format!(
404                "Found {} quality issue(s). Please address them.",
405                summary.quality_issues.len()
406            ));
407        } else if summary.files_created > 0 || summary.files_modified > 0 {
408            feedback.push("Code quality looks good! ✓".to_string());
409        }
410
411        feedback
412    }
413}
414
415impl Default for CodeMode {
416    fn default() -> Self {
417        Self::new()
418    }
419}
420
421#[async_trait]
422impl Mode for CodeMode {
423    fn id(&self) -> &str {
424        "code"
425    }
426
427    fn name(&self) -> &str {
428        "Code Mode"
429    }
430
431    fn description(&self) -> &str {
432        "Focused code generation and modification with full execution capabilities"
433    }
434
435    fn system_prompt(&self) -> &str {
436        &self.config.system_prompt
437    }
438
439    async fn process(&self, input: &str, context: &ModeContext) -> Result<ModeResponse> {
440        let start = Instant::now();
441
442        // Create response with input as content
443        let mut response = ModeResponse::new(input.to_string(), self.id().to_string());
444
445        // Add code generation action if input looks like a spec
446        if input.contains("generate") || input.contains("create") {
447            response.add_action(ModeAction::GenerateCode {
448                spec: input.to_string(),
449            });
450        }
451
452        // Add file operation action if input mentions files
453        if input.contains("file") || input.contains("modify") {
454            response.add_action(ModeAction::ModifyFile {
455                path: PathBuf::from("generated.rs"),
456                diff: "// Generated code".to_string(),
457            });
458        }
459
460        // Update metadata
461        response.metadata.duration = start.elapsed();
462        response.metadata.think_more_used = context.think_more_enabled;
463
464        Ok(response)
465    }
466
467    fn capabilities(&self) -> Vec<Capability> {
468        self.config.capabilities.clone()
469    }
470
471    fn config(&self) -> &ModeConfig {
472        &self.config
473    }
474
475    fn can_execute(&self, operation: &Operation) -> bool {
476        matches!(
477            operation,
478            Operation::GenerateCode
479                | Operation::ModifyFile
480                | Operation::ExecuteCommand
481                | Operation::RunTests
482                | Operation::ValidateQuality
483        )
484    }
485
486    fn constraints(&self) -> ModeConstraints {
487        self.config.constraints.clone()
488    }
489}
490
491#[cfg(test)]
492mod tests {
493    use super::*;
494
495    #[test]
496    fn test_code_mode_creation() {
497        let mode = CodeMode::new();
498        assert_eq!(mode.id(), "code");
499        assert_eq!(mode.name(), "Code Mode");
500    }
501
502    #[test]
503    fn test_code_mode_capabilities() {
504        let mode = CodeMode::new();
505        let capabilities = mode.capabilities();
506        assert!(capabilities.contains(&Capability::CodeGeneration));
507        assert!(capabilities.contains(&Capability::CodeModification));
508        assert!(capabilities.contains(&Capability::FileOperations));
509        assert!(capabilities.contains(&Capability::CommandExecution));
510        assert!(capabilities.contains(&Capability::TestExecution));
511        assert!(capabilities.contains(&Capability::QualityValidation));
512    }
513
514    #[test]
515    fn test_code_mode_can_execute() {
516        let mode = CodeMode::new();
517        assert!(mode.can_execute(&Operation::GenerateCode));
518        assert!(mode.can_execute(&Operation::ModifyFile));
519        assert!(mode.can_execute(&Operation::ExecuteCommand));
520        assert!(mode.can_execute(&Operation::RunTests));
521        assert!(mode.can_execute(&Operation::ValidateQuality));
522        assert!(!mode.can_execute(&Operation::AnswerQuestion));
523    }
524
525    #[test]
526    fn test_code_mode_constraints() {
527        let mode = CodeMode::new();
528        let constraints = mode.constraints();
529        assert!(constraints.allow_file_operations);
530        assert!(constraints.allow_command_execution);
531        assert!(constraints.allow_code_generation);
532        assert!(!constraints.require_specs);
533        assert_eq!(
534            constraints.auto_think_more_threshold,
535            Some(ComplexityLevel::Complex)
536        );
537    }
538
539    #[test]
540    fn test_code_mode_system_prompt() {
541        let mode = CodeMode::new();
542        let prompt = mode.system_prompt();
543        assert!(prompt.contains("code generation assistant"));
544        assert!(prompt.contains("clean"));
545        assert!(prompt.contains("best practices"));
546    }
547
548    #[tokio::test]
549    async fn test_code_mode_process() {
550        let mode = CodeMode::new();
551        let context = ModeContext::new("test-session".to_string());
552        let response = mode.process("test input", &context).await.unwrap();
553        assert_eq!(response.content, "test input");
554        assert_eq!(response.metadata.mode, "code");
555        assert!(!response.metadata.think_more_used);
556    }
557
558    #[tokio::test]
559    async fn test_code_mode_process_with_think_more() {
560        let mode = CodeMode::new();
561        let mut context = ModeContext::new("test-session".to_string());
562        context.think_more_enabled = true;
563        let response = mode.process("test input", &context).await.unwrap();
564        assert!(response.metadata.think_more_used);
565    }
566
567    #[test]
568    fn test_code_mode_default() {
569        let mode = CodeMode::default();
570        assert_eq!(mode.id(), "code");
571    }
572
573    #[test]
574    fn test_code_mode_with_custom_config() {
575        let custom_config = ModeConfig {
576            temperature: 0.5,
577            max_tokens: 2048,
578            system_prompt: "Custom prompt".to_string(),
579            capabilities: vec![Capability::CodeGeneration],
580            constraints: ModeConstraints {
581                allow_file_operations: false,
582                allow_command_execution: false,
583                allow_code_generation: true,
584                require_specs: true,
585                auto_think_more_threshold: None,
586            },
587        };
588        let mode = CodeMode::with_config(custom_config);
589        assert_eq!(mode.config().temperature, 0.5);
590        assert_eq!(mode.config().max_tokens, 2048);
591    }
592
593    #[test]
594    fn test_generate_code() {
595        let mode = CodeMode::new();
596        let spec = "Create a function that adds two numbers";
597        let result = mode.generate_code(spec);
598        assert!(result.is_ok());
599        let code = result.unwrap();
600        assert!(code.contains("Generated from spec"));
601        assert!(code.contains(spec));
602    }
603
604    #[tokio::test]
605    async fn test_create_file() {
606        let mode = CodeMode::new();
607        let temp_dir = std::env::temp_dir().join("ricecoder_test");
608        let _ = std::fs::create_dir_all(&temp_dir);
609        let file_path = temp_dir.join("test_file.rs");
610
611        let result = mode.create_file(&file_path, "fn main() {}").await;
612        assert!(result.is_ok());
613        assert!(file_path.exists());
614
615        // Cleanup
616        let _ = std::fs::remove_file(&file_path);
617        let _ = std::fs::remove_dir(&temp_dir);
618    }
619
620    #[tokio::test]
621    async fn test_create_file_with_nested_dirs() {
622        let mode = CodeMode::new();
623        let temp_dir = std::env::temp_dir().join("ricecoder_test_nested");
624        let file_path = temp_dir.join("src").join("lib.rs");
625
626        let result = mode.create_file(&file_path, "pub fn hello() {}").await;
627        assert!(result.is_ok());
628        assert!(file_path.exists());
629
630        // Cleanup
631        let _ = std::fs::remove_file(&file_path);
632        let _ = std::fs::remove_dir(temp_dir.join("src"));
633        let _ = std::fs::remove_dir(&temp_dir);
634    }
635
636    #[tokio::test]
637    async fn test_modify_file() {
638        let mode = CodeMode::new();
639        let temp_dir = std::env::temp_dir().join("ricecoder_test_modify");
640        let _ = std::fs::create_dir_all(&temp_dir);
641        let file_path = temp_dir.join("test_file.rs");
642
643        // Create initial file
644        std::fs::write(&file_path, "fn main() {}").unwrap();
645
646        // Modify file
647        let result = mode.modify_file(&file_path, "// Added comment").await;
648        assert!(result.is_ok());
649
650        // Verify modification
651        let content = std::fs::read_to_string(&file_path).unwrap();
652        assert!(content.contains("fn main() {}"));
653        assert!(content.contains("// Added comment"));
654
655        // Cleanup
656        let _ = std::fs::remove_file(&file_path);
657        let _ = std::fs::remove_dir(&temp_dir);
658    }
659
660    #[tokio::test]
661    async fn test_modify_file_blocked_when_disabled() {
662        let custom_config = ModeConfig {
663            temperature: 0.7,
664            max_tokens: 4096,
665            system_prompt: "Test".to_string(),
666            capabilities: vec![],
667            constraints: ModeConstraints {
668                allow_file_operations: false,
669                allow_command_execution: false,
670                allow_code_generation: false,
671                require_specs: false,
672                auto_think_more_threshold: None,
673            },
674        };
675        let mode = CodeMode::with_config(custom_config);
676        let temp_dir = std::env::temp_dir().join("ricecoder_test_blocked");
677        let _ = std::fs::create_dir_all(&temp_dir);
678        let file_path = temp_dir.join("test_file.rs");
679        std::fs::write(&file_path, "fn main() {}").unwrap();
680
681        let result = mode.modify_file(&file_path, "// comment").await;
682        assert!(result.is_err());
683
684        // Cleanup
685        let _ = std::fs::remove_file(&file_path);
686        let _ = std::fs::remove_dir(&temp_dir);
687    }
688
689    #[test]
690    fn test_track_changes() {
691        let mode = CodeMode::new();
692        let summary = mode.track_changes(3, 2);
693        assert_eq!(summary.files_created, 3);
694        assert_eq!(summary.files_modified, 2);
695        assert_eq!(summary.tests_run, 0);
696        assert_eq!(summary.tests_passed, 0);
697        assert!(summary.quality_issues.is_empty());
698    }
699
700    #[tokio::test]
701    async fn test_process_with_generate_keyword() {
702        let mode = CodeMode::new();
703        let context = ModeContext::new("test-session".to_string());
704        let response = mode.process("generate a function", &context).await.unwrap();
705        assert!(!response.actions.is_empty());
706    }
707
708    #[tokio::test]
709    async fn test_process_with_file_keyword() {
710        let mode = CodeMode::new();
711        let context = ModeContext::new("test-session".to_string());
712        let response = mode.process("modify file", &context).await.unwrap();
713        assert!(!response.actions.is_empty());
714    }
715
716    #[tokio::test]
717    async fn test_run_tests() {
718        let mode = CodeMode::new();
719        let temp_dir = std::env::temp_dir().join("ricecoder_test_run");
720        let _ = std::fs::create_dir_all(&temp_dir);
721        let test_file = temp_dir.join("test.rs");
722        std::fs::write(&test_file, "fn test() {}").unwrap();
723
724        let result = mode.run_tests(&[test_file.clone()]).await;
725        assert!(result.is_ok());
726        let (tests_run, tests_passed, failures) = result.unwrap();
727        assert_eq!(tests_run, 1);
728        assert_eq!(tests_passed, 1);
729        assert!(failures.is_empty());
730
731        // Cleanup
732        let _ = std::fs::remove_file(&test_file);
733        let _ = std::fs::remove_dir(&temp_dir);
734    }
735
736    #[tokio::test]
737    async fn test_run_tests_with_missing_file() {
738        let mode = CodeMode::new();
739        let missing_file = PathBuf::from("/nonexistent/test.rs");
740
741        let result = mode.run_tests(&[missing_file]).await;
742        assert!(result.is_ok());
743        let (tests_run, tests_passed, failures) = result.unwrap();
744        assert_eq!(tests_run, 1);
745        assert_eq!(tests_passed, 0);
746        assert!(!failures.is_empty());
747    }
748
749    #[test]
750    fn test_capture_test_results() {
751        let mode = CodeMode::new();
752        let summary = ChangeSummary {
753            files_created: 1,
754            files_modified: 0,
755            tests_run: 0,
756            tests_passed: 0,
757            quality_issues: Vec::new(),
758        };
759
760        let updated = mode.capture_test_results(summary, 5, 4, vec!["test 1 failed".to_string()]);
761        assert_eq!(updated.tests_run, 5);
762        assert_eq!(updated.tests_passed, 4);
763        assert_eq!(updated.quality_issues.len(), 1);
764    }
765
766    #[test]
767    fn test_report_test_failures_no_failures() {
768        let mode = CodeMode::new();
769        let report = mode.report_test_failures(&[]);
770        assert_eq!(report, "All tests passed!");
771    }
772
773    #[test]
774    fn test_report_test_failures_with_failures() {
775        let mode = CodeMode::new();
776        let failures = vec!["test 1 failed".to_string(), "test 2 failed".to_string()];
777        let report = mode.report_test_failures(&failures);
778        assert!(report.contains("Test failures:"));
779        assert!(report.contains("test 1 failed"));
780        assert!(report.contains("test 2 failed"));
781    }
782
783    #[tokio::test]
784    async fn test_run_tests_blocked_when_disabled() {
785        let custom_config = ModeConfig {
786            temperature: 0.7,
787            max_tokens: 4096,
788            system_prompt: "Test".to_string(),
789            capabilities: vec![],
790            constraints: ModeConstraints {
791                allow_file_operations: false,
792                allow_command_execution: false,
793                allow_code_generation: false,
794                require_specs: false,
795                auto_think_more_threshold: None,
796            },
797        };
798        let mode = CodeMode::with_config(custom_config);
799        let result = mode.run_tests(&[PathBuf::from("test.rs")]).await;
800        assert!(result.is_err());
801    }
802
803    #[tokio::test]
804    async fn test_validate_quality() {
805        let mode = CodeMode::new();
806        let temp_dir = std::env::temp_dir().join("ricecoder_test_quality");
807        let _ = std::fs::create_dir_all(&temp_dir);
808        let test_file = temp_dir.join("test.rs");
809        std::fs::write(&test_file, "fn main() {}").unwrap();
810
811        let result = mode.validate_quality(&[test_file.clone()]).await;
812        assert!(result.is_ok());
813        let issues = result.unwrap();
814        assert!(issues.is_empty());
815
816        // Cleanup
817        let _ = std::fs::remove_file(&test_file);
818        let _ = std::fs::remove_dir(&temp_dir);
819    }
820
821    #[tokio::test]
822    async fn test_validate_quality_with_todo() {
823        let mode = CodeMode::new();
824        let temp_dir = std::env::temp_dir().join("ricecoder_test_quality_todo");
825        let _ = std::fs::create_dir_all(&temp_dir);
826        let test_file = temp_dir.join("test.rs");
827        std::fs::write(&test_file, "fn main() {\n    // TODO: implement\n}").unwrap();
828
829        let result = mode.validate_quality(&[test_file.clone()]).await;
830        assert!(result.is_ok());
831        let issues = result.unwrap();
832        assert!(!issues.is_empty());
833        assert!(issues[0].contains("TODO"));
834
835        // Cleanup
836        let _ = std::fs::remove_file(&test_file);
837        let _ = std::fs::remove_dir(&temp_dir);
838    }
839
840    #[tokio::test]
841    async fn test_validate_quality_with_unwrap() {
842        let mode = CodeMode::new();
843        let temp_dir = std::env::temp_dir().join("ricecoder_test_quality_unwrap");
844        let _ = std::fs::create_dir_all(&temp_dir);
845        let test_file = temp_dir.join("test.rs");
846        std::fs::write(&test_file, "fn main() {\n    let x = Some(1).unwrap();\n}").unwrap();
847
848        let result = mode.validate_quality(&[test_file.clone()]).await;
849        assert!(result.is_ok());
850        let issues = result.unwrap();
851        assert!(!issues.is_empty());
852        assert!(issues[0].contains("Unwrap"));
853
854        // Cleanup
855        let _ = std::fs::remove_file(&test_file);
856        let _ = std::fs::remove_dir(&temp_dir);
857    }
858
859    #[tokio::test]
860    async fn test_validate_quality_missing_file() {
861        let mode = CodeMode::new();
862        let missing_file = PathBuf::from("/nonexistent/test.rs");
863
864        let result = mode.validate_quality(&[missing_file]).await;
865        assert!(result.is_ok());
866        let issues = result.unwrap();
867        assert!(!issues.is_empty());
868    }
869
870    #[test]
871    fn test_report_quality_issues_no_issues() {
872        let mode = CodeMode::new();
873        let report = mode.report_quality_issues(&[]);
874        assert_eq!(report, "No quality issues found!");
875    }
876
877    #[test]
878    fn test_report_quality_issues_with_issues() {
879        let mode = CodeMode::new();
880        let issues = vec!["TODO found".to_string(), "FIXME found".to_string()];
881        let report = mode.report_quality_issues(&issues);
882        assert!(report.contains("Quality issues:"));
883        assert!(report.contains("TODO found"));
884        assert!(report.contains("FIXME found"));
885    }
886
887    #[test]
888    fn test_suggest_improvements() {
889        let mode = CodeMode::new();
890        let issues = vec![
891            "TODO found in file.rs".to_string(),
892            "Unwrap found in file.rs".to_string(),
893        ];
894        let suggestions = mode.suggest_improvements(&issues);
895        assert!(!suggestions.is_empty());
896        assert!(suggestions.iter().any(|s| s.contains("TODO")));
897        assert!(suggestions.iter().any(|s| s.contains("unwrap")));
898    }
899
900    #[tokio::test]
901    async fn test_validate_quality_blocked_when_disabled() {
902        let custom_config = ModeConfig {
903            temperature: 0.7,
904            max_tokens: 4096,
905            system_prompt: "Test".to_string(),
906            capabilities: vec![],
907            constraints: ModeConstraints {
908                allow_file_operations: false,
909                allow_command_execution: false,
910                allow_code_generation: false,
911                require_specs: false,
912                auto_think_more_threshold: None,
913            },
914        };
915        let mode = CodeMode::with_config(custom_config);
916        let result = mode.validate_quality(&[PathBuf::from("test.rs")]).await;
917        assert!(result.is_err());
918    }
919
920    #[test]
921    fn test_generate_change_summary() {
922        let mode = CodeMode::new();
923        let summary = mode.generate_change_summary(2, 1, 5, 5, vec![]);
924        assert_eq!(summary.files_created, 2);
925        assert_eq!(summary.files_modified, 1);
926        assert_eq!(summary.tests_run, 5);
927        assert_eq!(summary.tests_passed, 5);
928        assert!(summary.quality_issues.is_empty());
929    }
930
931    #[test]
932    fn test_generate_change_summary_with_issues() {
933        let mode = CodeMode::new();
934        let issues = vec!["TODO found".to_string()];
935        let summary = mode.generate_change_summary(1, 0, 3, 2, issues);
936        assert_eq!(summary.files_created, 1);
937        assert_eq!(summary.quality_issues.len(), 1);
938    }
939
940    #[test]
941    fn test_format_change_summary() {
942        let mode = CodeMode::new();
943        let summary = ChangeSummary {
944            files_created: 2,
945            files_modified: 1,
946            tests_run: 5,
947            tests_passed: 5,
948            quality_issues: vec![],
949        };
950        let formatted = mode.format_change_summary(&summary);
951        assert!(formatted.contains("Change Summary"));
952        assert!(formatted.contains("Files created: 2"));
953        assert!(formatted.contains("Files modified: 1"));
954        assert!(formatted.contains("Tests run: 5"));
955        assert!(formatted.contains("Tests passed: 5"));
956        assert!(formatted.contains("Pass rate: 100.0%"));
957    }
958
959    #[test]
960    fn test_format_change_summary_with_issues() {
961        let mode = CodeMode::new();
962        let summary = ChangeSummary {
963            files_created: 1,
964            files_modified: 0,
965            tests_run: 0,
966            tests_passed: 0,
967            quality_issues: vec!["TODO found".to_string()],
968        };
969        let formatted = mode.format_change_summary(&summary);
970        assert!(formatted.contains("Quality issues: 1"));
971        assert!(formatted.contains("TODO found"));
972    }
973
974    #[test]
975    fn test_provide_feedback_all_passed() {
976        let mode = CodeMode::new();
977        let summary = ChangeSummary {
978            files_created: 2,
979            files_modified: 1,
980            tests_run: 5,
981            tests_passed: 5,
982            quality_issues: vec![],
983        };
984        let feedback = mode.provide_feedback(&summary);
985        assert!(!feedback.is_empty());
986        assert!(feedback.iter().any(|f| f.contains("created")));
987        assert!(feedback.iter().any(|f| f.contains("All tests passed")));
988        assert!(feedback.iter().any(|f| f.contains("quality looks good")));
989    }
990
991    #[test]
992    fn test_provide_feedback_with_failures() {
993        let mode = CodeMode::new();
994        let summary = ChangeSummary {
995            files_created: 1,
996            files_modified: 0,
997            tests_run: 5,
998            tests_passed: 3,
999            quality_issues: vec![],
1000        };
1001        let feedback = mode.provide_feedback(&summary);
1002        assert!(!feedback.is_empty());
1003        assert!(feedback.iter().any(|f| f.contains("2 test(s) failed")));
1004    }
1005
1006    #[test]
1007    fn test_provide_feedback_with_quality_issues() {
1008        let mode = CodeMode::new();
1009        let summary = ChangeSummary {
1010            files_created: 1,
1011            files_modified: 0,
1012            tests_run: 0,
1013            tests_passed: 0,
1014            quality_issues: vec!["TODO found".to_string(), "FIXME found".to_string()],
1015        };
1016        let feedback = mode.provide_feedback(&summary);
1017        assert!(!feedback.is_empty());
1018        assert!(feedback.iter().any(|f| f.contains("2 quality issue(s)")));
1019    }
1020
1021    #[test]
1022    fn test_provide_feedback_no_changes() {
1023        let mode = CodeMode::new();
1024        let summary = ChangeSummary {
1025            files_created: 0,
1026            files_modified: 0,
1027            tests_run: 0,
1028            tests_passed: 0,
1029            quality_issues: vec![],
1030        };
1031        let feedback = mode.provide_feedback(&summary);
1032        assert!(feedback.is_empty());
1033    }
1034}