- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
179 lines
4.2 KiB
Go
179 lines
4.2 KiB
Go
package postcard
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/testcontainers/testcontainers-go"
|
|
"github.com/testcontainers/testcontainers-go/wait"
|
|
)
|
|
|
|
const mailpitImage = "axllent/mailpit:v1.31.1"
|
|
|
|
type mailpitList struct {
|
|
Total int `json:"total"`
|
|
Count int `json:"count"`
|
|
Messages []struct {
|
|
ID string `json:"ID"`
|
|
Subject string `json:"Subject"`
|
|
} `json:"messages"`
|
|
}
|
|
|
|
type mailpitMessage struct {
|
|
ID string `json:"ID"`
|
|
Subject string `json:"Subject"`
|
|
HTML string `json:"HTML"`
|
|
Text string `json:"Text"`
|
|
To []struct {
|
|
Address string `json:"Address"`
|
|
} `json:"To"`
|
|
}
|
|
|
|
func TestSMTPMailpit(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("requires testcontainers mailpit")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute)
|
|
defer cancel()
|
|
|
|
ctr, err := testcontainers.Run(ctx, mailpitImage,
|
|
testcontainers.WithExposedPorts("1025/tcp", "8025/tcp"),
|
|
testcontainers.WithWaitStrategy(
|
|
wait.ForListeningPort("1025/tcp"),
|
|
wait.ForHTTP("/api/v1/info").WithPort("8025/tcp"),
|
|
),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("mailpit: testcontainers: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = testcontainers.TerminateContainer(ctr)
|
|
})
|
|
|
|
host, err := ctr.Host(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
smtpPort, err := ctr.MappedPort(ctx, "1025/tcp")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
httpPort, err := ctr.MappedPort(ctx, "8025/tcp")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
portNum, err := strconv.Atoi(smtpPort.Port())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
api := "http://" + host + ":" + httpPort.Port()
|
|
|
|
driver, err := NewSMTPDriver(SMTPConfig{
|
|
Host: host,
|
|
Port: portNum,
|
|
TLS: "none",
|
|
Timeout: 10 * time.Second,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mail := NewMailer(smokeCatalog(t), driver, Options{From: "from@example.test"})
|
|
if err := mail.Send(ctx, Message{
|
|
Template: "golem15.hello::mail.hello",
|
|
To: []string{"ada@example.test"},
|
|
Vars: map[string]any{"Name": "Ada", "URL": "https://example.test"},
|
|
}); err != nil {
|
|
t.Fatalf("Send: %v", err)
|
|
}
|
|
|
|
got := waitMailpitMessage(t, ctx, api)
|
|
if len(got.To) == 0 || got.To[0].Address != "ada@example.test" {
|
|
t.Fatalf("mailpit To = %+v", got.To)
|
|
}
|
|
if got.Subject != "Witaj Ada" {
|
|
t.Fatalf("mailpit subject = %q", got.Subject)
|
|
}
|
|
if !strings.Contains(got.HTML, "<strong>Ada</strong>") {
|
|
t.Fatalf("mailpit HTML = %q", got.HTML)
|
|
}
|
|
if !strings.Contains(got.Text, "Witaj **Ada**.") {
|
|
t.Fatalf("mailpit text = %q", got.Text)
|
|
}
|
|
}
|
|
|
|
func waitMailpitMessage(t *testing.T, ctx context.Context, api string) mailpitMessage {
|
|
t.Helper()
|
|
deadline := time.Now().Add(15 * time.Second)
|
|
var last string
|
|
for time.Now().Before(deadline) {
|
|
if err := ctx.Err(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
list, err := fetchMailpitList(ctx, api)
|
|
if err == nil && list.Total > 0 && len(list.Messages) > 0 {
|
|
msg, err := fetchMailpitMessage(ctx, api, list.Messages[0].ID)
|
|
if err == nil && msg.ID != "" {
|
|
return msg
|
|
}
|
|
last = fmt.Sprintf("message: %v", err)
|
|
} else if err != nil {
|
|
last = err.Error()
|
|
}
|
|
time.Sleep(150 * time.Millisecond)
|
|
}
|
|
t.Fatalf("mailpit did not receive a message: %s", last)
|
|
return mailpitMessage{}
|
|
}
|
|
|
|
func fetchMailpitList(ctx context.Context, api string) (mailpitList, error) {
|
|
var out mailpitList
|
|
body, err := getMailpit(ctx, api+"/api/v1/messages")
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
if err := json.Unmarshal(body, &out); err != nil {
|
|
return out, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func fetchMailpitMessage(ctx context.Context, api, id string) (mailpitMessage, error) {
|
|
var out mailpitMessage
|
|
body, err := getMailpit(ctx, api+"/api/v1/message/"+id)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
if err := json.Unmarshal(body, &out); err != nil {
|
|
return out, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func getMailpit(ctx context.Context, url string) ([]byte, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
|
}
|
|
return body, nil
|
|
}
|