From d7dd7d1959030f690985ca5862a0ffb800d49f20 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 7 Apr 2026 14:54:29 +0200 Subject: [PATCH] fix: failing tests --- CODE_GUIDELINES.md | 2 +- docs/smtp-configuration-concept.md | 2 + test/mv/mailer_smtp_config_test.exs | 93 +++++++++++++++++++---------- 3 files changed, 66 insertions(+), 31 deletions(-) diff --git a/CODE_GUIDELINES.md b/CODE_GUIDELINES.md index efa1678..199ceed 100644 --- a/CODE_GUIDELINES.md +++ b/CODE_GUIDELINES.md @@ -1286,7 +1286,7 @@ mix hex.outdated - `SMTP_SSL` values: `tls` (default, port 587), `ssl` (port 465), `none` (port 25). - When `SMTP_HOST` ENV is present at boot, `runtime.exs` configures `Swoosh.Adapters.SMTP` automatically. - When SMTP is configured only via Settings, `Mv.Mailer.smtp_config/0` builds the adapter config per-send. -- In test environment, `Swoosh.Adapters.Test` is used regardless of SMTP config. +- In test environment, `Swoosh.Adapters.Test` is used regardless of SMTP config. `smtp_config/0` returns `[]` when the mailer adapter is `Swoosh.Adapters.Test`, so per-send SMTP opts never bypass the test mailbox. Port 587/465 sockopts are unit-tested on `Mv.Smtp.ConfigBuilder.build_opts/1` (`test/mv/smtp/config_builder_test.exs`); `test/mv/mailer_smtp_config_test.exs` covers the Test-adapter guard and temporarily sets the adapter to `Swoosh.Adapters.Local` to assert `smtp_config/0` wiring from ENV. Use `Mv.DataCase` for those tests (not plain `ExUnit.Case`) because `smtp_config/0` pulls `Mv.Config` fields that may read Settings from the DB when SMTP user/password ENV vars are unset. - **TLS in OTP 27:** Verify mode defaults to `verify_none` for self-signed/internal certs. Set `SMTP_VERIFY_PEER=true` (or `1`/`yes`) in prod when using public SMTP (Gmail, Mailgun). Config key `:smtp_verify_peer` is set in `runtime.exs` and read by `Mv.Mailer.smtp_config/0`. - **Test email:** `Mv.Mailer.send_test_email(to_email)` sends a transactional test email; returns `{:ok, email}` or `{:error, classified_reason}`. Classified errors: `:sender_rejected`, `:auth_failed`, `:recipient_rejected`, `:tls_failed`, `:connection_failed`, `{:smtp_error, message}`. Each shows a specific message in the UI. - **Production warning:** When SMTP is not configured in production, a warning is shown in the Settings UI. Use `Application.get_env(:mv, :environment, :dev)` (or assign in mount) for environment checks in LiveView/templates; do not use `Mix.env()` at runtime (it is not available in releases). diff --git a/docs/smtp-configuration-concept.md b/docs/smtp-configuration-concept.md index 3cc01df..4ae7760 100644 --- a/docs/smtp-configuration-concept.md +++ b/docs/smtp-configuration-concept.md @@ -109,6 +109,8 @@ By default, TLS certificate verification is relaxed (`verify_none`) so self-sign Verify mode is set in `tls_options` for port 587 (STARTTLS). For port 465 (implicit SSL), the initial connection is `ssl:connect`, so we also pass `sockopts: [verify: verify_mode]` so the SSL handshake uses the same mode. For 587 we must not pass `verify` in sockopts—gen_tcp is used first and rejects it (ArgumentError). The logic lives in `Mv.Smtp.ConfigBuilder.build_opts/1` (single source of truth), used by `config/runtime.exs` (boot) and `Mv.Mailer.smtp_config/0` (Settings-only). +**Tests:** `Mv.Smtp.ConfigBuilderTest` asserts sockopts/TLS shape. `Mv.Mailer.smtp_config/0` returns `[]` when the mailer adapter is `Swoosh.Adapters.Test`; `test/mv/mailer_smtp_config_test.exs` asserts that guard and, with the adapter temporarily set to `Swoosh.Adapters.Local`, wiring from ENV. Those mailer tests use `Mv.DataCase` so Settings fallbacks in `Mv.Config` (e.g. SMTP username/password when ENV is unset) stay under the SQL sandbox. + --- ## 12. Summary Checklist diff --git a/test/mv/mailer_smtp_config_test.exs b/test/mv/mailer_smtp_config_test.exs index 22325df..901e84d 100644 --- a/test/mv/mailer_smtp_config_test.exs +++ b/test/mv/mailer_smtp_config_test.exs @@ -1,18 +1,22 @@ defmodule Mv.MailerSmtpConfigTest do @moduledoc """ - Unit tests for Mv.Mailer.smtp_config/0. + Integration-style tests for `Mv.Mailer.smtp_config/0`. - Ensures both port 587 (STARTTLS) and 465 (implicit SSL) work: - - 587: sockopts must NOT contain :verify (gen_tcp:connect would raise ArgumentError). - - 465: sockopts MUST contain :verify so initial ssl:connect uses verify_none/verify_peer. - Uses ENV to drive config; async: false. + With the default test mailer adapter (`Swoosh.Adapters.Test`), `smtp_config/0` + must return `[]` so per-send SMTP opts never bypass the test mailbox. + + Port 587 vs 465 / `:verify` in `sockopts` are covered by + `Mv.Smtp.ConfigBuilderTest`; here we assert `smtp_config/0` wiring from + `Mv.Config` when the mailer adapter is temporarily set to + `Swoosh.Adapters.Local` (`async: false`, global Application env). + + Uses `Mv.DataCase` so `Mv.Config.smtp_username/0` and `smtp_password/0` (Settings + fallback when ENV is unset) run under the SQL sandbox like the rest of the suite. """ use Mv.DataCase, async: false alias Mv.Mailer - defp set_smtp_env(key, value), do: System.put_env(key, value) - defp clear_smtp_env do System.delete_env("SMTP_HOST") System.delete_env("SMTP_PORT") @@ -22,31 +26,64 @@ defmodule Mv.MailerSmtpConfigTest do System.delete_env("SMTP_SSL") end - describe "smtp_config/0" do + describe "smtp_config/0 with Swoosh.Adapters.Test" do + setup do + previous = Application.get_env(:mv, Mv.Mailer) + Application.put_env(:mv, Mv.Mailer, adapter: Swoosh.Adapters.Test) + + on_exit(fn -> + Application.put_env(:mv, Mv.Mailer, previous) + end) + + :ok + end + + test "returns empty list when SMTP_* ENV is set so the test adapter is not overridden" do + System.put_env("SMTP_HOST", "smtp.example.com") + System.put_env("SMTP_PORT", "587") + System.put_env("SMTP_SSL", "tls") + on_exit(fn -> clear_smtp_env() end) + + assert Mailer.smtp_config() == [] + end + end + + describe "smtp_config/0 with a non-Test mailer adapter" do + setup do + previous = Application.get_env(:mv, Mv.Mailer) + Application.put_env(:mv, Mv.Mailer, adapter: Swoosh.Adapters.Local) + + on_exit(fn -> + Application.put_env(:mv, Mv.Mailer, previous) + end) + + :ok + end + test "port 587 (TLS): does not include :verify in sockopts so gen_tcp:connect does not crash" do - set_smtp_env("SMTP_HOST", "smtp.example.com") - set_smtp_env("SMTP_PORT", "587") - set_smtp_env("SMTP_SSL", "tls") + System.put_env("SMTP_HOST", "smtp.example.com") + System.put_env("SMTP_PORT", "587") + System.put_env("SMTP_SSL", "tls") + on_exit(fn -> clear_smtp_env() end) config = Mailer.smtp_config() - assert config != [], "expected non-empty config when SMTP_HOST is set" + assert config != [], + "expected non-empty config when SMTP_HOST is set and adapter is not Test" sockopts = Keyword.get(config, :sockopts, []) refute Keyword.has_key?(sockopts, :verify), "for 587 gen_tcp is used first; sockopts must not contain :verify" - after - clear_smtp_env() end test "port 465 (SSL): includes :verify in sockopts so initial ssl:connect accepts verify mode" do - set_smtp_env("SMTP_HOST", "smtp.example.com") - set_smtp_env("SMTP_PORT", "465") - set_smtp_env("SMTP_SSL", "ssl") + System.put_env("SMTP_HOST", "smtp.example.com") + System.put_env("SMTP_PORT", "465") + System.put_env("SMTP_SSL", "ssl") + on_exit(fn -> clear_smtp_env() end) config = Mailer.smtp_config() - assert config != [] sockopts = Keyword.get(config, :sockopts, []) @@ -54,36 +91,32 @@ defmodule Mv.MailerSmtpConfigTest do "for 465 initial connection is ssl:connect; sockopts must contain :verify" assert Keyword.get(sockopts, :verify) in [:verify_none, :verify_peer] - after - clear_smtp_env() end test "builds TLS mode for port 587 (STARTTLS)" do - set_smtp_env("SMTP_HOST", "smtp.example.com") - set_smtp_env("SMTP_PORT", "587") - set_smtp_env("SMTP_SSL", "tls") + System.put_env("SMTP_HOST", "smtp.example.com") + System.put_env("SMTP_PORT", "587") + System.put_env("SMTP_SSL", "tls") + on_exit(fn -> clear_smtp_env() end) config = Mailer.smtp_config() assert config != [] assert Keyword.get(config, :tls) == :always assert Keyword.get(config, :ssl) == false - after - clear_smtp_env() end test "builds SSL mode for port 465 (implicit SSL)" do - set_smtp_env("SMTP_HOST", "smtp.example.com") - set_smtp_env("SMTP_PORT", "465") - set_smtp_env("SMTP_SSL", "ssl") + System.put_env("SMTP_HOST", "smtp.example.com") + System.put_env("SMTP_PORT", "465") + System.put_env("SMTP_SSL", "ssl") + on_exit(fn -> clear_smtp_env() end) config = Mailer.smtp_config() assert config != [] assert Keyword.get(config, :ssl) == true assert Keyword.get(config, :tls) == :never - after - clear_smtp_env() end end end