package test import ( "RenewCMS/internal/domain/user" "RenewCMS/internal/infrastructure/useCases" "errors" "sort" "testing" "time" ) type stubHasher struct { hashErr error hashCalls int compareCalls int } func (s *stubHasher) Hash(value string) (string, error) { s.hashCalls++ if s.hashErr != nil { return "", s.hashErr } return "hashed:" + value, nil } func (s *stubHasher) Compare(hashed string, value string) error { s.compareCalls++ if hashed == "hashed:"+value { return nil } return errors.New("hash mismatch") } type mockUserRepo struct { users map[uint32]user.User nextID uint32 fail failSwitch clearVerificationCodeCalls int clearVerificationCodeUserIds []uint32 updatePasswordResetCodeCalls int lastUpdatePasswordResetCodeUserId uint32 lastUpdatePasswordResetCodeCode string lastUpdatePasswordResetCodeExpiration time.Time resetPasswordCalls int lastResetPasswordUserId uint32 lastResetPasswordHash string updateDisplayNameCalls int lastUpdateDisplayName string } func newMockUserRepo() *mockUserRepo { return &mockUserRepo{users: make(map[uint32]user.User)} } func newVerifiedUserRepo() *mockUserRepo { repo := newMockUserRepo() repo.users[1] = user.User{ID: 1, IsVerified: true} return repo } func (m *mockUserRepo) sortedIds() []uint32 { ids := make([]uint32, 0, len(m.users)) for id := range m.users { ids = append(ids, id) } sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) return ids } func (m *mockUserRepo) Get(id uint32) (user.User, error) { if err := m.fail.trigger("Get"); err != nil { return user.User{}, err } u, ok := m.users[id] if !ok { return user.User{}, user.ErrUserNotFound } return u, nil } func (m *mockUserRepo) GetByUsername(username string) (user.User, error) { if err := m.fail.trigger("GetByUsername"); err != nil { return user.User{}, err } for _, id := range m.sortedIds() { u := m.users[id] if u.Username == username { return u, nil } } return user.User{}, user.ErrUserNotFound } func (m *mockUserRepo) GetByEmail(email string) (user.User, error) { if err := m.fail.trigger("GetByEmail"); err != nil { return user.User{}, err } for _, id := range m.sortedIds() { u := m.users[id] if u.Email == email { return u, nil } } return user.User{}, user.ErrUserNotFound } func (m *mockUserRepo) GetAll() []user.User { result := make([]user.User, 0) for _, id := range m.sortedIds() { result = append(result, m.users[id]) } return result } func (m *mockUserRepo) Create(u user.User) (user.User, error) { if err := m.fail.trigger("Create"); err != nil { return user.User{}, err } m.nextID++ u.ID = m.nextID m.users[u.ID] = u return u, nil } func (m *mockUserRepo) Delete(id uint32) error { if err := m.fail.trigger("Delete"); err != nil { return err } if _, ok := m.users[id]; !ok { return user.ErrUserNotFound } delete(m.users, id) return nil } func (m *mockUserRepo) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) { if err := m.fail.trigger("UpdateVerificationStatus"); err != nil { return user.User{}, err } u, ok := m.users[userId] if !ok { return user.User{}, user.ErrUserNotFound } u.IsVerified = isVerified m.users[userId] = u return u, nil } func (m *mockUserRepo) UpdatePassword(userId uint32, password string) (user.User, error) { if err := m.fail.trigger("UpdatePassword"); err != nil { return user.User{}, err } u, ok := m.users[userId] if !ok { return user.User{}, user.ErrUserNotFound } u.Password = password m.users[userId] = u return u, nil } func (m *mockUserRepo) UpdatePasswordResetCode(userId uint32, code string, expiration time.Time) (user.User, error) { m.updatePasswordResetCodeCalls++ m.lastUpdatePasswordResetCodeUserId = userId m.lastUpdatePasswordResetCodeCode = code m.lastUpdatePasswordResetCodeExpiration = expiration if err := m.fail.trigger("UpdatePasswordResetCode"); err != nil { return user.User{}, err } u, ok := m.users[userId] if !ok { return user.User{}, user.ErrUserNotFound } u.PasswordResetCode = code u.PasswordResetExpiration = expiration m.users[userId] = u return u, nil } func (m *mockUserRepo) ClearVerificationCode(userId uint32) (user.User, error) { m.clearVerificationCodeCalls++ m.clearVerificationCodeUserIds = append(m.clearVerificationCodeUserIds, userId) if err := m.fail.trigger("ClearVerificationCode"); err != nil { return user.User{}, err } u, ok := m.users[userId] if !ok { return user.User{}, user.ErrUserNotFound } u.IsVerified = true u.VerificationCode = "" m.users[userId] = u return u, nil } func (m *mockUserRepo) ResetPassword(userId uint32, hashedPassword string) (user.User, error) { m.resetPasswordCalls++ m.lastResetPasswordUserId = userId m.lastResetPasswordHash = hashedPassword if err := m.fail.trigger("ResetPassword"); err != nil { return user.User{}, err } u, ok := m.users[userId] if !ok { return user.User{}, user.ErrUserNotFound } u.Password = hashedPassword u.PasswordResetCode = "" u.PasswordResetExpiration = time.Time{} u.TokenVersion++ m.users[userId] = u return u, nil } func (m *mockUserRepo) UpdateDisplayName(userId uint32, displayName string) (user.User, error) { m.updateDisplayNameCalls++ m.lastUpdateDisplayName = displayName if err := m.fail.trigger("UpdateDisplayName"); err != nil { return user.User{}, err } u, ok := m.users[userId] if !ok { return user.User{}, user.ErrUserNotFound } u.DisplayName = displayName m.users[userId] = u return u, nil } var _ user.Repository = &mockUserRepo{} func TestCreateUser(t *testing.T) { repo := newMockUserRepo() uc := useCases.NewCreateUserUseCase(repo, &stubHasher{}, newMockMailRepo(), "https://cms.example.com", discardRecorder) cmd := useCases.CreateUserCommand{ Username: "testuser", Password: "password123", Email: "test@example.com", } res, err := uc.CreateUser(testCtx, cmd) if err != nil { t.Fatalf("User creation failed: %v", err) } if res.Username != cmd.Username { t.Errorf("Username mismatch") } if res.VerificationCode == "" { t.Errorf("Verification code was not generated") } }