feat: mail service

This commit is contained in:
Ilia Denisov
2026-04-17 18:39:16 +02:00
committed by GitHub
parent 23ffcb7535
commit 5b7593e6f6
183 changed files with 31215 additions and 248 deletions
+74
View File
@@ -0,0 +1,74 @@
// Package idempotency defines the deduplication record used by Mail Service
// acceptance flows.
package idempotency
import (
"fmt"
"strings"
"time"
"galaxy/mail/internal/domain/common"
"galaxy/mail/internal/domain/delivery"
)
// Record stores the first accepted fingerprint bound to one `(source,
// idempotency_key)` scope.
type Record struct {
// Source stores the frozen delivery source vocabulary value.
Source delivery.Source
// IdempotencyKey stores the caller-owned deduplication key.
IdempotencyKey common.IdempotencyKey
// DeliveryID stores the accepted delivery linked to the scope.
DeliveryID common.DeliveryID
// RequestFingerprint stores the stable fingerprint of the first accepted
// request.
RequestFingerprint string
// CreatedAt stores when the deduplication record was created.
CreatedAt time.Time
// ExpiresAt stores when the deduplication record becomes invalid.
ExpiresAt time.Time
}
// Validate reports whether Record satisfies the frozen Stage 2 structural
// invariants.
func (record Record) Validate() error {
if !record.Source.IsKnown() {
return fmt.Errorf("idempotency source %q is unsupported", record.Source)
}
if err := record.IdempotencyKey.Validate(); err != nil {
return fmt.Errorf("idempotency key: %w", err)
}
if err := record.DeliveryID.Validate(); err != nil {
return fmt.Errorf("idempotency delivery id: %w", err)
}
if err := validateToken("idempotency request fingerprint", record.RequestFingerprint); err != nil {
return err
}
if err := common.ValidateTimestamp("idempotency created at", record.CreatedAt); err != nil {
return err
}
if err := common.ValidateTimestamp("idempotency expires at", record.ExpiresAt); err != nil {
return err
}
if !record.ExpiresAt.After(record.CreatedAt) {
return fmt.Errorf("idempotency expires at must be after created at")
}
return nil
}
func validateToken(name string, value string) error {
switch {
case strings.TrimSpace(value) == "":
return fmt.Errorf("%s must not be empty", name)
case strings.TrimSpace(value) != value:
return fmt.Errorf("%s must not contain surrounding whitespace", name)
default:
return nil
}
}
@@ -0,0 +1,74 @@
package idempotency
import (
"testing"
"time"
"galaxy/mail/internal/domain/common"
"galaxy/mail/internal/domain/delivery"
"github.com/stretchr/testify/require"
)
func TestRecordValidate(t *testing.T) {
t.Parallel()
createdAt := time.Unix(1_775_121_700, 0).UTC()
tests := []struct {
name string
record Record
wantErr bool
}{
{
name: "valid",
record: Record{
Source: delivery.SourceNotification,
IdempotencyKey: common.IdempotencyKey("notification:delivery-123"),
DeliveryID: common.DeliveryID("delivery-123"),
RequestFingerprint: "sha256:abcdef",
CreatedAt: createdAt,
ExpiresAt: createdAt.Add(7 * 24 * time.Hour),
},
},
{
name: "expires at must be after created at",
record: Record{
Source: delivery.SourceNotification,
IdempotencyKey: common.IdempotencyKey("notification:delivery-123"),
DeliveryID: common.DeliveryID("delivery-123"),
RequestFingerprint: "sha256:abcdef",
CreatedAt: createdAt,
ExpiresAt: createdAt,
},
wantErr: true,
},
{
name: "fingerprint required",
record: Record{
Source: delivery.SourceNotification,
IdempotencyKey: common.IdempotencyKey("notification:delivery-123"),
DeliveryID: common.DeliveryID("delivery-123"),
CreatedAt: createdAt,
ExpiresAt: createdAt.Add(time.Hour),
},
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := tt.record.Validate()
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}