2023-08-24 11:41:52 +02:00
package command
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
2023-11-22 12:56:43 +02:00
"go.uber.org/mock/gomock"
2023-08-24 11:41:52 +02:00
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
2024-09-26 09:14:33 +02:00
"github.com/zitadel/zitadel/internal/notification/senders"
2024-10-07 07:12:44 +02:00
"github.com/zitadel/zitadel/internal/notification/senders/mock"
2024-05-31 00:08:48 +02:00
"github.com/zitadel/zitadel/internal/repository/org"
2023-08-24 11:41:52 +02:00
"github.com/zitadel/zitadel/internal/repository/session"
"github.com/zitadel/zitadel/internal/repository/user"
2023-12-08 16:30:55 +02:00
"github.com/zitadel/zitadel/internal/zerrors"
2023-08-24 11:41:52 +02:00
)
func TestCommands_CreateOTPSMSChallengeReturnCode ( t * testing . T ) {
type fields struct {
2024-09-26 09:14:33 +02:00
userID string
eventstore func ( * testing . T ) * eventstore . Eventstore
createPhoneCode encryptedCodeGeneratorWithDefaultFunc
2023-08-24 11:41:52 +02:00
}
type res struct {
err error
returnCode string
commands [ ] eventstore . Command
}
tests := [ ] struct {
name string
fields fields
res res
} {
{
name : "userID missing, precondition error" ,
fields : fields {
userID : "" ,
eventstore : expectEventstore ( ) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-JKL3g" , "Errors.User.UserIDMissing" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "otp not ready, precondition error" ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter ( ) ,
) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-BJ2g3" , "Errors.User.MFA.OTP.NotReady" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "generate code" ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher (
user . NewHumanOTPSMSAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org" ) . Aggregate ) ,
) ,
) ,
) ,
2024-09-26 09:14:33 +02:00
createPhoneCode : mockEncryptedCodeGeneratorWithDefault ( "1234567" , 5 * time . Minute ) ,
2023-08-24 11:41:52 +02:00
} ,
res : res {
returnCode : "1234567" ,
commands : [ ] eventstore . Command {
session . NewOTPSMSChallengedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
& crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "1234567" ) ,
} ,
5 * time . Minute ,
true ,
2024-09-26 09:14:33 +02:00
"" ,
2023-08-24 11:41:52 +02:00
) ,
} ,
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c := & Commands {
// config will not be actively used for the test (is only for default),
// but not providing it would result in a nil pointer
defaultSecretGenerators : & SecretGenerators {
OTPSMS : emptyConfig ,
} ,
}
var dst string
cmd := c . CreateOTPSMSChallengeReturnCode ( & dst )
sessionModel := & SessionWriteModel {
UserID : tt . fields . userID ,
UserCheckedAt : testNow ,
State : domain . SessionStateActive ,
aggregate : & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
}
cmds := & SessionCommands {
sessionCommands : [ ] SessionCommand { cmd } ,
sessionWriteModel : sessionModel ,
eventstore : tt . fields . eventstore ( t ) ,
2024-09-26 09:14:33 +02:00
createPhoneCode : tt . fields . createPhoneCode ,
2023-08-24 11:41:52 +02:00
now : time . Now ,
}
2024-05-31 00:08:48 +02:00
gotCmds , err := cmd ( context . Background ( ) , cmds )
2023-08-24 11:41:52 +02:00
assert . ErrorIs ( t , err , tt . res . err )
2024-05-31 00:08:48 +02:00
assert . Empty ( t , gotCmds )
2023-08-24 11:41:52 +02:00
assert . Equal ( t , tt . res . returnCode , dst )
assert . Equal ( t , tt . res . commands , cmds . eventCommands )
} )
}
}
func TestCommands_CreateOTPSMSChallenge ( t * testing . T ) {
type fields struct {
2024-09-26 09:14:33 +02:00
userID string
eventstore func ( * testing . T ) * eventstore . Eventstore
createPhoneCode encryptedCodeGeneratorWithDefaultFunc
2023-08-24 11:41:52 +02:00
}
type res struct {
err error
commands [ ] eventstore . Command
}
tests := [ ] struct {
name string
fields fields
res res
} {
{
name : "userID missing, precondition error" ,
fields : fields {
userID : "" ,
eventstore : expectEventstore ( ) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-JKL3g" , "Errors.User.UserIDMissing" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "otp not ready, precondition error" ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter ( ) ,
) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-BJ2g3" , "Errors.User.MFA.OTP.NotReady" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "generate code" ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher (
user . NewHumanOTPSMSAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org" ) . Aggregate ) ,
) ,
) ,
) ,
2024-09-26 09:14:33 +02:00
createPhoneCode : mockEncryptedCodeGeneratorWithDefault ( "1234567" , 5 * time . Minute ) ,
2023-08-24 11:41:52 +02:00
} ,
res : res {
commands : [ ] eventstore . Command {
session . NewOTPSMSChallengedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
& crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "1234567" ) ,
} ,
5 * time . Minute ,
false ,
2024-09-26 09:14:33 +02:00
"" ,
) ,
} ,
} ,
} ,
{
name : "generate code externally" ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher (
user . NewHumanOTPSMSAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org" ) . Aggregate ) ,
) ,
) ,
) ,
createPhoneCode : mockEncryptedCodeGeneratorWithDefaultExternal ( "generatorID" ) ,
} ,
res : res {
commands : [ ] eventstore . Command {
session . NewOTPSMSChallengedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
nil ,
0 ,
false ,
"generatorID" ,
2023-08-24 11:41:52 +02:00
) ,
} ,
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c := & Commands {
// config will not be actively used for the test (is only for default),
// but not providing it would result in a nil pointer
defaultSecretGenerators : & SecretGenerators {
OTPSMS : emptyConfig ,
} ,
}
cmd := c . CreateOTPSMSChallenge ( )
sessionModel := & SessionWriteModel {
UserID : tt . fields . userID ,
UserCheckedAt : testNow ,
State : domain . SessionStateActive ,
aggregate : & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
}
cmds := & SessionCommands {
sessionCommands : [ ] SessionCommand { cmd } ,
sessionWriteModel : sessionModel ,
eventstore : tt . fields . eventstore ( t ) ,
2024-09-26 09:14:33 +02:00
createPhoneCode : tt . fields . createPhoneCode ,
2023-08-24 11:41:52 +02:00
now : time . Now ,
}
2024-05-31 00:08:48 +02:00
gotCmds , err := cmd ( context . Background ( ) , cmds )
2023-08-24 11:41:52 +02:00
assert . ErrorIs ( t , err , tt . res . err )
2024-05-31 00:08:48 +02:00
assert . Empty ( t , gotCmds )
2023-08-24 11:41:52 +02:00
assert . Equal ( t , tt . res . commands , cmds . eventCommands )
} )
}
}
func TestCommands_OTPSMSSent ( t * testing . T ) {
type fields struct {
eventstore func ( * testing . T ) * eventstore . Eventstore
}
type args struct {
ctx context . Context
sessionID string
resourceOwner string
2024-09-26 09:14:33 +02:00
generatorInfo * senders . CodeGeneratorInfo
2023-08-24 11:41:52 +02:00
}
tests := [ ] struct {
name string
fields fields
args args
wantErr error
} {
{
name : "not challenged, precondition error" ,
fields : fields {
eventstore : expectEventstore (
expectFilter ( ) ,
) ,
} ,
args : args {
ctx : context . Background ( ) ,
sessionID : "sessionID" ,
resourceOwner : "instanceID" ,
2024-09-26 09:14:33 +02:00
generatorInfo : & senders . CodeGeneratorInfo { } ,
2023-08-24 11:41:52 +02:00
} ,
2023-12-08 16:30:55 +02:00
wantErr : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-G3t31" , "Errors.User.Code.NotFound" ) ,
2023-08-24 11:41:52 +02:00
} ,
{
name : "challenged and sent" ,
fields : fields {
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher (
session . NewOTPSMSChallengedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
& crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "1234567" ) ,
} ,
5 * time . Minute ,
false ,
2024-09-26 09:14:33 +02:00
"" ,
2023-08-24 11:41:52 +02:00
) ,
) ,
) ,
expectPush (
2024-09-26 09:14:33 +02:00
session . NewOTPSMSSentEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate , & senders . CodeGeneratorInfo { } ) ,
2023-08-24 11:41:52 +02:00
) ,
) ,
} ,
args : args {
ctx : context . Background ( ) ,
sessionID : "sessionID" ,
resourceOwner : "instanceID" ,
2024-09-26 09:14:33 +02:00
generatorInfo : & senders . CodeGeneratorInfo { } ,
} ,
wantErr : nil ,
} ,
{
name : "challenged and sent (externally)" ,
fields : fields {
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher (
session . NewOTPSMSChallengedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
nil ,
0 ,
false ,
"" ,
) ,
) ,
) ,
expectPush (
session . NewOTPSMSSentEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate , & senders . CodeGeneratorInfo { ID : "generatorID" , VerificationID : "verificationID" } ) ,
) ,
) ,
} ,
args : args {
ctx : context . Background ( ) ,
sessionID : "sessionID" ,
resourceOwner : "instanceID" ,
generatorInfo : & senders . CodeGeneratorInfo {
ID : "generatorID" ,
VerificationID : "verificationID" ,
} ,
2023-08-24 11:41:52 +02:00
} ,
wantErr : nil ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c := & Commands {
eventstore : tt . fields . eventstore ( t ) ,
}
2024-09-26 09:14:33 +02:00
err := c . OTPSMSSent ( tt . args . ctx , tt . args . sessionID , tt . args . resourceOwner , tt . args . generatorInfo )
2023-08-24 11:41:52 +02:00
assert . ErrorIs ( t , err , tt . wantErr )
} )
}
}
func TestCommands_CreateOTPEmailChallengeURLTemplate ( t * testing . T ) {
type fields struct {
userID string
eventstore func ( * testing . T ) * eventstore . Eventstore
2024-04-05 12:35:49 +03:00
createCode encryptedCodeWithDefaultFunc
2023-08-24 11:41:52 +02:00
}
type args struct {
urlTmpl string
}
type res struct {
templateError error
err error
commands [ ] eventstore . Command
}
tests := [ ] struct {
name string
fields fields
args args
res res
} {
{
name : "invalid template, precondition error" ,
args : args {
urlTmpl : "https://example.com/mfa/email?userID={{.UserID}}&code={{.InvalidField}}" ,
} ,
fields : fields {
eventstore : expectEventstore ( ) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
templateError : zerrors . ThrowInvalidArgument ( nil , "DOMAIN-ieYa7" , "Errors.User.InvalidURLTemplate" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "userID missing, precondition error" ,
args : args {
urlTmpl : "https://example.com/mfa/email?userID={{.UserID}}&code={{.Code}}&lang={{.PreferredLanguage}}" ,
} ,
fields : fields {
eventstore : expectEventstore ( ) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-JK3gp" , "Errors.User.UserIDMissing" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "otp not ready, precondition error" ,
args : args {
urlTmpl : "https://example.com/mfa/email?userID={{.UserID}}&code={{.Code}}&lang={{.PreferredLanguage}}" ,
} ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter ( ) ,
) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-JKLJ3" , "Errors.User.MFA.OTP.NotReady" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "generate code" ,
args : args {
urlTmpl : "https://example.com/mfa/email?userID={{.UserID}}&code={{.Code}}&lang={{.PreferredLanguage}}" ,
} ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher (
user . NewHumanOTPEmailAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org" ) . Aggregate ) ,
) ,
) ,
) ,
2024-04-05 12:35:49 +03:00
createCode : mockEncryptedCodeWithDefault ( "1234567" , 5 * time . Minute ) ,
2023-08-24 11:41:52 +02:00
} ,
res : res {
commands : [ ] eventstore . Command {
session . NewOTPEmailChallengedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
& crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "1234567" ) ,
} ,
5 * time . Minute ,
false ,
"https://example.com/mfa/email?userID={{.UserID}}&code={{.Code}}&lang={{.PreferredLanguage}}" ,
) ,
} ,
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c := & Commands {
// config will not be actively used for the test (is only for default),
// but not providing it would result in a nil pointer
defaultSecretGenerators : & SecretGenerators {
OTPEmail : emptyConfig ,
} ,
}
cmd , err := c . CreateOTPEmailChallengeURLTemplate ( tt . args . urlTmpl )
assert . ErrorIs ( t , err , tt . res . templateError )
if tt . res . templateError != nil {
return
}
sessionModel := & SessionWriteModel {
UserID : tt . fields . userID ,
UserCheckedAt : testNow ,
State : domain . SessionStateActive ,
aggregate : & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
}
cmds := & SessionCommands {
sessionCommands : [ ] SessionCommand { cmd } ,
sessionWriteModel : sessionModel ,
eventstore : tt . fields . eventstore ( t ) ,
createCode : tt . fields . createCode ,
now : time . Now ,
}
2024-05-31 00:08:48 +02:00
gotCmds , err := cmd ( context . Background ( ) , cmds )
2023-08-24 11:41:52 +02:00
assert . ErrorIs ( t , err , tt . res . err )
2024-05-31 00:08:48 +02:00
assert . Empty ( t , gotCmds )
2023-08-24 11:41:52 +02:00
assert . Equal ( t , tt . res . commands , cmds . eventCommands )
} )
}
}
func TestCommands_CreateOTPEmailChallengeReturnCode ( t * testing . T ) {
type fields struct {
userID string
eventstore func ( * testing . T ) * eventstore . Eventstore
2024-04-05 12:35:49 +03:00
createCode encryptedCodeWithDefaultFunc
2023-08-24 11:41:52 +02:00
}
type res struct {
err error
returnCode string
commands [ ] eventstore . Command
}
tests := [ ] struct {
name string
fields fields
res res
} {
{
name : "userID missing, precondition error" ,
fields : fields {
eventstore : expectEventstore ( ) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-JK3gp" , "Errors.User.UserIDMissing" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "otp not ready, precondition error" ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter ( ) ,
) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-JKLJ3" , "Errors.User.MFA.OTP.NotReady" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "generate code" ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher (
user . NewHumanOTPEmailAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org" ) . Aggregate ) ,
) ,
) ,
) ,
2024-04-05 12:35:49 +03:00
createCode : mockEncryptedCodeWithDefault ( "1234567" , 5 * time . Minute ) ,
2023-08-24 11:41:52 +02:00
} ,
res : res {
returnCode : "1234567" ,
commands : [ ] eventstore . Command {
session . NewOTPEmailChallengedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
& crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "1234567" ) ,
} ,
5 * time . Minute ,
true ,
"" ,
) ,
} ,
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c := & Commands {
// config will not be actively used for the test (is only for default),
// but not providing it would result in a nil pointer
defaultSecretGenerators : & SecretGenerators {
OTPEmail : emptyConfig ,
} ,
}
var dst string
cmd := c . CreateOTPEmailChallengeReturnCode ( & dst )
sessionModel := & SessionWriteModel {
UserID : tt . fields . userID ,
UserCheckedAt : testNow ,
State : domain . SessionStateActive ,
aggregate : & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
}
cmds := & SessionCommands {
sessionCommands : [ ] SessionCommand { cmd } ,
sessionWriteModel : sessionModel ,
eventstore : tt . fields . eventstore ( t ) ,
createCode : tt . fields . createCode ,
now : time . Now ,
}
2024-05-31 00:08:48 +02:00
gotCmds , err := cmd ( context . Background ( ) , cmds )
2023-08-24 11:41:52 +02:00
assert . ErrorIs ( t , err , tt . res . err )
2024-05-31 00:08:48 +02:00
assert . Empty ( t , gotCmds )
2023-08-24 11:41:52 +02:00
assert . Equal ( t , tt . res . returnCode , dst )
assert . Equal ( t , tt . res . commands , cmds . eventCommands )
} )
}
}
func TestCommands_CreateOTPEmailChallenge ( t * testing . T ) {
type fields struct {
userID string
eventstore func ( * testing . T ) * eventstore . Eventstore
2024-04-05 12:35:49 +03:00
createCode encryptedCodeWithDefaultFunc
2023-08-24 11:41:52 +02:00
}
type res struct {
err error
commands [ ] eventstore . Command
}
tests := [ ] struct {
name string
fields fields
res res
} {
{
name : "userID missing, precondition error" ,
fields : fields {
eventstore : expectEventstore ( ) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-JK3gp" , "Errors.User.UserIDMissing" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "otp not ready, precondition error" ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter ( ) ,
) ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-JKLJ3" , "Errors.User.MFA.OTP.NotReady" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "generate code" ,
fields : fields {
userID : "userID" ,
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher (
user . NewHumanOTPEmailAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org" ) . Aggregate ) ,
) ,
) ,
) ,
2024-04-05 12:35:49 +03:00
createCode : mockEncryptedCodeWithDefault ( "1234567" , 5 * time . Minute ) ,
2023-08-24 11:41:52 +02:00
} ,
res : res {
commands : [ ] eventstore . Command {
session . NewOTPEmailChallengedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
& crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "1234567" ) ,
} ,
5 * time . Minute ,
false ,
"" ,
) ,
} ,
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c := & Commands {
// config will not be actively used for the test (is only for default),
// but not providing it would result in a nil pointer
defaultSecretGenerators : & SecretGenerators {
OTPEmail : emptyConfig ,
} ,
}
cmd := c . CreateOTPEmailChallenge ( )
sessionModel := & SessionWriteModel {
UserID : tt . fields . userID ,
UserCheckedAt : testNow ,
State : domain . SessionStateActive ,
aggregate : & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
}
cmds := & SessionCommands {
sessionCommands : [ ] SessionCommand { cmd } ,
sessionWriteModel : sessionModel ,
eventstore : tt . fields . eventstore ( t ) ,
createCode : tt . fields . createCode ,
now : time . Now ,
}
2024-05-31 00:08:48 +02:00
gotCmds , err := cmd ( context . Background ( ) , cmds )
2023-08-24 11:41:52 +02:00
assert . ErrorIs ( t , err , tt . res . err )
2024-05-31 00:08:48 +02:00
assert . Empty ( t , gotCmds )
2023-08-24 11:41:52 +02:00
assert . Equal ( t , tt . res . commands , cmds . eventCommands )
} )
}
}
func TestCommands_OTPEmailSent ( t * testing . T ) {
type fields struct {
eventstore func ( * testing . T ) * eventstore . Eventstore
}
type args struct {
ctx context . Context
sessionID string
resourceOwner string
}
tests := [ ] struct {
name string
fields fields
args args
wantErr error
} {
{
name : "not challenged, precondition error" ,
fields : fields {
eventstore : expectEventstore (
expectFilter ( ) ,
) ,
} ,
args : args {
ctx : context . Background ( ) ,
sessionID : "sessionID" ,
resourceOwner : "instanceID" ,
} ,
2023-12-08 16:30:55 +02:00
wantErr : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-SLr02" , "Errors.User.Code.NotFound" ) ,
2023-08-24 11:41:52 +02:00
} ,
{
name : "challenged and sent" ,
fields : fields {
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher (
session . NewOTPEmailChallengedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
& crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "1234567" ) ,
} ,
5 * time . Minute ,
false ,
"" ,
) ,
) ,
) ,
expectPush (
2023-10-19 12:19:10 +02:00
session . NewOTPEmailSentEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ) ,
2023-08-24 11:41:52 +02:00
) ,
) ,
} ,
args : args {
ctx : context . Background ( ) ,
sessionID : "sessionID" ,
resourceOwner : "instanceID" ,
} ,
wantErr : nil ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
c := & Commands {
eventstore : tt . fields . eventstore ( t ) ,
}
err := c . OTPEmailSent ( tt . args . ctx , tt . args . sessionID , tt . args . resourceOwner )
assert . ErrorIs ( t , err , tt . wantErr )
} )
}
}
func TestCheckOTPSMS ( t * testing . T ) {
type fields struct {
eventstore func ( * testing . T ) * eventstore . Eventstore
userID string
otpCodeChallenge * OTPCode
otpAlg crypto . EncryptionAlgorithm
2024-10-07 07:12:44 +02:00
getCodeVerifier func ( ctx context . Context , id string ) ( senders . CodeGenerator , error )
2023-08-24 11:41:52 +02:00
}
type args struct {
code string
}
type res struct {
2024-05-31 00:08:48 +02:00
err error
commands [ ] eventstore . Command
errorCommands [ ] eventstore . Command
2023-08-24 11:41:52 +02:00
}
tests := [ ] struct {
name string
fields fields
args args
res res
} {
{
name : "missing userID" ,
fields : fields {
eventstore : expectEventstore ( ) ,
userID : "" ,
} ,
args : args {
code : "code" ,
} ,
res : res {
2024-05-31 00:08:48 +02:00
err : zerrors . ThrowInvalidArgument ( nil , "COMMAND-S453v" , "Errors.User.UserIDMissing" ) ,
} ,
} ,
{
name : "missing code" ,
fields : fields {
eventstore : expectEventstore ( ) ,
userID : "userID" ,
} ,
args : args { } ,
res : res {
err : zerrors . ThrowInvalidArgument ( nil , "COMMAND-SJl2g" , "Errors.User.Code.Empty" ) ,
} ,
} ,
{
name : "not set up" ,
fields : fields {
eventstore : expectEventstore (
expectFilter ( ) ,
) ,
userID : "userID" ,
} ,
args : args {
code : "code" ,
} ,
res : res {
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-d2r52" , "Errors.User.MFA.OTP.NotReady" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "missing challenge" ,
fields : fields {
2024-05-31 00:08:48 +02:00
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPSMSAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
) ,
2023-08-24 11:41:52 +02:00
userID : "userID" ,
otpCodeChallenge : nil ,
} ,
args : args {
code : "code" ,
} ,
res : res {
2024-05-31 00:08:48 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-S34gh" , "Errors.User.Code.NotFound" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "invalid code" ,
fields : fields {
2024-05-31 00:08:48 +02:00
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPSMSAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
expectFilter ( ) , // recheck
expectFilter (
eventFromEventPusher (
org . NewLockoutPolicyAddedEvent ( context . Background ( ) , & org . NewAggregate ( "org1" ) . Aggregate ,
0 , 0 , false ,
) ,
) ,
) ,
) ,
userID : "userID" ,
otpCodeChallenge : & OTPCode {
Code : & crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "code" ) ,
} ,
Expiry : 5 * time . Minute ,
CreationDate : testNow . Add ( - 10 * time . Minute ) ,
} ,
otpAlg : crypto . CreateMockEncryptionAlg ( gomock . NewController ( t ) ) ,
} ,
args : args {
code : "code" ,
} ,
res : res {
err : zerrors . ThrowPreconditionFailed ( nil , "CODE-QvUQ4P" , "Errors.User.Code.Expired" ) ,
errorCommands : [ ] eventstore . Command {
user . NewHumanOTPSMSCheckFailedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate , nil ) ,
} ,
} ,
} ,
{
name : "invalid code, locked" ,
fields : fields {
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPSMSAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
expectFilter ( ) , // recheck
expectFilter (
eventFromEventPusher (
org . NewLockoutPolicyAddedEvent ( context . Background ( ) , & org . NewAggregate ( "org1" ) . Aggregate ,
0 , 1 , false ,
) ,
) ,
) ,
) ,
userID : "userID" ,
2023-08-24 11:41:52 +02:00
otpCodeChallenge : & OTPCode {
Code : & crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "code" ) ,
} ,
Expiry : 5 * time . Minute ,
CreationDate : testNow . Add ( - 10 * time . Minute ) ,
} ,
otpAlg : crypto . CreateMockEncryptionAlg ( gomock . NewController ( t ) ) ,
} ,
args : args {
code : "code" ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "CODE-QvUQ4P" , "Errors.User.Code.Expired" ) ,
2024-05-31 00:08:48 +02:00
errorCommands : [ ] eventstore . Command {
user . NewHumanOTPSMSCheckFailedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate , nil ) ,
user . NewUserLockedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ,
} ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "check ok" ,
fields : fields {
2024-05-31 00:08:48 +02:00
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPSMSAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
expectFilter ( ) , // recheck
) ,
userID : "userID" ,
2023-08-24 11:41:52 +02:00
otpCodeChallenge : & OTPCode {
Code : & crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "code" ) ,
} ,
Expiry : 5 * time . Minute ,
CreationDate : testNow ,
} ,
otpAlg : crypto . CreateMockEncryptionAlg ( gomock . NewController ( t ) ) ,
} ,
args : args {
code : "code" ,
} ,
res : res {
commands : [ ] eventstore . Command {
2024-05-31 00:08:48 +02:00
user . NewHumanOTPSMSCheckSucceededEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate , nil ) ,
2023-08-24 11:41:52 +02:00
session . NewOTPSMSCheckedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
testNow ,
) ,
} ,
} ,
} ,
2024-10-07 07:12:44 +02:00
{
name : "check ok (external)" ,
fields : fields {
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPSMSAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
expectFilter ( ) , // recheck
) ,
userID : "userID" ,
otpCodeChallenge : & OTPCode {
Code : nil ,
Expiry : 0 ,
GeneratorID : "generatorID" ,
VerificationID : "verificationID" ,
CreationDate : testNow ,
} ,
getCodeVerifier : func ( ctx context . Context , id string ) ( senders . CodeGenerator , error ) {
sender := mock . NewMockCodeGenerator ( gomock . NewController ( t ) )
sender . EXPECT ( ) . VerifyCode ( "verificationID" , "code" ) . Return ( nil )
return sender , nil
} ,
} ,
args : args {
code : "code" ,
} ,
res : res {
commands : [ ] eventstore . Command {
user . NewHumanOTPSMSCheckSucceededEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate , nil ) ,
session . NewOTPSMSCheckedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
testNow ,
) ,
} ,
} ,
} ,
2024-05-31 00:08:48 +02:00
{
name : "check ok, locked in the meantime" ,
fields : fields {
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPSMSAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
expectFilter (
user . NewUserLockedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ,
) ,
) ,
userID : "userID" ,
otpCodeChallenge : & OTPCode {
Code : & crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "code" ) ,
} ,
Expiry : 5 * time . Minute ,
CreationDate : testNow ,
} ,
otpAlg : crypto . CreateMockEncryptionAlg ( gomock . NewController ( t ) ) ,
} ,
args : args {
code : "code" ,
} ,
res : res {
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-S6h4R" , "Errors.User.Locked" ) ,
} ,
} ,
2023-08-24 11:41:52 +02:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
cmd := CheckOTPSMS ( tt . args . code )
sessionModel := & SessionWriteModel {
UserID : tt . fields . userID ,
UserCheckedAt : testNow ,
State : domain . SessionStateActive ,
OTPSMSCodeChallenge : tt . fields . otpCodeChallenge ,
aggregate : & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
}
cmds := & SessionCommands {
sessionCommands : [ ] SessionCommand { cmd } ,
sessionWriteModel : sessionModel ,
eventstore : tt . fields . eventstore ( t ) ,
otpAlg : tt . fields . otpAlg ,
2024-10-07 07:12:44 +02:00
getCodeVerifier : tt . fields . getCodeVerifier ,
2023-08-24 11:41:52 +02:00
now : func ( ) time . Time {
return testNow
} ,
2025-10-29 10:07:35 +01:00
tarpit : func ( failedAttempts uint64 ) {
} ,
2023-08-24 11:41:52 +02:00
}
2024-05-31 00:08:48 +02:00
gotCmds , err := cmd ( context . Background ( ) , cmds )
2023-08-24 11:41:52 +02:00
assert . ErrorIs ( t , err , tt . res . err )
2024-05-31 00:08:48 +02:00
assert . Equal ( t , tt . res . errorCommands , gotCmds )
2023-08-24 11:41:52 +02:00
assert . Equal ( t , tt . res . commands , cmds . eventCommands )
} )
}
}
func TestCheckOTPEmail ( t * testing . T ) {
type fields struct {
eventstore func ( * testing . T ) * eventstore . Eventstore
userID string
otpCodeChallenge * OTPCode
otpAlg crypto . EncryptionAlgorithm
2025-10-29 10:07:35 +01:00
tarpit Tarpit
2023-08-24 11:41:52 +02:00
}
type args struct {
code string
}
type res struct {
2024-05-31 00:08:48 +02:00
err error
commands [ ] eventstore . Command
errorCommands [ ] eventstore . Command
2023-08-24 11:41:52 +02:00
}
tests := [ ] struct {
name string
fields fields
args args
res res
} {
{
name : "missing userID" ,
fields : fields {
eventstore : expectEventstore ( ) ,
userID : "" ,
2025-10-29 10:07:35 +01:00
tarpit : expectTarpit ( 0 ) ,
2023-08-24 11:41:52 +02:00
} ,
args : args {
code : "code" ,
} ,
res : res {
2024-05-31 00:08:48 +02:00
err : zerrors . ThrowInvalidArgument ( nil , "COMMAND-S453v" , "Errors.User.UserIDMissing" ) ,
} ,
} ,
{
name : "missing code" ,
fields : fields {
eventstore : expectEventstore ( ) ,
userID : "userID" ,
2025-10-29 10:07:35 +01:00
tarpit : expectTarpit ( 0 ) ,
2024-05-31 00:08:48 +02:00
} ,
args : args { } ,
res : res {
err : zerrors . ThrowInvalidArgument ( nil , "COMMAND-SJl2g" , "Errors.User.Code.Empty" ) ,
} ,
} ,
{
name : "not set up" ,
fields : fields {
eventstore : expectEventstore (
expectFilter ( ) ,
) ,
userID : "userID" ,
2025-10-29 10:07:35 +01:00
tarpit : expectTarpit ( 0 ) ,
2024-05-31 00:08:48 +02:00
} ,
args : args {
code : "code" ,
} ,
res : res {
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-d2r52" , "Errors.User.MFA.OTP.NotReady" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "missing challenge" ,
fields : fields {
2024-05-31 00:08:48 +02:00
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPEmailAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
) ,
2023-08-24 11:41:52 +02:00
userID : "userID" ,
otpCodeChallenge : nil ,
2025-10-29 10:07:35 +01:00
tarpit : expectTarpit ( 0 ) ,
2023-08-24 11:41:52 +02:00
} ,
args : args {
code : "code" ,
} ,
res : res {
2024-05-31 00:08:48 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-S34gh" , "Errors.User.Code.NotFound" ) ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "invalid code" ,
fields : fields {
2024-05-31 00:08:48 +02:00
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPEmailAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
expectFilter ( ) , // recheck
expectFilter (
eventFromEventPusher (
org . NewLockoutPolicyAddedEvent ( context . Background ( ) , & org . NewAggregate ( "org1" ) . Aggregate ,
0 , 0 , false ,
) ,
) ,
) ,
) ,
userID : "userID" ,
otpCodeChallenge : & OTPCode {
Code : & crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "code" ) ,
} ,
Expiry : 5 * time . Minute ,
CreationDate : testNow . Add ( - 10 * time . Minute ) ,
} ,
otpAlg : crypto . CreateMockEncryptionAlg ( gomock . NewController ( t ) ) ,
2025-10-29 10:07:35 +01:00
tarpit : expectTarpit ( 1 ) ,
2024-05-31 00:08:48 +02:00
} ,
args : args {
code : "code" ,
} ,
res : res {
err : zerrors . ThrowPreconditionFailed ( nil , "CODE-QvUQ4P" , "Errors.User.Code.Expired" ) ,
errorCommands : [ ] eventstore . Command {
user . NewHumanOTPEmailCheckFailedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate , nil ) ,
} ,
} ,
} ,
{
name : "invalid code, locked" ,
fields : fields {
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPEmailAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
expectFilter ( ) , // recheck
expectFilter (
eventFromEventPusher (
org . NewLockoutPolicyAddedEvent ( context . Background ( ) , & org . NewAggregate ( "org1" ) . Aggregate ,
0 , 1 , false ,
) ,
) ,
) ,
) ,
userID : "userID" ,
2023-08-24 11:41:52 +02:00
otpCodeChallenge : & OTPCode {
Code : & crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "code" ) ,
} ,
Expiry : 5 * time . Minute ,
CreationDate : testNow . Add ( - 10 * time . Minute ) ,
} ,
otpAlg : crypto . CreateMockEncryptionAlg ( gomock . NewController ( t ) ) ,
2025-10-29 10:07:35 +01:00
tarpit : expectTarpit ( 1 ) ,
2023-08-24 11:41:52 +02:00
} ,
args : args {
code : "code" ,
} ,
res : res {
2023-12-08 16:30:55 +02:00
err : zerrors . ThrowPreconditionFailed ( nil , "CODE-QvUQ4P" , "Errors.User.Code.Expired" ) ,
2024-05-31 00:08:48 +02:00
errorCommands : [ ] eventstore . Command {
user . NewHumanOTPEmailCheckFailedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate , nil ) ,
user . NewUserLockedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ,
} ,
2023-08-24 11:41:52 +02:00
} ,
} ,
{
name : "check ok" ,
fields : fields {
2024-05-31 00:08:48 +02:00
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPEmailAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
expectFilter ( ) , // recheck
) ,
userID : "userID" ,
2023-08-24 11:41:52 +02:00
otpCodeChallenge : & OTPCode {
Code : & crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "code" ) ,
} ,
Expiry : 5 * time . Minute ,
CreationDate : testNow ,
} ,
otpAlg : crypto . CreateMockEncryptionAlg ( gomock . NewController ( t ) ) ,
2025-10-29 10:07:35 +01:00
tarpit : expectTarpit ( 0 ) ,
2023-08-24 11:41:52 +02:00
} ,
args : args {
code : "code" ,
} ,
res : res {
commands : [ ] eventstore . Command {
2024-05-31 00:08:48 +02:00
user . NewHumanOTPEmailCheckSucceededEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate , nil ) ,
2023-08-24 11:41:52 +02:00
session . NewOTPEmailCheckedEvent ( context . Background ( ) , & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
testNow ,
) ,
} ,
} ,
} ,
2024-05-31 00:08:48 +02:00
{
name : "check ok, locked in the meantime" ,
fields : fields {
eventstore : expectEventstore (
expectFilter (
eventFromEventPusher ( user . NewHumanOTPEmailAddedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ) ,
) ,
expectFilter (
user . NewUserLockedEvent ( context . Background ( ) , & user . NewAggregate ( "userID" , "org1" ) . Aggregate ) ,
) ,
) ,
userID : "userID" ,
otpCodeChallenge : & OTPCode {
Code : & crypto . CryptoValue {
CryptoType : crypto . TypeEncryption ,
Algorithm : "enc" ,
KeyID : "id" ,
Crypted : [ ] byte ( "code" ) ,
} ,
Expiry : 5 * time . Minute ,
CreationDate : testNow ,
} ,
otpAlg : crypto . CreateMockEncryptionAlg ( gomock . NewController ( t ) ) ,
2025-10-29 10:07:35 +01:00
tarpit : expectTarpit ( 0 ) ,
2024-05-31 00:08:48 +02:00
} ,
args : args {
code : "code" ,
} ,
res : res {
err : zerrors . ThrowPreconditionFailed ( nil , "COMMAND-S6h4R" , "Errors.User.Locked" ) ,
} ,
} ,
2023-08-24 11:41:52 +02:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
cmd := CheckOTPEmail ( tt . args . code )
sessionModel := & SessionWriteModel {
UserID : tt . fields . userID ,
UserCheckedAt : testNow ,
State : domain . SessionStateActive ,
OTPEmailCodeChallenge : tt . fields . otpCodeChallenge ,
aggregate : & session . NewAggregate ( "sessionID" , "instanceID" ) . Aggregate ,
}
cmds := & SessionCommands {
sessionCommands : [ ] SessionCommand { cmd } ,
sessionWriteModel : sessionModel ,
eventstore : tt . fields . eventstore ( t ) ,
otpAlg : tt . fields . otpAlg ,
now : func ( ) time . Time {
return testNow
} ,
2025-10-29 10:07:35 +01:00
tarpit : tt . fields . tarpit . tarpit ,
2023-08-24 11:41:52 +02:00
}
2024-05-31 00:08:48 +02:00
gotCmds , err := cmd ( context . Background ( ) , cmds )
2023-08-24 11:41:52 +02:00
assert . ErrorIs ( t , err , tt . res . err )
2024-05-31 00:08:48 +02:00
assert . Equal ( t , tt . res . errorCommands , gotCmds )
2023-08-24 11:41:52 +02:00
assert . Equal ( t , tt . res . commands , cmds . eventCommands )
2025-10-29 10:07:35 +01:00
tt . fields . tarpit . metExpectedCalls ( t )
2023-08-24 11:41:52 +02:00
} )
}
}
2025-10-29 10:07:35 +01:00
func expectTarpit ( requiredFailedAttempts uint64 ) Tarpit {
return & mockTarpit {
requiredFailedAttempts : requiredFailedAttempts ,
}
}
type mockTarpit struct {
requiredFailedAttempts uint64
failedAttempts uint64
}
func ( m * mockTarpit ) tarpit ( failedAttempts uint64 ) {
m . failedAttempts = failedAttempts
}
func ( m * mockTarpit ) metExpectedCalls ( t * testing . T ) bool {
t . Helper ( )
return assert . Equalf ( t , m . requiredFailedAttempts , m . failedAttempts , "tarpit was called with %d failed attempts, but %d were expected" , m . failedAttempts , m . requiredFailedAttempts )
}
type Tarpit interface {
tarpit ( failedAttempts uint64 )
metExpectedCalls ( t * testing . T ) bool
}