Add tests on authuser and authentication usecases
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
8c25aed1d0
commit
d6bc3b5b3f
8 changed files with 947 additions and 0 deletions
238
internal/usecase/authentication_test.go
Normal file
238
internal/usecase/authentication_test.go
Normal file
|
@ -0,0 +1,238 @@
|
|||
package usecase_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/storage/inmemory"
|
||||
"git.happydns.org/happyDomain/internal/usecase"
|
||||
userUC "git.happydns.org/happyDomain/internal/usecase/user"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
type testUserInfo struct {
|
||||
id happydns.Identifier
|
||||
email string
|
||||
newsletter bool
|
||||
}
|
||||
|
||||
func (u testUserInfo) GetUserId() happydns.Identifier { return u.id }
|
||||
func (u testUserInfo) GetEmail() string { return u.email }
|
||||
func (u testUserInfo) JoinNewsletter() bool { return u.newsletter }
|
||||
|
||||
func Test_CompleteAuthentication(t *testing.T) {
|
||||
mem, _ := inmemory.NewInMemoryStorage()
|
||||
userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil)
|
||||
authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase)
|
||||
|
||||
uinfo := testUserInfo{
|
||||
id: happydns.Identifier([]byte("user-123")),
|
||||
email: "john@example.com",
|
||||
newsletter: false,
|
||||
}
|
||||
|
||||
user, err := authenticationUsecase.CompleteAuthentication(uinfo)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if user.Email != "john@example.com" {
|
||||
t.Errorf("expected email 'john@example.com', got %s", user.Email)
|
||||
}
|
||||
|
||||
// Check the user is correctly stored in db
|
||||
stored, err := mem.GetUser(happydns.Identifier([]byte("user-123")))
|
||||
if err != nil {
|
||||
t.Fatalf("expected stored user, got error: %v", err)
|
||||
}
|
||||
if stored.Email != "john@example.com" {
|
||||
t.Errorf("expected stored email to be john@example.com, got %s", stored.Email)
|
||||
}
|
||||
}
|
||||
|
||||
type testNewsletterSubscription struct {
|
||||
userSubscribed happydns.UserInfo
|
||||
}
|
||||
|
||||
func (ds *testNewsletterSubscription) SubscribeToNewsletter(u happydns.UserInfo) error {
|
||||
ds.userSubscribed = u
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_CompleteAuthentication_WithNewsletter(t *testing.T) {
|
||||
mem, _ := inmemory.NewInMemoryStorage()
|
||||
mockNewsletterSubscription := &testNewsletterSubscription{}
|
||||
userUsecase := userUC.NewUserUsecases(mem, mockNewsletterSubscription, nil, nil)
|
||||
authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase)
|
||||
|
||||
uinfo := testUserInfo{
|
||||
id: happydns.Identifier([]byte("user-123")),
|
||||
email: "john@example.com",
|
||||
newsletter: true,
|
||||
}
|
||||
|
||||
_, err := authenticationUsecase.CompleteAuthentication(uinfo)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Check the user has been subscribed
|
||||
if mockNewsletterSubscription.userSubscribed == nil || mockNewsletterSubscription.userSubscribed.GetEmail() != uinfo.GetEmail() {
|
||||
t.Errorf("user not subscribed to newsletter after first login")
|
||||
}
|
||||
|
||||
// Reset the subscription state
|
||||
mockNewsletterSubscription.userSubscribed = nil
|
||||
|
||||
// Redo the authentication, now that the user is already registered
|
||||
_, err = authenticationUsecase.CompleteAuthentication(uinfo)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if mockNewsletterSubscription.userSubscribed != nil {
|
||||
t.Errorf("user has been re-subscribed to newsletter beyond first login")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AuthenticateUserWithPassword_WrongPassword(t *testing.T) {
|
||||
mem, _ := inmemory.NewInMemoryStorage()
|
||||
|
||||
authUser := &happydns.UserAuth{
|
||||
Email: "a@b.c",
|
||||
}
|
||||
err := authUser.DefinePassword("secure")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = mem.CreateAuthUser(authUser)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil)
|
||||
authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase)
|
||||
|
||||
_, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{
|
||||
Email: "a@b.c",
|
||||
Password: "wrong-password",
|
||||
})
|
||||
if err == nil || err.Error() != `tries to login as "a@b.c", but sent an invalid password` {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AuthenticateUserWithPassword_WeakPassword(t *testing.T) {
|
||||
mem, _ := inmemory.NewInMemoryStorage()
|
||||
|
||||
authUser := &happydns.UserAuth{
|
||||
Email: "a@b.c",
|
||||
}
|
||||
err := authUser.DefinePassword("weak")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = mem.CreateAuthUser(authUser)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil)
|
||||
authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase)
|
||||
|
||||
_, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{
|
||||
Email: "a@b.c",
|
||||
Password: "weak",
|
||||
})
|
||||
if err == nil || err.Error() != `tries to login as "a@b.c", but sent an invalid password` {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AuthenticateUserWithPassword_UnverifiedEmail(t *testing.T) {
|
||||
mem, _ := inmemory.NewInMemoryStorage()
|
||||
|
||||
authUser := &happydns.UserAuth{
|
||||
Email: "a@b.c",
|
||||
}
|
||||
err := authUser.DefinePassword("v3rySecure")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = mem.CreateAuthUser(authUser)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil)
|
||||
authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase)
|
||||
|
||||
_, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{
|
||||
Email: "a@b.c",
|
||||
Password: "v3rySecure",
|
||||
})
|
||||
if err == nil || err.Error() != `tries to login as "a@b.c", but has not verified email` {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AuthenticateUserWithPassword_NoEmail(t *testing.T) {
|
||||
mem, _ := inmemory.NewInMemoryStorage()
|
||||
|
||||
authUser := &happydns.UserAuth{
|
||||
Email: "a@b.c",
|
||||
}
|
||||
err := authUser.DefinePassword("v3rySecure")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = mem.CreateAuthUser(authUser)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil)
|
||||
authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{NoMail: true}, mem, userUsecase)
|
||||
|
||||
_, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{
|
||||
Email: "a@b.c",
|
||||
Password: "v3rySecure",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AuthenticateUserWithPassword(t *testing.T) {
|
||||
mem, _ := inmemory.NewInMemoryStorage()
|
||||
|
||||
now := time.Now()
|
||||
authUser := &happydns.UserAuth{
|
||||
Email: "a@b.c",
|
||||
EmailVerification: &now,
|
||||
}
|
||||
err := authUser.DefinePassword("v3rySecure")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
err = mem.CreateAuthUser(authUser)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil)
|
||||
authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase)
|
||||
|
||||
_, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{
|
||||
Email: "a@b.c",
|
||||
Password: "v3rySecure",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
122
internal/usecase/authuser/account_recovery_test.go
Normal file
122
internal/usecase/authuser/account_recovery_test.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package authuser_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/storage/inmemory"
|
||||
"git.happydns.org/happyDomain/internal/usecase/authuser"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
func TestGenAccountRecoveryHash(t *testing.T) {
|
||||
recoveryKey := make([]byte, 64)
|
||||
hash := authuser.GenAccountRecoveryHash(recoveryKey, false)
|
||||
if hash == "" {
|
||||
t.Error("Expected non-empty hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanRecoverAccount(t *testing.T) {
|
||||
recoveryKey := make([]byte, 64)
|
||||
user := &happydns.UserAuth{PasswordRecoveryKey: recoveryKey}
|
||||
hash := authuser.GenAccountRecoveryHash(recoveryKey, false)
|
||||
|
||||
err := authuser.CanRecoverAccount(user, hash)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type dummyMailer struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *dummyMailer) SendMail(to *mail.Address, subject, content string) (err error) {
|
||||
return m.err
|
||||
}
|
||||
|
||||
func TestGenerateLink(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
config := &happydns.Options{ExternalURL: url.URL{Scheme: "http", Host: "example.com"}}
|
||||
|
||||
uc := authuser.NewRecoverAccountUsecase(store, nil, config, nil)
|
||||
user := &happydns.UserAuth{Id: []byte("user1"), Email: "user@example.com"}
|
||||
|
||||
link, err := uc.GenerateLink(user)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
if link == "" {
|
||||
t.Error("Expected non-empty link")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendLink(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
mailer := &dummyMailer{}
|
||||
config := &happydns.Options{ExternalURL: url.URL{Scheme: "http", Host: "example.com"}}
|
||||
|
||||
uc := authuser.NewRecoverAccountUsecase(store, mailer, config, nil)
|
||||
user := &happydns.UserAuth{Id: []byte("user1"), Email: "user@example.com"}
|
||||
|
||||
err := uc.SendLink(user)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendLink_Error(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
mailer := &dummyMailer{err: errors.New("SMTP Error")}
|
||||
config := &happydns.Options{ExternalURL: url.URL{Scheme: "http", Host: "example.com"}}
|
||||
|
||||
uc := authuser.NewRecoverAccountUsecase(store, mailer, config, nil)
|
||||
user := &happydns.UserAuth{Id: []byte("user1"), Email: "user@example.com"}
|
||||
|
||||
err := uc.SendLink(user)
|
||||
if err == nil || err.Error() != "SMTP Error" {
|
||||
t.Errorf("Expected SMTP Error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetPassword(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
mailer := &dummyMailer{}
|
||||
config := &happydns.Options{ExternalURL: url.URL{Scheme: "http", Host: "example.com"}}
|
||||
changePassword := authuser.NewChangePasswordUsecase(store, authuser.NewCheckPasswordConstraintsUsecase())
|
||||
|
||||
uc := authuser.NewRecoverAccountUsecase(store, mailer, config, changePassword)
|
||||
user := &happydns.UserAuth{Id: []byte("user1"), Email: "user@example.com", PasswordRecoveryKey: make([]byte, 64)}
|
||||
|
||||
err := uc.ResetPassword(user, happydns.AccountRecoveryForm{
|
||||
Key: authuser.GenAccountRecoveryHash(user.PasswordRecoveryKey, false),
|
||||
Password: "StrongPassword123!",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Previous recovery hash should work too
|
||||
err = uc.ResetPassword(user, happydns.AccountRecoveryForm{
|
||||
Key: authuser.GenAccountRecoveryHash(user.PasswordRecoveryKey, true),
|
||||
Password: "StrongPassword123!",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Invalid key should not work
|
||||
otherKey := make([]byte, 64)
|
||||
otherKey[1] = byte('a')
|
||||
err = uc.ResetPassword(user, happydns.AccountRecoveryForm{
|
||||
Key: authuser.GenAccountRecoveryHash(otherKey, true),
|
||||
Password: "StrongPassword123!",
|
||||
})
|
||||
if err == nil || !strings.HasPrefix(err.Error(), "The account recovery link you follow is invalid or has expired (it is valid during ") {
|
||||
t.Errorf("Expected invalid recovery link, got %v", err)
|
||||
}
|
||||
}
|
51
internal/usecase/authuser/can_register_test.go
Normal file
51
internal/usecase/authuser/can_register_test.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package authuser_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/usecase/authuser"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
func TestCanRegisterUsecase_IsOpened(t *testing.T) {
|
||||
// Test case where registration is enabled
|
||||
t.Run("Registration is enabled", func(t *testing.T) {
|
||||
cfg := &happydns.Options{DisableRegistration: false}
|
||||
uc := authuser.NewCanRegisterUsecase(cfg)
|
||||
|
||||
if !uc.IsOpened() {
|
||||
t.Errorf("Expected registration to be enabled")
|
||||
}
|
||||
})
|
||||
|
||||
// Test case where registration is disabled
|
||||
t.Run("Registration is disabled", func(t *testing.T) {
|
||||
cfg := &happydns.Options{DisableRegistration: true}
|
||||
uc := authuser.NewCanRegisterUsecase(cfg)
|
||||
|
||||
if uc.IsOpened() {
|
||||
t.Errorf("Expected registration to be disabled")
|
||||
}
|
||||
})
|
||||
}
|
125
internal/usecase/authuser/change_password_test.go
Normal file
125
internal/usecase/authuser/change_password_test.go
Normal file
|
@ -0,0 +1,125 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package authuser_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/storage/inmemory"
|
||||
"git.happydns.org/happyDomain/internal/usecase/authuser"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
func TestChangePasswordUsecase_Change(t *testing.T) {
|
||||
// Setup in-memory storage
|
||||
storage, _ := inmemory.NewInMemoryStorage()
|
||||
user := &happydns.UserAuth{
|
||||
Email: "test@example.com",
|
||||
}
|
||||
user.DefinePassword("oldpassword")
|
||||
|
||||
// Create a user in the storage
|
||||
err := storage.CreateAuthUser(user)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Setup the usecase
|
||||
checkPasswordConstraints := authuser.NewCheckPasswordConstraintsUsecase()
|
||||
uc := authuser.NewChangePasswordUsecase(storage, checkPasswordConstraints)
|
||||
|
||||
// Test changing password
|
||||
newPassword := "newPa$$w0rd"
|
||||
err = uc.Change(user, newPassword)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Verify the password was changed
|
||||
updatedUser, err := storage.GetAuthUser(user.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if !updatedUser.CheckPassword(newPassword) {
|
||||
t.Error("Expected password to be updated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangePasswordUsecase_CheckNewPassword(t *testing.T) {
|
||||
// Setup in-memory storage
|
||||
storage, _ := inmemory.NewInMemoryStorage()
|
||||
user := &happydns.UserAuth{
|
||||
Email: "test@example.com",
|
||||
}
|
||||
user.DefinePassword("oldpassword")
|
||||
|
||||
// Create a user in the storage
|
||||
err := storage.CreateAuthUser(user)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Setup the usecase
|
||||
checkPasswordConstraints := authuser.NewCheckPasswordConstraintsUsecase()
|
||||
uc := authuser.NewChangePasswordUsecase(storage, checkPasswordConstraints)
|
||||
|
||||
// Test checking new password with correct current password
|
||||
form := happydns.ChangePasswordForm{
|
||||
Current: "oldpassword",
|
||||
Password: "newPa$$w0rd",
|
||||
PasswordConfirm: "newPa$$w0rd",
|
||||
}
|
||||
err = uc.CheckNewPassword(user, form)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Test checking new password with incorrect current password
|
||||
form.Current = "wrongpassword"
|
||||
err = uc.CheckNewPassword(user, form)
|
||||
if err == nil {
|
||||
t.Error("Expected error for incorrect current password")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangePasswordUsecase_CheckResetPassword(t *testing.T) {
|
||||
// Setup the usecase
|
||||
checkPasswordConstraints := authuser.NewCheckPasswordConstraintsUsecase()
|
||||
uc := authuser.NewChangePasswordUsecase(nil, checkPasswordConstraints)
|
||||
|
||||
// Test checking reset password with matching passwords
|
||||
form := happydns.ChangePasswordForm{
|
||||
Password: "newPa$$w0rd",
|
||||
PasswordConfirm: "newPa$$w0rd",
|
||||
}
|
||||
err := uc.CheckResetPassword(&happydns.UserAuth{}, form)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Test checking reset password with non-matching passwords
|
||||
form.PasswordConfirm = "differentpassword"
|
||||
err = uc.CheckResetPassword(&happydns.UserAuth{}, form)
|
||||
if err == nil {
|
||||
t.Error("Expected error for non-matching passwords")
|
||||
}
|
||||
}
|
144
internal/usecase/authuser/create_auth_user_test.go
Normal file
144
internal/usecase/authuser/create_auth_user_test.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package authuser_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/mailer"
|
||||
"git.happydns.org/happyDomain/internal/storage/inmemory"
|
||||
"git.happydns.org/happyDomain/internal/usecase/authuser"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
type dummyEmailValidation struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (d *dummyEmailValidation) GenerateLink(u *happydns.UserAuth) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *dummyEmailValidation) SendLink(u *happydns.UserAuth) error {
|
||||
return d.err
|
||||
}
|
||||
|
||||
func (d *dummyEmailValidation) Validate(user *happydns.UserAuth, form happydns.AddressValidationForm) error {
|
||||
return d.err
|
||||
}
|
||||
|
||||
func TestCreateAuthUser_Success(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
pwChecker := authuser.NewCheckPasswordConstraintsUsecase()
|
||||
emailValidation := &dummyEmailValidation{}
|
||||
usecase := authuser.NewCreateAuthUserUsecase(store, &mailer.Mailer{}, pwChecker, emailValidation)
|
||||
|
||||
reg := happydns.UserRegistration{
|
||||
Email: "test@example.com",
|
||||
Password: "StrongPassword123!",
|
||||
Newsletter: true,
|
||||
}
|
||||
|
||||
user, err := usecase.Create(reg)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if user.Email != reg.Email {
|
||||
t.Errorf("expected email %s, got %s", reg.Email, user.Email)
|
||||
}
|
||||
if user.Password == nil {
|
||||
t.Errorf("expected defined password, got %s", user.Password)
|
||||
}
|
||||
if !user.AllowCommercials {
|
||||
t.Error("expected user to have AllowCommercials = true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAuthUser_InvalidEmail(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
pwChecker := authuser.NewCheckPasswordConstraintsUsecase()
|
||||
usecase := authuser.NewCreateAuthUserUsecase(store, &mailer.Mailer{}, pwChecker, &dummyEmailValidation{})
|
||||
|
||||
reg := happydns.UserRegistration{
|
||||
Email: "bademail",
|
||||
Password: "StrongPassword123!",
|
||||
}
|
||||
|
||||
_, err := usecase.Create(reg)
|
||||
if err == nil || err.Error() != "the given email is invalid" {
|
||||
t.Errorf("expected validation error for email, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAuthUser_WeakPassword(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
pwChecker := authuser.NewCheckPasswordConstraintsUsecase()
|
||||
usecase := authuser.NewCreateAuthUserUsecase(store, &mailer.Mailer{}, pwChecker, &dummyEmailValidation{})
|
||||
|
||||
reg := happydns.UserRegistration{
|
||||
Email: "test@example.com",
|
||||
Password: "123",
|
||||
}
|
||||
|
||||
_, err := usecase.Create(reg)
|
||||
if err == nil || err.Error() != "password must be at least 8 characters long" {
|
||||
t.Errorf("expected password constraint error, got: %v", err)
|
||||
}
|
||||
|
||||
reg.Password = "Secur3$"
|
||||
_, err = usecase.Create(reg)
|
||||
if err == nil || err.Error() != "password must be at least 8 characters long" {
|
||||
t.Errorf("expected password constraint error, got: %v", err)
|
||||
}
|
||||
|
||||
reg.Password = "secure123"
|
||||
_, err = usecase.Create(reg)
|
||||
if err == nil || err.Error() != "Password must contain upper case letters." {
|
||||
t.Errorf("expected password constraint error, got: %v", err)
|
||||
}
|
||||
|
||||
reg.Password = "Secure123"
|
||||
_, err = usecase.Create(reg)
|
||||
if err == nil || err.Error() != "Password must be longer or contain symbols." {
|
||||
t.Errorf("expected password constraint error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAuthUser_EmailAlreadyUsed(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
pwChecker := authuser.NewCheckPasswordConstraintsUsecase()
|
||||
usecase := authuser.NewCreateAuthUserUsecase(store, &mailer.Mailer{}, pwChecker, &dummyEmailValidation{})
|
||||
|
||||
// Create a user first
|
||||
reg := happydns.UserRegistration{
|
||||
Email: "used@example.com",
|
||||
Password: "StrongPassword123!",
|
||||
}
|
||||
_, err := usecase.Create(reg)
|
||||
if err != nil {
|
||||
t.Fatalf("setup user creation failed: %v", err)
|
||||
}
|
||||
|
||||
// Try creating again with the same email
|
||||
_, err = usecase.Create(reg)
|
||||
if err == nil || err.Error() != "an account already exists with the given address. Try logging in." {
|
||||
t.Errorf("expected duplicate email error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAuthUser_EmailValidationFails(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
pwChecker := authuser.NewCheckPasswordConstraintsUsecase()
|
||||
emailValidation := &dummyEmailValidation{err: errors.New("SMTP error")}
|
||||
usecase := authuser.NewCreateAuthUserUsecase(store, &mailer.Mailer{}, pwChecker, emailValidation)
|
||||
|
||||
reg := happydns.UserRegistration{
|
||||
Email: "fail@example.com",
|
||||
Password: "StrongPassword123!",
|
||||
}
|
||||
|
||||
_, err := usecase.Create(reg)
|
||||
if err == nil || err.Error() != "unable to send validation email: SMTP error" {
|
||||
t.Errorf("expected internal error for email sending, got: %v", err)
|
||||
}
|
||||
}
|
101
internal/usecase/authuser/delete_auth_user_test.go
Normal file
101
internal/usecase/authuser/delete_auth_user_test.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package authuser_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/storage/inmemory"
|
||||
"git.happydns.org/happyDomain/internal/usecase/authuser"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
// MockCloseUserSessionsUsecase is a mock implementation of CloseUserSessionsUsecase.
|
||||
type MockCloseUserSessionsUsecase struct {
|
||||
CloseAllFunc func(user happydns.UserInfo) error
|
||||
}
|
||||
|
||||
func (m *MockCloseUserSessionsUsecase) CloseAll(user happydns.UserInfo) error {
|
||||
return m.CloseAllFunc(user)
|
||||
}
|
||||
|
||||
func (m *MockCloseUserSessionsUsecase) ByID(userID happydns.Identifier) error {
|
||||
return m.CloseAll(&happydns.UserAuth{Id: userID})
|
||||
}
|
||||
|
||||
func TestDeleteAuthUserUsecase_Delete(t *testing.T) {
|
||||
// Create an in-memory storage
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
|
||||
// Create a mock for CloseUserSessionsUsecase
|
||||
mockCloseUserSessions := &MockCloseUserSessionsUsecase{
|
||||
CloseAllFunc: func(user happydns.UserInfo) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// Create an instance of DeleteAuthUserUsecase
|
||||
uc := authuser.NewDeleteAuthUserUsecase(store, mockCloseUserSessions)
|
||||
|
||||
// Create a test user
|
||||
user := &happydns.UserAuth{
|
||||
Email: "test@example.com",
|
||||
}
|
||||
user.DefinePassword("test-password")
|
||||
|
||||
// Add the user to the storage
|
||||
err := store.CreateAuthUser(user)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Test case 1: Invalid password
|
||||
err = uc.Delete(user, "wrong-password")
|
||||
if err == nil || err.Error() != "invalid current password" {
|
||||
t.Errorf("Expected error 'invalid current password', got %v", err)
|
||||
}
|
||||
|
||||
// Test case 2: Error in closing sessions
|
||||
mockCloseUserSessions.CloseAllFunc = func(user happydns.UserInfo) error {
|
||||
return fmt.Errorf("error closing sessions")
|
||||
}
|
||||
err = uc.Delete(user, "test-password")
|
||||
if err == nil || err.Error() != "unable to delete user sessions: error closing sessions" {
|
||||
t.Errorf("Expected error 'unable to delete user sessions: error closing sessions', got %v", err)
|
||||
}
|
||||
|
||||
// Test case 3: Bad password when deleting user
|
||||
err = uc.Delete(user, "bad-password")
|
||||
if err == nil || err.Error() != "invalid current password" {
|
||||
t.Errorf("Expected error 'invalid current password', got %v", err)
|
||||
}
|
||||
|
||||
// Test case 4: Successful deletion
|
||||
mockCloseUserSessions.CloseAllFunc = func(user happydns.UserInfo) error {
|
||||
return nil
|
||||
}
|
||||
err = uc.Delete(user, "test-password")
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
}
|
95
internal/usecase/authuser/email_validation_test.go
Normal file
95
internal/usecase/authuser/email_validation_test.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
package authuser_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/storage/inmemory"
|
||||
"git.happydns.org/happyDomain/internal/usecase/authuser"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
func TestGenEmailValidationHash(t *testing.T) {
|
||||
hash := authuser.GenRegistrationHash(&happydns.UserAuth{CreatedAt: time.Now()}, false)
|
||||
if hash == "" {
|
||||
t.Error("Expected non-empty hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRegistrationLink(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
config := &happydns.Options{ExternalURL: url.URL{Scheme: "http", Host: "example.com"}}
|
||||
|
||||
uc := authuser.NewEmailValidationUsecase(store, nil, config)
|
||||
user := &happydns.UserAuth{Id: []byte("user1"), Email: "user@example.com"}
|
||||
|
||||
link := uc.GenerateLink(user)
|
||||
if link == "" {
|
||||
t.Error("Expected non-empty link")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendRegistrationLink(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
mailer := &dummyMailer{}
|
||||
config := &happydns.Options{ExternalURL: url.URL{Scheme: "http", Host: "example.com"}}
|
||||
|
||||
uc := authuser.NewEmailValidationUsecase(store, mailer, config)
|
||||
user := &happydns.UserAuth{Id: []byte("user1"), Email: "user@example.com"}
|
||||
|
||||
err := uc.SendLink(user)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendRegistrationLink_Error(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
mailer := &dummyMailer{err: errors.New("SMTP Error")}
|
||||
config := &happydns.Options{ExternalURL: url.URL{Scheme: "http", Host: "example.com"}}
|
||||
|
||||
uc := authuser.NewEmailValidationUsecase(store, mailer, config)
|
||||
user := &happydns.UserAuth{Id: []byte("user1"), Email: "user@example.com"}
|
||||
|
||||
err := uc.SendLink(user)
|
||||
if err == nil || err.Error() != "SMTP Error" {
|
||||
t.Errorf("Expected SMTP Error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEmail(t *testing.T) {
|
||||
store, _ := inmemory.NewInMemoryStorage()
|
||||
mailer := &dummyMailer{}
|
||||
config := &happydns.Options{ExternalURL: url.URL{Scheme: "http", Host: "example.com"}}
|
||||
|
||||
uc := authuser.NewEmailValidationUsecase(store, mailer, config)
|
||||
user := &happydns.UserAuth{Id: []byte("user1"), Email: "user@example.com", PasswordRecoveryKey: make([]byte, 64)}
|
||||
|
||||
err := uc.Validate(user, happydns.AddressValidationForm{
|
||||
Key: authuser.GenRegistrationHash(user, false),
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Previous recovery hash should work too
|
||||
err = uc.Validate(user, happydns.AddressValidationForm{
|
||||
Key: authuser.GenRegistrationHash(user, true),
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Non-matching user creation time should not work
|
||||
user2 := *user
|
||||
user2.CreatedAt = time.Now()
|
||||
err = uc.Validate(user, happydns.AddressValidationForm{
|
||||
Key: authuser.GenRegistrationHash(&user2, false),
|
||||
})
|
||||
if err == nil || !strings.HasPrefix(err.Error(), "bad email validation key: the validation address link you follow is invalid or has expired (it is valid during ") {
|
||||
t.Errorf("Expected invalid validation link, got %v", err)
|
||||
}
|
||||
}
|
71
internal/usecase/authuser/get_auth_user_test.go
Normal file
71
internal/usecase/authuser/get_auth_user_test.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package authuser_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/storage/inmemory"
|
||||
"git.happydns.org/happyDomain/internal/usecase/authuser"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
func TestGetAuthUserUsecase(t *testing.T) {
|
||||
memStore, err := inmemory.NewInMemoryStorage()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create in-memory storage: %v", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
user := &happydns.UserAuth{
|
||||
Email: "test@example.com",
|
||||
EmailVerification: &now,
|
||||
CreatedAt: now,
|
||||
LastLoggedIn: &now,
|
||||
Password: []byte("fakehash"),
|
||||
}
|
||||
|
||||
// Add new user in memory (and assign an ID)
|
||||
err = memStore.CreateAuthUser(user)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create auth user: %v", err)
|
||||
}
|
||||
if user.Id == nil {
|
||||
t.Fatalf("Expected non-nil user ID, got %s", user.Id)
|
||||
}
|
||||
|
||||
uc := authuser.NewGetAuthUserUsecase(memStore)
|
||||
|
||||
t.Run("ByID returns the correct user", func(t *testing.T) {
|
||||
got, err := uc.ByID(user.Id)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
if got.Email != "test@example.com" {
|
||||
t.Errorf("Expected email 'test@example.com', got %s", got.Email)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ByEmail returns the correct user", func(t *testing.T) {
|
||||
got, err := uc.ByEmail("test@example.com")
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
if !got.Id.Equals(user.Id) {
|
||||
t.Errorf("Expected ID '%s', got %s", user.Id, got.Id)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ByID returns error for unknown ID", func(t *testing.T) {
|
||||
_, err := uc.ByID([]byte("unknown-id"))
|
||||
if err == nil {
|
||||
t.Error("Expected error for unknown ID, got nil")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ByEmail returns error for unknown email", func(t *testing.T) {
|
||||
_, err := uc.ByEmail("unknown@example.com")
|
||||
if err == nil {
|
||||
t.Error("Expected error for unknown email, got nil")
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue