mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
test: add tests for recovery codes event reducers (#11773)
# Which Problems Are Solved Add integration tests for recovery codes event reducers in the RT model. # How the Problems Are Solved Added integration tests for recovery codes event reducers # Additional Changes N/A # Additional Context - Closes https://github.com/zitadel/zitadel/issues/11707 - Todo: add tests for other reducers in the `users_relational` projection. --------- Co-authored-by: Livio Spring <[email protected]>
This commit is contained in:
co-authored by
Livio Spring
parent
282b2486d9
commit
3f258953ce
@@ -424,6 +424,7 @@ func (p *userRelationalProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: user.HumanInviteCheckFailedType,
|
||||
Reduce: p.reduceInviteCheckFailed,
|
||||
},
|
||||
// recovery code reducers
|
||||
{
|
||||
Event: user.HumanRecoveryCodesAddedType,
|
||||
Reduce: p.reduceRecoveryCodesAdded,
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/sql"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestUserRelationalProjection_Reducers(t *testing.T) {
|
||||
handler := &userRelationalProjection{}
|
||||
rawTx, tx := getTransactions(t)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, rawTx.Rollback())
|
||||
})
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
// create instance
|
||||
instanceRepo := repository.InstanceRepository()
|
||||
instanceID := gofakeit.UUID()
|
||||
orgID := gofakeit.UUID()
|
||||
err := instanceRepo.Create(ctx, tx, &domain.Instance{
|
||||
ID: instanceID,
|
||||
Name: "test-instance",
|
||||
DefaultOrgID: orgID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// create org
|
||||
orgRepo := repository.OrganizationRepository()
|
||||
err = orgRepo.Create(ctx, tx, &domain.Organization{
|
||||
InstanceID: instanceID,
|
||||
ID: orgID,
|
||||
Name: "test-org",
|
||||
State: domain.OrgStateActive,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
userRepo := repository.UserRepository()
|
||||
|
||||
// TODO: add tests for other reducers in user_relational.go
|
||||
|
||||
// recovery code reducers
|
||||
t.Run("reduce user.human.mfa.recoverycode.added event", func(t *testing.T) {
|
||||
// create user
|
||||
existingUserID := gofakeit.UUID()
|
||||
existingUserAgg := createUser(t, ctx, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
|
||||
// set `user.human.mfa.recoverycode.added` event
|
||||
recoveryCodes := []string{"code1", "code2", "code3"}
|
||||
recoveryCodesAddedEvent := user.NewHumanRecoveryCodesAddedEvent(
|
||||
ctx,
|
||||
&existingUserAgg.Aggregate,
|
||||
recoveryCodes,
|
||||
nil)
|
||||
|
||||
// reduce the event
|
||||
eventReduced := callReduce(t, ctx, rawTx, handler, recoveryCodesAddedEvent)
|
||||
require.True(t, eventReduced)
|
||||
|
||||
// assert that the recovery codes are stored
|
||||
gotUser, err := userRepo.Get(ctx, tx, database.WithCondition(
|
||||
database.And(
|
||||
userRepo.IDCondition(existingUserID),
|
||||
userRepo.InstanceIDCondition(instanceID),
|
||||
),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, recoveryCodes, gotUser.Human.RecoveryCodes.Codes)
|
||||
assert.Zero(t, gotUser.Human.RecoveryCodes.LastSuccessfullyCheckedAt)
|
||||
assert.Zero(t, gotUser.Human.RecoveryCodes.FailedAttempts)
|
||||
})
|
||||
|
||||
t.Run("reduce user.human.mfa.recoverycode.removed event", func(t *testing.T) {
|
||||
// create user
|
||||
existingUserID := gofakeit.UUID()
|
||||
existingUserAgg := createUser(t, ctx, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
|
||||
// add recovery codes to the user
|
||||
_, err = userRepo.Update(ctx, tx,
|
||||
userRepo.PrimaryKeyCondition(instanceID, existingUserID),
|
||||
userRepo.Human().AddRecoveryCodes([]string{"code1", "code2", "code3"}))
|
||||
require.NoError(t, err)
|
||||
|
||||
// set `user.human.mfa.recoverycode.removed` event
|
||||
recoveryCodesRemovedEvent := user.NewHumanRecoveryCodeRemovedEvent(
|
||||
ctx,
|
||||
&existingUserAgg.Aggregate,
|
||||
nil)
|
||||
|
||||
// reduce the event
|
||||
eventReduced := callReduce(t, ctx, rawTx, handler, recoveryCodesRemovedEvent)
|
||||
require.True(t, eventReduced)
|
||||
|
||||
// assert that the recovery codes are removed
|
||||
gotUser, err := userRepo.Get(ctx, tx, database.WithCondition(
|
||||
database.And(
|
||||
userRepo.IDCondition(existingUserID),
|
||||
userRepo.InstanceIDCondition(instanceID),
|
||||
),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, gotUser.Human.RecoveryCodes.Codes)
|
||||
assert.Zero(t, gotUser.Human.RecoveryCodes.LastSuccessfullyCheckedAt)
|
||||
assert.Zero(t, gotUser.Human.RecoveryCodes.FailedAttempts)
|
||||
})
|
||||
|
||||
t.Run("reduce user.human.mfa.recoverycode.check.succeeded event", func(t *testing.T) {
|
||||
// create user
|
||||
existingUserID := gofakeit.UUID()
|
||||
existingUserAgg := createUser(t, ctx, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
|
||||
// add recovery codes to the user
|
||||
_, err = userRepo.Update(ctx, tx,
|
||||
userRepo.PrimaryKeyCondition(instanceID, existingUserID),
|
||||
userRepo.Human().AddRecoveryCodes([]string{"code1", "code2", "code3"}))
|
||||
require.NoError(t, err)
|
||||
|
||||
// set `user.human.mfa.recoverycode.check.succeeded` event
|
||||
recoveryCodeCheckSucceededEvent := user.NewHumanRecoveryCodeCheckSucceededEvent(
|
||||
ctx,
|
||||
&existingUserAgg.Aggregate,
|
||||
"code1",
|
||||
nil)
|
||||
|
||||
// reduce the event
|
||||
eventReduced := callReduce(t, ctx, rawTx, handler, recoveryCodeCheckSucceededEvent)
|
||||
require.True(t, eventReduced)
|
||||
|
||||
// assert that the recovery code is removed and the last_successfully_checked_at timestamp is set
|
||||
gotUser, err := userRepo.Get(ctx, tx, database.WithCondition(
|
||||
database.And(
|
||||
userRepo.IDCondition(existingUserID),
|
||||
userRepo.InstanceIDCondition(instanceID),
|
||||
),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, []string{"code2", "code3"}, gotUser.Human.RecoveryCodes.Codes)
|
||||
assert.NotZero(t, gotUser.Human.RecoveryCodes.LastSuccessfullyCheckedAt)
|
||||
assert.Zero(t, gotUser.Human.RecoveryCodes.FailedAttempts)
|
||||
})
|
||||
|
||||
t.Run("reduce user.human.mfa.recoverycode.check.failed event", func(t *testing.T) {
|
||||
// create user
|
||||
existingUserID := gofakeit.UUID()
|
||||
existingUserAgg := createUser(t, ctx, tx, userRepo, instanceID, orgID, existingUserID)
|
||||
|
||||
// add recovery codes to the user
|
||||
_, err = userRepo.Update(ctx, tx,
|
||||
userRepo.PrimaryKeyCondition(instanceID, existingUserID),
|
||||
userRepo.Human().AddRecoveryCodes([]string{"code1", "code2", "code3"}))
|
||||
require.NoError(t, err)
|
||||
|
||||
// set `user.human.mfa.recoverycode.check.failed` event
|
||||
recoveryCodeCheckFailedEvent := user.NewHumanRecoveryCodeCheckFailedEvent(
|
||||
ctx,
|
||||
&existingUserAgg.Aggregate,
|
||||
nil,
|
||||
)
|
||||
|
||||
// reduce the event
|
||||
eventReduced := callReduce(t, ctx, rawTx, handler, recoveryCodeCheckFailedEvent)
|
||||
require.True(t, eventReduced)
|
||||
|
||||
// assert that the recovery code failed_attempts is incremented
|
||||
gotUser, err := userRepo.Get(ctx, tx, database.WithCondition(
|
||||
database.And(
|
||||
userRepo.IDCondition(existingUserID),
|
||||
userRepo.InstanceIDCondition(instanceID),
|
||||
),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, []string{"code1", "code2", "code3"}, gotUser.Human.RecoveryCodes.Codes)
|
||||
assert.Zero(t, gotUser.Human.RecoveryCodes.LastSuccessfullyCheckedAt)
|
||||
assert.Equal(t, uint8(1), gotUser.Human.RecoveryCodes.FailedAttempts)
|
||||
})
|
||||
}
|
||||
|
||||
func createUser(t *testing.T,
|
||||
ctx context.Context,
|
||||
tx *sql.Transaction,
|
||||
userRepo domain.UserRepository,
|
||||
instanceID,
|
||||
orgID,
|
||||
userID string,
|
||||
) *user.Aggregate {
|
||||
userAgg := user.NewAggregate(userID, orgID)
|
||||
userAgg.InstanceID = instanceID
|
||||
|
||||
err := userRepo.Create(ctx, tx, &domain.User{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: orgID,
|
||||
ID: userID,
|
||||
Username: userID + gofakeit.Username(),
|
||||
State: domain.UserStateActive,
|
||||
Human: &domain.HumanUser{
|
||||
FirstName: gofakeit.FirstName(),
|
||||
LastName: gofakeit.LastName(),
|
||||
Email: domain.HumanEmail{
|
||||
Address: gofakeit.Email(),
|
||||
VerifiedAt: time.Now(),
|
||||
},
|
||||
},
|
||||
Metadata: []*domain.Metadata{
|
||||
{
|
||||
Key: "key1",
|
||||
Value: []byte("value1"),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return userAgg
|
||||
}
|
||||
Reference in New Issue
Block a user