diff --git a/backend/internal/notification/smtp_test.go b/backend/internal/notification/smtp_test.go index 11e7b12..36d3aa0 100644 --- a/backend/internal/notification/smtp_test.go +++ b/backend/internal/notification/smtp_test.go @@ -309,7 +309,7 @@ func TestSmailSystemSenderUsesSameEnvelopeAndHeader(t *testing.T) { t.Fatal("邮件头没有使用固定系统通知身份,或泄漏旧管理员发件地址") } if security && strings.Contains(message, "receiver@example.com") { - t.Fatal("验证码不得发送给普通通知收件人") + t.Fatal("验证码不得发送给普通通知收件äº?) } }) } @@ -340,7 +340,7 @@ func TestSmailSystemNotificationUsesSMTPOnly(t *testing.T) { server.mu.Lock() defer server.mu.Unlock() if len(server.messages) != 1 { - t.Fatalf("SMTP 系统通知数量错误:%d", len(server.messages)) + t.Fatalf("SMTP 系统通知数量错误ï¼?d", len(server.messages)) } } @@ -449,7 +449,38 @@ func TestBuildSMTPMessageIncludesStyledHTMLAlternative(t *testing.T) { "
HTML 通知
", } { if !strings.Contains(message, value) { - t.Fatalf("SMTP multipart 邮件缺少 %q:%s", value, message) + t.Fatalf("SMTP multipart 邮件缺少 %qï¼?s", value, message) } } } + +func TestSendTestMailImmediateFailureOnAuthError(t *testing.T) { + server := newSMTPTestServer(t, false, true) + server.rejectAuth = true + host, port := server.address() + cfg := smtpTestConfig(host, port, "starttls") + cfg.RetryCount = 3 + cfg.RetryIntervalsSecs = []int{30, 120, 600} + service := New(cfg, credentialReaderStub{values: map[string]string{ + "username": "user", + "password": "wrong-password", + }}) + service.smtpTLSConfig = server.clientTLSConfig + + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err := service.SendTestMail(ctx, "target@example.com") + elapsed := time.Since(start) + + if err == nil { + t.Fatalf("expected error on rejected auth, got nil") + } + if !strings.Contains(err.Error(), "认证失败") && !strings.Contains(err.Error(), "Authentication credentials invalid") { + t.Fatalf("expected auth failure error, got: %v", err) + } + if elapsed > 2*time.Second { + t.Fatalf("SendTestMail took %v, expected immediate return without retry backoff sleep", elapsed) + } +} diff --git a/backend/internal/notification/test_mail.go b/backend/internal/notification/test_mail.go index 86a374f..03f75b3 100644 --- a/backend/internal/notification/test_mail.go +++ b/backend/internal/notification/test_mail.go @@ -1,4 +1,4 @@ -package notification +package notification import ( "context" @@ -9,6 +9,18 @@ import ( "time" ) +// sendSMTPDirect 单次直接发送测试邮件,避免在交互测试时进入多轮重试退避导致超时掩盖真实错误 +func (s *Service) sendSMTPDirect(ctx context.Context, payload mailPayload) error { + if strings.TrimSpace(s.config.SMTPHost) == "" { + return errors.New("SMTP 主机不能为空") + } + credentials, err := s.loadSMTPCredentials(ctx) + if err != nil { + return err + } + return s.sendSMTPOnce(ctx, payload, credentials) +} + // SendTestMail 发送测试邮件,验证发信通道配置与网络连通性。 func (s *Service) SendTestMail(ctx context.Context, targetRecipient string) error { if s == nil || (!s.config.HTTPEnabled && !s.config.SMTPEnabled) { @@ -76,7 +88,7 @@ func (s *Service) SendTestMail(ctx context.Context, targetRecipient string) erro if !s.config.SMTPEnabled { return errors.New("Smail 系统通知账号必须启用 SMTP 通道") } - if err := s.sendSMTPWithRetry(ctx, payload); err != nil { + if err := s.sendSMTPDirect(ctx, payload); err != nil { return fmt.Errorf("SMTP 测试邮件发送失败:%w", err) } return nil @@ -87,10 +99,10 @@ func (s *Service) SendTestMail(ctx context.Context, targetRecipient string) erro } } if s.config.SMTPEnabled { - if err := s.sendSMTPWithRetry(ctx, payload); err != nil { + if err := s.sendSMTPDirect(ctx, payload); err != nil { return fmt.Errorf("SMTP 测试邮件发送失败:%w", err) } return nil } return errors.New("测试邮件发送失败,请检查通道设置及网络连通性") -} +} \ No newline at end of file