1mod ai_boost;
2mod auth;
3pub mod ci_workflow;
4#[path = "do.rs"]
5pub mod do_;
6pub mod docker;
7mod entity;
8mod make;
9pub mod module;
10mod project;
11mod scaffold;
12
13pub use ai_boost::*;
14pub use auth::*;
15pub use docker::*;
16pub use entity::*;
17pub use make::*;
18pub use project::*;
19pub use scaffold::*;
20
21#[cfg(test)]
26mod tests {
27 use super::*;
28
29 #[test]
34 fn test_cargo_toml_substitution() {
35 let result = cargo_toml("my_app", "A test app", "Test Author <test@example.com>");
36 assert!(result.contains("name = \"my_app\""));
37 assert!(result.contains("description = \"A test app\""));
38 assert!(result.contains("authors = [\"Test Author <test@example.com>\"]"));
39 }
40
41 #[test]
42 fn test_cargo_toml_empty_author() {
43 let result = cargo_toml("my_app", "A test app", "");
44 assert!(result.contains("name = \"my_app\""));
45 assert!(!result.contains("authors = "));
46 }
47
48 #[test]
49 fn test_main_rs_substitution() {
50 let result = main_rs("my_app");
51 assert!(result.contains("my_app"));
52 }
53
54 #[test]
55 fn test_routes_rs_not_empty() {
56 assert!(!routes_rs().is_empty());
57 assert!(routes_rs().contains("routes"));
58 }
59
60 #[test]
61 fn test_controllers_mod_not_empty() {
62 assert!(!controllers_mod().is_empty());
63 assert!(controllers_mod().contains("auth"));
64 assert!(controllers_mod().contains("dashboard"));
65 assert!(controllers_mod().contains("profile"));
66 assert!(controllers_mod().contains("settings"));
67 }
68
69 #[test]
70 fn test_home_controller_not_empty() {
71 assert!(!home_controller().is_empty());
72 assert!(home_controller().contains("async fn index"));
73 }
74
75 #[test]
76 fn test_auth_controller_not_empty() {
77 assert!(!auth_controller().is_empty());
78 assert!(auth_controller().contains("login"));
79 assert!(auth_controller().contains("register"));
80 }
81
82 #[test]
83 fn test_dashboard_controller_not_empty() {
84 assert!(!dashboard_controller().is_empty());
85 assert!(dashboard_controller().contains("Dashboard"));
86 }
87
88 #[test]
89 fn test_profile_controller_not_empty() {
90 let content = profile_controller();
91 assert!(!content.is_empty());
92 assert!(content.contains("Profile"));
93 assert!(content.contains("async fn"));
94 }
95
96 #[test]
97 fn test_settings_controller_not_empty() {
98 let content = settings_controller();
99 assert!(!content.is_empty());
100 assert!(content.contains("Settings"));
101 assert!(content.contains("async fn"));
102 }
103
104 #[test]
109 fn test_middleware_mod_not_empty() {
110 assert!(!middleware_mod().is_empty());
111 assert!(middleware_mod().contains("logging"));
112 }
113
114 #[test]
115 fn test_middleware_template_substitution() {
116 let result = middleware_template("auth", "AuthMiddleware");
117 assert!(result.contains("auth middleware"));
118 assert!(result.contains("pub struct AuthMiddleware"));
119 assert!(result.contains("impl Middleware for AuthMiddleware"));
120 }
121
122 #[test]
123 fn test_authenticate_middleware_not_empty() {
124 assert!(!authenticate_middleware().is_empty());
125 assert!(authenticate_middleware().contains("Middleware"));
126 }
127
128 #[test]
133 fn test_models_mod_not_empty() {
134 let content = models_mod();
135 assert!(!content.is_empty());
136 assert!(content.contains("user"));
137 assert!(content.contains("password_reset_tokens"));
138 }
139
140 #[test]
141 fn test_user_model_not_empty() {
142 assert!(!user_model().is_empty());
143 assert!(user_model().contains("User"));
144 }
145
146 #[test]
147 fn test_password_reset_tokens_model_not_empty() {
148 let content = password_reset_tokens_model();
149 assert!(!content.is_empty());
150 assert!(content.contains("password_reset_tokens"));
151 assert!(content.contains("email"));
152 assert!(content.contains("token"));
153 }
154
155 #[test]
160 fn test_migrations_mod_not_empty() {
161 let content = migrations_mod();
162 assert!(!content.is_empty());
163 assert!(content.contains("create_users_table"));
164 assert!(content.contains("create_sessions_table"));
165 assert!(content.contains("create_password_reset_tokens_table"));
166 }
167
168 #[test]
169 fn test_create_users_migration_not_empty() {
170 assert!(!create_users_migration().is_empty());
171 assert!(create_users_migration().contains("Users"));
172 }
173
174 #[test]
175 fn test_create_sessions_migration_not_empty() {
176 assert!(!create_sessions_migration().is_empty());
177 assert!(create_sessions_migration().contains("sessions"));
178 }
179
180 #[test]
181 fn test_create_password_reset_tokens_migration_not_empty() {
182 let content = create_password_reset_tokens_migration();
183 assert!(!content.is_empty());
184 assert!(content.contains("password_reset_tokens"));
185 }
186
187 #[test]
192 fn test_config_mod_not_empty() {
193 assert!(!config_mod().is_empty());
194 assert!(config_mod().contains("database"));
195 }
196
197 #[test]
198 fn test_config_database_not_empty() {
199 assert!(!config_database().is_empty());
200 }
201
202 #[test]
203 fn test_config_mail_not_empty() {
204 assert!(!config_mail().is_empty());
205 }
206
207 #[test]
212 fn test_package_json_substitution() {
213 let result = package_json("my-project");
214 assert!(result.contains("\"name\": \"my-project-frontend\""));
215 }
216
217 #[test]
218 fn test_vite_config_not_empty() {
219 assert!(!vite_config().is_empty());
220 assert!(vite_config().contains("vite"));
221 }
222
223 #[test]
224 fn test_tsconfig_not_empty() {
225 assert!(!tsconfig().is_empty());
226 assert!(tsconfig().contains("compilerOptions"));
227 }
228
229 #[test]
230 fn test_index_html_substitution() {
231 let result = index_html("My App");
232 assert!(result.contains("<title>My App</title>"));
233 }
234
235 #[test]
236 fn test_main_tsx_not_empty() {
237 assert!(!main_tsx().is_empty());
238 assert!(main_tsx().contains("createInertiaApp"));
239 }
240
241 #[test]
242 fn test_home_page_not_empty() {
243 assert!(!home_page().is_empty());
244 assert!(home_page().contains("Home"));
245 }
246
247 #[test]
248 fn test_inertia_props_types_not_empty() {
249 let content = inertia_props_types();
250 assert!(!content.is_empty());
251 assert!(content.contains("User"));
252 assert!(content.contains("DashboardProps"));
253 assert!(content.contains("ProfileProps"));
254 assert!(content.contains("SettingsProps"));
255 }
256
257 #[test]
262 fn test_app_layout_not_empty() {
263 let content = app_layout();
264 assert!(!content.is_empty());
265 assert!(content.contains("AppLayout"));
266 assert!(content.contains("Sidebar"));
267 }
268
269 #[test]
270 fn test_auth_layout_not_empty() {
271 let content = auth_layout();
272 assert!(!content.is_empty());
273 assert!(content.contains("AuthLayout"));
274 }
275
276 #[test]
277 fn test_layouts_index_not_empty() {
278 let content = layouts_index();
279 assert!(!content.is_empty());
280 assert!(content.contains("AppLayout"));
281 assert!(content.contains("AuthLayout"));
282 }
283
284 #[test]
285 fn test_globals_css_not_empty() {
286 let content = globals_css();
287 assert!(!content.is_empty());
288 assert!(content.contains("tailwindcss"));
290 }
291
292 #[test]
297 fn test_login_page_not_empty() {
298 let content = login_page();
299 assert!(!content.is_empty());
300 assert!(content.contains("Login"));
301 assert!(content.contains("AuthLayout"));
302 }
303
304 #[test]
305 fn test_register_page_not_empty() {
306 let content = register_page();
307 assert!(!content.is_empty());
308 assert!(content.contains("Register"));
309 assert!(content.contains("AuthLayout"));
310 }
311
312 #[test]
313 fn test_forgot_password_page_not_empty() {
314 let content = forgot_password_page();
315 assert!(!content.is_empty());
316 assert!(content.contains("ForgotPassword"));
317 assert!(content.contains("AuthLayout"));
318 }
319
320 #[test]
321 fn test_reset_password_page_not_empty() {
322 let content = reset_password_page();
323 assert!(!content.is_empty());
324 assert!(content.contains("ResetPassword"));
325 assert!(content.contains("AuthLayout"));
326 }
327
328 #[test]
333 fn test_dashboard_page_not_empty() {
334 let content = dashboard_page();
335 assert!(!content.is_empty());
336 assert!(content.contains("Dashboard"));
337 assert!(content.contains("AppLayout"));
338 }
339
340 #[test]
341 fn test_profile_page_not_empty() {
342 let content = profile_page();
343 assert!(!content.is_empty());
344 assert!(content.contains("Profile"));
345 assert!(content.contains("AppLayout"));
346 }
347
348 #[test]
349 fn test_settings_page_not_empty() {
350 let content = settings_page();
351 assert!(!content.is_empty());
352 assert!(content.contains("Settings"));
353 assert!(content.contains("AppLayout"));
354 }
355
356 #[test]
361 fn test_controller_template_substitution() {
362 let result = controller_template("users");
363 assert!(result.contains("users controller"));
364 assert!(result.contains("#[handler]"));
365 }
366
367 #[test]
372 fn test_action_template_substitution() {
373 let result = action_template("create_user", "CreateUser");
374 assert!(result.contains("create_user action"));
375 assert!(result.contains("pub struct CreateUser"));
376 assert!(result.contains("#[injectable]"));
377 }
378
379 #[test]
380 fn test_actions_mod_not_empty() {
381 assert!(!actions_mod().is_empty());
382 }
383
384 #[test]
389 fn test_error_template_substitution() {
390 let result = error_template("UserNotFound");
391 assert!(result.contains("UserNotFound error"));
392 assert!(result.contains("pub struct UserNotFound"));
393 assert!(result.contains("#[domain_error"));
394 }
395
396 #[test]
401 fn test_inertia_page_template_substitution() {
402 let result = inertia_page_template("Users");
403 assert!(result.contains("export default function Users()"));
404 assert!(result.contains("<h1"));
405 }
406
407 #[test]
412 fn test_event_template_substitution() {
413 let result = event_template("user_registered", "UserRegistered");
414 assert!(result.contains("UserRegistered"));
415 assert!(result.contains("impl Event for UserRegistered"));
416 }
417
418 #[test]
419 fn test_listener_template_substitution() {
420 let result = listener_template("send_welcome_email", "SendWelcomeEmail", "UserRegistered");
421 assert!(result.contains("SendWelcomeEmail"));
422 assert!(result.contains("impl Listener<UserRegistered>"));
423 }
424
425 #[test]
426 fn test_job_template_substitution() {
427 let result = job_template("send_email", "SendEmail");
428 assert!(result.contains("SendEmail"));
429 assert!(result.contains("impl Job for SendEmail"));
430 }
431
432 #[test]
433 fn test_events_mod_not_empty() {
434 assert!(!events_mod().is_empty());
435 }
436
437 #[test]
438 fn test_listeners_mod_not_empty() {
439 assert!(!listeners_mod().is_empty());
440 }
441
442 #[test]
443 fn test_jobs_mod_not_empty() {
444 assert!(!jobs_mod().is_empty());
445 }
446
447 #[test]
452 fn test_notification_template_substitution() {
453 let result = notification_template("order_shipped", "OrderShipped");
454 assert!(result.contains("OrderShipped"));
455 assert!(result.contains("impl Notification for OrderShipped"));
456 }
457
458 #[test]
459 fn test_notifications_mod_not_empty() {
460 assert!(!notifications_mod().is_empty());
461 }
462
463 #[test]
468 fn test_task_template_substitution() {
469 let result = task_template("cleanup_old_sessions", "CleanupOldSessions");
470 assert!(result.contains("CleanupOldSessions"));
471 assert!(result.contains("impl Task for CleanupOldSessions"));
472 }
473
474 #[test]
475 fn test_tasks_mod_not_empty() {
476 assert!(!tasks_mod().is_empty());
477 }
478
479 #[test]
480 fn test_schedule_rs_not_empty() {
481 assert!(!schedule_rs().is_empty());
482 }
483
484 #[test]
489 fn test_seeder_template_substitution() {
490 let result = seeder_template("users_seeder", "UsersSeeder");
491 assert!(result.contains("UsersSeeder"));
492 assert!(result.contains("impl Seeder for UsersSeeder"));
493 }
494
495 #[test]
496 fn test_seeders_mod_not_empty() {
497 assert!(!seeders_mod().is_empty());
498 }
499
500 #[test]
505 fn test_factory_template_substitution() {
506 let result = factory_template("user_factory", "UserFactory", "User");
507 assert!(result.contains("UserFactory"));
508 assert!(result.contains("impl Factory for UserFactory"));
509 }
510
511 #[test]
512 fn test_factories_mod_not_empty() {
513 assert!(!factories_mod().is_empty());
514 }
515
516 #[test]
521 fn test_policy_template_substitution() {
522 let result = policy_template("post_policy", "PostPolicy", "Post");
523 assert!(result.contains("PostPolicy"));
524 assert!(result.contains("impl Policy<Post>"));
525 }
526
527 #[test]
528 fn test_policies_mod_not_empty() {
529 assert!(!policies_mod().is_empty());
530 }
531
532 #[test]
537 fn test_dockerignore_template_not_empty() {
538 assert!(!dockerignore_template().is_empty());
539 }
540
541 #[test]
542 fn test_docker_compose_template_basic() {
543 let result = docker_compose_template("my_project", false, false);
544 assert!(result.contains("my_project"));
545 assert!(result.contains("postgres"));
546 }
547
548 #[test]
549 fn test_docker_compose_template_with_mailpit() {
550 let result = docker_compose_template("my_project", true, false);
551 assert!(result.contains("mailpit"));
552 }
553
554 #[test]
555 fn test_docker_compose_template_with_minio() {
556 let result = docker_compose_template("my_project", false, true);
557 assert!(result.contains("minio"));
558 }
559
560 #[test]
565 fn test_gitignore_not_empty() {
566 assert!(!gitignore().is_empty());
567 assert!(gitignore().contains("target"));
568 }
569
570 #[test]
571 fn test_env_substitution() {
572 let result = env("my_project");
573 assert!(result.contains("my_project"));
574 }
575
576 #[test]
577 fn test_env_example_not_empty() {
578 assert!(!env_example().is_empty());
579 }
580
581 #[test]
582 fn test_readme_substitution() {
583 let result = readme("my-app", "My App", "A test description");
584 assert!(result.contains("# My App"));
585 assert!(result.contains("A test description"));
586 assert!(result.contains("cd my-app"));
587 assert!(result.contains("ferro serve"));
588 assert!(result.contains("ferro db:migrate"));
589 }
590
591 #[test]
592 fn test_gitignore_does_not_ignore_cargo_lock() {
593 let content = gitignore();
595 assert!(
597 !content.lines().any(|l| l.trim() == "Cargo.lock"),
598 "gitignore template must not ignore Cargo.lock for binary crates"
599 );
600 }
601
602 #[test]
607 fn test_ferro_guidelines_template_not_empty() {
608 let content = ferro_guidelines_template();
609 assert!(!content.is_empty());
610 assert!(content.contains("Ferro Framework"));
611 }
612
613 #[test]
614 fn test_cursor_rules_template_not_empty() {
615 let content = cursor_rules_template();
616 assert!(!content.is_empty());
617 assert!(content.contains("Ferro"));
618 }
619
620 #[test]
621 fn test_claude_md_template_not_empty() {
622 let content = claude_md_template();
623 assert!(!content.is_empty());
624 assert!(content.contains("Ferro"));
625 }
626
627 #[test]
628 fn test_copilot_instructions_template_not_empty() {
629 let content = copilot_instructions_template();
630 assert!(!content.is_empty());
631 assert!(content.contains("Ferro"));
632 }
633
634 #[test]
639 fn test_entity_template_generates_valid_rust() {
640 let columns = vec![
641 ColumnInfo {
642 name: "id".to_string(),
643 col_type: "INTEGER".to_string(),
644 is_nullable: false,
645 is_primary_key: true,
646 },
647 ColumnInfo {
648 name: "name".to_string(),
649 col_type: "VARCHAR".to_string(),
650 is_nullable: false,
651 is_primary_key: false,
652 },
653 ColumnInfo {
654 name: "email".to_string(),
655 col_type: "VARCHAR".to_string(),
656 is_nullable: true,
657 is_primary_key: false,
658 },
659 ];
660
661 let result = entity_template("users", &columns);
662 assert!(result.contains("table_name = \"users\""));
663 assert!(result.contains("pub id: i32"));
664 assert!(result.contains("pub name: String"));
665 assert!(result.contains("pub email: Option<String>"));
666 assert!(result.contains("#[sea_orm(primary_key)]"));
667 }
668
669 #[test]
670 fn test_entity_template_handles_reserved_keywords() {
671 let columns = vec![
672 ColumnInfo {
673 name: "id".to_string(),
674 col_type: "INTEGER".to_string(),
675 is_nullable: false,
676 is_primary_key: true,
677 },
678 ColumnInfo {
679 name: "type".to_string(),
680 col_type: "VARCHAR".to_string(),
681 is_nullable: false,
682 is_primary_key: false,
683 },
684 ];
685
686 let result = entity_template("items", &columns);
687 assert!(result.contains("pub r#type: String"));
688 assert!(result.contains("column_name = \"type\""));
689 }
690
691 #[test]
692 fn test_user_model_template_generates_minimal_file() {
693 let columns = vec![
694 ColumnInfo {
695 name: "id".to_string(),
696 col_type: "INTEGER".to_string(),
697 is_nullable: false,
698 is_primary_key: true,
699 },
700 ColumnInfo {
701 name: "name".to_string(),
702 col_type: "VARCHAR".to_string(),
703 is_nullable: false,
704 is_primary_key: false,
705 },
706 ];
707
708 let result = user_model_template("users", "User", &columns);
709 assert!(result.contains("pub type User = Model"));
711 assert!(result.contains("pub use super::entities::users::*"));
713 assert!(result.contains("impl ferro::auth::Authenticatable for Model"));
715 assert!(!result.contains("pub fn query()"));
717 assert!(!result.contains("pub fn create()"));
718 assert!(!result.contains("pub struct UserBuilder"));
719 }
720
721 #[test]
722 fn test_entity_template_includes_ferro_model_derive() {
723 let columns = vec![ColumnInfo {
724 name: "id".to_string(),
725 col_type: "INTEGER".to_string(),
726 is_nullable: false,
727 is_primary_key: true,
728 }];
729
730 let result = entity_template("users", &columns);
731 assert!(result.contains("FerroModel"));
733 assert!(result.contains("use ferro::FerroModel"));
734 }
735
736 #[test]
737 fn test_entities_mod_template() {
738 let tables = vec![
739 TableInfo {
740 name: "users".to_string(),
741 columns: vec![],
742 },
743 TableInfo {
744 name: "posts".to_string(),
745 columns: vec![],
746 },
747 ];
748
749 let result = entities_mod_template(&tables);
750 assert!(result.contains("pub mod users;"));
751 assert!(result.contains("pub mod posts;"));
752 }
753
754 #[test]
759 fn test_sql_type_conversions() {
760 let test_cases = vec![
761 ("BIGINT", "i64"),
762 ("INT8", "i64"),
763 ("SMALLINT", "i16"),
764 ("INT2", "i16"),
765 ("INTEGER", "i32"),
766 ("INT", "i32"),
767 ("TEXT", "String"),
768 ("VARCHAR(255)", "String"),
769 ("CHAR(10)", "String"),
770 ("BOOLEAN", "bool"),
771 ("BOOL", "bool"),
772 ("REAL", "f32"),
773 ("FLOAT4", "f32"),
774 ("DOUBLE", "f64"),
775 ("FLOAT8", "f64"),
776 ("TIMESTAMP", "DateTimeUtc"),
777 ("DATETIME", "DateTimeUtc"),
778 ("DATE", "Date"),
779 ("TIME", "Time"),
780 ("UUID", "Uuid"),
781 ("JSON", "Json"),
782 ("JSONB", "Json"),
783 ("BYTEA", "Vec<u8>"),
784 ("BLOB", "Vec<u8>"),
785 ("DECIMAL", "Decimal"),
786 ("NUMERIC", "Decimal"),
787 ];
788
789 for (sql_type, expected_rust_type) in test_cases {
790 let columns = vec![ColumnInfo {
791 name: "test_col".to_string(),
792 col_type: sql_type.to_string(),
793 is_nullable: false,
794 is_primary_key: false,
795 }];
796
797 let result = entity_template("test_table", &columns);
798 assert!(
799 result.contains(&format!("pub test_col: {expected_rust_type}")),
800 "Failed for SQL type '{sql_type}': expected Rust type '{expected_rust_type}' not found in:\n{result}"
801 );
802 }
803 }
804
805 #[test]
806 fn test_nullable_types() {
807 let columns = vec![ColumnInfo {
808 name: "optional_name".to_string(),
809 col_type: "VARCHAR".to_string(),
810 is_nullable: true,
811 is_primary_key: false,
812 }];
813
814 let result = entity_template("test_table", &columns);
815 assert!(result.contains("pub optional_name: Option<String>"));
816 }
817
818 #[test]
823 fn test_api_controller_template_substitution() {
824 let result = api_controller_template(
825 "Post",
826 "post",
827 "posts",
828 " pub title: String,\n pub body: String,",
829 " .set_title(form.title.clone())\n .set_body(form.body.clone())\n",
830 " title: sea_orm::ActiveValue::Set(form.title.clone()),\n body: sea_orm::ActiveValue::Set(form.body.clone()),",
831 );
832 assert!(result.contains("Post API controller"));
833 assert!(result.contains("pub async fn index"));
834 assert!(result.contains("pub async fn show"));
835 assert!(result.contains("pub async fn store"));
836 assert!(result.contains("pub async fn update"));
837 assert!(result.contains("pub async fn destroy"));
838 assert!(result.contains("json_response!"));
839 assert!(!result.contains("Inertia"));
840 assert!(result.contains(".update()"));
842 assert!(result.contains(".set_title("));
843 assert!(result.contains(".save()"));
844 assert!(!result.contains("let mut post: post::ActiveModel"));
846 }
847}