feat: use postgres

This commit is contained in:
Ilia Denisov
2026-04-26 20:34:39 +02:00
committed by GitHub
parent 48b0056b49
commit fe829285a6
365 changed files with 29223 additions and 24049 deletions
@@ -0,0 +1,586 @@
package mailstore
import (
"context"
"errors"
"reflect"
"testing"
"time"
"galaxy/mail/internal/domain/attempt"
"galaxy/mail/internal/domain/common"
deliverydomain "galaxy/mail/internal/domain/delivery"
"galaxy/mail/internal/domain/idempotency"
"galaxy/mail/internal/domain/malformedcommand"
"galaxy/mail/internal/service/acceptauthdelivery"
"galaxy/mail/internal/service/acceptgenericdelivery"
"galaxy/mail/internal/service/executeattempt"
"galaxy/mail/internal/service/listdeliveries"
"galaxy/mail/internal/service/renderdelivery"
"galaxy/mail/internal/service/resenddelivery"
)
const (
fixtureDeliveryID common.DeliveryID = "delivery-001"
fixtureKey common.IdempotencyKey = "key-001"
fixtureFingerprint = "sha256:abcdef"
fixtureRecipient common.Email = "user@example.com"
)
func fixtureNow() time.Time {
return time.Date(2026, time.April, 26, 12, 0, 0, 0, time.UTC)
}
func fixtureAuthDelivery(id common.DeliveryID, key common.IdempotencyKey, status deliverydomain.Status) deliverydomain.Delivery {
now := fixtureNow()
record := deliverydomain.Delivery{
DeliveryID: id,
Source: deliverydomain.SourceAuthSession,
PayloadMode: deliverydomain.PayloadModeRendered,
Envelope: deliverydomain.Envelope{To: []common.Email{fixtureRecipient}},
Content: deliverydomain.Content{Subject: "Login code", TextBody: "Your code is 123456"},
IdempotencyKey: key,
Status: status,
AttemptCount: 1,
CreatedAt: now,
UpdatedAt: now,
}
if status == deliverydomain.StatusSuppressed {
record.AttemptCount = 0
record.SuppressedAt = &now
}
return record
}
func fixtureGenericDelivery(id common.DeliveryID, key common.IdempotencyKey) deliverydomain.Delivery {
now := fixtureNow()
return deliverydomain.Delivery{
DeliveryID: id,
Source: deliverydomain.SourceNotification,
PayloadMode: deliverydomain.PayloadModeTemplate,
TemplateID: common.TemplateID("generic-news"),
Locale: common.Locale("en"),
TemplateVariables: map[string]any{"name": "Alice"},
Envelope: deliverydomain.Envelope{To: []common.Email{fixtureRecipient}, ReplyTo: []common.Email{"reply@example.com"}},
Attachments: []common.AttachmentMetadata{{Filename: "f.txt", ContentType: "text/plain", SizeBytes: 5}},
IdempotencyKey: key,
Status: deliverydomain.StatusQueued,
AttemptCount: 1,
CreatedAt: now,
UpdatedAt: now,
}
}
func fixtureFirstAttempt(id common.DeliveryID, attemptNo int) attempt.Attempt {
now := fixtureNow().Add(time.Minute)
return attempt.Attempt{
DeliveryID: id,
AttemptNo: attemptNo,
Status: attempt.StatusScheduled,
ScheduledFor: now,
}
}
func fixtureIdempotency(source deliverydomain.Source, id common.DeliveryID, key common.IdempotencyKey) idempotency.Record {
now := fixtureNow()
return idempotency.Record{
Source: source,
IdempotencyKey: key,
DeliveryID: id,
RequestFingerprint: fixtureFingerprint,
CreatedAt: now,
ExpiresAt: now.Add(7 * 24 * time.Hour),
}
}
func TestPing(t *testing.T) {
store := newTestStore(t)
if err := store.Ping(context.Background()); err != nil {
t.Fatalf("ping: %v", err)
}
}
func TestAuthAcceptanceCreate_GetIdempotency_GetDelivery(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
delivery := fixtureAuthDelivery(fixtureDeliveryID, fixtureKey, deliverydomain.StatusQueued)
first := fixtureFirstAttempt(delivery.DeliveryID, 1)
idem := fixtureIdempotency(delivery.Source, delivery.DeliveryID, delivery.IdempotencyKey)
if err := store.CreateAcceptance(ctx, acceptauthdelivery.CreateAcceptanceInput{
Delivery: delivery,
FirstAttempt: &first,
Idempotency: idem,
}); err != nil {
t.Fatalf("create acceptance: %v", err)
}
got, ok, err := store.GetIdempotency(ctx, delivery.Source, delivery.IdempotencyKey)
if err != nil {
t.Fatalf("get idempotency: %v", err)
}
if !ok {
t.Fatal("idempotency not found")
}
if got.DeliveryID != delivery.DeliveryID || got.RequestFingerprint != fixtureFingerprint {
t.Fatalf("idempotency mismatch: %+v", got)
}
loaded, ok, err := store.GetDelivery(ctx, delivery.DeliveryID)
if err != nil {
t.Fatalf("get delivery: %v", err)
}
if !ok {
t.Fatal("delivery not found")
}
if loaded.DeliveryID != delivery.DeliveryID || loaded.Status != deliverydomain.StatusQueued {
t.Fatalf("delivery mismatch: %+v", loaded)
}
if !reflect.DeepEqual(loaded.Envelope.To, []common.Email{fixtureRecipient}) {
t.Fatalf("envelope.to mismatch: %+v", loaded.Envelope)
}
}
func TestAuthAcceptanceConflict(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
delivery := fixtureAuthDelivery(fixtureDeliveryID, fixtureKey, deliverydomain.StatusQueued)
first := fixtureFirstAttempt(delivery.DeliveryID, 1)
idem := fixtureIdempotency(delivery.Source, delivery.DeliveryID, delivery.IdempotencyKey)
if err := store.CreateAcceptance(ctx, acceptauthdelivery.CreateAcceptanceInput{
Delivery: delivery,
FirstAttempt: &first,
Idempotency: idem,
}); err != nil {
t.Fatalf("first create: %v", err)
}
dup := delivery
dup.DeliveryID = "delivery-002"
dupAttempt := fixtureFirstAttempt(dup.DeliveryID, 1)
dupIdem := idem
dupIdem.DeliveryID = dup.DeliveryID
err := store.CreateAcceptance(ctx, acceptauthdelivery.CreateAcceptanceInput{
Delivery: dup,
FirstAttempt: &dupAttempt,
Idempotency: dupIdem,
})
if !errors.Is(err, acceptauthdelivery.ErrConflict) {
t.Fatalf("expected acceptauthdelivery.ErrConflict, got %v", err)
}
}
func TestGenericAcceptanceCreate_GetDeliveryPayload(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
delivery := fixtureGenericDelivery(fixtureDeliveryID, fixtureKey)
first := fixtureFirstAttempt(delivery.DeliveryID, 1)
idem := fixtureIdempotency(delivery.Source, delivery.DeliveryID, delivery.IdempotencyKey)
payload := &acceptgenericdelivery.DeliveryPayload{
DeliveryID: delivery.DeliveryID,
Attachments: []acceptgenericdelivery.AttachmentPayload{{
Filename: "f.txt",
ContentType: "text/plain",
ContentBase64: "aGVsbG8=", // "hello"
SizeBytes: 5,
}},
}
handle := store.GenericAcceptance()
if err := handle.CreateAcceptance(ctx, acceptgenericdelivery.CreateAcceptanceInput{
Delivery: delivery,
FirstAttempt: first,
DeliveryPayload: payload,
Idempotency: idem,
}); err != nil {
t.Fatalf("create generic acceptance: %v", err)
}
got, ok, err := store.GetDeliveryPayload(ctx, delivery.DeliveryID)
if err != nil {
t.Fatalf("get delivery payload: %v", err)
}
if !ok {
t.Fatal("payload not found")
}
if got.DeliveryID != delivery.DeliveryID || len(got.Attachments) != 1 {
t.Fatalf("payload mismatch: %+v", got)
}
if got.Attachments[0].ContentBase64 != "aGVsbG8=" {
t.Fatalf("payload base64 mismatch: %+v", got.Attachments[0])
}
}
func TestSchedulerClaimAndCommit(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
delivery := fixtureAuthDelivery(fixtureDeliveryID, fixtureKey, deliverydomain.StatusQueued)
first := fixtureFirstAttempt(delivery.DeliveryID, 1)
idem := fixtureIdempotency(delivery.Source, delivery.DeliveryID, delivery.IdempotencyKey)
if err := store.CreateAcceptance(ctx, acceptauthdelivery.CreateAcceptanceInput{
Delivery: delivery,
FirstAttempt: &first,
Idempotency: idem,
}); err != nil {
t.Fatalf("create acceptance: %v", err)
}
scheduler := store.AttemptExecution()
now := first.ScheduledFor.Add(time.Second)
ids, err := scheduler.NextDueDeliveryIDs(ctx, now, 10)
if err != nil {
t.Fatalf("next due: %v", err)
}
if len(ids) != 1 || ids[0] != delivery.DeliveryID {
t.Fatalf("next due ids: %+v", ids)
}
claimed, ok, err := scheduler.ClaimDueAttempt(ctx, delivery.DeliveryID, now)
if err != nil {
t.Fatalf("claim due: %v", err)
}
if !ok {
t.Fatal("claim due: not found")
}
if claimed.Delivery.Status != deliverydomain.StatusSending {
t.Fatalf("expected sending, got %q", claimed.Delivery.Status)
}
if claimed.Attempt.Status != attempt.StatusInProgress {
t.Fatalf("expected in_progress, got %q", claimed.Attempt.Status)
}
// After claim, the row should not be picked up again.
again, err := scheduler.NextDueDeliveryIDs(ctx, now.Add(time.Second), 10)
if err != nil {
t.Fatalf("next due (after claim): %v", err)
}
if len(again) != 0 {
t.Fatalf("expected zero due deliveries after claim, got %+v", again)
}
completed := claimed.Attempt
finishedAt := now.Add(time.Second)
completed.Status = attempt.StatusProviderAccepted
completed.FinishedAt = &finishedAt
completed.ProviderClassification = "accepted"
completed.ProviderSummary = "ok"
finalDelivery := claimed.Delivery
finalDelivery.Status = deliverydomain.StatusSent
finalDelivery.LastAttemptStatus = attempt.StatusProviderAccepted
finalDelivery.SentAt = &finishedAt
finalDelivery.UpdatedAt = finishedAt
finalDelivery.ProviderSummary = "ok"
if err := scheduler.Commit(ctx, executeattempt.CommitStateInput{
Delivery: finalDelivery,
Attempt: completed,
}); err != nil {
t.Fatalf("commit attempt: %v", err)
}
loaded, ok, err := store.GetDelivery(ctx, delivery.DeliveryID)
if err != nil || !ok {
t.Fatalf("get delivery after commit: ok=%v err=%v", ok, err)
}
if loaded.Status != deliverydomain.StatusSent {
t.Fatalf("expected sent, got %q", loaded.Status)
}
}
func TestRenderMarkRendered(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
delivery := fixtureGenericDelivery(fixtureDeliveryID, fixtureKey)
first := fixtureFirstAttempt(delivery.DeliveryID, 1)
idem := fixtureIdempotency(delivery.Source, delivery.DeliveryID, delivery.IdempotencyKey)
if err := store.GenericAcceptance().CreateAcceptance(ctx, acceptgenericdelivery.CreateAcceptanceInput{
Delivery: delivery,
FirstAttempt: first,
Idempotency: idem,
}); err != nil {
t.Fatalf("create acceptance: %v", err)
}
rendered := delivery
rendered.Status = deliverydomain.StatusRendered
rendered.Content = deliverydomain.Content{Subject: "Hello Alice", TextBody: "Hi"}
rendered.UpdatedAt = fixtureNow().Add(time.Second)
if err := store.RenderDelivery().MarkRendered(ctx, renderdelivery.MarkRenderedInput{Delivery: rendered}); err != nil {
t.Fatalf("mark rendered: %v", err)
}
loaded, ok, err := store.GetDelivery(ctx, delivery.DeliveryID)
if err != nil || !ok {
t.Fatalf("get delivery: ok=%v err=%v", ok, err)
}
if loaded.Status != deliverydomain.StatusRendered {
t.Fatalf("expected rendered, got %q", loaded.Status)
}
if loaded.Content.Subject != "Hello Alice" {
t.Fatalf("subject mismatch: %q", loaded.Content.Subject)
}
}
func TestListDeliveriesPaging(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
for i := range 3 {
key := common.IdempotencyKey([]byte{'k', '0' + byte(i)})
id := common.DeliveryID([]byte{'d', '0' + byte(i)})
delivery := fixtureAuthDelivery(id, key, deliverydomain.StatusQueued)
// Stagger created_at so listing order is deterministic.
delivery.CreatedAt = fixtureNow().Add(time.Duration(i) * time.Second)
delivery.UpdatedAt = delivery.CreatedAt
first := fixtureFirstAttempt(id, 1)
first.ScheduledFor = delivery.CreatedAt.Add(time.Minute)
idem := fixtureIdempotency(delivery.Source, id, key)
idem.CreatedAt = delivery.CreatedAt
idem.ExpiresAt = delivery.CreatedAt.Add(7 * 24 * time.Hour)
if err := store.CreateAcceptance(ctx, acceptauthdelivery.CreateAcceptanceInput{
Delivery: delivery,
FirstAttempt: &first,
Idempotency: idem,
}); err != nil {
t.Fatalf("create %d: %v", i, err)
}
}
page1, err := store.List(ctx, listdeliveries.Input{Limit: 2})
if err != nil {
t.Fatalf("list page 1: %v", err)
}
if len(page1.Items) != 2 || page1.NextCursor == nil {
t.Fatalf("page 1 unexpected: items=%d cursor=%v", len(page1.Items), page1.NextCursor)
}
if page1.Items[0].DeliveryID != "d2" || page1.Items[1].DeliveryID != "d1" {
t.Fatalf("page 1 ordering: %+v", []common.DeliveryID{page1.Items[0].DeliveryID, page1.Items[1].DeliveryID})
}
page2, err := store.List(ctx, listdeliveries.Input{Limit: 2, Cursor: page1.NextCursor})
if err != nil {
t.Fatalf("list page 2: %v", err)
}
if len(page2.Items) != 1 || page2.NextCursor != nil {
t.Fatalf("page 2 unexpected: items=%d cursor=%v", len(page2.Items), page2.NextCursor)
}
if page2.Items[0].DeliveryID != "d0" {
t.Fatalf("page 2 expected d0, got %s", page2.Items[0].DeliveryID)
}
}
func TestListAttemptsAndDeadLetter(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
delivery := fixtureAuthDelivery(fixtureDeliveryID, fixtureKey, deliverydomain.StatusQueued)
first := fixtureFirstAttempt(delivery.DeliveryID, 1)
idem := fixtureIdempotency(delivery.Source, delivery.DeliveryID, delivery.IdempotencyKey)
if err := store.CreateAcceptance(ctx, acceptauthdelivery.CreateAcceptanceInput{
Delivery: delivery,
FirstAttempt: &first,
Idempotency: idem,
}); err != nil {
t.Fatalf("create acceptance: %v", err)
}
// Claim and commit a transport_failed → next attempt scheduled (delivery
// stays queued); then claim attempt 2 and commit dead-letter.
scheduler := store.AttemptExecution()
now := first.ScheduledFor.Add(time.Second)
claimed1, ok, err := scheduler.ClaimDueAttempt(ctx, delivery.DeliveryID, now)
if err != nil || !ok {
t.Fatalf("claim attempt 1: ok=%v err=%v", ok, err)
}
finishedAt1 := now.Add(time.Second)
terminal1 := claimed1.Attempt
terminal1.Status = attempt.StatusTransportFailed
terminal1.FinishedAt = &finishedAt1
terminal1.ProviderClassification = "transport_failed"
nextAttempt := attempt.Attempt{
DeliveryID: delivery.DeliveryID,
AttemptNo: 2,
Status: attempt.StatusScheduled,
ScheduledFor: finishedAt1.Add(5 * time.Minute),
}
delivery2 := claimed1.Delivery
delivery2.Status = deliverydomain.StatusQueued
delivery2.LastAttemptStatus = attempt.StatusTransportFailed
delivery2.AttemptCount = 2
delivery2.UpdatedAt = finishedAt1
if err := scheduler.Commit(ctx, executeattempt.CommitStateInput{
Delivery: delivery2,
Attempt: terminal1,
NextAttempt: &nextAttempt,
}); err != nil {
t.Fatalf("commit attempt 1: %v", err)
}
// Claim attempt 2.
now2 := nextAttempt.ScheduledFor.Add(time.Second)
claimed2, ok, err := scheduler.ClaimDueAttempt(ctx, delivery.DeliveryID, now2)
if err != nil || !ok {
t.Fatalf("claim attempt 2: ok=%v err=%v", ok, err)
}
finishedAt2 := now2.Add(time.Second)
terminal2 := claimed2.Attempt
terminal2.Status = attempt.StatusTransportFailed
terminal2.FinishedAt = &finishedAt2
terminal2.ProviderClassification = "retry_exhausted"
dlEntry := &deliverydomain.DeadLetterEntry{
DeliveryID: delivery.DeliveryID,
FinalAttemptNo: 2,
FailureClassification: "retry_exhausted",
CreatedAt: finishedAt2,
}
delivery3 := claimed2.Delivery
delivery3.Status = deliverydomain.StatusDeadLetter
delivery3.LastAttemptStatus = attempt.StatusTransportFailed
delivery3.DeadLetteredAt = &finishedAt2
delivery3.UpdatedAt = finishedAt2
if err := scheduler.Commit(ctx, executeattempt.CommitStateInput{
Delivery: delivery3,
Attempt: terminal2,
DeadLetter: dlEntry,
}); err != nil {
t.Fatalf("commit attempt 2: %v", err)
}
loaded, ok, err := store.GetDelivery(ctx, delivery.DeliveryID)
if err != nil || !ok {
t.Fatalf("get delivery: ok=%v err=%v", ok, err)
}
if loaded.Status != deliverydomain.StatusDeadLetter {
t.Fatalf("expected dead_letter, got %q", loaded.Status)
}
dl, ok, err := store.GetDeadLetter(ctx, delivery.DeliveryID)
if err != nil || !ok {
t.Fatalf("get dead-letter: ok=%v err=%v", ok, err)
}
if dl.FailureClassification != "retry_exhausted" {
t.Fatalf("dead-letter mismatch: %+v", dl)
}
attempts, err := store.ListAttempts(ctx, delivery.DeliveryID, loaded.AttemptCount)
if err != nil {
t.Fatalf("list attempts: %v", err)
}
if len(attempts) != 2 {
t.Fatalf("expected 2 attempts, got %d", len(attempts))
}
if attempts[0].AttemptNo != 1 || attempts[1].AttemptNo != 2 {
t.Fatalf("attempt sequence: %+v", attempts)
}
}
func TestMalformedCommandRecord(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
entry := malformedcommand.Entry{
StreamEntryID: "1234-0",
DeliveryID: "delivery-x",
Source: "notification",
IdempotencyKey: "k",
FailureCode: malformedcommand.FailureCodeInvalidPayload,
FailureMessage: "missing required field",
RawFields: map[string]any{"raw": "value"},
RecordedAt: fixtureNow(),
}
if err := store.Record(ctx, entry); err != nil {
t.Fatalf("record malformed: %v", err)
}
// Idempotent re-record: same entry should not error.
if err := store.Record(ctx, entry); err != nil {
t.Fatalf("re-record malformed: %v", err)
}
got, ok, err := store.GetMalformedCommand(ctx, entry.StreamEntryID)
if err != nil || !ok {
t.Fatalf("get malformed: ok=%v err=%v", ok, err)
}
if got.FailureCode != malformedcommand.FailureCodeInvalidPayload {
t.Fatalf("failure code mismatch: %q", got.FailureCode)
}
}
func TestResendCreate(t *testing.T) {
store := newTestStore(t)
ctx := context.Background()
parent := fixtureAuthDelivery(fixtureDeliveryID, fixtureKey, deliverydomain.StatusQueued)
parentAttempt := fixtureFirstAttempt(parent.DeliveryID, 1)
parentIdem := fixtureIdempotency(parent.Source, parent.DeliveryID, parent.IdempotencyKey)
if err := store.CreateAcceptance(ctx, acceptauthdelivery.CreateAcceptanceInput{
Delivery: parent,
FirstAttempt: &parentAttempt,
Idempotency: parentIdem,
}); err != nil {
t.Fatalf("create parent: %v", err)
}
cloneID := common.DeliveryID("clone-001")
cloneIdempKey := common.IdempotencyKey("resend-clone-001")
now := fixtureNow().Add(time.Hour)
clone := deliverydomain.Delivery{
DeliveryID: cloneID,
ResendParentDeliveryID: parent.DeliveryID,
Source: deliverydomain.SourceOperatorResend,
PayloadMode: deliverydomain.PayloadModeRendered,
Envelope: parent.Envelope,
Content: parent.Content,
IdempotencyKey: cloneIdempKey,
Status: deliverydomain.StatusQueued,
AttemptCount: 1,
CreatedAt: now,
UpdatedAt: now,
}
cloneAttempt := attempt.Attempt{
DeliveryID: cloneID,
AttemptNo: 1,
Status: attempt.StatusScheduled,
ScheduledFor: now.Add(time.Minute),
}
if err := store.CreateResend(ctx, resenddelivery.CreateResendInput{
Delivery: clone,
FirstAttempt: cloneAttempt,
}); err != nil {
t.Fatalf("create resend: %v", err)
}
loaded, ok, err := store.GetDelivery(ctx, cloneID)
if err != nil || !ok {
t.Fatalf("get clone: ok=%v err=%v", ok, err)
}
if loaded.ResendParentDeliveryID != parent.DeliveryID {
t.Fatalf("expected resend parent %q, got %q", parent.DeliveryID, loaded.ResendParentDeliveryID)
}
// Resend deliveries do not surface as idempotency hits.
_, ok, err = store.GetIdempotency(ctx, deliverydomain.SourceOperatorResend, cloneIdempKey)
if err != nil {
t.Fatalf("get idempotency for resend: %v", err)
}
if ok {
t.Fatal("resend delivery should not surface as idempotency hit")
}
}