Files
wehub-resource-sync 75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:11:07 +08:00

162 lines
4.4 KiB
Go

package email
import (
"bufio"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math/big"
"net"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"databasus-backend/internal/util/logger"
"databasus-backend/internal/util/testing/containers"
"databasus-backend/internal/util/testing/mailpit"
)
func Test_EmailSMTPSenderSendEmail_WhenSmtpServerAccepts_DeliversMessageToRecipient(t *testing.T) {
mailpitEndpoint := containers.StartMailpit(t)
mailpitClient := mailpit.NewClient(
fmt.Sprintf("%s:%d", mailpitEndpoint.HTTP.Host, mailpitEndpoint.HTTP.Port),
)
sender := &EmailSMTPSender{
logger: logger.GetLogger(),
smtpHost: mailpitEndpoint.SMTP.Host,
smtpPort: mailpitEndpoint.SMTP.Port,
smtpFrom: "sender@databasus.local",
isConfigured: true,
}
err := sender.SendEmail("recipient@databasus.local", "Password Reset Code", "<b>123456</b>")
require.NoError(t, err)
var delivered []mailpit.Message
require.Eventually(t, func() bool {
messages, fetchErr := mailpitClient.FetchMessages()
if fetchErr != nil {
return false
}
delivered = messages
return len(messages) == 1
}, 5*time.Second, 100*time.Millisecond, "Mailpit should receive exactly one message")
assert.Equal(t, "Password Reset Code", delivered[0].Subject)
require.Len(t, delivered[0].To, 1)
assert.Equal(t, "recipient@databasus.local", delivered[0].To[0].Address)
}
func Test_EmailSMTPSenderImplicitTLS_WithUntrustedCert_FailsUnlessSkipVerify(t *testing.T) {
host, port := startImplicitTLSGreetingServer(t, newSelfSignedTLSConfig(t))
t.Run("verification on rejects the untrusted certificate", func(t *testing.T) {
sender := &EmailSMTPSender{logger: logger.GetLogger(), smtpHost: host, smtpPort: port}
_, _, dialErr := sender.createImplicitTLSClient()
require.Error(t, dialErr)
assert.Contains(t, dialErr.Error(), "certificate")
})
t.Run("skip verify accepts the untrusted certificate", func(t *testing.T) {
sender := &EmailSMTPSender{
logger: logger.GetLogger(),
smtpHost: host,
smtpPort: port,
isInsecureSkipVerify: true,
}
client, cleanup, dialErr := sender.createImplicitTLSClient()
require.NoError(t, dialErr)
defer cleanup()
require.NotNil(t, client)
})
}
// newSelfSignedTLSConfig builds a TLS config whose certificate is not signed by any trusted
// root, so a verifying client rejects it and a skip-verify client accepts it.
func newSelfSignedTLSConfig(t *testing.T) *tls.Config {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "127.0.0.1"},
NotBefore: time.Now().UTC().Add(-time.Hour),
NotAfter: time.Now().UTC().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.IPv6loopback},
IsCA: true,
}
der, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
require.NoError(t, err)
return &tls.Config{
Certificates: []tls.Certificate{{Certificate: [][]byte{der}, PrivateKey: key}},
}
}
// startImplicitTLSGreetingServer accepts implicit-TLS connections and speaks just enough SMTP
// (greeting, EHLO, QUIT) for createImplicitTLSClient to connect and disconnect cleanly.
func startImplicitTLSGreetingServer(t *testing.T, tlsConfig *tls.Config) (host string, port int) {
t.Helper()
listener, err := tls.Listen("tcp", "127.0.0.1:0", tlsConfig)
require.NoError(t, err)
t.Cleanup(func() { _ = listener.Close() })
go func() {
for {
conn, acceptErr := listener.Accept()
if acceptErr != nil {
return
}
go serveMinimalSMTP(conn)
}
}()
addr := listener.Addr().(*net.TCPAddr)
return addr.IP.String(), addr.Port
}
func serveMinimalSMTP(conn net.Conn) {
defer func() { _ = conn.Close() }()
if _, err := conn.Write([]byte("220 test ESMTP\r\n")); err != nil {
return
}
reader := bufio.NewReader(conn)
for {
line, err := reader.ReadString('\n')
if err != nil {
return
}
if strings.HasPrefix(strings.ToUpper(line), "QUIT") {
_, _ = conn.Write([]byte("221 Bye\r\n"))
return
}
_, _ = conn.Write([]byte("250 OK\r\n"))
}
}