all repos — weimar @ fe3bf26c76d0c554ef9e8f0c9f52038b37ea886e

Unnamed repository; edit this file 'description' to name the repository.

internal/auth/auth_test.go (view raw)

  1package auth
  2
  3import (
  4	"context"
  5	"database/sql"
  6	"testing"
  7	"time"
  8
  9	"github.com/mjensen/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}