1use 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#[derive(Debug, Clone)]
23pub struct CodeMode {
24 config: ModeConfig,
25}
26
27impl CodeMode {
28 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 pub fn with_config(config: ModeConfig) -> Self {
58 Self { config }
59 }
60
61 pub fn generate_code(&self, spec: &str) -> Result<String> {
66 Ok(format!(
69 "// Generated from spec:\n// {}\n\n// TODO: Implement based on spec",
70 spec
71 ))
72 }
73
74 pub async fn create_file(&self, path: &Path, content: &str) -> Result<()> {
78 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 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 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 pub async fn modify_file(&self, path: &Path, diff: &str) -> Result<()> {
110 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 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 let new_content = format!("{}\n\n// Applied diff:\n{}", current_content, diff);
126
127 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 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 pub async fn run_tests(&self, paths: &[PathBuf]) -> Result<(usize, usize, Vec<String>)> {
152 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 path in paths {
170 if path.exists() {
171 tests_run += 1;
172 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 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 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 pub async fn validate_quality(&self, paths: &[PathBuf]) -> Result<Vec<String>> {
221 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 for path in paths {
237 if !path.exists() {
238 issues.push(format!("File not found: {}", path.display()));
239 continue;
240 }
241
242 match std::fs::read_to_string(path) {
244 Ok(content) => {
245 if content.is_empty() {
247 issues.push(format!("Empty file: {}", path.display()));
248 }
249
250 if content.contains("TODO") {
252 issues.push(format!("TODO found in: {}", path.display()));
253 }
254
255 if content.contains("FIXME") {
257 issues.push(format!("FIXME found in: {}", path.display()));
258 }
259
260 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 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 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 suggestions.sort();
315 suggestions.dedup();
316 suggestions
317 }
318
319 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 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 pub fn provide_feedback(&self, summary: &ChangeSummary) -> Vec<String> {
375 let mut feedback = Vec::new();
376
377 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 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 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 let mut response = ModeResponse::new(input.to_string(), self.id().to_string());
444
445 if input.contains("generate") || input.contains("create") {
447 response.add_action(ModeAction::GenerateCode {
448 spec: input.to_string(),
449 });
450 }
451
452 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 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 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 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 std::fs::write(&file_path, "fn main() {}").unwrap();
645
646 let result = mode.modify_file(&file_path, "// Added comment").await;
648 assert!(result.is_ok());
649
650 let content = std::fs::read_to_string(&file_path).unwrap();
652 assert!(content.contains("fn main() {}"));
653 assert!(content.contains("// Added comment"));
654
655 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 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 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 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 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 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}