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
@@ -5,6 +5,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
@@ -35,6 +36,10 @@ func TestMailServiceRESTCompatibilitySendEmailCodeSent(t *testing.T) {
assert.Equal(t, http.StatusOK, response.StatusCode)
assert.JSONEq(t, `{"challenge_id":"challenge-1"}`, response.Body)
assert.Equal(t, 1, harness.mailServer.CallCount())
deliveries := harness.mailServer.RecordedDeliveries()
require.Len(t, deliveries, 1)
assert.Equal(t, "en", deliveries[0].Locale)
assert.Equal(t, "challenge-1", deliveries[0].IdempotencyKey)
}
func TestMailServiceRESTCompatibilitySendEmailCodeSuppressed(t *testing.T) {
@@ -99,6 +104,29 @@ func TestMailServiceRESTCompatibilityThrottledSendSkipsMailService(t *testing.T)
assert.Equal(t, 1, harness.mailServer.CallCount())
}
func TestMailServiceRESTCompatibilitySendEmailCodeForwardsLocalizedLocale(t *testing.T) {
t.Parallel()
harness := newMailServiceRESTCompatibilityHarness(t, mailServiceRESTCompatibilityOptions{
MailStatusCode: http.StatusOK,
MailResponse: `{"outcome":"sent"}`,
})
response := gatewayCompatibilityPostJSONWithHeaders(
t,
harness.publicBaseURL+"/api/v1/public/auth/send-email-code",
`{"email":"pilot@example.com"}`,
map[string]string{"Accept-Language": "fr-FR, en;q=0.8"},
)
assert.Equal(t, http.StatusOK, response.StatusCode)
assert.JSONEq(t, `{"challenge_id":"challenge-1"}`, response.Body)
deliveries := harness.mailServer.RecordedDeliveries()
require.Len(t, deliveries, 1)
assert.Equal(t, "fr-FR", deliveries[0].Locale)
assert.Equal(t, "challenge-1", deliveries[0].IdempotencyKey)
}
type mailServiceRESTCompatibilityOptions struct {
MailStatusCode int
MailResponse string
@@ -191,6 +219,14 @@ type mailServiceStubServer struct {
statusCode int
response string
callCount int
deliveries []mailServiceStubDelivery
}
type mailServiceStubDelivery struct {
Email string
Code string
Locale string
IdempotencyKey string
}
func newMailServiceStubServer(statusCode int, response string) *mailServiceStubServer {
@@ -206,17 +242,18 @@ func (s *mailServiceStubServer) Handler() http.Handler {
http.NotFound(writer, request)
return
}
s.mu.Lock()
s.callCount++
s.mu.Unlock()
if strings.TrimSpace(request.Header.Get("Idempotency-Key")) == "" {
http.Error(writer, "Idempotency-Key header must not be empty", http.StatusBadRequest)
return
}
decoder := json.NewDecoder(request.Body)
decoder.DisallowUnknownFields()
var body struct {
Email string `json:"email"`
Code string `json:"code"`
Email string `json:"email"`
Code string `json:"code"`
Locale string `json:"locale"`
}
if err := decoder.Decode(&body); err != nil {
http.Error(writer, err.Error(), http.StatusBadRequest)
@@ -231,6 +268,16 @@ func (s *mailServiceStubServer) Handler() http.Handler {
return
}
s.mu.Lock()
s.callCount++
s.deliveries = append(s.deliveries, mailServiceStubDelivery{
Email: body.Email,
Code: body.Code,
Locale: body.Locale,
IdempotencyKey: request.Header.Get("Idempotency-Key"),
})
s.mu.Unlock()
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(s.statusCode)
_, _ = io.WriteString(writer, s.response)
@@ -243,3 +290,12 @@ func (s *mailServiceStubServer) CallCount() int {
return s.callCount
}
func (s *mailServiceStubServer) RecordedDeliveries() []mailServiceStubDelivery {
s.mu.Lock()
defer s.mu.Unlock()
cloned := make([]mailServiceStubDelivery, len(s.deliveries))
copy(cloned, s.deliveries)
return cloned
}