fix(ui): align SMTP settings with runtime configuration

Use appropriate input types and required markers for SMTP settings, keep authentication fields optional, and expose SMTP_USE_SSL in the Admin UI. Add regression coverage for the UI field contract and API descriptor resolution.
This commit is contained in:
Milan Cizek 2026-08-02 22:07:24 +02:00
parent 491eda319c
commit 40805ba4cc
4 changed files with 56 additions and 7 deletions

View file

@ -12,6 +12,7 @@ EMAIL_DESCRIPTORS: tuple[FieldDescriptor, ...] = (
FieldDescriptor("SMTP_HOST", "SMTP_HOST", "SMTP_HOST"),
FieldDescriptor("SMTP_PORT", "SMTP_PORT", "SMTP_PORT", default="587"),
FieldDescriptor("SMTP_TLS", "SMTP_TLS", "SMTP_TLS", default="True"),
FieldDescriptor("SMTP_USE_SSL", "SMTP_USE_SSL", "SMTP_USE_SSL", default="False"),
FieldDescriptor("SMTP_USERNAME", "SMTP_USERNAME", "SMTP_USERNAME", is_secret=True),
FieldDescriptor("SMTP_PASSWORD", "SMTP_PASSWORD", "SMTP_PASSWORD", is_secret=True),
FieldDescriptor("SMTP_SENDER_EMAIL", "SMTP_SENDER_EMAIL", "SMTP_SENDER_EMAIL"),

View file

@ -1218,6 +1218,7 @@ def test_get_config_returns_email_settings_set_only_in_process_env(monkeypatch):
monkeypatch.setenv("SMTP_HOST", "smtp.env-host.com")
monkeypatch.setenv("SMTP_PORT", "2525")
monkeypatch.setenv("SMTP_TLS", "False")
monkeypatch.setenv("SMTP_USE_SSL", "True")
monkeypatch.setenv("SMTP_USERNAME", "env-user")
monkeypatch.setenv("SMTP_PASSWORD", smtp_password)
monkeypatch.setenv("SMTP_SENDER_EMAIL", "alerts@env-host.com")
@ -1236,6 +1237,7 @@ def test_get_config_returns_email_settings_set_only_in_process_env(monkeypatch):
assert variables["SMTP_HOST"] == "smtp.env-host.com"
assert variables["SMTP_PORT"] == "2525"
assert variables["SMTP_TLS"] == "False"
assert variables["SMTP_USE_SSL"] == "True"
assert variables["SMTP_USERNAME"] == "env-user"
assert variables["SMTP_SENDER_EMAIL"] == "alerts@env-host.com"
assert variables["TEST_EMAIL_ADDRESS"] == "admin@env-host.com"
@ -1269,7 +1271,15 @@ def test_get_config_email_settings_prefer_stored_over_process_env(monkeypatch):
def test_get_config_email_settings_absent_everywhere_stay_none(monkeypatch):
"""A field set in neither source is reported unset rather than invented."""
for var in ("SMTP_HOST", "SMTP_PORT", "SMTP_TLS", "SMTP_USERNAME", "SMTP_PASSWORD", "SMTP_SENDER_EMAIL"):
for var in (
"SMTP_HOST",
"SMTP_PORT",
"SMTP_TLS",
"SMTP_USE_SSL",
"SMTP_USERNAME",
"SMTP_PASSWORD",
"SMTP_SENDER_EMAIL",
):
monkeypatch.delenv(var, raising=False)
variables = _get_email_alert_variables(

View file

@ -22,7 +22,12 @@ const alerts = [
variables: {
SMTP_HOST: "smtp.example.com",
SMTP_PORT: "587",
SMTP_TLS: "True",
SMTP_USE_SSL: "False",
SMTP_USERNAME: "smtp-user",
SMTP_PASSWORD: "********",
SMTP_SENDER_EMAIL: "alerts@example.com",
TEST_EMAIL_ADDRESS: "admin@example.com",
EMAIL_LOGO_URL: "https://example.com/logo.png",
},
},
@ -53,7 +58,15 @@ describe("EmailSettings", () => {
expect(inputNamed("SMTP_HOST")).toHaveValue("smtp.example.com");
expect(inputNamed("SMTP_PORT")).toHaveValue("587");
expect(inputNamed("SMTP_PORT")).toHaveAttribute("type", "number");
expect(inputNamed("SMTP_HOST")).toHaveAttribute("type", "text");
expect(inputNamed("SMTP_TLS")).toHaveAttribute("type", "text");
expect(inputNamed("SMTP_USE_SSL")).toHaveAttribute("type", "text");
expect(inputNamed("SMTP_USERNAME")).toHaveAttribute("type", "text");
expect(inputNamed("SMTP_PASSWORD")).toHaveValue("********");
expect(inputNamed("SMTP_SENDER_EMAIL")).toHaveAttribute("type", "text");
expect(inputNamed("TEST_EMAIL_ADDRESS")).toHaveAttribute("type", "text");
expect(inputNamed("EMAIL_LOGO_URL")).toHaveAttribute("type", "text");
expect(document.querySelector('input[name="SLACK_WEBHOOK_URL"]')).toBeNull();
});
@ -63,6 +76,21 @@ describe("EmailSettings", () => {
expect(screen.getByText("SMTP_HOST")).toBeInTheDocument();
expect(screen.getByText(/Enter the SMTP host address/)).toBeInTheDocument();
expect(screen.getByText(/Enter the SMTP port number/)).toBeInTheDocument();
expect(screen.getByText(/Optional SMTP username/)).toBeInTheDocument();
expect(screen.getByText(/Optional SMTP password/)).toBeInTheDocument();
});
it("only requires the SMTP host and sender email", () => {
renderWithProviders(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
expect(inputNamed("SMTP_HOST")).toBeRequired();
expect(inputNamed("SMTP_SENDER_EMAIL")).toBeRequired();
expect(inputNamed("SMTP_PORT")).not.toBeRequired();
expect(inputNamed("SMTP_TLS")).not.toBeRequired();
expect(inputNamed("SMTP_USE_SSL")).not.toBeRequired();
expect(inputNamed("SMTP_USERNAME")).not.toBeRequired();
expect(inputNamed("SMTP_PASSWORD")).not.toBeRequired();
expect(inputNamed("TEST_EMAIL_ADDRESS")).not.toBeRequired();
});
it("submits only the fields the admin actually edited", async () => {

View file

@ -14,13 +14,22 @@ interface EmailSettingsProps {
const REQUIRED_MARKER = <span className="text-destructive"> Required * </span>;
const REQUIRED_FIELDS = new Set(["SMTP_HOST", "SMTP_SENDER_EMAIL"]);
const FIELD_TYPES: Record<string, React.HTMLInputTypeAttribute> = {
SMTP_PORT: "number",
SMTP_PASSWORD: "password",
};
const FIELD_HELP: Record<string, React.ReactNode> = {
SMTP_HOST: <>Enter the SMTP host address, e.g. `smtp.resend.com`{REQUIRED_MARKER}</>,
SMTP_PORT: <>Enter the SMTP port number, e.g. `587`{REQUIRED_MARKER}</>,
SMTP_USERNAME: <>Enter the SMTP username, e.g. `username`{REQUIRED_MARKER}</>,
SMTP_PASSWORD: REQUIRED_MARKER,
SMTP_PORT: <>Enter the SMTP port number, e.g. `587`</>,
SMTP_TLS: <>Use STARTTLS for the SMTP connection. Defaults to `True`</>,
SMTP_USE_SSL: <>Use implicit SSL for the SMTP connection. Defaults to `False`</>,
SMTP_USERNAME: <>Optional SMTP username for authenticated servers</>,
SMTP_PASSWORD: <>Optional SMTP password for authenticated servers</>,
SMTP_SENDER_EMAIL: <>Enter the sender email address, e.g. `sender@berri.ai`{REQUIRED_MARKER}</>,
TEST_EMAIL_ADDRESS: <>Email Address to send `Test Email Alert` to. example: `info@berri.ai`{REQUIRED_MARKER}</>,
TEST_EMAIL_ADDRESS: <>Optional address for the `Test Email Alert` action</>,
EMAIL_LOGO_URL: <>(Optional) Customize the Logo that appears in the email, pass a url to your logo</>,
EMAIL_SUPPORT_CONTACT: (
<>(Optional) Customize the support email address that appears in the email. Default is support@berri.ai</>
@ -115,8 +124,9 @@ const EmailSettings: React.FC<EmailSettingsProps> = ({ accessToken, premiumUser,
)}
<Input
name={key}
defaultValue={value as string}
type="password"
defaultValue={value as string | undefined}
type={FIELD_TYPES[key] ?? "text"}
required={REQUIRED_FIELDS.has(key)}
disabled={isLocked}
className="max-w-100"
/>