slack-auth-1770277926/services/auth-api/internal/service/user_test.go
rdev-worker fd9bf961bb
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
build: /implement-feature auth-system --requirements 'User model with email/...
2026-02-05 07:59:55 +00:00

294 lines
6.3 KiB
Go

package service
import (
"context"
"sync"
"testing"
"git.threesix.ai/jordan/slack-auth-1770277926/pkg/logging"
"git.threesix.ai/jordan/slack-auth-1770277926/services/auth-api/internal/domain"
"git.threesix.ai/jordan/slack-auth-1770277926/services/auth-api/internal/port"
)
// mockUserRepository implements port.UserRepository for testing.
type mockUserRepository struct {
mu sync.RWMutex
users map[domain.UserID]*domain.User
}
var _ port.UserRepository = (*mockUserRepository)(nil)
func newMockUserRepository() *mockUserRepository {
return &mockUserRepository{
users: make(map[domain.UserID]*domain.User),
}
}
func (m *mockUserRepository) Get(ctx context.Context, id domain.UserID) (*domain.User, error) {
m.mu.RLock()
defer m.mu.RUnlock()
u, ok := m.users[id]
if !ok {
return nil, domain.ErrUserNotFound
}
cpy := *u
return &cpy, nil
}
func (m *mockUserRepository) GetByEmail(ctx context.Context, email string) (*domain.User, error) {
m.mu.RLock()
defer m.mu.RUnlock()
for _, u := range m.users {
if u.Email == email {
cpy := *u
return &cpy, nil
}
}
return nil, domain.ErrUserNotFound
}
func (m *mockUserRepository) Create(ctx context.Context, user *domain.User) error {
m.mu.Lock()
defer m.mu.Unlock()
cpy := *user
m.users[user.ID] = &cpy
return nil
}
func (m *mockUserRepository) ExistsByEmail(ctx context.Context, email string) (bool, error) {
m.mu.RLock()
defer m.mu.RUnlock()
for _, u := range m.users {
if u.Email == email {
return true, nil
}
}
return false, nil
}
var testJWTSecret = []byte("test-secret-key-for-testing-only")
func newTestUserService() (*UserService, *mockUserRepository) {
repo := newMockUserRepository()
svc := NewUserService(repo, testJWTSecret, "slack-auth-1770277926", logging.Nop())
return svc, repo
}
func TestUserService_Register(t *testing.T) {
tests := []struct {
name string
input RegisterInput
setup func(*mockUserRepository)
wantErr error
}{
{
name: "valid registration",
input: RegisterInput{
Email: "newuser@example.com",
Password: "password123",
},
wantErr: nil,
},
{
name: "duplicate email",
input: RegisterInput{
Email: "existing@example.com",
Password: "password123",
},
setup: func(repo *mockUserRepository) {
user, _ := domain.NewUser("existing-id", "existing@example.com", "password123")
_ = repo.Create(context.Background(), user)
},
wantErr: domain.ErrDuplicateUser,
},
{
name: "invalid email",
input: RegisterInput{
Email: "not-valid",
Password: "password123",
},
wantErr: domain.ErrInvalidEmail,
},
{
name: "short password",
input: RegisterInput{
Email: "user@example.com",
Password: "short",
},
wantErr: domain.ErrInvalidPassword,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
svc, repo := newTestUserService()
if tt.setup != nil {
tt.setup(repo)
}
output, err := svc.Register(context.Background(), tt.input)
if tt.wantErr != nil {
if err != tt.wantErr {
t.Errorf("expected error %v, got %v", tt.wantErr, err)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.User == nil {
t.Fatal("expected user in output")
}
if output.User.Email != tt.input.Email {
t.Errorf("expected email %s, got %s", tt.input.Email, output.User.Email)
}
if output.Token == "" {
t.Error("expected token in output")
}
// Verify user was persisted
persisted, err := repo.GetByEmail(context.Background(), tt.input.Email)
if err != nil {
t.Fatalf("failed to get persisted user: %v", err)
}
if persisted.Email != tt.input.Email {
t.Errorf("persisted user email mismatch")
}
})
}
}
func TestUserService_Login(t *testing.T) {
tests := []struct {
name string
input LoginInput
setup func(*mockUserRepository)
wantErr error
}{
{
name: "valid login",
input: LoginInput{
Email: "user@example.com",
Password: "correctpassword",
},
setup: func(repo *mockUserRepository) {
user, _ := domain.NewUser("user-id", "user@example.com", "correctpassword")
_ = repo.Create(context.Background(), user)
},
wantErr: nil,
},
{
name: "wrong password",
input: LoginInput{
Email: "user@example.com",
Password: "wrongpassword",
},
setup: func(repo *mockUserRepository) {
user, _ := domain.NewUser("user-id", "user@example.com", "correctpassword")
_ = repo.Create(context.Background(), user)
},
wantErr: domain.ErrInvalidCredentials,
},
{
name: "user not found",
input: LoginInput{
Email: "nonexistent@example.com",
Password: "password123",
},
wantErr: domain.ErrInvalidCredentials,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
svc, repo := newTestUserService()
if tt.setup != nil {
tt.setup(repo)
}
output, err := svc.Login(context.Background(), tt.input)
if tt.wantErr != nil {
if err != tt.wantErr {
t.Errorf("expected error %v, got %v", tt.wantErr, err)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if output.User == nil {
t.Fatal("expected user in output")
}
if output.Token == "" {
t.Error("expected token in output")
}
})
}
}
func TestUserService_GetByID(t *testing.T) {
tests := []struct {
name string
userID domain.UserID
setup func(*mockUserRepository)
wantErr error
}{
{
name: "existing user",
userID: "user-123",
setup: func(repo *mockUserRepository) {
user, _ := domain.NewUser("user-123", "user@example.com", "password123")
_ = repo.Create(context.Background(), user)
},
wantErr: nil,
},
{
name: "user not found",
userID: "nonexistent",
wantErr: domain.ErrUserNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
svc, repo := newTestUserService()
if tt.setup != nil {
tt.setup(repo)
}
user, err := svc.GetByID(context.Background(), tt.userID)
if tt.wantErr != nil {
if err != tt.wantErr {
t.Errorf("expected error %v, got %v", tt.wantErr, err)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.ID != tt.userID {
t.Errorf("expected user ID %s, got %s", tt.userID, user.ID)
}
})
}
}