#[path = "../support/mod.rs"]
mod support;
use std::fs;
use support::{Sandbox, text};
#[test]
fn mailer_generates_templates_and_registers_the_module() {
let sandbox = Sandbox::new();
let root = sandbox.new_app("shop", &[]);
let (report, ok) = sandbox.json(&["g", "mailer", "UserMailer", "welcome", "PasswordReset"], &root);
assert!(ok, "{report}");
assert_eq!(
report["created"],
serde_json::json!([
"src/mailers/user.rs",
"templates/mailers/user/welcome.txt",
"templates/mailers/user/welcome.html",
"templates/mailers/user/password_reset.txt",
"templates/mailers/user/password_reset.html",
"templates/mailers/layout.html",
"templates/mailers/layout.txt",
"src/mailers/mod.rs",
])
);
assert_eq!(report["updated"], serde_json::json!(["src/lib.rs"]));
assert_eq!(
report["next"][0],
"send it from a handler: ocre::mail::send(&ctx, mailers::user::welcome(&address)?).await?"
);
let mailer = fs::read_to_string(root.join("src/mailers/user.rs")).unwrap();
assert!(mailer.contains("Generated by `ocre g mailer UserMailer welcome password_reset`"), "{mailer}");
assert!(mailer.contains("#[template(path = \"mailers/user/password_reset.html\")]\nstruct PasswordResetHtml<'a>"));
assert!(mailer.contains(
"pub fn password_reset(to: &str) -> Result<Email> {\n let text = PasswordResetText { to }.render()?;\n \
let html = PasswordResetHtml { to }.render()?;\n Ok(super::defaults(Email::new(to, \"Password reset\", \
text).html(html)))\n}"
));
let welcome = fs::read_to_string(root.join("templates/mailers/user/welcome.txt")).unwrap();
assert!(
welcome.starts_with(
"{% extends \"mailers/layout.txt\" %}\n{% block content -%}\nHello {{ to }},\n\nThis is the welcome email."
),
"{welcome}"
);
let html = fs::read_to_string(root.join("templates/mailers/user/password_reset.html")).unwrap();
assert!(html.starts_with("{% extends \"mailers/layout.html\" %}\n{% block content %}\n"), "{html}");
assert!(html.contains("<p>Hello {{ to }},</p>\n<p>This is the password reset email."), "{html}");
let layout = fs::read_to_string(root.join("templates/mailers/layout.html")).unwrap();
assert!(layout.contains("{% block content %}{% endblock %}"), "{layout}");
let lib = fs::read_to_string(root.join("src/lib.rs")).unwrap();
assert!(lib.contains("// ocre:modules\nmod mailers;\n"), "{lib}");
assert!(lib.contains("// ocre:routes\n .merge(ocre::mail::dev_routes(mailers::PREVIEWS))\n"), "{lib}");
let output = sandbox.ocre(&["g", "mailer", "billing", "receipt"], &root);
assert!(output.status.success());
assert!(text(&output).0.starts_with(" create src/mailers/billing.rs\n"), "{}", text(&output).0);
assert!(!text(&output).0.contains("layout"), "layouts are created once");
let registry = fs::read_to_string(root.join("src/mailers/mod.rs")).unwrap();
assert!(registry.contains("\n// ocre:mailers\npub mod billing;\npub mod user;\n"), "{registry}");
assert!(registry.contains("pub fn defaults(email: Email) -> Email {\n email\n}"), "{registry}");
assert!(
registry.contains(
" // ocre:mailer-previews\n Preview::new(\"billing/receipt\", || billing::receipt(\"ada@example.com\")),\n \
Preview::new(\"user/welcome\", || user::welcome(\"ada@example.com\")),\n \
Preview::new(\"user/password_reset\", || user::password_reset(\"ada@example.com\")),\n];"
),
"{registry}"
);
let lib = fs::read_to_string(root.join("src/lib.rs")).unwrap();
assert_eq!((lib.matches("mod mailers;").count(), lib.matches("dev_routes").count()), (1, 1));
}
#[test]
fn mailer_registries_from_older_versions_keep_compiling() {
let sandbox = Sandbox::new();
let root = sandbox.new_app("shop", &["--api"]);
fs::create_dir_all(root.join("src/mailers")).unwrap();
fs::write(root.join("src/mailers/mod.rs"), "// ocre:mailers\npub mod user;\n").unwrap();
let (report, ok) = sandbox.json(&["g", "mailer", "Billing", "receipt"], &root);
assert!(ok, "{report}");
assert_eq!(report["updated"], serde_json::json!(["src/mailers/mod.rs"]));
let mailer = fs::read_to_string(root.join("src/mailers/billing.rs")).unwrap();
assert!(mailer.contains(" Ok(Email::new(to, \"Receipt\", text))\n"), "no `defaults` to call: {mailer}");
assert!(!fs::read_to_string(root.join("src/lib.rs")).unwrap().contains("dev_routes"), "no previews to serve");
}
#[test]
fn a_mailer_after_the_mailbox_serves_its_previews() {
let sandbox = Sandbox::new();
let root = sandbox.new_app("shop", &[]);
sandbox.json(&["g", "mailbox"], &root);
assert!(fs::read_to_string(root.join("src/lib.rs")).unwrap().contains(".merge(ocre::mail::dev_routes(&[]))"));
let (report, ok) = sandbox.json(&["g", "mailer", "User", "welcome"], &root);
assert!(ok, "{report}");
let lib = fs::read_to_string(root.join("src/lib.rs")).unwrap();
assert!(lib.contains(".merge(ocre::mail::dev_routes(mailers::PREVIEWS))") && !lib.contains("(&[])"), "{lib}");
fs::write(
root.join("src/lib.rs"),
lib.replace(" .merge(ocre::mail::dev_routes(mailers::PREVIEWS))\n", "").replace("// ocre:routes", ""),
)
.unwrap();
fs::remove_file(root.join("src/mailers/user.rs")).unwrap();
let (report, ok) = sandbox.json(&["g", "mailer", "Team", "invite"], &root);
assert!(!ok);
assert_eq!(report["error"], "src/lib.rs is missing the `// ocre:routes` marker");
assert!(!root.join("src/mailers/team.rs").exists());
}
#[test]
fn mailer_in_an_api_only_app_builds_text_with_format() {
let sandbox = Sandbox::new();
let root = sandbox.new_app("api", &["--api"]);
let (report, ok) = sandbox.json(&["g", "mailer", "Account", "welcome"], &root);
assert!(ok, "{report}");
assert_eq!(report["created"], serde_json::json!(["src/mailers/account.rs", "src/mailers/mod.rs"]));
let mailer = fs::read_to_string(root.join("src/mailers/account.rs")).unwrap();
assert!(!mailer.contains("askama") && !root.join("templates").exists(), "{mailer}");
assert!(mailer.contains(
"pub fn welcome(to: &str) -> Result<Email> {\n let text = format!(\"Hello {to},\\n\\nThis is the welcome \
email. Edit it in src/mailers/account.rs.\\n\");\n Ok(super::defaults(Email::new(to, \"Welcome\", text)))\n}"
), "{mailer}");
}
#[test]
fn mailer_rejects_bad_names_and_writes_nothing() {
let sandbox = Sandbox::new();
let root = sandbox.new_app("shop", &[]);
sandbox.json(&["g", "mailer", "User", "welcome"], &root);
let cases: &[(&[&str], &str)] = &[
(&["g", "mailer", "User", "reset"], "src/mailers/user.rs already exists"),
(&["g", "mailer", "2Fast", "welcome"], "invalid mailer name `2Fast`"),
(&["g", "mailer", "Type", "welcome"], "invalid mailer name `Type`"),
(&["g", "mailer", "Team", "9lives"], "invalid action name `9lives`"),
(&["g", "mailer", "Team", "type"], "invalid action name `type`"),
(&["g", "mailer", "Team", "invite", "Invite"], "action `invite` is listed twice"),
];
for (args, error) in cases {
let (report, ok) = sandbox.json(args, &root);
assert!(!ok);
assert_eq!(report["error"], *error, "{args:?}");
assert!(report["hint"].is_string(), "{report}");
}
assert!(!root.join("src/mailers/team.rs").exists() && !root.join("templates/mailers/team").exists());
fs::write(root.join("src/lib.rs"), "fn main() {}\n").unwrap();
fs::remove_dir_all(root.join("src/mailers")).unwrap();
let (report, ok) = sandbox.json(&["g", "mailer", "Team", "invite"], &root);
assert!(!ok && report["hint"].as_str().unwrap().contains("// ocre:modules"), "{report}");
assert!(!root.join("src/mailers").exists() && !root.join("templates/mailers/team").exists());
fs::create_dir_all(root.join("src/mailers")).unwrap();
fs::write(root.join("src/mailers/mod.rs"), "pub mod user;\n").unwrap();
let (report, _) = sandbox.json(&["g", "mailer", "Team", "invite"], &root);
assert_eq!(report["error"], "src/mailers/mod.rs is missing the `// ocre:mailers` marker");
}
#[test]
fn mailbox_wires_the_email_event_once() {
let sandbox = Sandbox::new();
let root = sandbox.new_app("shop", &[]);
let (report, ok) = sandbox.json(&["g", "mailbox"], &root);
assert!(ok, "{report}");
assert_eq!(report["created"], serde_json::json!(["src/mailbox.rs"]));
assert_eq!(report["updated"], serde_json::json!(["src/lib.rs"]));
assert_eq!(report["next"][0], "ocre dev");
let mailbox = fs::read_to_string(root.join("src/mailbox.rs")).unwrap();
assert!(mailbox.contains("pub async fn receive(_ctx: Ctx, _email: InboundEmail) -> Result<()> {"), "{mailbox}");
let lib = fs::read_to_string(root.join("src/lib.rs")).unwrap();
assert!(lib.contains("// ocre:modules\nmod mailbox;\n"), "{lib}");
assert!(
lib.ends_with(
"#[worker::event(email)]\nasync fn email(message: worker::ForwardableEmailMessage, env: worker::Env, _ctx: \
worker::Context) -> worker::Result<()> {\n ocre::mail::receive(message, env, mailbox::receive).await\n}\n"
),
"{lib}"
);
let (report, ok) = sandbox.json(&["g", "mailbox"], &root);
assert!(!ok);
assert_eq!(report["error"], "src/mailbox.rs already exists");
fs::remove_file(root.join("src/mailbox.rs")).unwrap();
let (report, _) = sandbox.json(&["g", "mailbox"], &root);
assert_eq!(report["error"], "src/lib.rs already handles the `email` event");
assert!(!root.join("src/mailbox.rs").exists(), "nothing written on failure");
fs::write(root.join("src/lib.rs"), "fn main() {}\n").unwrap();
let (report, _) = sandbox.json(&["g", "mailbox"], &root);
assert_eq!(report["error"], "src/lib.rs is missing the `// ocre:modules` marker");
assert!(!root.join("src/mailbox.rs").exists());
}
#[test]
fn mailbox_after_a_mailer_keeps_its_previews() {
let sandbox = Sandbox::new();
let root = sandbox.new_app("shop", &[]);
let (report, ok) = sandbox.json(&["g", "mailer", "User", "welcome"], &root);
assert!(ok, "{report}");
let (report, ok) = sandbox.json(&["g", "mailbox"], &root);
assert!(ok, "{report}");
let lib = fs::read_to_string(root.join("src/lib.rs")).unwrap();
assert_eq!(lib.matches("dev_routes").count(), 1, "{lib}");
assert!(lib.contains(".merge(ocre::mail::dev_routes(mailers::PREVIEWS))"), "{lib}");
}
#[test]
fn mailbox_needs_the_routes_marker_for_its_dev_pages() {
let sandbox = Sandbox::new();
let root = sandbox.new_app("shop", &[]);
let lib = fs::read_to_string(root.join("src/lib.rs")).unwrap().replace("// ocre:routes", "");
fs::write(root.join("src/lib.rs"), &lib).unwrap();
let (report, ok) = sandbox.json(&["g", "mailbox"], &root);
assert!(!ok);
assert_eq!(report["error"], "src/lib.rs is missing the `// ocre:routes` marker");
assert!(!root.join("src/mailbox.rs").exists(), "nothing written on failure");
assert_eq!(fs::read_to_string(root.join("src/lib.rs")).unwrap(), lib);
}