205 lines
4.5 KiB
Go
205 lines
4.5 KiB
Go
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||
|
|
|
||
|
|
package store
|
||
|
|
|
||
|
|
import (
|
||
|
|
"database/sql"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
_ "modernc.org/sqlite"
|
||
|
|
|
||
|
|
"kode.naiv.no/olemd/favoritter/internal/database"
|
||
|
|
)
|
||
|
|
|
||
|
|
func testDB(t *testing.T) *sql.DB {
|
||
|
|
t.Helper()
|
||
|
|
db, err := database.Open(":memory:")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("open test db: %v", err)
|
||
|
|
}
|
||
|
|
if err := database.Migrate(db); err != nil {
|
||
|
|
t.Fatalf("migrate test db: %v", err)
|
||
|
|
}
|
||
|
|
t.Cleanup(func() { db.Close() })
|
||
|
|
return db
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCreateAndAuthenticate(t *testing.T) {
|
||
|
|
db := testDB(t)
|
||
|
|
users := NewUserStore(db)
|
||
|
|
|
||
|
|
// Use fast Argon2 parameters for tests.
|
||
|
|
Argon2Memory = 1024
|
||
|
|
Argon2Time = 1
|
||
|
|
defer func() {
|
||
|
|
Argon2Memory = 65536
|
||
|
|
Argon2Time = 3
|
||
|
|
}()
|
||
|
|
|
||
|
|
// Create a user.
|
||
|
|
user, err := users.Create("testuser", "password123", "user")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("create user: %v", err)
|
||
|
|
}
|
||
|
|
if user.Username != "testuser" {
|
||
|
|
t.Errorf("username = %q, want %q", user.Username, "testuser")
|
||
|
|
}
|
||
|
|
if user.Role != "user" {
|
||
|
|
t.Errorf("role = %q, want %q", user.Role, "user")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Authenticate with correct password.
|
||
|
|
authed, err := users.Authenticate("testuser", "password123")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("authenticate: %v", err)
|
||
|
|
}
|
||
|
|
if authed.ID != user.ID {
|
||
|
|
t.Errorf("authenticated user ID = %d, want %d", authed.ID, user.ID)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Authenticate with wrong password.
|
||
|
|
_, err = users.Authenticate("testuser", "wrongpassword")
|
||
|
|
if err != ErrInvalidCredentials {
|
||
|
|
t.Errorf("wrong password error = %v, want ErrInvalidCredentials", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Authenticate with non-existent user.
|
||
|
|
_, err = users.Authenticate("nouser", "password123")
|
||
|
|
if err != ErrInvalidCredentials {
|
||
|
|
t.Errorf("non-existent user error = %v, want ErrInvalidCredentials", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCreateDuplicate(t *testing.T) {
|
||
|
|
db := testDB(t)
|
||
|
|
users := NewUserStore(db)
|
||
|
|
|
||
|
|
Argon2Memory = 1024
|
||
|
|
Argon2Time = 1
|
||
|
|
defer func() {
|
||
|
|
Argon2Memory = 65536
|
||
|
|
Argon2Time = 3
|
||
|
|
}()
|
||
|
|
|
||
|
|
_, err := users.Create("testuser", "password123", "user")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("create user: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err = users.Create("testuser", "password456", "user")
|
||
|
|
if err != ErrUserExists {
|
||
|
|
t.Errorf("duplicate error = %v, want ErrUserExists", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Case-insensitive duplicate.
|
||
|
|
_, err = users.Create("TestUser", "password456", "user")
|
||
|
|
if err != ErrUserExists {
|
||
|
|
t.Errorf("case-insensitive duplicate error = %v, want ErrUserExists", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUpdatePassword(t *testing.T) {
|
||
|
|
db := testDB(t)
|
||
|
|
users := NewUserStore(db)
|
||
|
|
|
||
|
|
Argon2Memory = 1024
|
||
|
|
Argon2Time = 1
|
||
|
|
defer func() {
|
||
|
|
Argon2Memory = 65536
|
||
|
|
Argon2Time = 3
|
||
|
|
}()
|
||
|
|
|
||
|
|
user, err := users.CreateWithReset("admin", "temppass", "admin")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("create user: %v", err)
|
||
|
|
}
|
||
|
|
if !user.MustResetPassword {
|
||
|
|
t.Error("expected must_reset_password to be true")
|
||
|
|
}
|
||
|
|
|
||
|
|
err = users.UpdatePassword(user.ID, "newpassword123")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("update password: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify old password no longer works.
|
||
|
|
_, err = users.Authenticate("admin", "temppass")
|
||
|
|
if err != ErrInvalidCredentials {
|
||
|
|
t.Error("old password should not work after reset")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify new password works and reset flag is cleared.
|
||
|
|
updated, err := users.Authenticate("admin", "newpassword123")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("authenticate with new password: %v", err)
|
||
|
|
}
|
||
|
|
if updated.MustResetPassword {
|
||
|
|
t.Error("must_reset_password should be false after update")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestEnsureAdmin(t *testing.T) {
|
||
|
|
db := testDB(t)
|
||
|
|
users := NewUserStore(db)
|
||
|
|
|
||
|
|
Argon2Memory = 1024
|
||
|
|
Argon2Time = 1
|
||
|
|
defer func() {
|
||
|
|
Argon2Memory = 65536
|
||
|
|
Argon2Time = 3
|
||
|
|
}()
|
||
|
|
|
||
|
|
// First call creates the admin.
|
||
|
|
err := users.EnsureAdmin("admin", "adminpass")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ensure admin: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
admin, err := users.GetByUsername("admin")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("get admin: %v", err)
|
||
|
|
}
|
||
|
|
if !admin.IsAdmin() {
|
||
|
|
t.Error("expected admin role")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Second call is a no-op.
|
||
|
|
err = users.EnsureAdmin("admin", "adminpass")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ensure admin (second call): %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
count, _ := users.Count()
|
||
|
|
if count != 1 {
|
||
|
|
t.Errorf("user count = %d, want 1", count)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDisabledUser(t *testing.T) {
|
||
|
|
db := testDB(t)
|
||
|
|
users := NewUserStore(db)
|
||
|
|
|
||
|
|
Argon2Memory = 1024
|
||
|
|
Argon2Time = 1
|
||
|
|
defer func() {
|
||
|
|
Argon2Memory = 65536
|
||
|
|
Argon2Time = 3
|
||
|
|
}()
|
||
|
|
|
||
|
|
user, err := users.Create("testuser", "password123", "user")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("create user: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Disable the user.
|
||
|
|
err = users.SetDisabled(user.ID, true)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("disable user: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Authentication should fail.
|
||
|
|
_, err = users.Authenticate("testuser", "password123")
|
||
|
|
if err != ErrUserDisabled {
|
||
|
|
t.Errorf("disabled user error = %v, want ErrUserDisabled", err)
|
||
|
|
}
|
||
|
|
}
|