311 lines
11 KiB
Go
311 lines
11 KiB
Go
package sendemailcode
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"github.com/stretchr/testify/require"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/authsession/internal/domain/challenge"
|
|
"galaxy/authsession/internal/domain/common"
|
|
"galaxy/authsession/internal/domain/userresolution"
|
|
"galaxy/authsession/internal/ports"
|
|
"galaxy/authsession/internal/service/shared"
|
|
"galaxy/authsession/internal/testkit"
|
|
)
|
|
|
|
func TestExecuteSendsChallengeForExistingAndCreatableUsers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
seed func(*testkit.InMemoryUserDirectory) error
|
|
email string
|
|
}{
|
|
{
|
|
name: "existing",
|
|
seed: func(directory *testkit.InMemoryUserDirectory) error {
|
|
return directory.SeedExisting(common.Email("pilot@example.com"), common.UserID("user-1"))
|
|
},
|
|
email: " pilot@example.com ",
|
|
},
|
|
{
|
|
name: "creatable",
|
|
seed: func(*testkit.InMemoryUserDirectory) error { return nil },
|
|
email: "new@example.com",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
challengeStore := &testkit.InMemoryChallengeStore{}
|
|
userDirectory := &testkit.InMemoryUserDirectory{}
|
|
if err := tt.seed(userDirectory); err != nil {
|
|
require.Failf(t, "test failed", "seed() returned error: %v", err)
|
|
}
|
|
mailSender := &testkit.RecordingMailSender{}
|
|
service, err := New(
|
|
challengeStore,
|
|
userDirectory,
|
|
&testkit.SequenceIDGenerator{ChallengeIDs: []common.ChallengeID{"challenge-1"}},
|
|
testkit.FixedCodeGenerator{Code: "654321"},
|
|
testkit.DeterministicCodeHasher{},
|
|
mailSender,
|
|
testkit.FixedClock{Time: time.Unix(10, 0).UTC()},
|
|
)
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "New() returned error: %v", err)
|
|
}
|
|
|
|
result, err := service.Execute(context.Background(), Input{Email: tt.email})
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Execute() returned error: %v", err)
|
|
}
|
|
if result.ChallengeID != "challenge-1" {
|
|
require.Failf(t, "test failed", "Execute().ChallengeID = %q, want %q", result.ChallengeID, "challenge-1")
|
|
}
|
|
if len(mailSender.RecordedInputs()) != 1 {
|
|
require.Failf(t, "test failed", "RecordedInputs() length = %d, want 1", len(mailSender.RecordedInputs()))
|
|
}
|
|
|
|
record, err := challengeStore.Get(context.Background(), common.ChallengeID("challenge-1"))
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Get() returned error: %v", err)
|
|
}
|
|
if record.Status != challenge.StatusSent || record.DeliveryState != challenge.DeliverySent {
|
|
require.Failf(t, "test failed", "challenge state = %q/%q", record.Status, record.DeliveryState)
|
|
}
|
|
if record.Attempts.Send != 1 {
|
|
require.Failf(t, "test failed", "Attempts.Send = %d, want 1", record.Attempts.Send)
|
|
}
|
|
if string(record.CodeHash) == "654321" {
|
|
require.FailNow(t, "CodeHash stored cleartext code")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExecuteSuppressesDeliveryForBlockedEmail(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
challengeStore := &testkit.InMemoryChallengeStore{}
|
|
userDirectory := &testkit.InMemoryUserDirectory{}
|
|
if err := userDirectory.SeedBlockedEmail(common.Email("pilot@example.com"), userresolution.BlockReasonCode("policy_block")); err != nil {
|
|
require.Failf(t, "test failed", "SeedBlockedEmail() returned error: %v", err)
|
|
}
|
|
mailSender := &testkit.RecordingMailSender{}
|
|
|
|
service, err := New(
|
|
challengeStore,
|
|
userDirectory,
|
|
&testkit.SequenceIDGenerator{ChallengeIDs: []common.ChallengeID{"challenge-1"}},
|
|
testkit.FixedCodeGenerator{Code: "654321"},
|
|
testkit.DeterministicCodeHasher{},
|
|
mailSender,
|
|
testkit.FixedClock{Time: time.Unix(10, 0).UTC()},
|
|
)
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "New() returned error: %v", err)
|
|
}
|
|
|
|
result, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Execute() returned error: %v", err)
|
|
}
|
|
if result.ChallengeID != "challenge-1" {
|
|
require.Failf(t, "test failed", "Execute().ChallengeID = %q, want %q", result.ChallengeID, "challenge-1")
|
|
}
|
|
if len(mailSender.RecordedInputs()) != 0 {
|
|
require.Failf(t, "test failed", "RecordedInputs() length = %d, want 0", len(mailSender.RecordedInputs()))
|
|
}
|
|
|
|
record, err := challengeStore.Get(context.Background(), common.ChallengeID("challenge-1"))
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Get() returned error: %v", err)
|
|
}
|
|
if record.Status != challenge.StatusDeliverySuppressed || record.DeliveryState != challenge.DeliverySuppressed {
|
|
require.Failf(t, "test failed", "challenge state = %q/%q", record.Status, record.DeliveryState)
|
|
}
|
|
}
|
|
|
|
func TestExecuteHandlesMailSenderSuppressedOutcome(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
challengeStore := &testkit.InMemoryChallengeStore{}
|
|
mailSender := &testkit.RecordingMailSender{
|
|
DefaultResult: ports.SendLoginCodeResult{Outcome: ports.SendLoginCodeOutcomeSuppressed},
|
|
}
|
|
|
|
service, err := New(
|
|
challengeStore,
|
|
&testkit.InMemoryUserDirectory{},
|
|
&testkit.SequenceIDGenerator{ChallengeIDs: []common.ChallengeID{"challenge-1"}},
|
|
testkit.FixedCodeGenerator{Code: "654321"},
|
|
testkit.DeterministicCodeHasher{},
|
|
mailSender,
|
|
testkit.FixedClock{Time: time.Unix(10, 0).UTC()},
|
|
)
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "New() returned error: %v", err)
|
|
}
|
|
|
|
_, err = service.Execute(context.Background(), Input{Email: "pilot@example.com"})
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Execute() returned error: %v", err)
|
|
}
|
|
|
|
record, err := challengeStore.Get(context.Background(), common.ChallengeID("challenge-1"))
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Get() returned error: %v", err)
|
|
}
|
|
if record.Status != challenge.StatusDeliverySuppressed || record.DeliveryState != challenge.DeliverySuppressed {
|
|
require.Failf(t, "test failed", "challenge state = %q/%q", record.Status, record.DeliveryState)
|
|
}
|
|
}
|
|
|
|
func TestExecuteMarksChallengeFailedWhenMailSenderFails(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
challengeStore := &testkit.InMemoryChallengeStore{}
|
|
mailSender := &testkit.RecordingMailSender{Err: errors.New("mail failed")}
|
|
|
|
service, err := New(
|
|
challengeStore,
|
|
&testkit.InMemoryUserDirectory{},
|
|
&testkit.SequenceIDGenerator{ChallengeIDs: []common.ChallengeID{"challenge-1"}},
|
|
testkit.FixedCodeGenerator{Code: "654321"},
|
|
testkit.DeterministicCodeHasher{},
|
|
mailSender,
|
|
testkit.FixedClock{Time: time.Unix(10, 0).UTC()},
|
|
)
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "New() returned error: %v", err)
|
|
}
|
|
|
|
_, err = service.Execute(context.Background(), Input{Email: "pilot@example.com"})
|
|
if shared.CodeOf(err) != shared.ErrorCodeServiceUnavailable {
|
|
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeServiceUnavailable)
|
|
}
|
|
|
|
record, err := challengeStore.Get(context.Background(), common.ChallengeID("challenge-1"))
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Get() returned error: %v", err)
|
|
}
|
|
if record.Status != challenge.StatusFailed || record.DeliveryState != challenge.DeliveryFailed {
|
|
require.Failf(t, "test failed", "challenge state = %q/%q", record.Status, record.DeliveryState)
|
|
}
|
|
}
|
|
|
|
func TestExecuteReturnsInvalidRequestForBadEmail(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
service, err := New(
|
|
&testkit.InMemoryChallengeStore{},
|
|
&testkit.InMemoryUserDirectory{},
|
|
&testkit.SequenceIDGenerator{},
|
|
testkit.FixedCodeGenerator{Code: "654321"},
|
|
testkit.DeterministicCodeHasher{},
|
|
&testkit.RecordingMailSender{},
|
|
testkit.FixedClock{Time: time.Unix(10, 0).UTC()},
|
|
)
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "New() returned error: %v", err)
|
|
}
|
|
|
|
_, err = service.Execute(context.Background(), Input{Email: "pilot"})
|
|
if shared.CodeOf(err) != shared.ErrorCodeInvalidRequest {
|
|
require.Failf(t, "test failed", "Execute() error code = %q, want %q", shared.CodeOf(err), shared.ErrorCodeInvalidRequest)
|
|
}
|
|
}
|
|
|
|
func TestExecuteCreatesFreshChallengeForRepeatedSend(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
challengeStore := &testkit.InMemoryChallengeStore{}
|
|
mailSender := &testkit.RecordingMailSender{}
|
|
clock := testkit.FixedClock{Time: time.Unix(10, 0).UTC()}
|
|
|
|
service, err := New(
|
|
challengeStore,
|
|
&testkit.InMemoryUserDirectory{},
|
|
&testkit.SequenceIDGenerator{
|
|
ChallengeIDs: []common.ChallengeID{"challenge-1", "challenge-2"},
|
|
},
|
|
testkit.FixedCodeGenerator{Code: "654321"},
|
|
testkit.DeterministicCodeHasher{},
|
|
mailSender,
|
|
clock,
|
|
)
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "New() returned error: %v", err)
|
|
}
|
|
|
|
first, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "first Execute() returned error: %v", err)
|
|
}
|
|
second, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "second Execute() returned error: %v", err)
|
|
}
|
|
if first.ChallengeID == second.ChallengeID {
|
|
require.Failf(t, "test failed", "challenge ids are equal: %q", first.ChallengeID)
|
|
}
|
|
|
|
firstRecord, err := challengeStore.Get(context.Background(), common.ChallengeID(first.ChallengeID))
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Get(%q) returned error: %v", first.ChallengeID, err)
|
|
}
|
|
secondRecord, err := challengeStore.Get(context.Background(), common.ChallengeID(second.ChallengeID))
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Get(%q) returned error: %v", second.ChallengeID, err)
|
|
}
|
|
if firstRecord.Status != challenge.StatusSent {
|
|
require.Failf(t, "test failed", "first challenge status = %q, want %q", firstRecord.Status, challenge.StatusSent)
|
|
}
|
|
if secondRecord.Status != challenge.StatusSent {
|
|
require.Failf(t, "test failed", "second challenge status = %q, want %q", secondRecord.Status, challenge.StatusSent)
|
|
}
|
|
if len(mailSender.RecordedInputs()) != 2 {
|
|
require.Failf(t, "test failed", "RecordedInputs() length = %d, want 2", len(mailSender.RecordedInputs()))
|
|
}
|
|
}
|
|
|
|
func TestExecuteSetsChallengeExpirationFromInitialTTL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := time.Unix(10, 0).UTC()
|
|
challengeStore := &testkit.InMemoryChallengeStore{}
|
|
|
|
service, err := New(
|
|
challengeStore,
|
|
&testkit.InMemoryUserDirectory{},
|
|
&testkit.SequenceIDGenerator{ChallengeIDs: []common.ChallengeID{"challenge-1"}},
|
|
testkit.FixedCodeGenerator{Code: "654321"},
|
|
testkit.DeterministicCodeHasher{},
|
|
&testkit.RecordingMailSender{},
|
|
testkit.FixedClock{Time: now},
|
|
)
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "New() returned error: %v", err)
|
|
}
|
|
|
|
if _, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"}); err != nil {
|
|
require.Failf(t, "test failed", "Execute() returned error: %v", err)
|
|
}
|
|
|
|
record, err := challengeStore.Get(context.Background(), common.ChallengeID("challenge-1"))
|
|
if err != nil {
|
|
require.Failf(t, "test failed", "Get() returned error: %v", err)
|
|
}
|
|
wantExpiresAt := now.Add(challenge.InitialTTL)
|
|
if !record.ExpiresAt.Equal(wantExpiresAt) {
|
|
require.Failf(t, "test failed", "ExpiresAt = %s, want %s", record.ExpiresAt, wantExpiresAt)
|
|
}
|
|
}
|