|
1 | 1 | package service_test |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "context" |
| 5 | + "errors" |
4 | 6 | "testing" |
| 7 | + "time" |
5 | 8 |
|
| 9 | + "github.com/capyrpi/api/internal/auth/ports" |
6 | 10 | "github.com/capyrpi/api/internal/auth/service" |
| 11 | + "github.com/capyrpi/api/internal/database" |
| 12 | + "github.com/google/uuid" |
| 13 | + "github.com/jackc/pgx/v5" |
| 14 | + "github.com/jackc/pgx/v5/pgtype" |
7 | 15 | "github.com/stretchr/testify/assert" |
| 16 | + "github.com/stretchr/testify/mock" |
8 | 17 | ) |
9 | 18 |
|
10 | | -func TestAuthService(t *testing.T) { |
11 | | - // Just a dummy test to ensure the package imports are correct |
12 | | - // and the struct can be instantiated (with nil dependencies for now) |
13 | | - svc := service.NewAuthService(nil, nil, nil, nil) |
14 | | - assert.NotNil(t, svc) |
| 19 | +// Mocks |
| 20 | +type MockUserRepo struct { |
| 21 | + mock.Mock |
| 22 | +} |
| 23 | + |
| 24 | +func (m *MockUserRepo) GetUserByEmail(ctx context.Context, email pgtype.Text) (database.User, error) { |
| 25 | + args := m.Called(ctx, email) |
| 26 | + return args.Get(0).(database.User), args.Error(1) |
| 27 | +} |
| 28 | + |
| 29 | +func (m *MockUserRepo) CreateUser(ctx context.Context, arg database.CreateUserParams) (database.User, error) { |
| 30 | + args := m.Called(ctx, arg) |
| 31 | + return args.Get(0).(database.User), args.Error(1) |
| 32 | +} |
| 33 | + |
| 34 | +func (m *MockUserRepo) GetUserByID(ctx context.Context, uid uuid.UUID) (database.User, error) { |
| 35 | + args := m.Called(ctx, uid) |
| 36 | + return args.Get(0).(database.User), args.Error(1) |
| 37 | +} |
| 38 | + |
| 39 | +type MockBotRepo struct { |
| 40 | + mock.Mock |
| 41 | +} |
| 42 | + |
| 43 | +func (m *MockBotRepo) CreateBotToken(ctx context.Context, arg database.CreateBotTokenParams) (database.BotToken, error) { |
| 44 | + args := m.Called(ctx, arg) |
| 45 | + return args.Get(0).(database.BotToken), args.Error(1) |
| 46 | +} |
| 47 | + |
| 48 | +type MockTokenProvider struct { |
| 49 | + mock.Mock |
| 50 | +} |
| 51 | + |
| 52 | +func (m *MockTokenProvider) GenerateToken(user database.User) (string, error) { |
| 53 | + args := m.Called(user) |
| 54 | + return args.String(0), args.Error(1) |
| 55 | +} |
| 56 | + |
| 57 | +type MockOAuthProvider struct { |
| 58 | + mock.Mock |
| 59 | +} |
| 60 | + |
| 61 | +func (m *MockOAuthProvider) GetAuthURL(state string) string { |
| 62 | + args := m.Called(state) |
| 63 | + return args.String(0) |
| 64 | +} |
| 65 | + |
| 66 | +func (m *MockOAuthProvider) ExchangeCode(ctx context.Context, code string) (*ports.UserInfo, error) { |
| 67 | + args := m.Called(ctx, code) |
| 68 | + if args.Get(0) == nil { |
| 69 | + return nil, args.Error(1) |
| 70 | + } |
| 71 | + return args.Get(0).(*ports.UserInfo), args.Error(1) |
| 72 | +} |
| 73 | + |
| 74 | +func TestAuthService_HandleOAuthCallback(t *testing.T) { |
| 75 | + // Setup |
| 76 | + mockUserRepo := new(MockUserRepo) |
| 77 | + mockBotRepo := new(MockBotRepo) |
| 78 | + mockTokenProvider := new(MockTokenProvider) |
| 79 | + mockGoogle := new(MockOAuthProvider) |
| 80 | + mockMicrosoft := new(MockOAuthProvider) |
| 81 | + |
| 82 | + svc := service.NewAuthService(mockUserRepo, mockBotRepo, mockTokenProvider, mockGoogle, mockMicrosoft) |
| 83 | + ctx := context.Background() |
| 84 | + |
| 85 | + t.Run("Success existing user", func(t *testing.T) { |
| 86 | + email := "test@example.com" |
| 87 | + userInfo := &ports.UserInfo{Email: email, FirstName: "John", LastName: "Doe"} |
| 88 | + user := database.User{ |
| 89 | + Uid: uuid.New(), |
| 90 | + FirstName: "John", |
| 91 | + LastName: "Doe", |
| 92 | + PersonalEmail: pgtype.Text{String: email, Valid: true}, |
| 93 | + } |
| 94 | + token := "jwt-token" |
| 95 | + |
| 96 | + mockGoogle.On("ExchangeCode", ctx, "auth-code").Return(userInfo, nil) |
| 97 | + mockUserRepo.On("GetUserByEmail", ctx, pgtype.Text{String: email, Valid: true}).Return(user, nil) |
| 98 | + mockTokenProvider.On("GenerateToken", user).Return(token, nil) |
| 99 | + |
| 100 | + res, err := svc.HandleOAuthCallback(ctx, "google", "auth-code") |
| 101 | + |
| 102 | + assert.NoError(t, err) |
| 103 | + assert.Equal(t, user, res.User) |
| 104 | + assert.Equal(t, token, res.Token) |
| 105 | + mockGoogle.AssertExpectations(t) |
| 106 | + mockUserRepo.AssertExpectations(t) |
| 107 | + mockTokenProvider.AssertExpectations(t) |
| 108 | + }) |
| 109 | + |
| 110 | + t.Run("Success new user", func(t *testing.T) { |
| 111 | + email := "new@example.com" |
| 112 | + userInfo := &ports.UserInfo{Email: email, FirstName: "Jane", LastName: "Doe"} |
| 113 | + user := database.User{ |
| 114 | + Uid: uuid.New(), |
| 115 | + FirstName: "Jane", |
| 116 | + LastName: "Doe", |
| 117 | + PersonalEmail: pgtype.Text{String: email, Valid: true}, |
| 118 | + } |
| 119 | + token := "jwt-token" |
| 120 | + |
| 121 | + mockGoogle.On("ExchangeCode", ctx, "new-code").Return(userInfo, nil) |
| 122 | + mockUserRepo.On("GetUserByEmail", ctx, pgtype.Text{String: email, Valid: true}).Return(database.User{}, pgx.ErrNoRows) |
| 123 | + mockUserRepo.On("CreateUser", ctx, mock.AnythingOfType("database.CreateUserParams")).Return(user, nil) |
| 124 | + mockTokenProvider.On("GenerateToken", user).Return(token, nil) |
| 125 | + |
| 126 | + res, err := svc.HandleOAuthCallback(ctx, "google", "new-code") |
| 127 | + |
| 128 | + assert.NoError(t, err) |
| 129 | + assert.Equal(t, user, res.User) |
| 130 | + assert.Equal(t, token, res.Token) |
| 131 | + }) |
| 132 | + |
| 133 | + t.Run("Invalid provider", func(t *testing.T) { |
| 134 | + res, err := svc.HandleOAuthCallback(ctx, "invalid", "code") |
| 135 | + assert.Error(t, err) |
| 136 | + assert.Nil(t, res) |
| 137 | + }) |
| 138 | + |
| 139 | + t.Run("Exchange code error", func(t *testing.T) { |
| 140 | + mockGoogle.On("ExchangeCode", ctx, "bad-code").Return(nil, errors.New("exchange error")) |
| 141 | + res, err := svc.HandleOAuthCallback(ctx, "google", "bad-code") |
| 142 | + assert.Error(t, err) |
| 143 | + assert.Nil(t, res) |
| 144 | + }) |
| 145 | +} |
| 146 | + |
| 147 | +func TestAuthService_GenerateBotToken(t *testing.T) { |
| 148 | + // Setup |
| 149 | + mockUserRepo := new(MockUserRepo) |
| 150 | + mockBotRepo := new(MockBotRepo) |
| 151 | + mockTokenProvider := new(MockTokenProvider) |
| 152 | + mockGoogle := new(MockOAuthProvider) |
| 153 | + mockMicrosoft := new(MockOAuthProvider) |
| 154 | + |
| 155 | + svc := service.NewAuthService(mockUserRepo, mockBotRepo, mockTokenProvider, mockGoogle, mockMicrosoft) |
| 156 | + ctx := context.Background() |
| 157 | + uid := uuid.New() |
| 158 | + |
| 159 | + t.Run("Success", func(t *testing.T) { |
| 160 | + name := "My Bot" |
| 161 | + botToken := database.BotToken{ |
| 162 | + TokenID: uuid.New(), |
| 163 | + Name: name, |
| 164 | + CreatedBy: uid, |
| 165 | + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, |
| 166 | + } |
| 167 | + |
| 168 | + // Use mock.MatchedBy to check arguments |
| 169 | + mockBotRepo.On("CreateBotToken", ctx, mock.MatchedBy(func(arg database.CreateBotTokenParams) bool { |
| 170 | + return arg.Name == name && arg.CreatedBy == uid && len(arg.TokenHash) > 0 |
| 171 | + })).Return(botToken, nil) |
| 172 | + |
| 173 | + res, err := svc.GenerateBotToken(ctx, name, uid, nil) |
| 174 | + |
| 175 | + assert.NoError(t, err) |
| 176 | + assert.Equal(t, botToken, res.Token) |
| 177 | + assert.NotEmpty(t, res.RawToken) |
| 178 | + mockBotRepo.AssertExpectations(t) |
| 179 | + }) |
15 | 180 | } |
0 commit comments