package auth import ( "context" "errors" "testing" "time" "github.com/google/uuid" "github.com/tdvorak/seen/backend/internal/config" "github.com/tdvorak/seen/backend/internal/domain" "github.com/tdvorak/seen/backend/internal/repositories/postgres" "go.uber.org/zap" ) type inMemoryRepo struct { usersByEmail map[string]domain.User sessions map[string]domain.Session } func newInMemoryRepo() *inMemoryRepo { return &inMemoryRepo{ usersByEmail: make(map[string]domain.User), sessions: make(map[string]domain.Session), } } func (r *inMemoryRepo) CreateUser(_ context.Context, user domain.User) error { if _, exists := r.usersByEmail[user.Email]; exists { return postgres.ErrUserAlreadyExists } r.usersByEmail[user.Email] = user return nil } func (r *inMemoryRepo) FindUserByEmail(_ context.Context, email string) (*domain.User, error) { user, exists := r.usersByEmail[email] if !exists { return nil, nil } copy := user return ©, nil } func (r *inMemoryRepo) FindUserByID(_ context.Context, userID uuid.UUID) (*domain.User, error) { for _, user := range r.usersByEmail { if user.ID == userID { copy := user return ©, nil } } return nil, nil } func (r *inMemoryRepo) CreateSession(_ context.Context, session domain.Session) error { r.sessions[session.RefreshToken] = session return nil } func (r *inMemoryRepo) FindSessionByRefreshToken(_ context.Context, refreshToken string) (*domain.Session, error) { session, exists := r.sessions[refreshToken] if !exists { return nil, nil } copy := session return ©, nil } func (r *inMemoryRepo) RevokeSession(_ context.Context, sessionID uuid.UUID) error { now := time.Now().UTC() for token, session := range r.sessions { if session.ID == sessionID { session.RevokedAt = &now r.sessions[token] = session return nil } } return errors.New("session not found") } func TestRegisterValidation(t *testing.T) { svc := NewService(newInMemoryRepo(), config.AuthConfig{AccessTokenTTLMinutes: 10, RefreshTokenTTLHours: 1, JWTSecret: "test"}, zap.NewNop()) _, err := svc.Register(context.Background(), RegisterInput{ Email: "", Password: "short", }) if !errors.Is(err, ErrInvalidInput) { t.Fatalf("expected invalid input error, got %v", err) } } func TestRegisterAndLoginFlow(t *testing.T) { repo := newInMemoryRepo() svc := NewService(repo, config.AuthConfig{AccessTokenTTLMinutes: 10, RefreshTokenTTLHours: 1, JWTSecret: "test"}, zap.NewNop()) registered, err := svc.Register(context.Background(), RegisterInput{ Email: "user@example.com", Password: "password123", DisplayName: "Seen User", }) if err != nil { t.Fatalf("register failed: %v", err) } if registered.AccessToken == "" || registered.RefreshToken == "" { t.Fatalf("expected issued tokens") } loggedIn, err := svc.Login(context.Background(), LoginInput{ Email: "user@example.com", Password: "password123", }) if err != nil { t.Fatalf("login failed: %v", err) } if loggedIn.AccessToken == "" { t.Fatalf("expected login access token") } } func TestLoginWrongPassword(t *testing.T) { repo := newInMemoryRepo() svc := NewService(repo, config.AuthConfig{AccessTokenTTLMinutes: 10, RefreshTokenTTLHours: 1, JWTSecret: "test"}, zap.NewNop()) _, err := svc.Register(context.Background(), RegisterInput{ Email: "user@example.com", Password: "password123", }) if err != nil { t.Fatalf("register failed: %v", err) } _, err = svc.Login(context.Background(), LoginInput{ Email: "user@example.com", Password: "wrongpass", }) if !errors.Is(err, ErrInvalidCredentials) { t.Fatalf("expected invalid credentials error, got %v", err) } } func TestUserFromAccessToken(t *testing.T) { repo := newInMemoryRepo() svc := NewService(repo, config.AuthConfig{AccessTokenTTLMinutes: 10, RefreshTokenTTLHours: 1, JWTSecret: "test"}, zap.NewNop()) authResult, err := svc.Register(context.Background(), RegisterInput{ Email: "user@example.com", Password: "password123", }) if err != nil { t.Fatalf("register failed: %v", err) } user, err := svc.UserFromAccessToken(context.Background(), authResult.AccessToken) if err != nil { t.Fatalf("user from access token failed: %v", err) } if user.Email != "user@example.com" { t.Fatalf("expected user email, got %s", user.Email) } }