This commit is contained in:
2026-03-15 20:24:29 -04:00
parent c84bc2522e
commit d40f8fc375
20 changed files with 2316 additions and 1240 deletions

View File

@@ -9,19 +9,90 @@ import (
"net/http/httptest"
"testing"
"github.com/stretchr/testify/suite"
argon2 "github.com/alexedwards/argon2id"
"reichard.io/antholume/config"
"reichard.io/antholume/database"
)
func TestAPILogin(t *testing.T) {
db := setupTestDB(t)
cfg := testConfig()
server := NewServer(db, cfg)
type AuthTestSuite struct {
suite.Suite
db *database.DBManager
cfg *config.Config
srv *Server
}
// First, create a user
createTestUser(t, db, "testuser", "testpass")
func (suite *AuthTestSuite) setupConfig() *config.Config {
return &config.Config{
ListenPort: "8080",
DBType: "memory",
DBName: "test",
ConfigPath: "/tmp",
CookieAuthKey: "test-auth-key-32-bytes-long-enough",
CookieEncKey: "0123456789abcdef",
CookieSecure: false,
CookieHTTPOnly: true,
Version: "test",
DemoMode: false,
RegistrationEnabled: true,
}
}
func TestAuth(t *testing.T) {
suite.Run(t, new(AuthTestSuite))
}
func (suite *AuthTestSuite) SetupTest() {
suite.cfg = suite.setupConfig()
suite.db = database.NewMgr(suite.cfg)
suite.srv = NewServer(suite.db, suite.cfg)
}
func (suite *AuthTestSuite) createTestUser(username, password string) {
md5Hash := fmt.Sprintf("%x", md5.Sum([]byte(password)))
hashedPassword, err := argon2.CreateHash(md5Hash, argon2.DefaultParams)
suite.Require().NoError(err)
authHash := "test-auth-hash"
_, err = suite.db.Queries.CreateUser(suite.T().Context(), database.CreateUserParams{
ID: username,
Pass: &hashedPassword,
AuthHash: &authHash,
Admin: true,
})
suite.Require().NoError(err)
}
func (suite *AuthTestSuite) login(username, password string) *http.Cookie {
reqBody := LoginRequest{
Username: username,
Password: password,
}
body, err := json.Marshal(reqBody)
suite.Require().NoError(err)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
w := httptest.NewRecorder()
suite.srv.ServeHTTP(w, req)
suite.Equal(http.StatusOK, w.Code, "login should return 200")
var resp LoginResponse
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
cookies := w.Result().Cookies()
suite.Require().Len(cookies, 1, "should have session cookie")
return cookies[0]
}
func (suite *AuthTestSuite) TestAPILogin() {
suite.createTestUser("testuser", "testpass")
// Test login
reqBody := LoginRequest{
Username: "testuser",
Password: "testpass",
@@ -31,27 +102,16 @@ func TestAPILogin(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
suite.srv.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("Expected 200, got %d: %s", w.Code, w.Body.String())
}
suite.Equal(http.StatusOK, w.Code)
var resp LoginResponse
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("Failed to unmarshal response: %v", err)
}
if resp.Username != "testuser" {
t.Errorf("Expected username 'testuser', got '%s'", resp.Username)
}
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
suite.Equal("testuser", resp.Username)
}
func TestAPILoginInvalidCredentials(t *testing.T) {
db := setupTestDB(t)
cfg := testConfig()
server := NewServer(db, cfg)
func (suite *AuthTestSuite) TestAPILoginInvalidCredentials() {
reqBody := LoginRequest{
Username: "testuser",
Password: "wrongpass",
@@ -61,124 +121,46 @@ func TestAPILoginInvalidCredentials(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
suite.srv.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("Expected 401, got %d", w.Code)
}
suite.Equal(http.StatusUnauthorized, w.Code)
}
func TestAPILogout(t *testing.T) {
db := setupTestDB(t)
cfg := testConfig()
server := NewServer(db, cfg)
func (suite *AuthTestSuite) TestAPILogout() {
suite.createTestUser("testuser", "testpass")
cookie := suite.login("testuser", "testpass")
// Create user and login
createTestUser(t, db, "testuser", "testpass")
// Login first
reqBody := LoginRequest{Username: "testuser", Password: "testpass"}
body, _ := json.Marshal(reqBody)
loginReq := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
loginResp := httptest.NewRecorder()
server.ServeHTTP(loginResp, loginReq)
// Get session cookie
cookies := loginResp.Result().Cookies()
if len(cookies) == 0 {
t.Fatal("No session cookie returned")
}
// Logout
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil)
req.AddCookie(cookies[0])
req.AddCookie(cookie)
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
suite.srv.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("Expected 200, got %d", w.Code)
}
suite.Equal(http.StatusOK, w.Code)
}
func TestAPIGetMe(t *testing.T) {
db := setupTestDB(t)
cfg := testConfig()
server := NewServer(db, cfg)
func (suite *AuthTestSuite) TestAPIGetMe() {
suite.createTestUser("testuser", "testpass")
cookie := suite.login("testuser", "testpass")
// Create user and login
createTestUser(t, db, "testuser", "testpass")
// Login first
reqBody := LoginRequest{Username: "testuser", Password: "testpass"}
body, _ := json.Marshal(reqBody)
loginReq := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
loginResp := httptest.NewRecorder()
server.ServeHTTP(loginResp, loginReq)
// Get session cookie
cookies := loginResp.Result().Cookies()
if len(cookies) == 0 {
t.Fatal("No session cookie returned")
}
// Get me
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
req.AddCookie(cookies[0])
req.AddCookie(cookie)
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
suite.srv.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("Expected 200, got %d", w.Code)
}
suite.Equal(http.StatusOK, w.Code)
var resp UserData
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("Failed to unmarshal response: %v", err)
}
if resp.Username != "testuser" {
t.Errorf("Expected username 'testuser', got '%s'", resp.Username)
}
suite.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
suite.Equal("testuser", resp.Username)
}
func TestAPIGetMeUnauthenticated(t *testing.T) {
db := setupTestDB(t)
cfg := testConfig()
server := NewServer(db, cfg)
func (suite *AuthTestSuite) TestAPIGetMeUnauthenticated() {
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
suite.srv.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("Expected 401, got %d", w.Code)
}
}
func createTestUser(t *testing.T, db *database.DBManager, username, password string) {
t.Helper()
// MD5 hash for KOSync compatibility (matches existing system)
md5Hash := fmt.Sprintf("%x", md5.Sum([]byte(password)))
// Then argon2 hash the MD5
hashedPassword, err := argon2.CreateHash(md5Hash, argon2.DefaultParams)
if err != nil {
t.Fatalf("Failed to hash password: %v", err)
}
authHash := "test-auth-hash"
_, err = db.Queries.CreateUser(t.Context(), database.CreateUserParams{
ID: username,
Pass: &hashedPassword,
AuthHash: &authHash,
Admin: true,
})
if err != nil {
t.Fatalf("Failed to create user: %v", err)
}
suite.Equal(http.StatusUnauthorized, w.Code)
}