feat: authsession service
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
package sendemailcode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"galaxy/authsession/internal/domain/challenge"
|
||||
"galaxy/authsession/internal/domain/common"
|
||||
"galaxy/authsession/internal/domain/userresolution"
|
||||
"galaxy/authsession/internal/ports"
|
||||
"galaxy/authsession/internal/testkit"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExecuteCreatesThrottledChallengeWithoutUserDirectoryOrMail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
challengeStore := &testkit.InMemoryChallengeStore{}
|
||||
abuseProtector := &testkit.InMemorySendEmailCodeAbuseProtector{}
|
||||
now := time.Unix(10, 0).UTC()
|
||||
require.NoError(t, reserveSendCooldown(abuseProtector, common.Email("pilot@example.com"), now))
|
||||
|
||||
userDirectory := &countingUserDirectory{}
|
||||
mailSender := &testkit.RecordingMailSender{}
|
||||
service, err := NewWithRuntime(
|
||||
challengeStore,
|
||||
userDirectory,
|
||||
&testkit.SequenceIDGenerator{ChallengeIDs: []common.ChallengeID{"challenge-1"}},
|
||||
testkit.FixedCodeGenerator{Code: "654321"},
|
||||
testkit.DeterministicCodeHasher{},
|
||||
mailSender,
|
||||
abuseProtector,
|
||||
testkit.FixedClock{Time: now},
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "challenge-1", result.ChallengeID)
|
||||
assert.Zero(t, userDirectory.resolveCalls)
|
||||
assert.Empty(t, mailSender.RecordedInputs())
|
||||
|
||||
record, getErr := challengeStore.Get(context.Background(), common.ChallengeID("challenge-1"))
|
||||
require.NoError(t, getErr)
|
||||
assert.Equal(t, challenge.StatusDeliveryThrottled, record.Status)
|
||||
assert.Equal(t, challenge.DeliveryThrottled, record.DeliveryState)
|
||||
assert.Equal(t, 1, record.Attempts.Send)
|
||||
}
|
||||
|
||||
func TestExecuteBlockedEmailOutsideThrottleStillSuppressesDelivery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
challengeStore := &testkit.InMemoryChallengeStore{}
|
||||
userDirectory := &testkit.InMemoryUserDirectory{}
|
||||
require.NoError(t, userDirectory.SeedBlockedEmail(common.Email("pilot@example.com"), userresolution.BlockReasonCode("policy_block")))
|
||||
mailSender := &testkit.RecordingMailSender{}
|
||||
|
||||
service, err := NewWithRuntime(
|
||||
challengeStore,
|
||||
userDirectory,
|
||||
&testkit.SequenceIDGenerator{ChallengeIDs: []common.ChallengeID{"challenge-1"}},
|
||||
testkit.FixedCodeGenerator{Code: "654321"},
|
||||
testkit.DeterministicCodeHasher{},
|
||||
mailSender,
|
||||
&testkit.InMemorySendEmailCodeAbuseProtector{},
|
||||
testkit.FixedClock{Time: time.Unix(10, 0).UTC()},
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "challenge-1", result.ChallengeID)
|
||||
assert.Empty(t, mailSender.RecordedInputs())
|
||||
|
||||
record, getErr := challengeStore.Get(context.Background(), common.ChallengeID("challenge-1"))
|
||||
require.NoError(t, getErr)
|
||||
assert.Equal(t, challenge.StatusDeliverySuppressed, record.Status)
|
||||
assert.Equal(t, challenge.DeliverySuppressed, record.DeliveryState)
|
||||
}
|
||||
|
||||
func TestExecuteAllowsAgainAfterCooldown(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
challengeStore := &testkit.InMemoryChallengeStore{}
|
||||
userDirectory := &testkit.InMemoryUserDirectory{}
|
||||
mailSender := &testkit.RecordingMailSender{}
|
||||
abuseProtector := &testkit.InMemorySendEmailCodeAbuseProtector{}
|
||||
clock := &mutableClock{time: time.Unix(10, 0).UTC()}
|
||||
idGenerator := &testkit.SequenceIDGenerator{
|
||||
ChallengeIDs: []common.ChallengeID{"challenge-1", "challenge-2"},
|
||||
}
|
||||
|
||||
service, err := NewWithRuntime(
|
||||
challengeStore,
|
||||
userDirectory,
|
||||
idGenerator,
|
||||
testkit.FixedCodeGenerator{Code: "654321"},
|
||||
testkit.DeterministicCodeHasher{},
|
||||
mailSender,
|
||||
abuseProtector,
|
||||
clock,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
first, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "challenge-1", first.ChallengeID)
|
||||
|
||||
clock.time = clock.time.Add(challenge.ResendThrottleCooldown)
|
||||
|
||||
second, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "challenge-2", second.ChallengeID)
|
||||
require.Len(t, mailSender.RecordedInputs(), 2)
|
||||
|
||||
secondRecord, getErr := challengeStore.Get(context.Background(), common.ChallengeID("challenge-2"))
|
||||
require.NoError(t, getErr)
|
||||
assert.Equal(t, challenge.StatusSent, secondRecord.Status)
|
||||
assert.Equal(t, challenge.DeliverySent, secondRecord.DeliveryState)
|
||||
}
|
||||
|
||||
func reserveSendCooldown(protector ports.SendEmailCodeAbuseProtector, email common.Email, now time.Time) error {
|
||||
_, err := protector.CheckAndReserve(context.Background(), ports.SendEmailCodeAbuseInput{
|
||||
Email: email,
|
||||
Now: now,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
type mutableClock struct {
|
||||
time time.Time
|
||||
}
|
||||
|
||||
func (c *mutableClock) Now() time.Time {
|
||||
return c.time
|
||||
}
|
||||
|
||||
type countingUserDirectory struct {
|
||||
resolveCalls int
|
||||
}
|
||||
|
||||
func (d *countingUserDirectory) ResolveByEmail(_ context.Context, _ common.Email) (userresolution.Result, error) {
|
||||
d.resolveCalls++
|
||||
return userresolution.Result{Kind: userresolution.KindCreatable}, nil
|
||||
}
|
||||
|
||||
func (d *countingUserDirectory) ExistsByUserID(context.Context, common.UserID) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *countingUserDirectory) EnsureUserByEmail(context.Context, common.Email) (ports.EnsureUserResult, error) {
|
||||
return ports.EnsureUserResult{}, nil
|
||||
}
|
||||
|
||||
func (d *countingUserDirectory) BlockByUserID(context.Context, ports.BlockUserByIDInput) (ports.BlockUserResult, error) {
|
||||
return ports.BlockUserResult{}, nil
|
||||
}
|
||||
|
||||
func (d *countingUserDirectory) BlockByEmail(context.Context, ports.BlockUserByEmailInput) (ports.BlockUserResult, error) {
|
||||
return ports.BlockUserResult{}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user