Ctrl K
ComunidadGuías…

server/internal/handlers/admin_users_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"
	"testing"
	"time"

	"blackbox/server/internal/auth"
	"blackbox/server/internal/handlers"
	"blackbox/server/internal/hub"
	"blackbox/server/internal/models"
	"github.com/go-chi/chi/v5"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func adminUserContext(userID string) context.Context {
	return context.WithValue(context.Background(), auth.ClaimsKey, &auth.Claims{
		UserID:  userID,
		IsAdmin: true,
	})
}

func TestListAdminUsers_ReturnsList(t *testing.T) {
	database := newTestDB(t)
	database.Create(&models.User{ID: "u1", Username: "alice", IsAdmin: true})
	database.Create(&models.User{ID: "u2", Username: "bob", IsAdmin: false})

	req := httptest.NewRequest(http.MethodGet, "/api/admin/users", nil)
	req = req.WithContext(adminUserContext("u1"))
	w := httptest.NewRecorder()

	handlers.ListAdminUsers(database)(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var result []map[string]interface{}
	require.NoError(t, json.NewDecoder(w.Body).Decode(&result))
	assert.Len(t, result, 2)
	// password hash must not be in response
	for _, u := range result {
		_, hasHash := u["password_hash"]
		assert.False(t, hasHash)
	}
}

func TestUpdateAdminUser_ToggleAdmin(t *testing.T) {
	database := newTestDB(t)
	database.Create(&models.User{ID: "admin1", Username: "admin", IsAdmin: true})
	database.Create(&models.User{ID: "user1", Username: "alice", IsAdmin: false})

	body := `{"is_admin": true}`
	req := httptest.NewRequest(http.MethodPatch, "/api/admin/users/user1", bytes.NewBufferString(body))
	req.Header.Set("Content-Type", "application/json")
	req = req.WithContext(adminUserContext("admin1"))

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", "user1")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))

	w := httptest.NewRecorder()
	handlers.UpdateAdminUser(database, nil)(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var user models.User
	require.NoError(t, database.First(&user, "id = ?", "user1").Error)
	assert.True(t, user.IsAdmin)
	assert.Equal(t, 1, user.TokenVersion)
}

func TestUpdateAdminUser_CannotDemoteSelf(t *testing.T) {
	database := newTestDB(t)
	database.Create(&models.User{ID: "admin1", Username: "admin", IsAdmin: true})

	body := `{"is_admin": false}`
	req := httptest.NewRequest(http.MethodPatch, "/api/admin/users/admin1", bytes.NewBufferString(body))
	req.Header.Set("Content-Type", "application/json")
	req = req.WithContext(adminUserContext("admin1"))

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", "admin1")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))

	w := httptest.NewRecorder()
	handlers.UpdateAdminUser(database, nil)(w, req)

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

func TestUpdateAdminUser_RequiresExplicitIsAdmin(t *testing.T) {
	database := newTestDB(t)
	require.NoError(t, database.Create(&models.User{ID: "admin1", Username: "admin", IsAdmin: true}).Error)
	require.NoError(t, database.Create(&models.User{ID: "user1", Username: "alice", IsAdmin: false}).Error)

	req := httptest.NewRequest(http.MethodPatch, "/api/admin/users/user1", bytes.NewBufferString(`{}`))
	req.Header.Set("Content-Type", "application/json")
	req = req.WithContext(adminUserContext("admin1"))

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", "user1")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))

	w := httptest.NewRecorder()
	handlers.UpdateAdminUser(database, nil)(w, req)

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

func TestUpdateAdminUser_InvalidatesActiveSessionsOnRoleChange(t *testing.T) {
	database := newTestDB(t)
	require.NoError(t, database.Create(&models.User{ID: "admin1", Username: "admin", IsAdmin: true}).Error)
	require.NoError(t, database.Create(&models.User{ID: "user1", Username: "alice", IsAdmin: false, TokenVersion: 7}).Error)

	eventHub := hub.New()
	_, _, disconnect, unsub, err := eventHub.Subscribe("user1", "10.0.0.1")
	require.NoError(t, err)
	defer unsub()

	req := httptest.NewRequest(http.MethodPatch, "/api/admin/users/user1", bytes.NewBufferString(`{"is_admin": true}`))
	req.Header.Set("Content-Type", "application/json")
	req = req.WithContext(adminUserContext("admin1"))

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", "user1")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))

	w := httptest.NewRecorder()
	handlers.UpdateAdminUser(database, eventHub)(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	select {
	case reason := <-disconnect:
		assert.Equal(t, "session invalidated", reason)
	case <-time.After(100 * time.Millisecond):
		t.Fatal("role change did not invalidate active sessions")
	}
}

func TestForceLogoutUser_IncrementsTokenVersion(t *testing.T) {
	database := newTestDB(t)
	database.Create(&models.User{ID: "admin1", Username: "admin", IsAdmin: true, TokenVersion: 0})
	database.Create(&models.User{ID: "user1", Username: "alice", IsAdmin: false, TokenVersion: 0})

	req := httptest.NewRequest(http.MethodPost, "/api/admin/users/user1/force-logout", nil)
	req = req.WithContext(adminUserContext("admin1"))

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", "user1")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))

	w := httptest.NewRecorder()
	handlers.ForceLogoutUser(database, nil)(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var user models.User
	require.NoError(t, database.First(&user, "id = ?", "user1").Error)
	assert.Equal(t, 1, user.TokenVersion)
}

func TestDeleteAdminUser_DeletesUser(t *testing.T) {
	database := newTestDB(t)
	database.Create(&models.User{ID: "admin1", Username: "admin", IsAdmin: true})
	database.Create(&models.User{ID: "user1", Username: "alice", IsAdmin: false})

	req := httptest.NewRequest(http.MethodDelete, "/api/admin/users/user1", nil)
	req = req.WithContext(adminUserContext("admin1"))

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", "user1")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))

	w := httptest.NewRecorder()
	handlers.DeleteAdminUser(database, nil)(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var count int64
	database.Model(&models.User{}).Where("id = ?", "user1").Count(&count)
	assert.EqualValues(t, 0, count)
}

func TestDeleteAdminUser_CannotDeleteSelf(t *testing.T) {
	database := newTestDB(t)
	database.Create(&models.User{ID: "admin1", Username: "admin", IsAdmin: true})

	req := httptest.NewRequest(http.MethodDelete, "/api/admin/users/admin1", nil)
	req = req.WithContext(adminUserContext("admin1"))

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", "admin1")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))

	w := httptest.NewRecorder()
	handlers.DeleteAdminUser(database, nil)(w, req)

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