feat: mail service
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -57,7 +58,9 @@ func TestUserServiceRESTCompatibilityPublicSendUsesResolveByEmailOutcomes(t *tes
|
||||
attempts := harness.mailSender.RecordedAttempts()
|
||||
require.Len(t, attempts, 2)
|
||||
assert.Equal(t, common.Email("existing@example.com"), attempts[0].Input.Email)
|
||||
assert.Equal(t, "en", attempts[0].Input.Locale)
|
||||
assert.Equal(t, common.Email("creatable@example.com"), attempts[1].Input.Email)
|
||||
assert.Equal(t, "en", attempts[1].Input.Locale)
|
||||
}
|
||||
|
||||
func TestUserServiceRESTCompatibilityPublicConfirmUsesEnsureOutcomes(t *testing.T) {
|
||||
@@ -162,12 +165,34 @@ func TestUserServiceRESTCompatibilityInternalBlockUserUsesRESTClient(t *testing.
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserServiceRESTCompatibilityAcceptLanguageDrivesMailLocaleAndRegistrationContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
harness := newUserServiceRESTCompatibilityHarness(t)
|
||||
require.NoError(t, harness.directory.QueueCreatedUserIDs(common.UserID("user-created")))
|
||||
|
||||
challengeID := harness.sendChallengeIDWithAcceptLanguage(t, "localized@example.com", "fr-FR, en;q=0.8", "fr-FR")
|
||||
|
||||
attempts := harness.mailSender.RecordedAttempts()
|
||||
require.Len(t, attempts, 1)
|
||||
assert.Equal(t, "fr-FR", attempts[0].Input.Locale)
|
||||
|
||||
response := gatewayCompatibilityPostJSONValue(
|
||||
t,
|
||||
harness.publicBaseURL+"/api/v1/public/auth/confirm-email-code",
|
||||
gatewayCompatibilityConfirmRequest(challengeID, userServiceRESTCompatibilityCode, gatewayCompatibilityClientPublicKey),
|
||||
)
|
||||
assert.Equal(t, http.StatusOK, response.StatusCode)
|
||||
assert.JSONEq(t, `{"device_session_id":"device-session-1"}`, response.Body)
|
||||
}
|
||||
|
||||
type userServiceRESTCompatibilityHarness struct {
|
||||
publicBaseURL string
|
||||
internalBaseURL string
|
||||
mailSender *mail.StubSender
|
||||
sessionStore *testkit.InMemorySessionStore
|
||||
directory *userservice.StubDirectory
|
||||
publicBaseURL string
|
||||
internalBaseURL string
|
||||
mailSender *mail.StubSender
|
||||
sessionStore *testkit.InMemorySessionStore
|
||||
directory *userservice.StubDirectory
|
||||
preferredLanguageExpectations *preferredLanguageExpectationStore
|
||||
}
|
||||
|
||||
func newUserServiceRESTCompatibilityHarness(t *testing.T) userServiceRESTCompatibilityHarness {
|
||||
@@ -176,8 +201,9 @@ func newUserServiceRESTCompatibilityHarness(t *testing.T) userServiceRESTCompati
|
||||
challengeStore := &testkit.InMemoryChallengeStore{}
|
||||
sessionStore := &testkit.InMemorySessionStore{}
|
||||
directory := &userservice.StubDirectory{}
|
||||
preferredLanguageExpectations := newPreferredLanguageExpectationStore()
|
||||
|
||||
userServiceServer := httptest.NewServer(newUserServiceStubHandler(directory))
|
||||
userServiceServer := httptest.NewServer(newUserServiceStubHandler(directory, preferredLanguageExpectations))
|
||||
t.Cleanup(userServiceServer.Close)
|
||||
|
||||
userDirectory, err := userservice.NewRESTClient(userservice.Config{
|
||||
@@ -261,18 +287,31 @@ func newUserServiceRESTCompatibilityHarness(t *testing.T) userServiceRESTCompati
|
||||
gatewayCompatibilityRunServer(t, internalServer.Run, internalServer.Shutdown, internalCfg.Addr)
|
||||
|
||||
return userServiceRESTCompatibilityHarness{
|
||||
publicBaseURL: "http://" + publicCfg.Addr,
|
||||
internalBaseURL: "http://" + internalCfg.Addr,
|
||||
mailSender: mailSender,
|
||||
sessionStore: sessionStore,
|
||||
directory: directory,
|
||||
publicBaseURL: "http://" + publicCfg.Addr,
|
||||
internalBaseURL: "http://" + internalCfg.Addr,
|
||||
mailSender: mailSender,
|
||||
sessionStore: sessionStore,
|
||||
directory: directory,
|
||||
preferredLanguageExpectations: preferredLanguageExpectations,
|
||||
}
|
||||
}
|
||||
|
||||
func (h userServiceRESTCompatibilityHarness) sendChallengeID(t *testing.T, email string) string {
|
||||
t.Helper()
|
||||
|
||||
response := gatewayCompatibilityPostJSON(t, h.publicBaseURL+"/api/v1/public/auth/send-email-code", fmt.Sprintf(`{"email":"%s"}`, email))
|
||||
return h.sendChallengeIDWithAcceptLanguage(t, email, "", "en")
|
||||
}
|
||||
|
||||
func (h userServiceRESTCompatibilityHarness) sendChallengeIDWithAcceptLanguage(t *testing.T, email string, acceptLanguage string, expectedPreferredLanguage string) string {
|
||||
t.Helper()
|
||||
|
||||
h.preferredLanguageExpectations.Set(email, expectedPreferredLanguage)
|
||||
response := gatewayCompatibilityPostJSONWithHeaders(
|
||||
t,
|
||||
h.publicBaseURL+"/api/v1/public/auth/send-email-code",
|
||||
fmt.Sprintf(`{"email":"%s"}`, email),
|
||||
map[string]string{"Accept-Language": acceptLanguage},
|
||||
)
|
||||
assert.Equal(t, http.StatusOK, response.StatusCode)
|
||||
|
||||
var body struct {
|
||||
@@ -284,7 +323,7 @@ func (h userServiceRESTCompatibilityHarness) sendChallengeID(t *testing.T, email
|
||||
return body.ChallengeID
|
||||
}
|
||||
|
||||
func newUserServiceStubHandler(directory *userservice.StubDirectory) http.Handler {
|
||||
func newUserServiceStubHandler(directory *userservice.StubDirectory, preferredLanguageExpectations *preferredLanguageExpectationStore) http.Handler {
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
switch {
|
||||
case request.Method == http.MethodPost && request.URL.Path == "/api/v1/internal/user-resolutions/by-email":
|
||||
@@ -349,8 +388,13 @@ func newUserServiceStubHandler(directory *userservice.StubDirectory) http.Handle
|
||||
writeUserServiceStubError(writer, http.StatusBadRequest, errors.New("registration_context must be present"))
|
||||
return
|
||||
}
|
||||
if ensureInput.RegistrationContext.PreferredLanguage != "en" {
|
||||
writeUserServiceStubError(writer, http.StatusBadRequest, errors.New("registration_context.preferred_language must equal en during rollout"))
|
||||
expectedPreferredLanguage := preferredLanguageExpectations.Expected(input.Email)
|
||||
if ensureInput.RegistrationContext.PreferredLanguage != expectedPreferredLanguage {
|
||||
writeUserServiceStubError(
|
||||
writer,
|
||||
http.StatusBadRequest,
|
||||
fmt.Errorf("registration_context.preferred_language must equal %s", expectedPreferredLanguage),
|
||||
)
|
||||
return
|
||||
}
|
||||
if ensureInput.RegistrationContext.TimeZone != gatewayCompatibilityTimeZone {
|
||||
@@ -434,6 +478,44 @@ func newUserServiceStubHandler(directory *userservice.StubDirectory) http.Handle
|
||||
})
|
||||
}
|
||||
|
||||
type preferredLanguageExpectationStore struct {
|
||||
mu sync.Mutex
|
||||
byEmail map[string]string
|
||||
}
|
||||
|
||||
func newPreferredLanguageExpectationStore() *preferredLanguageExpectationStore {
|
||||
return &preferredLanguageExpectationStore{
|
||||
byEmail: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *preferredLanguageExpectationStore) Set(email string, preferredLanguage string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.byEmail[email] = preferredLanguage
|
||||
}
|
||||
|
||||
func (s *preferredLanguageExpectationStore) Expected(email string) string {
|
||||
if s == nil {
|
||||
return "en"
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
preferredLanguage := s.byEmail[email]
|
||||
if preferredLanguage == "" {
|
||||
return "en"
|
||||
}
|
||||
|
||||
return preferredLanguage
|
||||
}
|
||||
|
||||
func decodeUserServiceStubRequest(writer http.ResponseWriter, request *http.Request, target any) bool {
|
||||
decoder := json.NewDecoder(request.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
Reference in New Issue
Block a user