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) } }) } }