use doido_auth::generators::{
register, AuthControllerGenerator, AuthGenerator, AuthGeneratorRegistry, AuthInstallGenerator,
AuthScaffoldGenerator,
};
struct TestRegistry {
names: Vec<String>,
}
impl AuthGeneratorRegistry for TestRegistry {
fn register_auth(&mut self, generator: Box<dyn AuthGenerator>) {
self.names.push(generator.name().to_string());
}
}
#[test]
fn register_exports_all_three_generators() {
let mut reg = TestRegistry { names: Vec::new() };
register(&mut reg);
assert!(reg.names.contains(&"auth:install".to_string()));
assert!(reg.names.contains(&"auth:controller".to_string()));
assert!(reg.names.contains(&"auth:scaffold".to_string()));
assert_eq!(reg.names.len(), 3);
}
#[test]
fn auth_install_emits_user_migration_controllers_and_routes() {
let files = AuthInstallGenerator.generate(&[]).unwrap();
assert!(files.iter().any(|f| f.path.contains("create_users_table")));
assert!(files.iter().any(|f| f.path == "app/models/user.rs"));
assert!(files
.iter()
.any(|f| f.path == "app/controllers/auth/sessions_controller.rs"));
assert!(files
.iter()
.any(|f| f.path == "app/controllers/auth/registrations_controller.rs"));
assert!(files
.iter()
.any(|f| f.path == "app/controllers/auth/passwords_controller.rs"));
assert!(files
.iter()
.any(|f| f.path == "app/controllers/auth/oauth_controller.rs"));
let routes = files
.iter()
.find(|f| f.path == "config/routes.rs")
.expect("routes.rs emitted");
assert!(routes.content.contains("SessionsController::create"));
assert!(routes.content.contains("use crate::controllers::auth;"));
}
#[test]
fn auth_install_route_injection_is_idempotent() {
let first = AuthInstallGenerator.generate(&[]).unwrap();
let routes = first
.iter()
.find(|f| f.path == "config/routes.rs")
.unwrap()
.content
.clone();
std::fs::create_dir_all("config").ok();
std::fs::write("config/routes.rs", &routes).unwrap();
let second = AuthInstallGenerator.generate(&[]).unwrap();
let routes2 = second
.iter()
.find(|f| f.path == "config/routes.rs")
.unwrap();
assert_eq!(routes.matches("SessionsController::create").count(), 1);
assert_eq!(
routes2
.content
.matches("SessionsController::create")
.count(),
1
);
let _ = std::fs::remove_file("config/routes.rs");
}
#[test]
fn auth_install_api_flag_skips_views() {
let files = AuthInstallGenerator.generate(&["--api"]).unwrap();
assert!(!files.iter().any(|f| f.path.starts_with("app/views/auth/")));
}
#[test]
fn auth_install_two_factor_flag_adds_columns() {
let files = AuthInstallGenerator.generate(&["--two-factor"]).unwrap();
let migration = files
.iter()
.find(|f| f.path.contains("create_users_table"))
.unwrap();
assert!(migration.content.contains("two_factor_secret"));
assert!(migration.content.contains("two_factor_enabled"));
assert!(files
.iter()
.any(|f| f.path.ends_with("two_factor_controller.rs")));
}
#[test]
fn auth_controller_emits_require_user_guard() {
let files = AuthControllerGenerator
.generate(&["Projects", "index"])
.unwrap();
let controller = files
.iter()
.find(|f| f.path == "app/controllers/projects_controller.rs")
.unwrap();
assert!(controller
.content
.contains("#[before_action(require_user)]"));
assert!(controller.content.contains("CurrentUser<User>"));
}
#[test]
fn auth_scaffold_adds_user_reference_and_auth_guards() {
let files = AuthScaffoldGenerator
.generate(&["Article", "title:string"])
.unwrap();
let migration = files
.iter()
.find(|f| f.path.contains("create_articles_table"))
.expect("articles migration");
assert!(migration.content.contains("references(\"user\")"));
let controller = files
.iter()
.find(|f| f.path.ends_with("articles_controller.rs"))
.unwrap();
assert!(controller.content.contains("require_user"));
assert!(controller.content.contains("user_id: Set(user.id())"));
let routes = files.iter().find(|f| f.path == "config/routes.rs").unwrap();
assert!(routes
.content
.contains("resources!(articles, ArticlesController);"));
}
#[test]
fn auth_scaffold_runs_install_when_user_model_missing() {
let user_path = std::path::Path::new("app/models/user.rs");
let had_user = user_path.exists();
if had_user {
let _ = std::fs::rename(user_path, "app/models/user.rs.bak");
}
let files = AuthScaffoldGenerator
.generate(&["Note", "body:text"])
.unwrap();
assert!(files.iter().any(|f| f.path == "app/models/user.rs"));
if had_user {
let _ = std::fs::rename("app/models/user.rs.bak", user_path);
} else {
let _ = std::fs::remove_file(user_path);
}
}