internal/auth/auth_test.go (view raw)
1package auth
2
3import (
4 "context"
5 "database/sql"
6 "testing"
7 "time"
8
9 "codeberg.org/maxwelljensen/weimar/internal/db"
10)
11
12func setupTestDB(t *testing.T) *db.DB {
13 t.Helper()
14 database, err := db.Open(":memory:")
15 if err != nil {
16 t.Fatalf("open :memory: db: %v", err)
17 }
18 t.Cleanup(func() { database.Close() })
19 if err := database.Migrate(context.Background()); err != nil {
20 t.Fatalf("migrate: %v", err)
21 }
22 return database
23}
24
25func TestRegister_Success(t *testing.T) {
26 database := setupTestDB(t)
27 ctx := context.Background()
28
29 user, err := Register(ctx, database, "alice", "password123", false)
30 if err != nil {
31 t.Fatalf("Register: %v", err)
32 }
33 if user.Username != "alice" {
34 t.Fatalf("username = %q, want alice", user.Username)
35 }
36 if user.IsAdmin {
37 t.Fatal("expected non-admin user")
38 }
39}
40
41func TestRegister_Admin(t *testing.T) {
42 database := setupTestDB(t)
43 ctx := context.Background()
44
45 user, err := Register(ctx, database, "admin", "password123", true)
46 if err != nil {
47 t.Fatalf("Register: %v", err)
48 }
49 if !user.IsAdmin {
50 t.Fatal("expected admin user")
51 }
52}
53
54func TestRegister_UsernameTooShort(t *testing.T) {
55 database := setupTestDB(t)
56 _, err := Register(context.Background(), database, "ab", "password123", false)
57 if err != ErrUsernameTooShort {
58 t.Fatalf("expected ErrUsernameTooShort, got %v", err)
59 }
60}
61
62func TestRegister_PasswordTooShort(t *testing.T) {
63 database := setupTestDB(t)
64 _, err := Register(context.Background(), database, "alice", "short", false)
65 if err != ErrPasswordTooShort {
66 t.Fatalf("expected ErrPasswordTooShort, got %v", err)
67 }
68}
69
70func TestRegister_UsernameTaken(t *testing.T) {
71 database := setupTestDB(t)
72 ctx := context.Background()
73
74 if _, err := Register(ctx, database, "alice", "password123", false); err != nil {
75 t.Fatalf("first register: %v", err)
76 }
77 _, err := Register(ctx, database, "alice", "otherpass1", false)
78 if err != ErrUsernameTaken {
79 t.Fatalf("expected ErrUsernameTaken, got %v", err)
80 }
81}
82
83func TestSessionManager_Login_Success(t *testing.T) {
84 database := setupTestDB(t)
85 ctx := context.Background()
86
87 Register(ctx, database, "alice", "password123", false)
88 sm := NewSessionManager(database)
89
90 user, token, err := sm.Login(ctx, "alice", "password123")
91 if err != nil {
92 t.Fatalf("Login: %v", err)
93 }
94 if user.Username != "alice" {
95 t.Fatalf("username = %q", user.Username)
96 }
97 if token == "" {
98 t.Fatal("expected non-empty token")
99 }
100 if len(token) != 64 {
101 t.Fatalf("token length = %d, want 64", len(token))
102 }
103}
104
105func TestSessionManager_Login_InvalidPassword(t *testing.T) {
106 database := setupTestDB(t)
107 ctx := context.Background()
108
109 Register(ctx, database, "alice", "password123", false)
110 sm := NewSessionManager(database)
111
112 _, _, err := sm.Login(ctx, "alice", "wrongpass")
113 if err != ErrInvalidCredentials {
114 t.Fatalf("expected ErrInvalidCredentials, got %v", err)
115 }
116}
117
118func TestSessionManager_Login_UserNotFound(t *testing.T) {
119 database := setupTestDB(t)
120 sm := NewSessionManager(database)
121
122 _, _, err := sm.Login(context.Background(), "nobody", "password123")
123 if err != ErrInvalidCredentials {
124 t.Fatalf("expected ErrInvalidCredentials, got %v", err)
125 }
126}
127
128func TestSessionManager_Authenticate_Success(t *testing.T) {
129 database := setupTestDB(t)
130 ctx := context.Background()
131
132 Register(ctx, database, "alice", "password123", false)
133 sm := NewSessionManager(database)
134 _, token, _ := sm.Login(ctx, "alice", "password123")
135
136 user, err := sm.Authenticate(ctx, token)
137 if err != nil {
138 t.Fatalf("Authenticate: %v", err)
139 }
140 if user.Username != "alice" {
141 t.Fatalf("username = %q", user.Username)
142 }
143}
144
145func TestSessionManager_Authenticate_Expired(t *testing.T) {
146 database := setupTestDB(t)
147 ctx := context.Background()
148
149 // Create a user and a session directly with an already-expired timestamp.
150 Register(ctx, database, "alice", "password123", false)
151 user, _ := database.GetUserByUsername(ctx, "alice")
152 token := "exptoken123456789012345678901234567890123456789012345678901234567890"
153 expiredAt := time.Now().UTC().Add(-time.Hour)
154 database.CreateSession(ctx, user.ID, token, expiredAt)
155
156 sm := NewSessionManager(database)
157 _, err := sm.Authenticate(ctx, token)
158 if err != ErrSessionExpired {
159 t.Fatalf("expected ErrSessionExpired, got %v", err)
160 }
161
162 // Session should be cleaned up.
163 _, err = database.GetSessionByToken(ctx, token)
164 if err != sql.ErrNoRows {
165 t.Fatal("expired session should have been deleted")
166 }
167}
168
169func TestSessionManager_Authenticate_TokenNotFound(t *testing.T) {
170 database := setupTestDB(t)
171 sm := NewSessionManager(database)
172
173 _, err := sm.Authenticate(context.Background(), "nonexistenttoken")
174 if err != ErrSessionNotFound {
175 t.Fatalf("expected ErrSessionNotFound, got %v", err)
176 }
177}
178
179func TestSessionManager_Logout(t *testing.T) {
180 database := setupTestDB(t)
181 ctx := context.Background()
182
183 Register(ctx, database, "alice", "password123", false)
184 sm := NewSessionManager(database)
185 _, token, _ := sm.Login(ctx, "alice", "password123")
186
187 if err := sm.Logout(ctx, token); err != nil {
188 t.Fatalf("Logout: %v", err)
189 }
190
191 // Session should be gone.
192 _, err := sm.Authenticate(ctx, token)
193 if err != ErrSessionNotFound {
194 t.Fatalf("expected ErrSessionNotFound after logout, got %v", err)
195 }
196}
197
198func TestSessionManager_Authenticate_ExtendsExpiry(t *testing.T) {
199 database := setupTestDB(t)
200 ctx := context.Background()
201
202 Register(ctx, database, "alice", "password123", false)
203 sm := NewSessionManager(database)
204 _, token, _ := sm.Login(ctx, "alice", "password123")
205
206 // Authenticate should extend the session.
207 if _, err := sm.Authenticate(ctx, token); err != nil {
208 t.Fatalf("first Authenticate: %v", err)
209 }
210
211 sess, err := database.GetSessionByToken(ctx, token)
212 if err != nil {
213 t.Fatalf("GetSessionByToken: %v", err)
214 }
215
216 // Expiry should be far in the future (SessionDuration ~30 days).
217 expectedMin := time.Now().UTC().Add(29 * 24 * time.Hour)
218 if sess.ExpiresAt.Before(expectedMin) {
219 t.Fatalf("session expires at %v, want after %v", sess.ExpiresAt, expectedMin)
220 }
221}