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") } }