Add Cloudflare email and collaboration workflows

This commit is contained in:
2026-07-22 12:59:16 -04:00
parent 09f51d1ef4
commit 4454e918c2
35 changed files with 1596 additions and 487 deletions

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"strings"
"sync/atomic"
"testing"
"time"
@@ -152,9 +153,9 @@ func TestQueue_RetryThenSucceed(t *testing.T) {
go q.Run(ctx)
if err := waitFor(ctx, func() bool {
return sender.successCount >= 1
return sender.successCount.Load() >= 1
}); err != nil {
t.Fatalf("email never succeeded: successCount=%d attempts=%d: %v", sender.successCount, sender.attempts, err)
t.Fatalf("email never succeeded: successCount=%d attempts=%d: %v", sender.successCount.Load(), sender.attempts.Load(), err)
}
// Verify the row is marked sent.
@@ -196,6 +197,32 @@ func TestQueue_PermanentFailure(t *testing.T) {
}
}
func TestQueue_ProviderRejectionFailsImmediately(t *testing.T) {
db := newTestDB(t)
sender := &recordingSender{err: &PermanentError{Cause: errors.New("sender domain not verified")}}
q := NewQueue(db, sender, testLogger(t))
id, err := q.Enqueue(context.Background(), "notif:test-1", "alice@example.com", "Fail", "Body")
if err != nil {
t.Fatalf("Enqueue: %v", err)
}
rows, err := q.claimBatch(context.Background(), 1)
if err != nil || len(rows) != 1 {
t.Fatalf("claimBatch = %#v, %v", rows, err)
}
if err := q.deliver(context.Background(), rows[0]); err != nil {
t.Fatalf("deliver: %v", err)
}
var status string
var attempts int
if err := db.QueryRow(`SELECT status, attempts FROM email_queue WHERE id = ?`, id).Scan(&status, &attempts); err != nil {
t.Fatalf("query row: %v", err)
}
if status != "failed" || attempts != 1 {
t.Fatalf("status=%q attempts=%d, want failed after one attempt", status, attempts)
}
}
func TestQueue_NilSenderLeavesPending(t *testing.T) {
db := newTestDB(t)
q := NewQueue(db, nil, testLogger(t))
@@ -218,17 +245,17 @@ func TestQueue_NilSenderLeavesPending(t *testing.T) {
// flakySender fails the first N attempts then succeeds.
type flakySender struct {
failTimes int
attempts int
successCount int
failTimes int32
attempts atomic.Int32
successCount atomic.Int32
}
func (f *flakySender) Send(ctx context.Context, to []string, subject, body string) error {
f.attempts++
if f.attempts <= f.failTimes {
attempt := f.attempts.Add(1)
if attempt <= f.failTimes {
return errors.New("transient failure")
}
f.successCount++
f.successCount.Add(1)
return nil
}