精简初始化通知设置并默认使用Smail系统通知身份发信
This commit is contained in:
parent
229d150a08
commit
a9937c3e6a
@ -97,6 +97,13 @@ func (completer *Completer) prepareNotification(ctx context.Context, current con
|
||||
return candidate, nil, completeError(CompleteErrorInput, "SMTP 通知主机、账号、密码或连接安全参数无效", nil)
|
||||
}
|
||||
credentials := make(map[string]map[string]string)
|
||||
if candidate.SMTPEnabled {
|
||||
name, email, err := candidate.SenderIdentity()
|
||||
if err != nil {
|
||||
return candidate, nil, completeError(CompleteErrorInput, "SMTP 发件账号无效,请使用 Smail 系统通知页提供的完整用户名", err)
|
||||
}
|
||||
candidate.SenderName, candidate.SenderEmail = name, email
|
||||
}
|
||||
if candidate.HTTPEnabled && candidate.HTTPAuthMode != "none" {
|
||||
values, err := notificationCredentials(candidate.HTTPAuthMode, input.HTTPCredentials)
|
||||
if err != nil {
|
||||
|
||||
@ -15,7 +15,7 @@ func completionNotificationInput() *NotificationInput {
|
||||
return &NotificationInput{
|
||||
HTTPEnabled: true, Endpoint: "https://mail.example.test", HTTPAuthMode: "session",
|
||||
HTTPCredentials: map[string]string{"account": "mail-owner", "password": "private-http-password"},
|
||||
SMTPEnabled: true, SMTPHost: "smtp.example.test", SMTPUsername: "smtp-owner", SMTPPassword: "private-smtp-password",
|
||||
SMTPEnabled: true, SMTPHost: "smtp.example.test", SMTPUsername: "notify:smtp-owner@example.test", SMTPPassword: "private-smtp-password",
|
||||
IncludeManagementURL: true, IncludeCertificateSerial: true,
|
||||
Recipients: []string{"notify@example.test"}, TimeoutSecs: 15, RetryCount: 0, RetryIntervalsSecs: []int{},
|
||||
}
|
||||
@ -59,6 +59,9 @@ func TestCompleterStoresNotificationCredentialsWithoutPlaintext(t *testing.T) {
|
||||
t.Fatalf("通知选项未完整保存:%+v", stored.Notification)
|
||||
}
|
||||
httpCredentials, err := fixture.store.Get(t.Context(), stored.Notification.HTTPCredentialRef)
|
||||
if stored.Notification.SenderEmail != "notification+system@example.test" || stored.Notification.SenderName != "系统通知" {
|
||||
t.Fatal("初始化没有保存 Smail 系统通知发件人")
|
||||
}
|
||||
if err != nil || httpCredentials["password"] != "private-http-password" {
|
||||
t.Fatalf("HTTP 通知凭据未进入敏感存储:%v", err)
|
||||
}
|
||||
|
||||
@ -235,7 +235,7 @@ func newInitCommandWithDependencies(firewallManager firewall.Manager, issue init
|
||||
command.Flags().StringVar(&emailHTTPHeaders, "email-http-headers", "", "HTTP 自定义请求头 JSON")
|
||||
command.Flags().StringVar(&emailEventMode, "email-event-mode", "detailed", "通知事件模式:detailed、unified")
|
||||
command.Flags().StringVar(&emailSenderName, "email-sender-name", "Scert", "通知发件人名称")
|
||||
command.Flags().StringVar(&emailSenderEmail, "email-sender-email", "", "通知发件人地址,留空使用管理员邮箱")
|
||||
command.Flags().StringVar(&emailSenderEmail, "email-sender-email", "", "兼容通知发件人地址;Smail 系统通知账号自动确定,其他 SMTP 留空使用完整邮箱账号")
|
||||
command.Flags().BoolVar(&emailIncludeDeploymentDetails, "email-include-deployment-details", true, "是否发送服务器部署明细")
|
||||
command.Flags().BoolVar(&emailIncludeCertificateSerial, "email-include-certificate-serial", true, "是否发送证书序列号")
|
||||
command.Flags().BoolVar(&emailIncludeManagementURL, "email-include-management-url", true, "是否发送管理后台链接")
|
||||
|
||||
36
backend/internal/config/notification_sender.go
Normal file
36
backend/internal/config/notification_sender.go
Normal file
@ -0,0 +1,36 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/mail"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SenderIdentity 根据系统通知账号确定发件人,不能将管理员邮箱当作 SMTP 发件人。
|
||||
func (c NotificationConfig) SenderIdentity() (string, string, error) {
|
||||
name := strings.TrimSpace(c.SenderName)
|
||||
email := strings.TrimSpace(c.SenderEmail)
|
||||
if !c.SMTPEnabled {
|
||||
return name, email, nil
|
||||
}
|
||||
username := strings.ToLower(strings.TrimSpace(c.SMTPUsername))
|
||||
if strings.HasPrefix(username, "notify:") {
|
||||
account := strings.TrimPrefix(username, "notify:")
|
||||
parsed, err := mail.ParseAddress(account)
|
||||
parts := strings.Split(account, "@")
|
||||
if err != nil || parsed.Address != account || parsed.Name != "" || len(parts) != 2 ||
|
||||
strings.ContainsAny(account, "\r\n<>()") {
|
||||
return "", "", errors.New("Smail 系统通知用户名必须使用 notify:账号@邮件域名 格式")
|
||||
}
|
||||
// Smail 保留该发件地址;不能使用登录账号、管理员邮箱或自定义名称替代。
|
||||
return "系统通知", "notification+system@" + parts[1], nil
|
||||
}
|
||||
if email == "" {
|
||||
parsed, err := mail.ParseAddress(username)
|
||||
if err != nil || parsed.Address != username || parsed.Name != "" {
|
||||
return "", "", errors.New("请填写 Smail 系统通知用户名;兼容 SMTP 账号必须是完整邮箱地址")
|
||||
}
|
||||
email = username
|
||||
}
|
||||
return name, email, nil
|
||||
}
|
||||
40
backend/internal/config/notification_sender_test.go
Normal file
40
backend/internal/config/notification_sender_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNotificationSenderIdentity(t *testing.T) {
|
||||
for _, item := range []struct {
|
||||
name, username, sender, wantName, wantEmail string
|
||||
}{
|
||||
{"system", "notify:scert@example.test", "", "系统通知", "notification+system@example.test"},
|
||||
{"stale_admin", "notify:scert@example.test", "admin@other.test", "系统通知", "notification+system@example.test"},
|
||||
{"normalized", " NOTIFY:SCERT@EXAMPLE.TEST ", "", "系统通知", "notification+system@example.test"},
|
||||
{"smtp_compatibility", "sender@example.test", "", "Scert", "sender@example.test"},
|
||||
{"explicit_legacy", "smtp-login", "sender@example.test", "Scert", "sender@example.test"},
|
||||
} {
|
||||
t.Run(item.name, func(t *testing.T) {
|
||||
cfg := NotificationConfig{SMTPEnabled: true, SMTPUsername: item.username, SenderName: "Scert", SenderEmail: item.sender}
|
||||
name, email, err := cfg.SenderIdentity()
|
||||
if err != nil || name != item.wantName || email != item.wantEmail {
|
||||
t.Fatalf("发件身份不匹配:name=%q email=%q err=%v", name, email, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationSenderRejectsInvalidSystemAccount(t *testing.T) {
|
||||
for _, username := range []string{"notify:", "notify:user", "notify:@example.test", "notify:a@b@example.test", "notify:User <a@example.test>", "notify:a@example.test\r\nBcc:other@example.test", "smtp-login", ""} {
|
||||
cfg := NotificationConfig{SMTPEnabled: true, SMTPUsername: username}
|
||||
if _, _, err := cfg.SenderIdentity(); err == nil {
|
||||
t.Fatal("无效账号不得回退到管理员邮箱")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationSenderPreservesHTTPIdentity(t *testing.T) {
|
||||
cfg := NotificationConfig{SMTPUsername: "notify:unused@example.test", SenderName: "兼容通知", SenderEmail: "sender@example.test"}
|
||||
name, email, err := cfg.SenderIdentity()
|
||||
if err != nil || name != cfg.SenderName || email != cfg.SenderEmail {
|
||||
t.Fatal("未启用 SMTP 时不得改写原有 HTTP 发件人")
|
||||
}
|
||||
}
|
||||
@ -716,7 +716,7 @@ func TestSetupLateFailureDeletesNewNotificationCredentials(t *testing.T) {
|
||||
"email_smtp_enabled":true,
|
||||
"email_smtp_host":"smtp.example.test",
|
||||
"email_smtp_port":587,
|
||||
"email_smtp_username":"smtp-user",
|
||||
"email_smtp_username":"notify:smtp-user@example.test",
|
||||
"email_smtp_password":"smtp-secret",
|
||||
"email_smtp_mode":"starttls",`, 1)
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/v1/setup", strings.NewReader(payload))
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"html"
|
||||
"net/mail"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SendSecurityCode 仅向绑定邮箱发送验证码,不使用证书通知的收件人列表。
|
||||
@ -24,7 +23,10 @@ func (s *Service) SendSecurityCode(ctx context.Context, recipient, code string)
|
||||
return errors.New("验证码格式无效")
|
||||
}
|
||||
}
|
||||
from := strings.TrimSpace(s.config.SenderEmail)
|
||||
fromName, from, err := s.config.SenderIdentity()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, value := range []string{from, recipient} {
|
||||
parsed, err := mail.ParseAddress(value)
|
||||
if err != nil || parsed.Address != value {
|
||||
@ -34,7 +36,7 @@ func (s *Service) SendSecurityCode(ctx context.Context, recipient, code string)
|
||||
text := "有人正在部署服务器上重置 Scert 超级管理员密码。\n验证码:" + code +
|
||||
"\n验证码仅在当前本机重置会话中有效,有效期不超过两分钟。\n如果不是您本人操作,请勿提供此验证码,并检查服务器访问权限。"
|
||||
payload := mailPayload{
|
||||
From: address{Name: s.config.SenderName, Email: from},
|
||||
From: address{Name: fromName, Email: from},
|
||||
To: []address{{Email: recipient}},
|
||||
Subject: "[Scert] 超级管理员密码重置验证",
|
||||
Text: text, HTML: "<pre>" + html.EscapeString(text) + "</pre>",
|
||||
|
||||
@ -371,7 +371,10 @@ func (s *Service) loadCredentials(ctx context.Context) (map[string]string, error
|
||||
}
|
||||
|
||||
func (s *Service) buildPayload(event Event) (mailPayload, error) {
|
||||
fromEmail := strings.TrimSpace(s.config.SenderEmail)
|
||||
fromName, fromEmail, err := s.config.SenderIdentity()
|
||||
if err != nil {
|
||||
return mailPayload{}, err
|
||||
}
|
||||
if fromEmail == "" {
|
||||
return mailPayload{}, errors.New("通知发件人地址不能为空")
|
||||
}
|
||||
@ -410,7 +413,7 @@ func (s *Service) buildPayload(event Event) (mailPayload, error) {
|
||||
}
|
||||
text := buildText(event, title, status, domainName)
|
||||
return mailPayload{
|
||||
From: address{Name: s.config.SenderName, Email: fromEmail},
|
||||
From: address{Name: fromName, Email: fromEmail},
|
||||
To: recipients,
|
||||
Subject: fmt.Sprintf("[Scert] %s:%s(%s)", title, domainName, status),
|
||||
Text: text,
|
||||
|
||||
@ -28,6 +28,7 @@ type smtpTestServer struct {
|
||||
rejectAuth bool
|
||||
mu sync.Mutex
|
||||
messages [][]byte
|
||||
mailFrom string
|
||||
connections int
|
||||
}
|
||||
|
||||
@ -121,6 +122,7 @@ func (s *smtpTestServer) handle(connection net.Conn, startTLS bool) {
|
||||
}
|
||||
case strings.HasPrefix(upper, "MAIL FROM:"):
|
||||
s.mu.Lock()
|
||||
s.mailFrom = command
|
||||
fail := s.failMailOnce
|
||||
s.failMailOnce = false
|
||||
s.mu.Unlock()
|
||||
@ -275,6 +277,42 @@ func TestSMTPSendsUTF8WithAllowedPlainAuthentication(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmailSystemSenderUsesSameEnvelopeAndHeader(t *testing.T) {
|
||||
for _, security := range []bool{false, true} {
|
||||
t.Run(map[bool]string{false: "notification", true: "security_code"}[security], func(t *testing.T) {
|
||||
server := newSMTPTestServer(t, false, true)
|
||||
host, port := server.address()
|
||||
cfg := smtpTestConfig(host, port, "starttls")
|
||||
cfg.SMTPUsername = "notify:scert@example.test"
|
||||
cfg.SenderEmail = "admin@other.test"
|
||||
service := New(cfg, credentialReaderStub{values: map[string]string{"password": "test-only-notification-credential"}})
|
||||
service.smtpTLSConfig = server.clientTLSConfig
|
||||
var err error
|
||||
if security {
|
||||
err = service.SendSecurityCode(t.Context(), "bound@example.test", "01234567")
|
||||
} else {
|
||||
err = service.Send(t.Context(), Event{Type: "certificate_issue", Status: "success", Domain: "example.test"})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server.mu.Lock()
|
||||
envelope := server.mailFrom
|
||||
server.mu.Unlock()
|
||||
if !strings.HasPrefix(envelope, "MAIL FROM:<notification+system@example.test>") {
|
||||
t.Fatal("SMTP 信封没有使用系统通知地址")
|
||||
}
|
||||
message := string(server.message())
|
||||
if !strings.Contains(message, formatSMTPAddress(address{Name: "系统通知", Email: "notification+system@example.test"})) || strings.Contains(message, "admin@other.test") {
|
||||
t.Fatal("邮件头没有使用固定系统通知身份,或泄漏旧管理员发件地址")
|
||||
}
|
||||
if security && strings.Contains(message, "receiver@example.com") {
|
||||
t.Fatal("验证码不得发送给普通通知收件人")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSMTPStartTLS(t *testing.T) {
|
||||
server := newSMTPTestServer(t, false, true)
|
||||
host, port := server.address()
|
||||
|
||||
@ -170,8 +170,17 @@ func Apply(cfg *config.Config, input Input) error {
|
||||
}
|
||||
if strings.TrimSpace(input.EmailSenderEmail) != "" {
|
||||
cfg.Notification.SenderEmail = strings.ToLower(strings.TrimSpace(input.EmailSenderEmail))
|
||||
} else {
|
||||
} else if !cfg.Notification.SMTPEnabled {
|
||||
cfg.Notification.SenderEmail = adminEmail
|
||||
} else {
|
||||
cfg.Notification.SenderEmail = ""
|
||||
}
|
||||
if cfg.Notification.SMTPEnabled {
|
||||
name, email, err := cfg.Notification.SenderIdentity()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.Notification.SenderName, cfg.Notification.SenderEmail = name, email
|
||||
}
|
||||
if input.EmailTimeoutSecs > 0 {
|
||||
cfg.Notification.TimeoutSecs = input.EmailTimeoutSecs
|
||||
|
||||
18
doc/Smail系统通知接入说明.md
Normal file
18
doc/Smail系统通知接入说明.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Smail 系统通知接入说明
|
||||
|
||||
初始化向导仅配置 SMTP 连接、系统通知凭据和通知收件人,不再显示自定义发件人名称、发件人地址及通知页底部的密码重置地址说明条。
|
||||
|
||||
## 使用方式
|
||||
|
||||
1. 在 Smail 的系统通知页面启用对应账号的发信授权,并获取连接参数和独立发信凭据。
|
||||
2. 将该页面提供的 SMTP 主机、端口、连接模式和完整用户名填入 Scert。用户名格式为 `notify:账号@邮件主域名`,密码栏填写系统通知专用凭据,不是普通登录密码。
|
||||
3. Scert 自动使用 `notification+system@邮件主域名` 作为发件地址,显示名称固定为“系统通知”。不要从 SMTP 主机名或 Scert 管理域名猜测邮件主域名,应原样使用 Smail 给出的用户名。
|
||||
4. 通知收件人单独配置;管理员邮箱不再作为 SMTP 默认发件人。未填写通知收件人时仍沿用管理员邮箱作为收件人。
|
||||
|
||||
## 兼容与安全边界
|
||||
|
||||
- Smail 系统通知身份优先于历史自定义发件人配置。普通 SMTP 配置仍保留兼容能力;未设置发件地址时只能使用完整邮箱形式的 SMTP 账号,不能回退为管理员邮箱。
|
||||
- 已有 HTTP 通知配置保留兼容,不在初始化向导恢复 HTTP 入口。
|
||||
- 密码重置安全邮件仍只发往绑定邮箱;隐藏说明条不改变受保护的重置流程。
|
||||
- 凭据仍进入加密存储,不写入正式配置或文档。保留 TLS 校验,不为发信测试关闭认证或证书校验。
|
||||
- 本说明描述接入规则,不代表真实 Smail 认证、投递及收件箱验收已完成;协议单元测试不能代替生产实发。
|
||||
@ -1108,8 +1108,8 @@ button:disabled {
|
||||
|
||||
.notification-service-row,
|
||||
.notification-api-row,
|
||||
.notification-sender-row {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
.notification-recipient-row {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.notification-auth-row {
|
||||
|
||||
@ -84,8 +84,6 @@ export function useSetupWizard(props, emit) {
|
||||
email_include_deployment_details: true,
|
||||
email_include_certificate_serial: true,
|
||||
email_include_management_url: true,
|
||||
email_sender_name: "Scert",
|
||||
email_sender_email: "",
|
||||
email_timeout_secs: 10,
|
||||
email_retry_count: 3,
|
||||
email_retry_intervals_secs: "30,120,600",
|
||||
@ -687,8 +685,6 @@ export function useSetupWizard(props, emit) {
|
||||
email_include_certificate_serial:
|
||||
form.value.email_include_certificate_serial,
|
||||
email_include_management_url: form.value.email_include_management_url,
|
||||
email_sender_name: form.value.email_sender_name,
|
||||
email_sender_email: form.value.email_sender_email,
|
||||
email_timeout_secs: Number(form.value.email_timeout_secs),
|
||||
email_retry_count: Number(form.value.email_retry_count),
|
||||
email_retry_intervals_secs: retryIntervals(),
|
||||
|
||||
@ -365,12 +365,17 @@ const {
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>SMTP 用户名</span>
|
||||
<span>
|
||||
SMTP 用户名
|
||||
<HelpTip>
|
||||
使用 Smail 系统通知页提供的 notify:账号@邮件域名和专用发信凭据,自动使用系统通知发件人,不使用管理员邮箱。
|
||||
</HelpTip>
|
||||
</span>
|
||||
<input
|
||||
id="email-smtp-username"
|
||||
name="email_smtp_username"
|
||||
v-model.trim="form.email_smtp_username"
|
||||
placeholder="发件邮箱或账号"
|
||||
placeholder="notify:账号@邮件域名"
|
||||
autocomplete="username"
|
||||
/>
|
||||
</label>
|
||||
@ -381,7 +386,7 @@ const {
|
||||
name="email_smtp_password"
|
||||
v-model="form.email_smtp_password"
|
||||
type="password"
|
||||
placeholder="请输入 SMTP 密码或授权码"
|
||||
placeholder="请输入 Smail 系统通知发信凭据"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</label>
|
||||
@ -409,24 +414,7 @@ const {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="notification-row notification-sender-row">
|
||||
<label>
|
||||
<span>发件人名称</span>
|
||||
<input
|
||||
id="email-sender-name"
|
||||
v-model.trim="form.email_sender_name"
|
||||
placeholder="Scert"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>发件人地址(可选)</span>
|
||||
<input
|
||||
id="email-sender-email"
|
||||
v-model.trim="form.email_sender_email"
|
||||
type="email"
|
||||
placeholder="留空使用管理员邮箱"
|
||||
/>
|
||||
</label>
|
||||
<div class="notification-row notification-recipient-row">
|
||||
<label>
|
||||
<span>
|
||||
通知收件人
|
||||
@ -444,12 +432,6 @@ const {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="provider-description reset-preview">
|
||||
<strong>密码重置地址</strong>
|
||||
<span>{{ resetURLPreview }}</span>
|
||||
<span>只有手动启动 Scert-admin 后,此地址才会临时可用。</span>
|
||||
</div>
|
||||
|
||||
<div v-if="form.email_smtp_enabled" class="notification-advanced-panel">
|
||||
<button
|
||||
class="notification-advanced-toggle"
|
||||
|
||||
@ -112,7 +112,9 @@ for (const driver of ["sqlite", "mysql", "postgres"]) {
|
||||
email_http_auth_mode: "bearer",
|
||||
email_smtp_enabled: true,
|
||||
email_smtp_host: "smtp.example.test",
|
||||
email_smtp_username: "sender@example.test",
|
||||
email_smtp_username: "notify:sender@example.test",
|
||||
email_sender_name: "Ignored sender",
|
||||
email_sender_email: "ignored@example.test",
|
||||
email_smtp_password: "smtp-test-only",
|
||||
email_retry_count: 0,
|
||||
recipients: "first@example.test,second@example.test",
|
||||
@ -134,13 +136,13 @@ for (const driver of ["sqlite", "mysql", "postgres"]) {
|
||||
assert.equal(submitted[0].notification.http_enabled, false);
|
||||
assert.equal(submitted[0].notification.smtp_enabled, true);
|
||||
assert.equal(submitted[0].notification.smtp_host, "smtp.example.test");
|
||||
assert.equal(submitted[0].notification.smtp_username, "sender@example.test");
|
||||
assert.equal(submitted[0].notification.smtp_username, "notify:sender@example.test");
|
||||
assert.equal(submitted[0].notification.smtp_password, "smtp-test-only");
|
||||
assert.equal(submitted[0].notification.smtp_port, 587);
|
||||
assert.equal(submitted[0].notification.smtp_mode, "starttls");
|
||||
assert.equal(submitted[0].notification.retry_count, 0);
|
||||
assert.deepEqual(submitted[0].notification.recipients, ["first@example.test", "second@example.test"]);
|
||||
for (const key of ["endpoint", "http_auth_mode", "http_credentials", "http_path", "http_headers"]) {
|
||||
for (const key of ["endpoint", "http_auth_mode", "http_credentials", "http_path", "http_headers", "sender_name", "sender_email"]) {
|
||||
assert.equal(Object.hasOwn(submitted[0].notification, key), false);
|
||||
}
|
||||
assert.equal(submitted[0].certificate_root, "/srv/scert/ssl");
|
||||
|
||||
@ -33,23 +33,25 @@ test("certificate root remains editable during bootstrap", () => {
|
||||
&& prop.exp?.content === "form.certificate_root"));
|
||||
});
|
||||
|
||||
test("notification step exposes SMTP only and keeps sender fields in its panel", () => {
|
||||
test("notification step uses the system sender and keeps only recipient settings", () => {
|
||||
const root = template("./SetupWizard.vue");
|
||||
const step = find(root, hasClass("setup-notification-step"));
|
||||
const panel = find(step, hasClass("smtp-basic-panel"));
|
||||
const id = (value) => (node) => node.props?.some((prop) =>
|
||||
prop.name === "id" && prop.value?.content === value);
|
||||
assert.ok(find(step, id("email-smtp-enabled")));
|
||||
for (const value of ["email-http-enabled", "email-endpoint", "email-http-path", "email-http-auth-mode"]) {
|
||||
for (const value of ["email-http-enabled", "email-endpoint", "email-http-path", "email-http-auth-mode", "email-sender-name", "email-sender-email"]) {
|
||||
assert.equal(find(step, id(value)), undefined);
|
||||
}
|
||||
for (const value of ["email-smtp-host", "email-smtp-password", "email-sender-name", "email-sender-email", "recipients"]) {
|
||||
for (const value of ["email-smtp-host", "email-smtp-password", "recipients"]) {
|
||||
assert.ok(find(panel, id(value)), value);
|
||||
}
|
||||
const advanced = find(step, hasClass("notification-advanced-panel"));
|
||||
assert.equal(advanced.props.find((prop) => prop.name === "if").exp.content, "form.email_smtp_enabled");
|
||||
assert.ok(find(advanced, hasClass("notification-delivery-row")));
|
||||
assert.ok(find(advanced, hasClass("email-option-grid")));
|
||||
assert.equal(find(step, hasClass("reset-preview")), undefined);
|
||||
assert.ok(find(panel, hasClass("notification-recipient-row")));
|
||||
});
|
||||
|
||||
test("notification checkboxes stay compact and centered with their labels", () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user