Files
sxchnge/backend/edge/shared/storage/verification/verification_attempts_test.go
T

149 lines
3.9 KiB
Go

package verification
import (
"context"
"errors"
"testing"
"time"
"git.sendico.io/tech/sxchnge/bff/pkg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/v2/bson"
)
func TestConsume_MaxRetriesExceeded(t *testing.T) {
db := newTestVerificationDB(t)
ctx := context.Background()
accountRef := bson.NewObjectID()
r := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithMaxRetries(2)
raw, err := db.Create(ctx, r)
require.NoError(t, err)
repo := db.Repository.(*memoryTokenRepository)
repo.mu.Lock()
for _, tok := range repo.data {
tok.Attempts = 2
}
repo.mu.Unlock()
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw)
require.Error(t, err)
assert.True(t, errors.Is(err, ErrTokenAttemptsExceeded))
}
func TestConsume_UnderMaxRetriesSucceeds(t *testing.T) {
db := newTestVerificationDB(t)
ctx := context.Background()
accountRef := bson.NewObjectID()
r := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithMaxRetries(3)
raw, err := db.Create(ctx, r)
require.NoError(t, err)
repo := db.Repository.(*memoryTokenRepository)
repo.mu.Lock()
for _, tok := range repo.data {
tok.Attempts = 2
}
repo.mu.Unlock()
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw)
require.NoError(t, err)
assert.Equal(t, accountRef, tok.AccountRef)
}
func TestConsume_NoMaxRetriesIgnoresAttempts(t *testing.T) {
db := newTestVerificationDB(t)
ctx := context.Background()
accountRef := bson.NewObjectID()
raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
require.NoError(t, err)
repo := db.Repository.(*memoryTokenRepository)
repo.mu.Lock()
for _, tok := range repo.data {
tok.Attempts = 100
}
repo.mu.Unlock()
tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw)
require.NoError(t, err)
assert.Equal(t, accountRef, tok.AccountRef)
}
func TestConsume_WrongHashReturnsNotFound(t *testing.T) {
db := newTestVerificationDB(t)
ctx := context.Background()
accountRef := bson.NewObjectID()
_, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
require.NoError(t, err)
_, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", "wrong-code")
require.Error(t, err)
assert.True(t, errors.Is(err, ErrTokenNotFound))
}
func TestConsume_WrongOTPAttemptsPersistAfterTransactionRollback(t *testing.T) {
db := newRollbackTestVerificationDB(t)
ctx := context.Background()
accountRef := bson.NewObjectID()
raw, err := db.Create(
ctx,
NewOTPRequest(accountRef, model.PurposeEmailChange, "new@example.com").
WithTTL(time.Hour).
WithMaxRetries(2),
)
require.NoError(t, err)
wrongCode := differentOTP(raw)
for attempt := 1; attempt <= 2; attempt++ {
_, err = db.Consume(ctx, accountRef, model.PurposeEmailChange, "", wrongCode)
require.Error(t, err)
assert.True(t, errors.Is(err, ErrTokenNotFound))
assert.Equal(t, attempt, storedTokenAttempts(t, db))
}
_, err = db.Consume(ctx, accountRef, model.PurposeEmailChange, "", raw)
require.Error(t, err)
assert.True(t, errors.Is(err, ErrTokenAttemptsExceeded))
}
func TestConsume_ContextMismatchReturnsNotFound(t *testing.T) {
db := newTestVerificationDB(t)
ctx := context.Background()
accountRef := bson.NewObjectID()
otherAccount := bson.NewObjectID()
raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour))
require.NoError(t, err)
_, err = db.Consume(ctx, otherAccount, model.PurposePasswordReset, "", raw)
require.Error(t, err)
assert.True(t, errors.Is(err, ErrTokenNotFound))
}
func differentOTP(raw string) string {
if raw == "000000" {
return "111111"
}
return "000000"
}
func storedTokenAttempts(t *testing.T, db *verificationDB) int {
t.Helper()
repo := db.Repository.(*memoryTokenRepository)
repo.mu.Lock()
defer repo.mu.Unlock()
require.Len(t, repo.data, 1)
for _, tok := range repo.data {
return tok.Attempts
}
return 0
}