readysite / pkg / application / emailers / emailers_test.go
6.0 KB
emailers_test.go
package emailers

import (
	"embed"
	"encoding/json"
	"html/template"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

//go:embed all:emails
var testEmails embed.FS

func TestNewLogger(t *testing.T) {
	l := NewLogger(testEmails, nil)

	if l == nil {
		t.Fatal("expected logger to be created")
	}
}

func TestNewLogger_WithFuncs(t *testing.T) {
	funcs := template.FuncMap{
		"upper": strings.ToUpper,
	}

	l := NewLogger(testEmails, funcs)

	if l == nil {
		t.Fatal("expected logger to be created with custom funcs")
	}
}

func TestLogger_Send(t *testing.T) {
	l := NewLogger(testEmails, nil)

	err := l.Send(
		"user@example.com",
		"Welcome!",
		"welcome.html",
		map[string]any{"name": "Alice"},
	)

	if err != nil {
		t.Fatalf("expected no error, got: %v", err)
	}
}

func TestLogger_Send_MultipleTemplates(t *testing.T) {
	l := NewLogger(testEmails, nil)

	err := l.Send(
		"user@example.com",
		"Reset Password",
		"reset.html",
		map[string]any{"name": "Bob", "link": "https://example.com/reset/abc123"},
	)

	if err != nil {
		t.Fatalf("expected no error, got: %v", err)
	}
}

func TestLogger_Send_InvalidTemplate(t *testing.T) {
	l := NewLogger(testEmails, nil)

	err := l.Send(
		"user@example.com",
		"Test",
		"nonexistent.html",
		nil,
	)

	if err == nil {
		t.Error("expected error for nonexistent template")
	}
}

func TestNewResend(t *testing.T) {
	r := NewResend(testEmails, "re_test_key", "noreply@example.com", nil)

	if r == nil {
		t.Fatal("expected resend emailer to be created")
	}
	if r.apiKey != "re_test_key" {
		t.Errorf("expected apiKey 're_test_key', got %q", r.apiKey)
	}
	if r.from != "noreply@example.com" {
		t.Errorf("expected from 'noreply@example.com', got %q", r.from)
	}
}

func TestNewResend_WithFuncs(t *testing.T) {
	funcs := template.FuncMap{
		"upper": strings.ToUpper,
	}

	r := NewResend(testEmails, "re_test_key", "noreply@example.com", funcs)

	if r == nil {
		t.Fatal("expected resend emailer to be created with custom funcs")
	}
}

func TestResend_Send(t *testing.T) {
	// Create a mock Resend API server
	var receivedBody map[string]any
	var receivedAuth string
	var receivedContentType string

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		receivedAuth = r.Header.Get("Authorization")
		receivedContentType = r.Header.Get("Content-Type")

		body, err := io.ReadAll(r.Body)
		if err != nil {
			t.Errorf("failed to read request body: %v", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		if err := json.Unmarshal(body, &receivedBody); err != nil {
			t.Errorf("failed to unmarshal request body: %v", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"id": "test-email-id"}`))
	}))
	defer server.Close()

	// Override http.DefaultClient to redirect requests to our test server
	originalTransport := http.DefaultClient.Transport
	http.DefaultClient.Transport = &testTransport{
		targetURL: server.URL,
	}
	defer func() {
		http.DefaultClient.Transport = originalTransport
	}()

	r := NewResend(testEmails, "re_test_api_key", "sender@example.com", nil)

	err := r.Send(
		"recipient@example.com",
		"Welcome!",
		"welcome.html",
		map[string]any{"name": "Alice"},
	)

	if err != nil {
		t.Fatalf("expected no error, got: %v", err)
	}

	// Verify authorization header
	if receivedAuth != "Bearer re_test_api_key" {
		t.Errorf("expected Authorization 'Bearer re_test_api_key', got %q", receivedAuth)
	}

	// Verify content type
	if receivedContentType != "application/json" {
		t.Errorf("expected Content-Type 'application/json', got %q", receivedContentType)
	}

	// Verify payload
	if receivedBody["from"] != "sender@example.com" {
		t.Errorf("expected from 'sender@example.com', got %v", receivedBody["from"])
	}
	if receivedBody["subject"] != "Welcome!" {
		t.Errorf("expected subject 'Welcome!', got %v", receivedBody["subject"])
	}

	// Verify 'to' is an array with recipient
	toSlice, ok := receivedBody["to"].([]any)
	if !ok {
		t.Fatalf("expected 'to' to be an array, got %T", receivedBody["to"])
	}
	if len(toSlice) != 1 || toSlice[0] != "recipient@example.com" {
		t.Errorf("expected to ['recipient@example.com'], got %v", toSlice)
	}

	// Verify HTML body was rendered
	htmlBody, ok := receivedBody["html"].(string)
	if !ok {
		t.Fatal("expected 'html' field in payload")
	}
	if !strings.Contains(htmlBody, "Alice") {
		t.Error("expected rendered HTML to contain 'Alice'")
	}
	if !strings.Contains(htmlBody, "Welcome") {
		t.Error("expected rendered HTML to contain 'Welcome'")
	}
}

func TestResend_Send_APIError(t *testing.T) {
	// Create a mock server that returns an error
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte(`{"message": "Invalid API key"}`))
	}))
	defer server.Close()

	// Override http.DefaultClient transport
	originalTransport := http.DefaultClient.Transport
	http.DefaultClient.Transport = &testTransport{
		targetURL: server.URL,
	}
	defer func() {
		http.DefaultClient.Transport = originalTransport
	}()

	r := NewResend(testEmails, "invalid_key", "sender@example.com", nil)

	err := r.Send(
		"recipient@example.com",
		"Test",
		"welcome.html",
		map[string]any{"name": "Bob"},
	)

	if err == nil {
		t.Error("expected error for API error response")
	}
	if !strings.Contains(err.Error(), "401") {
		t.Errorf("expected error to mention status code, got: %v", err)
	}
}

func TestResend_Send_InvalidTemplate(t *testing.T) {
	r := NewResend(testEmails, "re_test_key", "sender@example.com", nil)

	err := r.Send(
		"recipient@example.com",
		"Test",
		"nonexistent.html",
		nil,
	)

	if err == nil {
		t.Error("expected error for nonexistent template")
	}
}

// testTransport redirects all HTTP requests to the test server URL.
type testTransport struct {
	targetURL string
}

func (t *testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	// Redirect the request to our test server
	req.URL.Scheme = "http"
	req.URL.Host = strings.TrimPrefix(t.targetURL, "http://")
	return http.DefaultTransport.RoundTrip(req)
}
← Back