mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
* fix(crypto): use authenticated encryption for IDP intent tokens IDP intent tokens were encrypted with AES-CFB which provides no integrity protection, making them malleable. Switch to A256GCM via the existing AuthAlgorithm interface (EncryptToken/DecryptToken), the same approach already used for OIDC and session tokens. The idpConfigEncryption key type is widened from EncryptionAlgorithm to AuthEncryptionAlgorithm so that data-at-rest encryption (IDP client secrets, LDAP bind passwords, SAML keys) continues to use the CFB path unmodified. Co-authored-by: Cursor <[email protected]> * fix(proto): raise IDP intent token max length to 500 for authenticated tokens The switch to authenticated A256GCM (JWE compact serialization) makes IDP intent tokens longer (~270 chars) than the previous AES-CFB base64 output. Raise the max_len/max_length validation and OpenAPI constraints on every idp_intent_token field from 200 to 500 so the new tokens are accepted: user v2/v2beta (idp + user_service), session v2/v2beta, and resources user v3alpha. Co-Authored-By: Claude Opus 4.8 <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: Claude Opus 4.8 <[email protected]>
858 lines
35 KiB
Go
858 lines
35 KiB
Go
package domain_test
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/muhlemmer/gu"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
|
domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
|
|
noopdb "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/noop"
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/repository/idpintent"
|
|
"github.com/zitadel/zitadel/internal/repository/session"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
func TestIDPIntentCheckCommand_Events(t *testing.T) {
|
|
t.Parallel()
|
|
tt := []struct {
|
|
name string
|
|
command *domain.IDPIntentCheckCommand
|
|
expectedEvents []eventstore.Command
|
|
}{
|
|
{
|
|
name: "nil CheckIntent returns nil events",
|
|
command: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: nil,
|
|
IsCheckComplete: true,
|
|
},
|
|
},
|
|
{
|
|
name: "isCheckComplete false returns nil events",
|
|
command: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{},
|
|
},
|
|
},
|
|
{
|
|
name: "valid command returns events",
|
|
command: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123"},
|
|
SessionID: "session-456",
|
|
InstanceID: "instance-789",
|
|
IsCheckComplete: true,
|
|
IntentLastVerified: time.Now(),
|
|
},
|
|
|
|
expectedEvents: []eventstore.Command{
|
|
session.NewIntentCheckedEvent(t.Context(), &session.NewAggregate("session-456", "instance-789").Aggregate, time.Now()),
|
|
idpintent.NewConsumedEvent(t.Context(), &idpintent.NewAggregate("intent-123", "").Aggregate),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Test
|
|
events, err := tc.command.Events(t.Context(), &domain.InvokeOpts{})
|
|
|
|
// Verify
|
|
assert.NoError(t, err)
|
|
|
|
require.Len(t, events, len(tc.expectedEvents))
|
|
|
|
for i, expectedType := range tc.expectedEvents {
|
|
require.IsType(t, expectedType, events[i])
|
|
switch expectedType.(type) {
|
|
case *session.IntentCheckedEvent:
|
|
actualAssertedType, ok := events[i].(*session.IntentCheckedEvent)
|
|
require.True(t, ok)
|
|
assert.Equal(t, tc.command.IntentLastVerified, actualAssertedType.CheckedAt)
|
|
case *idpintent.ConsumedEvent:
|
|
_, ok := events[i].(*idpintent.ConsumedEvent)
|
|
require.True(t, ok)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIDPIntentCheckCommand_Validate(t *testing.T) {
|
|
t.Parallel()
|
|
getErr := errors.New("get error")
|
|
notFoundErr := database.NewNoRowFoundError(nil)
|
|
decryptionError := errors.New("decryption error")
|
|
invalidTokenEncoding := base64.RawURLEncoding.EncodeToString([]byte("invalid-token"))
|
|
validTokenEncoding := base64.RawURLEncoding.EncodeToString([]byte("token-123"))
|
|
|
|
tt := []struct {
|
|
testName string
|
|
sessionRepo func(ctrl *gomock.Controller) domain.SessionRepository
|
|
idpIntentRepo func(ctrl *gomock.Controller) domain.IDPIntentRepository
|
|
userRepo func(ctrl *gomock.Controller) domain.UserRepository
|
|
encryptionAlgorithm func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm
|
|
cmd *domain.IDPIntentCheckCommand
|
|
expectedError error
|
|
expectedUser domain.User
|
|
}{
|
|
{
|
|
testName: "when CheckIntent is nil should return no error",
|
|
cmd: &domain.IDPIntentCheckCommand{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "when session ID is not set should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "",
|
|
InstanceID: "instance-1",
|
|
},
|
|
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-5Y8pb4", "Errors.Missing.SessionID"),
|
|
},
|
|
{
|
|
testName: "when instance ID is not set should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "",
|
|
},
|
|
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-Q4YFIq", "Errors.Missing.InstanceID"),
|
|
},
|
|
{
|
|
testName: "when retrieving session fails should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(nil, getErr)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowInternal(getErr, "DOM-EhIgey", "failed fetching session"),
|
|
},
|
|
{
|
|
testName: "when session not found should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(nil, notFoundErr)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowNotFound(notFoundErr, "DOM-EhIgey", "session not found"),
|
|
},
|
|
{
|
|
testName: "when session userID is empty should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: ""}, nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-IJcVkV", "Errors.User.UserIDMissing"),
|
|
},
|
|
{
|
|
testName: "when token verification fails should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: invalidTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("", decryptionError)
|
|
return cryptoAlg
|
|
},
|
|
expectedError: zerrors.ThrowPermissionDenied(decryptionError, "CRYPTO-Sf4gt", "Errors.Intent.InvalidToken"),
|
|
},
|
|
{
|
|
testName: "when retrieving intent fails should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(nil, getErr)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowInternal(getErr, "DOM-5XkWJV", "failed fetching intent"),
|
|
},
|
|
{
|
|
testName: "when intent not found should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(nil, notFoundErr)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowNotFound(notFoundErr, "DOM-5XkWJV", "intent not found"),
|
|
},
|
|
{
|
|
testName: "when intent state is not succeeded should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateFailed, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour))}, nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-0UKHku", "Errors.Intent.NotSucceeded"),
|
|
},
|
|
{
|
|
testName: "when intent is expired should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(-time.Hour))}, nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-kDR1XK", "Errors.Intent.Expired"),
|
|
},
|
|
{
|
|
testName: "when intent expiration is nil should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: nil}, nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-kDR1XK", "Errors.Intent.Expired"),
|
|
},
|
|
{
|
|
testName: "when retrieving user fails should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: ""}, nil)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
|
|
Return(nil, getErr)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowInternal(getErr, "DOM-Vnx2G9", "failed fetching user"),
|
|
},
|
|
{
|
|
testName: "when user is not found should return not found error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: ""}, nil)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
|
|
Return(nil, notFoundErr)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowNotFound(notFoundErr, "DOM-Vnx2G9", "user not found"),
|
|
},
|
|
{
|
|
testName: "when user is not human should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: ""}, nil)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
|
|
Return(&domain.User{ID: "user-1", State: domain.UserStateActive, Human: nil}, nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowInternal(nil, "DOM-FkX5lZ", "user not human"),
|
|
},
|
|
{
|
|
testName: "when intent has user and it matches session user should return no error and set fetched user",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
|
|
Return(&domain.User{
|
|
ID: "user-1",
|
|
State: domain.UserStateActive,
|
|
Human: &domain.HumanUser{IdentityProviderLinks: []*domain.IdentityProviderLink{{ProviderID: "idp-123"}}},
|
|
}, nil)
|
|
return repo
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
expectedError: nil,
|
|
expectedUser: domain.User{
|
|
ID: "user-1",
|
|
State: domain.UserStateActive,
|
|
Human: &domain.HumanUser{
|
|
IdentityProviderLinks: []*domain.IdentityProviderLink{
|
|
{ProviderID: "idp-123"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
testName: "when intent has user but it does not match session user should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
|
|
Return(&domain.User{
|
|
ID: "user-1",
|
|
State: domain.UserStateActive,
|
|
Human: &domain.HumanUser{IdentityProviderLinks: []*domain.IdentityProviderLink{{ProviderID: "idp-123"}}},
|
|
}, nil)
|
|
return repo
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: "user-2"}, nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-FLdnLH", "Errors.Intent.OtherUser"),
|
|
},
|
|
{
|
|
testName: "when user has no matching IDP link should return error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: "", IDPID: "idp-123"}, nil)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
|
|
Return(&domain.User{
|
|
ID: "user-1",
|
|
State: domain.UserStateActive,
|
|
Human: &domain.HumanUser{IdentityProviderLinks: []*domain.IdentityProviderLink{{ProviderID: "idp-456"}}},
|
|
}, nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowPreconditionFailed(nil, "DOM-XuNkt7", "Errors.Intent.OtherUser"),
|
|
},
|
|
{
|
|
testName: "when all validations pass should return no error",
|
|
cmd: &domain.IDPIntentCheckCommand{
|
|
CheckIntent: &domain.CheckIDPIntentType{ID: "intent-123", Token: validTokenEncoding},
|
|
SessionID: "session-1",
|
|
InstanceID: "instance-1",
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "session-1")))).
|
|
Return(&domain.Session{ID: "session-1", UserID: "user-1"}, nil)
|
|
return repo
|
|
},
|
|
encryptionAlgorithm: func(ctrl *gomock.Controller) crypto.AuthEncryptionAlgorithm {
|
|
cryptoAlg := crypto.NewMockAuthEncryptionAlgorithm(ctrl)
|
|
cryptoAlg.EXPECT().
|
|
DecryptToken(gomock.Any()).
|
|
Times(1).
|
|
Return("intent-123", nil)
|
|
return cryptoAlg
|
|
},
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "intent-123")))).
|
|
Return(&domain.IDPIntent{ID: "intent-123", State: domain.IDPIntentStateSucceeded, ExpiresAt: gu.Ptr(time.Now().Add(time.Hour)), UserID: "", IDPID: "idp-123"}, nil)
|
|
return repo
|
|
},
|
|
userRepo: func(ctrl *gomock.Controller) domain.UserRepository {
|
|
repo := domainmock.NewUserRepo(ctrl)
|
|
repo.EXPECT().
|
|
Get(gomock.Any(), gomock.Any(), dbmock.QueryOptions(database.WithCondition(repo.PrimaryKeyCondition("instance-1", "user-1")))).
|
|
Return(&domain.User{
|
|
ID: "user-1",
|
|
State: domain.UserStateActive,
|
|
Human: &domain.HumanUser{
|
|
IdentityProviderLinks: []*domain.IdentityProviderLink{
|
|
{ProviderID: "idp-123"},
|
|
},
|
|
},
|
|
}, nil)
|
|
return repo
|
|
},
|
|
expectedError: nil,
|
|
expectedUser: domain.User{
|
|
ID: "user-1",
|
|
State: domain.UserStateActive,
|
|
Human: &domain.HumanUser{
|
|
IdentityProviderLinks: []*domain.IdentityProviderLink{
|
|
{ProviderID: "idp-123"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
t.Run(tc.testName, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Given
|
|
ctrl := gomock.NewController(t)
|
|
ctx := authz.NewMockContext(tc.cmd.InstanceID, "", "")
|
|
|
|
opts := &domain.InvokeOpts{
|
|
Invoker: domain.NewTransactionInvoker(nil),
|
|
}
|
|
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
|
|
|
|
if tc.sessionRepo != nil {
|
|
domain.WithSessionRepo(tc.sessionRepo(ctrl))(opts)
|
|
}
|
|
if tc.userRepo != nil {
|
|
domain.WithUserRepo(tc.userRepo(ctrl))(opts)
|
|
}
|
|
if tc.idpIntentRepo != nil {
|
|
domain.WithIDPIntentRepo(tc.idpIntentRepo(ctrl))(opts)
|
|
}
|
|
if tc.encryptionAlgorithm != nil {
|
|
tc.cmd.EncAlgo = tc.encryptionAlgorithm(ctrl)
|
|
}
|
|
|
|
// Test
|
|
err := tc.cmd.Validate(ctx, opts)
|
|
|
|
// Verify
|
|
assert.ErrorIs(t, err, tc.expectedError)
|
|
assert.Equal(t, tc.expectedUser, tc.cmd.FetchedUser)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIDPIntentCheckCommand_Execute(t *testing.T) {
|
|
t.Parallel()
|
|
deleteErr := errors.New("delete error")
|
|
updateErr := errors.New("update error")
|
|
|
|
tt := []struct {
|
|
testName string
|
|
idpIntentRepo func(ctrl *gomock.Controller) domain.IDPIntentRepository
|
|
sessionRepo func(ctrl *gomock.Controller) domain.SessionRepository
|
|
cmd *domain.IDPIntentCheckCommand
|
|
expectedError error
|
|
expectComplete bool
|
|
}{
|
|
{
|
|
testName: "when CheckIntent is nil should return no error and not mark complete",
|
|
cmd: &domain.IDPIntentCheckCommand{},
|
|
expectedError: nil,
|
|
expectComplete: false,
|
|
},
|
|
{
|
|
testName: "when intent deletion fails should return error",
|
|
cmd: domain.NewIDPIntentCheckCommand(&domain.CheckIDPIntentType{ID: "intent-123"}, "session-1", "instance-1", nil),
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Delete(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "intent-123")).
|
|
Return(int64(0), deleteErr)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowInternal(deleteErr, "DOM-j1s5Eu", "failed deleting IDP intent"),
|
|
expectComplete: false,
|
|
},
|
|
{
|
|
testName: "when intent deletion returns unexpected row count should return error",
|
|
cmd: domain.NewIDPIntentCheckCommand(&domain.CheckIDPIntentType{ID: "intent-123"}, "session-1", "instance-1", nil),
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Delete(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "intent-123")).
|
|
Return(int64(0), nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowInternal(domain.NewRowsReturnedMismatchError(1, 0), "DOM-3CBpdB", "unexpected number of rows deleted"),
|
|
expectComplete: false,
|
|
},
|
|
{
|
|
testName: "when session update fails should return internal error",
|
|
cmd: domain.NewIDPIntentCheckCommand(&domain.CheckIDPIntentType{ID: "intent-123"}, "session-1", "instance-1", nil),
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Delete(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "intent-123")).
|
|
Return(int64(1), nil)
|
|
return repo
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Update(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "session-1"), repo.SetFactor(&domain.SessionFactorIdentityProviderIntent{LastVerifiedAt: time.Now()})).
|
|
Times(1).
|
|
Return(int64(0), updateErr)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowInternal(updateErr, "DOM-pec0al", "failed updating session"),
|
|
},
|
|
{
|
|
testName: "when session update returns no rows should return not found error",
|
|
cmd: domain.NewIDPIntentCheckCommand(&domain.CheckIDPIntentType{ID: "intent-123"}, "session-1", "instance-1", nil),
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Delete(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "intent-123")).
|
|
Return(int64(1), nil)
|
|
return repo
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Update(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "session-1"), repo.SetFactor(&domain.SessionFactorIdentityProviderIntent{LastVerifiedAt: time.Now()})).
|
|
Times(1).
|
|
Return(int64(0), nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowNotFound(nil, "DOM-CopO4e", "session not found"),
|
|
},
|
|
{
|
|
testName: "when session update returns no too many rows should return internal error",
|
|
cmd: domain.NewIDPIntentCheckCommand(&domain.CheckIDPIntentType{ID: "intent-123"}, "session-1", "instance-1", nil),
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Delete(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "intent-123")).
|
|
Return(int64(1), nil)
|
|
return repo
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Update(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "session-1"), repo.SetFactor(&domain.SessionFactorIdentityProviderIntent{LastVerifiedAt: time.Now()})).
|
|
Times(1).
|
|
Return(int64(2), nil)
|
|
return repo
|
|
},
|
|
expectedError: zerrors.ThrowInternal(domain.NewMultipleObjectsUpdatedError(1, 2), "DOM-mlbibw", "unexpected number of rows updated"),
|
|
},
|
|
{
|
|
testName: "when session update returns 1 row updated should return no error",
|
|
cmd: domain.NewIDPIntentCheckCommand(&domain.CheckIDPIntentType{ID: "intent-123"}, "session-1", "instance-1", nil),
|
|
idpIntentRepo: func(ctrl *gomock.Controller) domain.IDPIntentRepository {
|
|
repo := domainmock.NewIDPIntentRepo(ctrl)
|
|
repo.EXPECT().
|
|
Delete(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "intent-123")).
|
|
Return(int64(1), nil)
|
|
return repo
|
|
},
|
|
sessionRepo: func(ctrl *gomock.Controller) domain.SessionRepository {
|
|
repo := domainmock.NewSessionRepo(ctrl)
|
|
repo.EXPECT().
|
|
Update(gomock.Any(), gomock.Any(), repo.PrimaryKeyCondition("instance-1", "session-1"), repo.SetFactor(&domain.SessionFactorIdentityProviderIntent{LastVerifiedAt: time.Now()})).
|
|
Times(1).
|
|
Return(int64(1), nil)
|
|
return repo
|
|
},
|
|
expectComplete: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
t.Run(tc.testName, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Given
|
|
ctrl := gomock.NewController(t)
|
|
ctx := authz.NewMockContext(tc.cmd.InstanceID, "", "")
|
|
|
|
opts := &domain.InvokeOpts{
|
|
Invoker: domain.NewTransactionInvoker(nil),
|
|
}
|
|
domain.WithQueryExecutor(new(noopdb.Pool))(opts)
|
|
|
|
if tc.idpIntentRepo != nil {
|
|
domain.WithIDPIntentRepo(tc.idpIntentRepo(ctrl))(opts)
|
|
}
|
|
if tc.sessionRepo != nil {
|
|
domain.WithSessionRepo(tc.sessionRepo(ctrl))(opts)
|
|
}
|
|
|
|
// Test
|
|
err := tc.cmd.Execute(ctx, opts)
|
|
|
|
// Verify
|
|
assert.ErrorIs(t, err, tc.expectedError)
|
|
assert.Equal(t, tc.expectComplete, tc.cmd.IsCheckComplete)
|
|
if tc.expectedError == nil && tc.expectComplete {
|
|
assert.NotZero(t, tc.cmd.IntentLastVerified)
|
|
}
|
|
})
|
|
}
|
|
}
|