1use std::path::{Path, PathBuf};
15
16use clap::Subcommand;
17
18use crate::error::CliError;
19use crate::stubs::{self, render_template};
20
21#[derive(Subcommand, Debug)]
25pub enum MakeCommand {
26 #[command(name = "model")]
28 Model {
29 name: String,
31 },
32
33 #[command(name = "controller")]
35 Controller {
36 name: String,
38 #[arg(long)]
40 api: bool,
41 #[arg(long)]
43 plain: bool,
44 },
45
46 #[command(name = "migration")]
48 Migration {
49 name: String,
51 #[arg(short = 'p', long, default_value = "migrations")]
53 path: String,
54 },
55
56 #[command(name = "seeder")]
60 Seeder {
61 name: String,
63 #[arg(short = 'p', long, default_value = "seeds")]
65 path: String,
66 },
67
68 #[command(name = "guard")]
70 Guard {
71 name: String,
73 },
74
75 #[command(name = "validate")]
79 Validate {
80 name: String,
82 },
83
84 #[command(name = "event")]
88 Event {
89 name: String,
91 },
92
93 #[command(name = "listener")]
97 Listener {
98 name: String,
100 #[arg(long)]
102 event: Option<String>,
103 },
104
105 #[command(name = "command")]
109 Command {
110 name: String,
112 },
113
114 #[command(name = "service")]
118 Service {
119 name: String,
121 },
122
123 #[command(name = "middleware")]
128 Middleware {
129 name: String,
131 },
132
133 #[command(name = "scaffold")]
135 Scaffold {
136 name: String,
138 },
139}
140
141pub fn execute(cmd: &MakeCommand) -> Result<(), CliError> {
143 match cmd {
144 MakeCommand::Model { name } => execute_make_model(name),
145 MakeCommand::Controller { name, api, plain } => execute_make_controller(name, *api, *plain),
146 MakeCommand::Migration { name, path } => execute_make_migration(name, path),
147 MakeCommand::Seeder { name, path } => execute_make_seeder(name, path),
148 MakeCommand::Guard { name } => execute_make_guard(name),
149 MakeCommand::Validate { name } => execute_make_validate(name),
150 MakeCommand::Event { name } => execute_make_event(name),
151 MakeCommand::Listener { name, event } => execute_make_listener(name, event.as_deref()),
152 MakeCommand::Command { name } => execute_make_command(name),
153 MakeCommand::Service { name } => execute_make_service(name),
154 MakeCommand::Middleware { name } => execute_make_middleware(name),
155 MakeCommand::Scaffold { name } => execute_make_scaffold(name),
156 }
157}
158
159fn execute_make_model(name: &str) -> Result<(), CliError> {
163 let (class_name, module_path, file_path) = resolve_target(name, "model");
164 check_file_exists(&file_path)?;
165
166 let namespace = format!("app::{}", module_path);
167 let table_name = class_to_snake(&class_name);
168 let content = render_template(
169 stubs::MODEL_STUB,
170 &[
171 ("{%className%}", &class_name),
172 ("{%namespace%}", &namespace),
173 ("{%table_name%}", &table_name),
174 ],
175 );
176
177 write_file(&file_path, &content)?;
178 println!("Model created: {}", file_path.display());
179 Ok(())
180}
181
182fn execute_make_controller(name: &str, api: bool, plain: bool) -> Result<(), CliError> {
186 let (class_name, module_path, file_path) = resolve_target(name, "controller");
187 check_file_exists(&file_path)?;
188
189 let namespace = format!("app::{}", module_path);
190 let route = class_to_snake(&class_name);
191
192 let template = if plain {
193 stubs::CONTROLLER_PLAIN_STUB
194 } else if api {
195 stubs::CONTROLLER_API_STUB
196 } else {
197 stubs::CONTROLLER_STUB
198 };
199
200 let content = render_template(
201 template,
202 &[
203 ("{%className%}", &class_name),
204 ("{%namespace%}", &namespace),
205 ("{%route%}", &route),
206 ],
207 );
208
209 write_file(&file_path, &content)?;
210 println!("Controller created: {}", file_path.display());
211 Ok(())
212}
213
214fn execute_make_migration(name: &str, path: &str) -> Result<(), CliError> {
218 let dir = Path::new(path);
219 std::fs::create_dir_all(dir)?;
220
221 let timestamp = chrono::Utc::now().format("%Y%m%d%H%M%S").to_string();
222 let table_name = name_to_table(name);
223
224 let up_file = dir.join(format!("{}_{}_up.sql", timestamp, name));
225 let down_file = dir.join(format!("{}_{}_down.sql", timestamp, name));
226
227 check_file_exists(&up_file)?;
228 check_file_exists(&down_file)?;
229
230 let up_content = render_template(
231 stubs::MIGRATION_UP_STUB,
232 &[
233 ("{%name%}", name),
234 ("{%timestamp%}", ×tamp),
235 ("{%table_name%}", &table_name),
236 ],
237 );
238 let down_content = render_template(
239 stubs::MIGRATION_DOWN_STUB,
240 &[
241 ("{%name%}", name),
242 ("{%timestamp%}", ×tamp),
243 ("{%table_name%}", &table_name),
244 ],
245 );
246
247 write_file(&up_file, &up_content)?;
248 write_file(&down_file, &down_content)?;
249 println!(
250 "Migration created: {} & {}",
251 up_file.display(),
252 down_file.display()
253 );
254 Ok(())
255}
256
257fn execute_make_seeder(name: &str, path: &str) -> Result<(), CliError> {
261 let dir = Path::new(path);
262 std::fs::create_dir_all(dir)?;
263
264 let file_path = dir.join(format!("{}.sql", name));
265 check_file_exists(&file_path)?;
266
267 let timestamp = chrono::Utc::now()
268 .format("%Y-%m-%d %H:%M:%S UTC")
269 .to_string();
270 let content = render_template(
271 stubs::SEED_STUB,
272 &[("{%name%}", name), ("{%timestamp%}", ×tamp)],
273 );
274
275 write_file(&file_path, &content)?;
276 println!("Seeder created: {}", file_path.display());
277 Ok(())
278}
279
280fn execute_make_guard(name: &str) -> Result<(), CliError> {
282 let (class_name, _module_path, file_path) = resolve_target(name, "guard");
283 check_file_exists(&file_path)?;
284
285 let content = format!(
286 "//! Guard: {class_name}\n//!\n//! 由 `sz-rust make:guard` 生成。\n//!\n//! 对齐 NestJS Guard + Spring Security 模式。\n\nuse sz_rust_core::guard::Guard;\nuse sz_rust_core::request::Request;\n\n/// {class_name} Guard\npub struct {class_name};\n\nimpl Guard for {class_name} {{\n async fn can_activate(&self, _req: &Request) -> bool {{\n // 在此实现鉴权逻辑\n true\n }}\n}}\n"
287 );
288
289 write_file(&file_path, &content)?;
290 println!("Guard created: {}", file_path.display());
291 Ok(())
292}
293
294fn execute_make_validate(name: &str) -> Result<(), CliError> {
299 let (class_name, module_path, file_path) = resolve_target(name, "validate");
300 check_file_exists(&file_path)?;
301
302 let namespace = format!("app::{}", module_path);
303 let content = render_template(
304 stubs::VALIDATE_STUB,
305 &[
306 ("{%className%}", &class_name),
307 ("{%namespace%}", &namespace),
308 ],
309 );
310
311 write_file(&file_path, &content)?;
312 println!("Validator created: {}", file_path.display());
313 Ok(())
314}
315
316fn execute_make_event(name: &str) -> Result<(), CliError> {
320 let (class_name, module_path, file_path) = resolve_target(name, "event");
321 check_file_exists(&file_path)?;
322
323 let namespace = format!("app::{}", module_path);
324 let event_name = class_name.clone();
326 let content = render_template(
327 stubs::EVENT_STUB,
328 &[
329 ("{%className%}", &class_name),
330 ("{%namespace%}", &namespace),
331 ("{%event_name%}", &event_name),
332 ],
333 );
334
335 write_file(&file_path, &content)?;
336 println!("Event created: {}", file_path.display());
337 Ok(())
338}
339
340fn execute_make_listener(name: &str, event: Option<&str>) -> Result<(), CliError> {
345 let (class_name, module_path, file_path) = resolve_target(name, "listener");
346 check_file_exists(&file_path)?;
347
348 let namespace = format!("app::{}", module_path);
349 let event_name = event.unwrap_or(&class_name).to_string();
351 let content = render_template(
352 stubs::LISTENER_STUB,
353 &[
354 ("{%className%}", &class_name),
355 ("{%namespace%}", &namespace),
356 ("{%event_name%}", &event_name),
357 ],
358 );
359
360 write_file(&file_path, &content)?;
361 println!("Listener created: {}", file_path.display());
362 Ok(())
363}
364
365fn execute_make_command(name: &str) -> Result<(), CliError> {
370 let (class_name, module_path, file_path) = resolve_target(name, "command");
371 check_file_exists(&file_path)?;
372
373 let namespace = format!("app::{}", module_path);
374 let command_name = class_to_snake(&class_name);
376 let content = render_template(
377 stubs::COMMAND_STUB,
378 &[
379 ("{%className%}", &class_name),
380 ("{%namespace%}", &namespace),
381 ("{%command_name%}", &command_name),
382 ],
383 );
384
385 write_file(&file_path, &content)?;
386 println!("Command created: {}", file_path.display());
387 Ok(())
388}
389
390fn execute_make_service(name: &str) -> Result<(), CliError> {
394 let (class_name, module_path, file_path) = resolve_target(name, "service");
395 check_file_exists(&file_path)?;
396
397 let namespace = format!("app::{}", module_path);
398 let content = render_template(
399 stubs::SERVICE_STUB,
400 &[
401 ("{%className%}", &class_name),
402 ("{%namespace%}", &namespace),
403 ],
404 );
405
406 write_file(&file_path, &content)?;
407 println!("Service created: {}", file_path.display());
408 Ok(())
409}
410
411fn execute_make_middleware(name: &str) -> Result<(), CliError> {
416 let (class_name, module_path, file_path) = resolve_target(name, "middleware");
417 check_file_exists(&file_path)?;
418
419 let namespace = format!("app::{}", module_path);
420 let content = render_template(
421 stubs::MIDDLEWARE_STUB,
422 &[
423 ("{%className%}", &class_name),
424 ("{%namespace%}", &namespace),
425 ],
426 );
427
428 write_file(&file_path, &content)?;
429 println!("Middleware created: {}", file_path.display());
430 Ok(())
431}
432
433fn execute_make_scaffold(name: &str) -> Result<(), CliError> {
435 println!("Scaffolding for: {}", name);
436 execute_make_model(name)?;
437 execute_make_controller(name, false, false)?;
438 execute_make_migration(&class_to_snake(name), "migrations")?;
439 println!("Scaffold complete.");
440 Ok(())
441}
442
443fn resolve_target(name: &str, layer: &str) -> (String, String, PathBuf) {
455 let (app, class_part) = if let Some(idx) = name.find('@') {
457 (&name[..idx], &name[idx + 1..])
458 } else {
459 ("", name)
460 };
461
462 let segments: Vec<&str> = class_part.split('/').collect();
464 let class_name = segments.last().unwrap_or(&"").to_string();
465
466 let parent_segments: Vec<&str> = if segments.len() > 1 {
468 segments[..segments.len() - 1].to_vec()
469 } else {
470 Vec::new()
471 };
472
473 let module_path = if app.is_empty() {
474 if parent_segments.is_empty() {
475 layer.to_string()
476 } else {
477 format!("{}::{}", layer, parent_segments.join("::"))
478 }
479 } else if parent_segments.is_empty() {
480 format!("{}::{}", app, layer)
481 } else {
482 format!("{}::{}::{}", app, layer, parent_segments.join("::"))
483 };
484
485 let mut path = PathBuf::from("app");
487 if !app.is_empty() {
488 path.push(app);
489 }
490 path.push(layer);
492 for seg in &parent_segments {
494 path.push(seg);
495 }
496 path.push(format!("{}.rs", class_name));
498
499 (class_name, module_path, path)
500}
501
502fn check_file_exists(path: &Path) -> Result<(), CliError> {
506 if path.exists() {
507 return Err(CliError::FileExists(path.display().to_string()));
508 }
509 Ok(())
510}
511
512fn write_file(path: &Path, content: &str) -> Result<(), CliError> {
516 if let Some(parent) = path.parent() {
517 std::fs::create_dir_all(parent)?;
518 }
519 std::fs::write(path, content)?;
520 Ok(())
521}
522
523fn class_to_snake(s: &str) -> String {
527 let mut result = String::new();
528 for (i, ch) in s.chars().enumerate() {
529 if ch.is_uppercase() && i > 0 {
530 result.push('_');
531 }
532 result.push(ch.to_lowercase().next().unwrap_or(ch));
533 }
534 result
535}
536
537fn name_to_table(name: &str) -> String {
541 if let Some(rest) = name.strip_prefix("create_") {
543 return rest.to_string();
544 }
545 if let Some(rest) = name.strip_prefix("add_") {
546 if let Some(to_pos) = rest.find("_to_") {
548 return rest[to_pos + 4..].to_string();
549 }
550 return rest.to_string();
551 }
552 name.to_string()
553}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558
559 struct CwdGuard {
565 original: Option<PathBuf>,
566 }
567
568 impl CwdGuard {
569 fn switch(new_dir: &Path) -> std::io::Result<Self> {
571 let original = std::env::current_dir().ok();
572 std::env::set_current_dir(new_dir)?;
573 Ok(Self { original })
574 }
575 }
576
577 impl Drop for CwdGuard {
578 fn drop(&mut self) {
579 if let Some(ref orig) = self.original {
580 let _ = std::env::set_current_dir(orig);
581 }
582 }
583 }
584
585 #[test]
586 fn test_resolve_target_simple_model() {
587 let (class, module, path) = resolve_target("User", "model");
588 assert_eq!(class, "User");
589 assert_eq!(module, "model");
590 assert_eq!(path, PathBuf::from("app/model/User.rs"));
591 }
592
593 #[test]
594 fn test_resolve_target_nested_controller() {
595 let (class, module, path) = resolve_target("admin/User", "controller");
596 assert_eq!(class, "User");
597 assert_eq!(module, "controller::admin");
598 assert_eq!(path, PathBuf::from("app/controller/admin/User.rs"));
599 }
600
601 #[test]
602 fn test_resolve_target_with_app() {
603 let (class, module, _path) = resolve_target("admin@User", "model");
604 assert_eq!(class, "User");
605 assert_eq!(module, "admin::model");
606 }
607
608 #[test]
609 fn test_class_to_snake() {
610 assert_eq!(class_to_snake("User"), "user");
611 assert_eq!(class_to_snake("OrderItem"), "order_item");
612 assert_eq!(class_to_snake("API"), "a_p_i");
613 }
614
615 #[test]
616 fn test_name_to_table_create() {
617 assert_eq!(name_to_table("create_users"), "users");
618 assert_eq!(name_to_table("create_orders"), "orders");
619 }
620
621 #[test]
622 fn test_name_to_table_add() {
623 assert_eq!(name_to_table("add_index_to_orders"), "orders");
624 assert_eq!(name_to_table("add_status"), "status");
625 }
626
627 #[test]
628 fn test_name_to_table_other() {
629 assert_eq!(name_to_table("custom_migration"), "custom_migration");
630 }
631
632 #[test]
633 fn test_check_file_exists_nonexistent() {
634 let result = check_file_exists(Path::new("/nonexistent/path/file.txt"));
635 assert!(result.is_ok());
636 }
637
638 #[test]
639 fn test_check_file_exists_existing() {
640 let temp = tempfile::NamedTempFile::new().unwrap();
642 let result = check_file_exists(temp.path());
643 assert!(matches!(result, Err(CliError::FileExists(_))));
644 }
645
646 #[test]
647 fn test_write_and_read_file() {
648 let temp_dir = tempfile::tempdir().unwrap();
649 let file_path = temp_dir.path().join("test_file.txt");
650
651 write_file(&file_path, "test content").unwrap();
652 assert_eq!(std::fs::read_to_string(&file_path).unwrap(), "test content");
653 }
654
655 #[test]
656 fn test_execute_make_migration_creates_files() {
657 let temp_dir = tempfile::tempdir().unwrap();
658 let path = temp_dir.path().to_str().unwrap();
659
660 execute_make_migration("create_test_table", path).unwrap();
661
662 let entries: Vec<_> = std::fs::read_dir(path).unwrap().collect();
663 assert_eq!(entries.len(), 2); let mut has_up = false;
666 let mut has_down = false;
667 for entry in entries {
668 let name = entry.unwrap().file_name();
669 let name = name.to_string_lossy();
670 if name.ends_with("_up.sql") {
671 has_up = true;
672 }
673 if name.ends_with("_down.sql") {
674 has_down = true;
675 }
676 }
677 assert!(has_up);
678 assert!(has_down);
679 }
680
681 #[test]
682 fn test_execute_make_model_in_temp() {
683 let temp_dir = tempfile::tempdir().unwrap();
684 let _lock = super::super::test_support::acquire_global_lock();
685 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
686
687 execute_make_model("TestUser").unwrap();
688
689 let model_path = temp_dir.path().join("app/model/TestUser.rs");
690 assert!(model_path.exists());
691
692 let content = std::fs::read_to_string(&model_path).unwrap();
693 assert!(content.contains("TestUser"));
694 assert!(content.contains("test_user"));
695 }
696
697 #[test]
698 fn test_execute_make_seeder_creates_file() {
699 let temp_dir = tempfile::tempdir().unwrap();
700 let path = temp_dir.path().to_str().unwrap();
701
702 execute_make_seeder("001_test_seed", path).unwrap();
703
704 let seed_path = Path::new(path).join("001_test_seed.sql");
705 assert!(seed_path.exists());
706
707 let content = std::fs::read_to_string(&seed_path).unwrap();
708 assert!(content.contains("001_test_seed"));
709 assert!(content.contains("-- Seed:"));
710 assert!(!content.contains("{%"));
712 }
713
714 #[test]
715 fn test_execute_make_seeder_file_already_exists() {
716 let temp_dir = tempfile::tempdir().unwrap();
717 let path = temp_dir.path().to_str().unwrap();
718
719 execute_make_seeder("001_dup_seed", path).unwrap();
721 let result = execute_make_seeder("001_dup_seed", path);
723 assert!(matches!(result, Err(CliError::FileExists(_))));
724 }
725
726 #[test]
727 fn test_execute_make_seeder_creates_directory() {
728 let temp_dir = tempfile::tempdir().unwrap();
729 let nested = temp_dir.path().join("nested").join("seeds");
730 let path = nested.to_str().unwrap();
731
732 execute_make_seeder("001_seed", path).unwrap();
734 assert!(nested.exists());
735 }
736
737 #[test]
738 fn test_make_validate_creates_file() {
739 let temp_dir = tempfile::tempdir().unwrap();
740 let _lock = super::super::test_support::acquire_global_lock();
741 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
742
743 let cmd = MakeCommand::Validate {
745 name: "Order".to_string(),
746 };
747 execute(&cmd).unwrap();
748
749 let validate_path = temp_dir.path().join("app/validate/Order.rs");
750 assert!(validate_path.exists());
751
752 let content = std::fs::read_to_string(&validate_path).unwrap();
753 assert!(content.contains("pub struct OrderValidate;"));
755 assert!(content.contains("use sz_rust_core::validate::Validate"));
756 assert!(content.contains("app::validate"));
757 assert!(!content.contains("{%"));
759 }
760
761 #[test]
762 fn test_validate_stub_contains_required_elements() {
763 assert!(stubs::VALIDATE_STUB.contains("pub struct {%className%}Validate;"));
765 assert!(stubs::VALIDATE_STUB.contains("use sz_rust_core::validate::Validate"));
766 assert!(stubs::VALIDATE_STUB.contains("impl {%className%}Validate"));
767 assert!(stubs::VALIDATE_STUB.contains("pub fn new() -> Validate"));
768 assert!(stubs::VALIDATE_STUB.contains("{%className%}"));
769 assert!(stubs::VALIDATE_STUB.contains("{%namespace%}"));
770 }
771
772 #[test]
773 fn test_execute_make_validate_creates_file() {
774 let temp_dir = tempfile::tempdir().unwrap();
775 let _lock = super::super::test_support::acquire_global_lock();
776 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
777
778 execute_make_validate("User").unwrap();
779
780 let validate_path = temp_dir.path().join("app/validate/User.rs");
781 assert!(validate_path.exists());
782
783 let content = std::fs::read_to_string(&validate_path).unwrap();
784 assert!(content.contains("UserValidate"));
785 assert!(content.contains("use sz_rust_core::validate::Validate"));
786 assert!(content.contains("app::validate"));
787 assert!(!content.contains("{%"));
789 }
790
791 #[test]
792 fn test_execute_make_validate_file_already_exists() {
793 let temp_dir = tempfile::tempdir().unwrap();
794 let _lock = super::super::test_support::acquire_global_lock();
795 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
796
797 execute_make_validate("User").unwrap();
799 let result = execute_make_validate("User");
801 assert!(matches!(result, Err(CliError::FileExists(_))));
802 }
803
804 #[test]
805 fn test_execute_make_validate_nested_path() {
806 let temp_dir = tempfile::tempdir().unwrap();
807 let _lock = super::super::test_support::acquire_global_lock();
808 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
809
810 execute_make_validate("admin/User").unwrap();
812
813 let validate_path = temp_dir.path().join("app/validate/admin/User.rs");
814 assert!(validate_path.exists());
815
816 let content = std::fs::read_to_string(&validate_path).unwrap();
817 assert!(content.contains("UserValidate"));
818 assert!(content.contains("app::validate::admin"));
819 }
820
821 #[test]
824 fn test_execute_make_event_creates_file() {
825 let temp_dir = tempfile::tempdir().unwrap();
826 let _lock = super::super::test_support::acquire_global_lock();
827 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
828
829 execute_make_event("UserLogin").unwrap();
830
831 let event_path = temp_dir.path().join("app/event/UserLogin.rs");
832 assert!(event_path.exists());
833
834 let content = std::fs::read_to_string(&event_path).unwrap();
835 assert!(content.contains("pub struct UserLogin;"));
836 assert!(content.contains("app::event"));
837 assert!(content.contains("UserLogin"));
838 assert!(content.contains("use serde_json::Value"));
839 assert!(!content.contains("{%"));
841 }
842
843 #[test]
844 fn test_execute_make_event_file_already_exists() {
845 let temp_dir = tempfile::tempdir().unwrap();
846 let _lock = super::super::test_support::acquire_global_lock();
847 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
848
849 execute_make_event("UserLogin").unwrap();
850 let result = execute_make_event("UserLogin");
851 assert!(matches!(result, Err(CliError::FileExists(_))));
852 }
853
854 #[test]
855 fn test_execute_make_event_nested_path() {
856 let temp_dir = tempfile::tempdir().unwrap();
857 let _lock = super::super::test_support::acquire_global_lock();
858 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
859
860 execute_make_event("admin/UserLogin").unwrap();
861
862 let event_path = temp_dir.path().join("app/event/admin/UserLogin.rs");
863 assert!(event_path.exists());
864
865 let content = std::fs::read_to_string(&event_path).unwrap();
866 assert!(content.contains("app::event::admin"));
867 }
868
869 #[test]
870 fn test_execute_make_listener_default_event_name() {
871 let temp_dir = tempfile::tempdir().unwrap();
872 let _lock = super::super::test_support::acquire_global_lock();
873 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
874
875 execute_make_listener("SendWelcomeEmail", None).unwrap();
877
878 let listener_path = temp_dir.path().join("app/listener/SendWelcomeEmail.rs");
879 assert!(listener_path.exists());
880
881 let content = std::fs::read_to_string(&listener_path).unwrap();
882 assert!(content.contains("pub struct SendWelcomeEmail;"));
883 assert!(content.contains("app::listener"));
884 assert!(content.contains("use sz_rust_core::event::{EventError, Listener}"));
885 assert!(content.contains("impl Listener for SendWelcomeEmail"));
886 assert!(content.contains(r#""SendWelcomeEmail""#));
888 assert!(!content.contains("{%"));
889 }
890
891 #[test]
892 fn test_execute_make_listener_custom_event_name() {
893 let temp_dir = tempfile::tempdir().unwrap();
894 let _lock = super::super::test_support::acquire_global_lock();
895 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
896
897 execute_make_listener("SendWelcomeEmail", Some("UserLogin")).unwrap();
899
900 let listener_path = temp_dir.path().join("app/listener/SendWelcomeEmail.rs");
901 assert!(listener_path.exists());
902
903 let content = std::fs::read_to_string(&listener_path).unwrap();
904 assert!(content.contains(r#""UserLogin""#));
905 assert!(!content.contains(r#""SendWelcomeEmail""#));
906 assert!(!content.contains("{%"));
907 }
908
909 #[test]
910 fn test_execute_make_listener_file_already_exists() {
911 let temp_dir = tempfile::tempdir().unwrap();
912 let _lock = super::super::test_support::acquire_global_lock();
913 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
914
915 execute_make_listener("SendWelcomeEmail", None).unwrap();
916 let result = execute_make_listener("SendWelcomeEmail", None);
917 assert!(matches!(result, Err(CliError::FileExists(_))));
918 }
919
920 #[test]
921 fn test_execute_make_command_creates_file() {
922 let temp_dir = tempfile::tempdir().unwrap();
923 let _lock = super::super::test_support::acquire_global_lock();
924 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
925
926 execute_make_command("SyncData").unwrap();
927
928 let command_path = temp_dir.path().join("app/command/SyncData.rs");
929 assert!(command_path.exists());
930
931 let content = std::fs::read_to_string(&command_path).unwrap();
932 assert!(content.contains("pub struct SyncData;"));
933 assert!(content.contains("app::command"));
934 assert!(content.contains("use sz_rust_cli::console::{Command, CommandSignature}"));
935 assert!(content.contains("impl Command for SyncData"));
936 assert!(content.contains(r#""sync_data""#));
938 assert!(!content.contains("{%"));
939 }
940
941 #[test]
942 fn test_execute_make_command_file_already_exists() {
943 let temp_dir = tempfile::tempdir().unwrap();
944 let _lock = super::super::test_support::acquire_global_lock();
945 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
946
947 execute_make_command("SyncData").unwrap();
948 let result = execute_make_command("SyncData");
949 assert!(matches!(result, Err(CliError::FileExists(_))));
950 }
951
952 #[test]
953 fn test_execute_make_command_nested_path() {
954 let temp_dir = tempfile::tempdir().unwrap();
955 let _lock = super::super::test_support::acquire_global_lock();
956 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
957
958 execute_make_command("admin/SyncData").unwrap();
959
960 let command_path = temp_dir.path().join("app/command/admin/SyncData.rs");
961 assert!(command_path.exists());
962
963 let content = std::fs::read_to_string(&command_path).unwrap();
964 assert!(content.contains("app::command::admin"));
965 }
966
967 #[test]
968 fn test_execute_make_service_creates_file() {
969 let temp_dir = tempfile::tempdir().unwrap();
970 let _lock = super::super::test_support::acquire_global_lock();
971 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
972
973 execute_make_service("UserService").unwrap();
974
975 let service_path = temp_dir.path().join("app/service/UserService.rs");
976 assert!(service_path.exists());
977
978 let content = std::fs::read_to_string(&service_path).unwrap();
979 assert!(content.contains("pub struct UserService;"));
980 assert!(content.contains("app::service"));
981 assert!(content.contains("impl Default for UserService"));
982 assert!(content.contains("pub fn new() -> Self"));
983 assert!(!content.contains("{%"));
984 }
985
986 #[test]
987 fn test_execute_make_service_file_already_exists() {
988 let temp_dir = tempfile::tempdir().unwrap();
989 let _lock = super::super::test_support::acquire_global_lock();
990 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
991
992 execute_make_service("UserService").unwrap();
993 let result = execute_make_service("UserService");
994 assert!(matches!(result, Err(CliError::FileExists(_))));
995 }
996
997 #[test]
998 fn test_execute_make_service_nested_path() {
999 let temp_dir = tempfile::tempdir().unwrap();
1000 let _lock = super::super::test_support::acquire_global_lock();
1001 let _guard = CwdGuard::switch(temp_dir.path()).unwrap();
1002
1003 execute_make_service("admin/UserService").unwrap();
1004
1005 let service_path = temp_dir.path().join("app/service/admin/UserService.rs");
1006 assert!(service_path.exists());
1007
1008 let content = std::fs::read_to_string(&service_path).unwrap();
1009 assert!(content.contains("app::service::admin"));
1010 }
1011}