feat: mail service
This commit is contained in:
@@ -92,7 +92,9 @@ func (c *HTTPAuthServiceClient) Close() error {
|
||||
// SendEmailCode delegates the public send-email-code route to the configured
|
||||
// Auth / Session Service public HTTP API.
|
||||
func (c *HTTPAuthServiceClient) SendEmailCode(ctx context.Context, input SendEmailCodeInput) (SendEmailCodeResult, error) {
|
||||
payload, statusCode, err := c.doJSONRequest(ctx, authServiceSendEmailCodePath, input)
|
||||
payload, statusCode, err := c.doJSONRequest(ctx, authServiceSendEmailCodePath, input, map[string]string{
|
||||
"Accept-Language": resolvePreferredLanguage(input.PreferredLanguage),
|
||||
})
|
||||
if err != nil {
|
||||
return SendEmailCodeResult{}, fmt.Errorf("send email code via auth service: %w", err)
|
||||
}
|
||||
@@ -123,7 +125,7 @@ func (c *HTTPAuthServiceClient) SendEmailCode(ctx context.Context, input SendEma
|
||||
// ConfirmEmailCode delegates the public confirm-email-code route to the
|
||||
// configured Auth / Session Service public HTTP API.
|
||||
func (c *HTTPAuthServiceClient) ConfirmEmailCode(ctx context.Context, input ConfirmEmailCodeInput) (ConfirmEmailCodeResult, error) {
|
||||
payload, statusCode, err := c.doJSONRequest(ctx, authServiceConfirmEmailCodePath, input)
|
||||
payload, statusCode, err := c.doJSONRequest(ctx, authServiceConfirmEmailCodePath, input, nil)
|
||||
if err != nil {
|
||||
return ConfirmEmailCodeResult{}, fmt.Errorf("confirm email code via auth service: %w", err)
|
||||
}
|
||||
@@ -151,7 +153,7 @@ func (c *HTTPAuthServiceClient) ConfirmEmailCode(ctx context.Context, input Conf
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HTTPAuthServiceClient) doJSONRequest(ctx context.Context, path string, requestBody any) ([]byte, int, error) {
|
||||
func (c *HTTPAuthServiceClient) doJSONRequest(ctx context.Context, path string, requestBody any, headers map[string]string) ([]byte, int, error) {
|
||||
if c == nil || c.httpClient == nil {
|
||||
return nil, 0, errors.New("nil client")
|
||||
}
|
||||
@@ -172,6 +174,12 @@ func (c *HTTPAuthServiceClient) doJSONRequest(ctx context.Context, path string,
|
||||
return nil, 0, fmt.Errorf("build request: %w", err)
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
for key, value := range headers {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
continue
|
||||
}
|
||||
request.Header.Set(key, value)
|
||||
}
|
||||
|
||||
response, err := c.httpClient.Do(request)
|
||||
if err != nil {
|
||||
|
||||
@@ -66,12 +66,14 @@ func TestHTTPAuthServiceClientSendEmailCodeSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var requestContentType string
|
||||
var requestAcceptLanguage string
|
||||
var requestBody string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, http.MethodPost, r.Method)
|
||||
assert.Equal(t, authServiceSendEmailCodePath, r.URL.Path)
|
||||
|
||||
requestContentType = r.Header.Get("Content-Type")
|
||||
requestAcceptLanguage = r.Header.Get("Accept-Language")
|
||||
payload, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
requestBody = string(payload)
|
||||
@@ -85,14 +87,35 @@ func TestHTTPAuthServiceClientSendEmailCodeSuccess(t *testing.T) {
|
||||
client := newTestHTTPAuthServiceClient(t, server)
|
||||
|
||||
result, err := client.SendEmailCode(context.Background(), SendEmailCodeInput{
|
||||
Email: "pilot@example.com",
|
||||
Email: "pilot@example.com",
|
||||
PreferredLanguage: "fr-FR",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, SendEmailCodeResult{ChallengeID: "challenge-123"}, result)
|
||||
assert.Equal(t, "application/json", requestContentType)
|
||||
assert.Equal(t, "fr-FR", requestAcceptLanguage)
|
||||
assert.JSONEq(t, `{"email":"pilot@example.com"}`, requestBody)
|
||||
}
|
||||
|
||||
func TestHTTPAuthServiceClientSendEmailCodeDefaultsAcceptLanguageToEnglish(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var requestAcceptLanguage string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestAcceptLanguage = r.Header.Get("Accept-Language")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := io.WriteString(w, `{"challenge_id":"challenge-123"}`)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := newTestHTTPAuthServiceClient(t, server)
|
||||
|
||||
_, err := client.SendEmailCode(context.Background(), SendEmailCodeInput{Email: "pilot@example.com"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "en", requestAcceptLanguage)
|
||||
}
|
||||
|
||||
func TestHTTPAuthServiceClientConfirmEmailCodeSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package restapi
|
||||
|
||||
import "golang.org/x/text/language"
|
||||
|
||||
const defaultPreferredLanguage = "en"
|
||||
|
||||
func resolvePreferredLanguage(value string) string {
|
||||
tags, _, err := language.ParseAcceptLanguage(value)
|
||||
if err != nil {
|
||||
return defaultPreferredLanguage
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
canonical := tag.String()
|
||||
switch canonical {
|
||||
case "", "und", "mul":
|
||||
continue
|
||||
default:
|
||||
return canonical
|
||||
}
|
||||
}
|
||||
|
||||
return defaultPreferredLanguage
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package restapi
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestResolvePreferredLanguage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "canonical valid tag",
|
||||
value: "fr-FR, en;q=0.8",
|
||||
want: "fr-FR",
|
||||
},
|
||||
{
|
||||
name: "quality ordering",
|
||||
value: "en-US;q=0.9, fr",
|
||||
want: "fr",
|
||||
},
|
||||
{
|
||||
name: "wildcard falls back",
|
||||
value: "*",
|
||||
want: "en",
|
||||
},
|
||||
{
|
||||
name: "malformed falls back",
|
||||
value: "fr-FR, @@",
|
||||
want: "en",
|
||||
},
|
||||
{
|
||||
name: "missing falls back",
|
||||
value: "",
|
||||
want: "en",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got := resolvePreferredLanguage(tt.value); got != tt.want {
|
||||
t.Fatalf("resolvePreferredLanguage(%q) = %q, want %q", tt.value, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,11 @@ type SendEmailCodeInput struct {
|
||||
// Email is the single client e-mail address that should receive the login
|
||||
// code challenge.
|
||||
Email string `json:"email"`
|
||||
|
||||
// PreferredLanguage stores the canonical BCP 47 language tag derived from
|
||||
// the public Accept-Language header for upstream auth-mail localization and
|
||||
// create-only user registration context.
|
||||
PreferredLanguage string `json:"-"`
|
||||
}
|
||||
|
||||
// SendEmailCodeResult describes the public REST and adapter payload returned
|
||||
@@ -204,6 +209,7 @@ func handleSendEmailCode(authService AuthServiceClient, timeout time.Duration) g
|
||||
abortInvalidRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
input.PreferredLanguage = resolvePreferredLanguage(c.Request.Header.Get("Accept-Language"))
|
||||
|
||||
callCtx, cancel := context.WithTimeout(c.Request.Context(), timeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -34,6 +34,7 @@ func TestSendEmailCodeHandlerSuccess(t *testing.T) {
|
||||
strings.NewReader(`{"email":" pilot@example.com "}`),
|
||||
)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept-Language", "fr-FR, en;q=0.8")
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(recorder, req)
|
||||
@@ -43,7 +44,10 @@ func TestSendEmailCodeHandlerSuccess(t *testing.T) {
|
||||
assert.Equal(t, `{"challenge_id":"challenge-123"}`, recorder.Body.String())
|
||||
assert.Equal(t, 1, authService.sendEmailCodeCalls)
|
||||
assert.Equal(t, 0, authService.confirmEmailCodeCalls)
|
||||
assert.Equal(t, SendEmailCodeInput{Email: "pilot@example.com"}, authService.sendEmailCodeInput)
|
||||
assert.Equal(t, SendEmailCodeInput{
|
||||
Email: "pilot@example.com",
|
||||
PreferredLanguage: "fr-FR",
|
||||
}, authService.sendEmailCodeInput)
|
||||
assert.True(t, authService.sendEmailCodeRouteClassOK)
|
||||
assert.Equal(t, PublicRouteClassPublicAuth, authService.sendEmailCodeRouteClass)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user