feat: mail service
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
package redisstate
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"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/acceptgenericdelivery"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func validDelivery(t require.TestingT) deliverydomain.Delivery {
|
||||
locale, err := common.ParseLocale("fr-fr")
|
||||
require.NoError(t, err)
|
||||
|
||||
createdAt := time.Unix(1_775_121_700, 0).UTC()
|
||||
updatedAt := createdAt.Add(2 * time.Minute)
|
||||
sentAt := updatedAt.Add(15 * time.Second)
|
||||
|
||||
record := deliverydomain.Delivery{
|
||||
DeliveryID: common.DeliveryID("delivery-123"),
|
||||
ResendParentDeliveryID: common.DeliveryID("delivery-parent-001"),
|
||||
Source: deliverydomain.SourceOperatorResend,
|
||||
PayloadMode: deliverydomain.PayloadModeTemplate,
|
||||
TemplateID: common.TemplateID("auth.login_code"),
|
||||
Envelope: deliverydomain.Envelope{
|
||||
To: []common.Email{common.Email("pilot@example.com")},
|
||||
Cc: []common.Email{common.Email("copilot@example.com")},
|
||||
Bcc: []common.Email{common.Email("ops@example.com")},
|
||||
ReplyTo: []common.Email{common.Email("noreply@example.com")},
|
||||
},
|
||||
Content: deliverydomain.Content{
|
||||
Subject: "Your login code",
|
||||
TextBody: "Code: 123456",
|
||||
HTMLBody: "<p>Code: <strong>123456</strong></p>",
|
||||
},
|
||||
Attachments: []common.AttachmentMetadata{
|
||||
{Filename: "instructions.txt", ContentType: "text/plain; charset=utf-8", SizeBytes: 128},
|
||||
},
|
||||
Locale: locale,
|
||||
TemplateVariables: map[string]any{
|
||||
"code": "123456",
|
||||
},
|
||||
LocaleFallbackUsed: true,
|
||||
IdempotencyKey: common.IdempotencyKey("operator:resend:delivery-123"),
|
||||
Status: deliverydomain.StatusSent,
|
||||
AttemptCount: 2,
|
||||
LastAttemptStatus: attempt.StatusProviderAccepted,
|
||||
ProviderSummary: "queued by provider",
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
SentAt: &sentAt,
|
||||
}
|
||||
require.NoError(t, record.Validate())
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
func validScheduledAttempt(t require.TestingT, deliveryID common.DeliveryID) attempt.Attempt {
|
||||
scheduledFor := time.Unix(1_775_121_820, 0).UTC()
|
||||
|
||||
record := attempt.Attempt{
|
||||
DeliveryID: deliveryID,
|
||||
AttemptNo: 1,
|
||||
ScheduledFor: scheduledFor,
|
||||
Status: attempt.StatusScheduled,
|
||||
}
|
||||
require.NoError(t, record.Validate())
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
func validQueuedTemplateDelivery(t require.TestingT) deliverydomain.Delivery {
|
||||
record := validDelivery(t)
|
||||
record.DeliveryID = common.DeliveryID("delivery-queued")
|
||||
record.ResendParentDeliveryID = ""
|
||||
record.Source = deliverydomain.SourceNotification
|
||||
record.Status = deliverydomain.StatusQueued
|
||||
record.AttemptCount = 1
|
||||
record.LastAttemptStatus = ""
|
||||
record.ProviderSummary = ""
|
||||
record.LocaleFallbackUsed = false
|
||||
record.Content = deliverydomain.Content{}
|
||||
record.CreatedAt = time.Unix(1_775_121_700, 0).UTC()
|
||||
record.UpdatedAt = record.CreatedAt
|
||||
record.SentAt = nil
|
||||
record.SuppressedAt = nil
|
||||
record.FailedAt = nil
|
||||
record.DeadLetteredAt = nil
|
||||
record.IdempotencyKey = common.IdempotencyKey("notification:delivery-queued")
|
||||
require.NoError(t, record.Validate())
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
func validTerminalAttempt(t require.TestingT, deliveryID common.DeliveryID) attempt.Attempt {
|
||||
scheduledFor := time.Unix(1_775_121_820, 0).UTC()
|
||||
startedAt := scheduledFor.Add(5 * time.Second)
|
||||
finishedAt := startedAt.Add(2 * time.Second)
|
||||
|
||||
record := attempt.Attempt{
|
||||
DeliveryID: deliveryID,
|
||||
AttemptNo: 2,
|
||||
ScheduledFor: scheduledFor,
|
||||
StartedAt: &startedAt,
|
||||
FinishedAt: &finishedAt,
|
||||
Status: attempt.StatusProviderAccepted,
|
||||
ProviderClassification: "accepted",
|
||||
ProviderSummary: "queued by provider",
|
||||
}
|
||||
require.NoError(t, record.Validate())
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
func validRenderFailedAttempt(t require.TestingT, deliveryID common.DeliveryID) attempt.Attempt {
|
||||
record := validScheduledAttempt(t, deliveryID)
|
||||
startedAt := record.ScheduledFor.Add(time.Second)
|
||||
finishedAt := startedAt
|
||||
record.StartedAt = &startedAt
|
||||
record.FinishedAt = &finishedAt
|
||||
record.Status = attempt.StatusRenderFailed
|
||||
record.ProviderClassification = "missing_required_variable"
|
||||
record.ProviderSummary = "missing required variables: player.name"
|
||||
require.NoError(t, record.Validate())
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
func validIdempotencyRecord(t require.TestingT, source deliverydomain.Source, deliveryID common.DeliveryID, key common.IdempotencyKey) idempotency.Record {
|
||||
createdAt := time.Now().UTC().Truncate(time.Millisecond).Add(-time.Minute)
|
||||
|
||||
record := idempotency.Record{
|
||||
Source: source,
|
||||
IdempotencyKey: key,
|
||||
DeliveryID: deliveryID,
|
||||
RequestFingerprint: "sha256:abcdef123456",
|
||||
CreatedAt: createdAt,
|
||||
ExpiresAt: createdAt.Add(IdempotencyTTL),
|
||||
}
|
||||
require.NoError(t, record.Validate())
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
func validDeadLetterEntry(t require.TestingT, deliveryID common.DeliveryID) deliverydomain.DeadLetterEntry {
|
||||
entry := deliverydomain.DeadLetterEntry{
|
||||
DeliveryID: deliveryID,
|
||||
FinalAttemptNo: 3,
|
||||
FailureClassification: "retry_exhausted",
|
||||
ProviderSummary: "smtp timeout",
|
||||
CreatedAt: time.Unix(1_775_122_000, 0).UTC(),
|
||||
RecoveryHint: "check SMTP connectivity",
|
||||
}
|
||||
require.NoError(t, entry.Validate())
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
func validDeliveryPayload(t require.TestingT, deliveryID common.DeliveryID) acceptgenericdelivery.DeliveryPayload {
|
||||
payload := acceptgenericdelivery.DeliveryPayload{
|
||||
DeliveryID: deliveryID,
|
||||
Attachments: []acceptgenericdelivery.AttachmentPayload{
|
||||
{
|
||||
Filename: "instructions.txt",
|
||||
ContentType: "text/plain; charset=utf-8",
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte("read me")),
|
||||
SizeBytes: int64(len([]byte("read me"))),
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, payload.Validate())
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
func validMalformedCommandEntry(t require.TestingT) malformedcommand.Entry {
|
||||
entry := malformedcommand.Entry{
|
||||
StreamEntryID: "1775121700000-0",
|
||||
DeliveryID: "mail-123",
|
||||
Source: "notification",
|
||||
IdempotencyKey: "notification:mail-123",
|
||||
FailureCode: malformedcommand.FailureCodeInvalidPayload,
|
||||
FailureMessage: "payload_json.subject is required",
|
||||
RawFields: map[string]any{
|
||||
"delivery_id": "mail-123",
|
||||
"source": "notification",
|
||||
"payload_mode": "rendered",
|
||||
"idempotency_key": "notification:mail-123",
|
||||
},
|
||||
RecordedAt: time.Unix(1_775_121_700, 0).UTC(),
|
||||
}
|
||||
require.NoError(t, entry.Validate())
|
||||
|
||||
return entry
|
||||
}
|
||||
Reference in New Issue
Block a user