Ctrl K
ComunidadGuías…

server/internal/handlers/auth_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handlers_test

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"

	"blackbox/server/internal/auth"
	"blackbox/server/internal/db"
	"blackbox/server/internal/handlers"
	"blackbox/server/internal/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func sessionClaimsFromResponse(t *testing.T, w *httptest.ResponseRecorder, secret string) *auth.Claims {
	t.Helper()

	var sessionCookie *http.Cookie
	for _, cookie := range w.Result().Cookies() {
		if cookie.Name == auth.SessionCookieName {
			sessionCookie = cookie
			break
		}
	}
	require.NotNil(t, sessionCookie)

	claims, err := auth.VerifyJWT(sessionCookie.Value, secret)
	require.NoError(t, err)
	return claims
}

func TestBootstrap_CreatesAdminAndSetsSessionCookie(t *testing.T) {
	database := newTestDB(t)

	body := `{"username":"admin","password":"SuperSecret1!","email":"admin@example.com"}`
	req := httptest.NewRequest(http.MethodPost, "/api/auth/bootstrap", bytes.NewBufferString(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	handlers.Bootstrap(database, "jwt-test-secret")(w, req)

	assert.Equal(t, http.StatusCreated, w.Code)
	var resp map[string]map[string]interface{}
	require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
	claims := sessionClaimsFromResponse(t, w, "jwt-test-secret")
	assert.Equal(t, "admin", claims.Username)
	assert.Equal(t, "admin@example.com", claims.Email)

	var user models.User
	require.NoError(t, database.First(&user, "username = ?", "admin").Error)
	assert.True(t, user.IsAdmin)
	assert.Equal(t, "admin@example.com", user.Email)
}

func TestBootstrap_RejectsIfAlreadyBootstrapped(t *testing.T) {
	database := newTestDB(t)
	database.Create(&models.User{ID: "01EXISTING", Username: "existing", IsAdmin: true})

	body := `{"username":"hacker","password":"password"}`
	req := httptest.NewRequest(http.MethodPost, "/api/auth/bootstrap", bytes.NewBufferString(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	handlers.Bootstrap(database, "jwt-test-secret")(w, req)

	assert.Equal(t, http.StatusConflict, w.Code)
}

func TestBootstrap_ConcurrentRequestsOnlyOneSucceeds(t *testing.T) {
	database := newTestDB(t)
	handler := handlers.Bootstrap(database, "jwt-test-secret")

	results := make([]int, 2)
	var wg sync.WaitGroup
	wg.Add(2)
	for i, username := range []string{"admin-a", "admin-b"} {
		go func(i int, username string) {
			defer wg.Done()
			body := `{"username":"` + username + `","password":"SuperSecret1!"}`
			req := httptest.NewRequest(http.MethodPost, "/api/auth/bootstrap", bytes.NewBufferString(body))
			req.Header.Set("Content-Type", "application/json")
			w := httptest.NewRecorder()
			handler(w, req)
			results[i] = w.Code
		}(i, username)
	}
	wg.Wait()

	created := 0
	for _, code := range results {
		if code == http.StatusCreated {
			created++
		}
	}
	assert.Equal(t, 1, created)

	var count int64
	require.NoError(t, database.Model(&models.User{}).Count(&count).Error)
	assert.EqualValues(t, 1, count)
}

func TestLogin_ValidCredentials(t *testing.T) {
	database := newTestDB(t)
	body := `{"username":"admin","password":"MyPassword1!","email":"admin@example.com"}`
	req := httptest.NewRequest(http.MethodPost, "/api/auth/bootstrap", bytes.NewBufferString(body))
	req.Header.Set("Content-Type", "application/json")
	handlers.Bootstrap(database, "jwt-test-secret")(httptest.NewRecorder(), req)

	loginBody := `{"username":"admin","password":"MyPassword1!"}`
	req2 := httptest.NewRequest(http.MethodPost, "/api/auth/login", bytes.NewBufferString(loginBody))
	req2.Header.Set("Content-Type", "application/json")
	w2 := httptest.NewRecorder()

	handlers.Login(database, "jwt-test-secret")(w2, req2)

	assert.Equal(t, http.StatusOK, w2.Code)
	var resp map[string]map[string]interface{}
	require.NoError(t, json.NewDecoder(w2.Body).Decode(&resp))
	claims := sessionClaimsFromResponse(t, w2, "jwt-test-secret")
	assert.Equal(t, "admin", claims.Username)
	assert.Equal(t, "admin@example.com", claims.Email)
}

func TestLogin_WrongPassword(t *testing.T) {
	database := newTestDB(t)
	body := `{"username":"admin","password":"correct-pass"}`
	req := httptest.NewRequest(http.MethodPost, "/api/auth/bootstrap", bytes.NewBufferString(body))
	req.Header.Set("Content-Type", "application/json")
	handlers.Bootstrap(database, "jwt-test-secret")(httptest.NewRecorder(), req)

	loginBody := `{"username":"admin","password":"wrong-pass"}`
	req2 := httptest.NewRequest(http.MethodPost, "/api/auth/login", bytes.NewBufferString(loginBody))
	req2.Header.Set("Content-Type", "application/json")
	w2 := httptest.NewRecorder()

	handlers.Login(database, "jwt-test-secret")(w2, req2)

	assert.Equal(t, http.StatusUnauthorized, w2.Code)
}

func TestLogin_DBError_Returns503(t *testing.T) {
	database, err := db.Init(":memory:")
	require.NoError(t, err)
	sqlDB, err := database.DB()
	require.NoError(t, err)
	require.NoError(t, sqlDB.Close())

	body, err := json.Marshal(map[string]string{"username": "admin", "password": "pass"})
	require.NoError(t, err)
	req := httptest.NewRequest(http.MethodPost, "/api/auth/login", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	handlers.Login(database, "test-secret")(w, req)

	assert.Equal(t, http.StatusServiceUnavailable, w.Code)
}

func TestCurrentUser_ReturnsSessionUser(t *testing.T) {
	req := httptest.NewRequest(http.MethodGet, "/api/auth/me", nil)
	req = req.WithContext(context.WithValue(req.Context(), auth.ClaimsKey, &auth.Claims{
		UserID:   "user-1",
		Username: "alice",
		Email:    "alice@example.com",
		IsAdmin:  true,
	}))
	w := httptest.NewRecorder()

	handlers.CurrentUser()(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp struct {
		User struct {
			Username string `json:"username"`
			Email    string `json:"email"`
			IsAdmin  bool   `json:"is_admin"`
		} `json:"user"`
	}
	require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
	assert.Equal(t, "alice", resp.User.Username)
	assert.Equal(t, "alice@example.com", resp.User.Email)
	assert.True(t, resp.User.IsAdmin)
}

func TestLogout_ClearsSessionCookie(t *testing.T) {
	req := httptest.NewRequest(http.MethodPost, "/api/auth/logout", nil)
	w := httptest.NewRecorder()

	handlers.Logout()(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var sessionCookie *http.Cookie
	for _, cookie := range w.Result().Cookies() {
		if cookie.Name == auth.SessionCookieName {
			sessionCookie = cookie
			break
		}
	}
	require.NotNil(t, sessionCookie)
	assert.Equal(t, -1, sessionCookie.MaxAge)
}