use indexmap::IndexMap;
use crate::tree::Tree;
pub struct FactoryGenerator;
impl FactoryGenerator {
pub fn generate(tree: &Tree) -> IndexMap<String, String> {
let mut files = IndexMap::new();
let mut factory_lines = vec![
"//! Factory helpers for creating test data.".to_string(),
"//! Each function creates a row with sensible defaults.".to_string(),
String::new(),
"use sqlx::PgPool;".to_string(),
"use crate::models::prelude::*;".to_string(),
String::new(),
];
for model in tree.all_models() {
if model.pivot {
continue;
}
let name = &model.name;
let lower = name.to_lowercase();
let table = model.table_name();
let mut params = vec![];
let mut binds = vec![];
let mut fields = vec![];
for col in model.fillable_columns().iter().take(5) {
let place = format!("${}", params.len() + 1);
let (val, faker) = Self::column_to_faker(col, &place);
params.push(val);
binds.push(faker);
fields.push(col.name.clone());
}
let field_list = fields.join(", ");
let bind_lines: Vec<String> = binds.iter().map(|b| format!(" .{}", b)).collect();
let bind_str = bind_lines.join("\n");
let factory_fn = format!(
"pub async fn create_{}(pool: &PgPool{}) -> {} {{\n let row = sqlx::query_as::<_, {}>(\n \"INSERT INTO {} ({}) VALUES ({}) RETURNING *\"\n )\n{}\n .fetch_one(pool)\n .await\n .expect(\"failed to create {}\");\n row\n}}",
lower,
params.join(""),
name,
name,
table,
field_list,
(1..=fields.len()).map(|i| format!("${}", i)).collect::<Vec<_>>().join(", "),
bind_str,
lower
);
factory_lines.push(factory_fn);
factory_lines.push(String::new());
let default_fn = format!(
"pub async fn create_{}_default(pool: &PgPool) -> {} {{\n create_{}(pool).await\n}}",
lower, name, lower
);
factory_lines.push(default_fn);
factory_lines.push(String::new());
}
let content = factory_lines.join("\n");
files.insert("src/db/factories.rs".to_string(), content);
files
}
fn column_to_faker(column: &crate::models::Column, place: &str) -> (String, String) {
let bind = match column.data_type.as_str() {
"string" | "char" | "text" | "longtext" | "mediumtext" => {
if column.name == "email" || column.name.ends_with("_email") {
format!("bind(\"test@example.com\")")
} else if column.name == "name" || column.name.ends_with("_name") {
format!("bind(\"Test {}\")", column.name)
} else {
format!("bind(\"test_{}\")", column.name)
}
}
"integer" | "bigInteger" | "unsignedInteger" | "unsignedBigInteger" => {
format!("bind(1)")
}
"smallInteger" | "tinyInteger" => format!("bind(1i16)"),
"boolean" => format!("bind(true)"),
"float" | "double" => format!("bind(1.0)"),
"date" => format!("bind(chrono::Utc::now().date_naive())"),
"datetime" | "timestamp" => format!("bind(chrono::Utc::now().naive_utc())"),
"json" | "jsonb" => format!("bind(serde_json::Value::Null)"),
"uuid" => format!("bind(uuid::Uuid::new_v4())"),
_ => format!("bind(\"test\")"),
};
let param = match column.data_type.as_str() {
"boolean" => String::new(),
_ if column.has_modifier("nullable") => format!(", Option<{}>", crate::models::column::Column::new("x").php_type()),
_ => String::new(),
};
(param, bind)
}
}