Add queued email notifications for collaboration events

This commit is contained in:
2026-07-22 08:51:42 -04:00
parent b6d2ded141
commit 09f51d1ef4
20 changed files with 1507 additions and 102 deletions

View File

@@ -0,0 +1,113 @@
package email
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestPostmarkSender_Send(t *testing.T) {
var gotBody postmarkRequest
var gotToken string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotToken = r.Header.Get("X-Postmark-Server-Token")
body, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(body, &gotBody)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"ErrorCode":0,"Message":"OK","To":"alice@example.com"}`))
}))
defer srv.Close()
sender := NewPostmarkSender("test-token", "noreply@example.com", srv.URL, testLogger(t))
if err := sender.Send(context.Background(), []string{"alice@example.com"}, "Hello", "World"); err != nil {
t.Fatalf("Send: %v", err)
}
if gotToken != "test-token" {
t.Errorf("token = %q, want test-token", gotToken)
}
if gotBody.From != "noreply@example.com" {
t.Errorf("from = %q", gotBody.From)
}
if gotBody.To != "alice@example.com" {
t.Errorf("to = %q", gotBody.To)
}
if gotBody.Subject != "Hello" {
t.Errorf("subject = %q", gotBody.Subject)
}
if gotBody.TextBody != "World" {
t.Errorf("body = %q", gotBody.TextBody)
}
}
func TestPostmarkSender_ErrorCode(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"ErrorCode":406,"Message":"Inactive recipient"}`))
}))
defer srv.Close()
sender := NewPostmarkSender("tok", "noreply@example.com", srv.URL, testLogger(t))
err := sender.Send(context.Background(), []string{"x@x.com"}, "s", "b")
if err == nil {
t.Fatal("expected error, got nil")
}
if !strings.Contains(err.Error(), "406") {
t.Errorf("error should mention code 406: %v", err)
}
}
func TestPostmarkSender_ServerError_Temporary(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("boom"))
}))
defer srv.Close()
sender := NewPostmarkSender("tok", "noreply@example.com", srv.URL, testLogger(t))
err := sender.Send(context.Background(), []string{"x@x.com"}, "s", "b")
if err == nil {
t.Fatal("expected error")
}
var temp *TemporaryError
if !errors.As(err, &temp) {
t.Errorf("expected TemporaryError, got %T: %v", err, err)
}
}
func TestPostmarkSender_Validation(t *testing.T) {
sender := NewPostmarkSender("", "noreply@example.com", "", testLogger(t))
if err := sender.Send(context.Background(), []string{"x@x.com"}, "s", "b"); err == nil {
t.Fatal("expected error for missing token")
}
sender = NewPostmarkSender("tok", "", "", testLogger(t))
if err := sender.Send(context.Background(), []string{"x@x.com"}, "s", "b"); err == nil {
t.Fatal("expected error for missing from")
}
sender = NewPostmarkSender("tok", "noreply@example.com", "", testLogger(t))
if err := sender.Send(context.Background(), nil, "s", "b"); err == nil {
t.Fatal("expected error for empty recipients")
}
}
func TestPostmarkSender_MultipleRecipients(t *testing.T) {
var gotBody postmarkRequest
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(body, &gotBody)
_, _ = w.Write([]byte(`{"ErrorCode":0,"Message":"OK"}`))
}))
defer srv.Close()
sender := NewPostmarkSender("tok", "noreply@example.com", srv.URL, testLogger(t))
if err := sender.Send(context.Background(), []string{"a@x.com", "b@x.com"}, "s", "b"); err != nil {
t.Fatalf("Send: %v", err)
}
if !strings.Contains(gotBody.To, "a@x.com") || !strings.Contains(gotBody.To, "b@x.com") {
t.Errorf("to = %q, expected both recipients", gotBody.To)
}
}