chore: clean .gitignore cache
This commit is contained in:
@@ -1,463 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Wikid82/charon/backend/internal/models"
|
||||
"github.com/Wikid82/charon/backend/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// permissiveLAPIURLValidator allows any localhost URL for testing with mock servers.
|
||||
func permissiveLAPIURLValidator(raw string) (*url.URL, error) {
|
||||
return url.Parse(raw)
|
||||
}
|
||||
|
||||
// mockStopExecutor is a mock for the CrowdsecExecutor interface for Stop tests
|
||||
type mockStopExecutor struct {
|
||||
stopCalled bool
|
||||
stopErr error
|
||||
}
|
||||
|
||||
func (m *mockStopExecutor) Start(_ context.Context, _, _ string) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (m *mockStopExecutor) Stop(_ context.Context, _ string) error {
|
||||
m.stopCalled = true
|
||||
return m.stopErr
|
||||
}
|
||||
|
||||
func (m *mockStopExecutor) Status(_ context.Context, _ string) (running bool, pid int, err error) {
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
// createTestSecurityService creates a SecurityService for testing
|
||||
func createTestSecurityService(t *testing.T, db *gorm.DB) *services.SecurityService {
|
||||
t.Helper()
|
||||
return services.NewSecurityService(db)
|
||||
}
|
||||
|
||||
// TestCrowdsecHandler_Stop_Success tests the Stop handler with successful execution
|
||||
func TestCrowdsecHandler_Stop_Success(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))
|
||||
|
||||
// Create security config to be updated on stop
|
||||
cfg := models.SecurityConfig{Enabled: true, CrowdSecMode: "enabled"}
|
||||
require.NoError(t, db.Create(&cfg).Error)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
mockExec := &mockStopExecutor{}
|
||||
h := &CrowdsecHandler{
|
||||
DB: db,
|
||||
Executor: mockExec,
|
||||
CmdExec: &mockCommandExecutor{},
|
||||
DataDir: tmpDir,
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.POST("/stop", h.Stop)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/stop", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.True(t, mockExec.stopCalled)
|
||||
|
||||
var response map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "stopped", response["status"])
|
||||
|
||||
// Verify config was updated
|
||||
var updatedCfg models.SecurityConfig
|
||||
require.NoError(t, db.First(&updatedCfg).Error)
|
||||
assert.Equal(t, "disabled", updatedCfg.CrowdSecMode)
|
||||
assert.False(t, updatedCfg.Enabled)
|
||||
|
||||
// Verify setting was synced
|
||||
var setting models.Setting
|
||||
require.NoError(t, db.Where("key = ?", "security.crowdsec.enabled").First(&setting).Error)
|
||||
assert.Equal(t, "false", setting.Value)
|
||||
}
|
||||
|
||||
// TestCrowdsecHandler_Stop_Error tests the Stop handler with an execution error
|
||||
func TestCrowdsecHandler_Stop_Error(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
mockExec := &mockStopExecutor{stopErr: assert.AnError}
|
||||
h := &CrowdsecHandler{
|
||||
DB: db,
|
||||
Executor: mockExec,
|
||||
CmdExec: &mockCommandExecutor{},
|
||||
DataDir: tmpDir,
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.POST("/stop", h.Stop)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/stop", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.True(t, mockExec.stopCalled)
|
||||
}
|
||||
|
||||
// TestCrowdsecHandler_Stop_NoSecurityConfig tests Stop when there's no existing SecurityConfig
|
||||
func TestCrowdsecHandler_Stop_NoSecurityConfig(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))
|
||||
|
||||
// Don't create security config - test the path where no config exists
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
mockExec := &mockStopExecutor{}
|
||||
h := &CrowdsecHandler{
|
||||
DB: db,
|
||||
Executor: mockExec,
|
||||
CmdExec: &mockCommandExecutor{},
|
||||
DataDir: tmpDir,
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.POST("/stop", h.Stop)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/stop", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
// Should still return OK even without existing config
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.True(t, mockExec.stopCalled)
|
||||
}
|
||||
|
||||
// TestGetLAPIDecisions_WithMockServer tests GetLAPIDecisions with a mock LAPI server
|
||||
func TestGetLAPIDecisions_WithMockServer(t *testing.T) {
|
||||
// Use permissive validator for testing with mock server on random port
|
||||
orig := validateCrowdsecLAPIBaseURLFunc
|
||||
validateCrowdsecLAPIBaseURLFunc = permissiveLAPIURLValidator
|
||||
defer func() { validateCrowdsecLAPIBaseURLFunc = orig }()
|
||||
|
||||
// Create a mock LAPI server
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`[{"id":1,"origin":"cscli","scope":"Ip","value":"1.2.3.4","type":"ban","duration":"4h","scenario":"manual ban"}]`))
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
// Create security config with mock LAPI URL
|
||||
cfg := models.SecurityConfig{CrowdSecAPIURL: mockLAPI.URL}
|
||||
require.NoError(t, db.Create(&cfg).Error)
|
||||
|
||||
secSvc := createTestSecurityService(t, db)
|
||||
h := &CrowdsecHandler{
|
||||
DB: db,
|
||||
Security: secSvc,
|
||||
CmdExec: &mockCommandExecutor{},
|
||||
DataDir: t.TempDir(),
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.GET("/decisions/lapi", h.GetLAPIDecisions)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/decisions/lapi", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "lapi", response["source"])
|
||||
|
||||
decisions, ok := response["decisions"].([]any)
|
||||
require.True(t, ok)
|
||||
assert.Len(t, decisions, 1)
|
||||
}
|
||||
|
||||
// TestGetLAPIDecisions_Unauthorized tests GetLAPIDecisions when LAPI returns 401
|
||||
func TestGetLAPIDecisions_Unauthorized(t *testing.T) {
|
||||
// Use permissive validator for testing with mock server on random port
|
||||
orig := validateCrowdsecLAPIBaseURLFunc
|
||||
validateCrowdsecLAPIBaseURLFunc = permissiveLAPIURLValidator
|
||||
defer func() { validateCrowdsecLAPIBaseURLFunc = orig }()
|
||||
|
||||
// Create a mock LAPI server that returns 401
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
cfg := models.SecurityConfig{CrowdSecAPIURL: mockLAPI.URL}
|
||||
require.NoError(t, db.Create(&cfg).Error)
|
||||
|
||||
secSvc := createTestSecurityService(t, db)
|
||||
h := &CrowdsecHandler{
|
||||
DB: db,
|
||||
Security: secSvc,
|
||||
CmdExec: &mockCommandExecutor{},
|
||||
DataDir: t.TempDir(),
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.GET("/decisions/lapi", h.GetLAPIDecisions)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/decisions/lapi", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
}
|
||||
|
||||
// TestGetLAPIDecisions_NullResponse tests GetLAPIDecisions when LAPI returns null
|
||||
func TestGetLAPIDecisions_NullResponse(t *testing.T) {
|
||||
// Use permissive validator for testing with mock server on random port
|
||||
orig := validateCrowdsecLAPIBaseURLFunc
|
||||
validateCrowdsecLAPIBaseURLFunc = permissiveLAPIURLValidator
|
||||
defer func() { validateCrowdsecLAPIBaseURLFunc = orig }()
|
||||
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`null`))
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
cfg := models.SecurityConfig{CrowdSecAPIURL: mockLAPI.URL}
|
||||
require.NoError(t, db.Create(&cfg).Error)
|
||||
|
||||
secSvc := createTestSecurityService(t, db)
|
||||
h := &CrowdsecHandler{
|
||||
DB: db,
|
||||
Security: secSvc,
|
||||
CmdExec: &mockCommandExecutor{},
|
||||
DataDir: t.TempDir(),
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.GET("/decisions/lapi", h.GetLAPIDecisions)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/decisions/lapi", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "lapi", response["source"])
|
||||
assert.Equal(t, float64(0), response["total"])
|
||||
}
|
||||
|
||||
// TestGetLAPIDecisions_NonJSONContentType tests the fallback when LAPI returns non-JSON
|
||||
func TestGetLAPIDecisions_NonJSONContentType(t *testing.T) {
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`<html>Error</html>`))
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
cfg := models.SecurityConfig{CrowdSecAPIURL: mockLAPI.URL}
|
||||
require.NoError(t, db.Create(&cfg).Error)
|
||||
|
||||
secSvc := createTestSecurityService(t, db)
|
||||
h := &CrowdsecHandler{
|
||||
DB: db,
|
||||
Security: secSvc,
|
||||
CmdExec: &mockCommandExecutor{output: []byte(`[]`)}, // Fallback mock
|
||||
DataDir: t.TempDir(),
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.GET("/decisions/lapi", h.GetLAPIDecisions)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/decisions/lapi", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
// Should fallback to cscli and return OK
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
// TestCheckLAPIHealth_WithMockServer tests CheckLAPIHealth with a healthy LAPI
|
||||
func TestCheckLAPIHealth_WithMockServer(t *testing.T) {
|
||||
// Use permissive validator for testing with mock server on random port
|
||||
orig := validateCrowdsecLAPIBaseURLFunc
|
||||
validateCrowdsecLAPIBaseURLFunc = permissiveLAPIURLValidator
|
||||
defer func() { validateCrowdsecLAPIBaseURLFunc = orig }()
|
||||
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/health" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"status":"ok"}`))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
cfg := models.SecurityConfig{CrowdSecAPIURL: mockLAPI.URL}
|
||||
require.NoError(t, db.Create(&cfg).Error)
|
||||
|
||||
secSvc := createTestSecurityService(t, db)
|
||||
h := &CrowdsecHandler{
|
||||
DB: db,
|
||||
Security: secSvc,
|
||||
CmdExec: &mockCommandExecutor{},
|
||||
DataDir: t.TempDir(),
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.GET("/health", h.CheckLAPIHealth)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/health", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, response["healthy"].(bool))
|
||||
}
|
||||
|
||||
// TestCheckLAPIHealth_FallbackToDecisions tests the fallback to /v1/decisions endpoint
|
||||
// when the primary /health endpoint is unreachable
|
||||
func TestCheckLAPIHealth_FallbackToDecisions(t *testing.T) {
|
||||
// Use permissive validator for testing with mock server on random port
|
||||
orig := validateCrowdsecLAPIBaseURLFunc
|
||||
validateCrowdsecLAPIBaseURLFunc = permissiveLAPIURLValidator
|
||||
defer func() { validateCrowdsecLAPIBaseURLFunc = orig }()
|
||||
|
||||
// Create a mock server that only responds to /v1/decisions, not /health
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/v1/decisions" {
|
||||
// Return 401 which indicates LAPI is running (just needs auth)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
} else {
|
||||
// Close connection without responding to simulate unreachable endpoint
|
||||
panic(http.ErrAbortHandler)
|
||||
}
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
cfg := models.SecurityConfig{CrowdSecAPIURL: mockLAPI.URL}
|
||||
require.NoError(t, db.Create(&cfg).Error)
|
||||
|
||||
secSvc := createTestSecurityService(t, db)
|
||||
h := &CrowdsecHandler{
|
||||
DB: db,
|
||||
Security: secSvc,
|
||||
CmdExec: &mockCommandExecutor{},
|
||||
DataDir: t.TempDir(),
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.GET("/health", h.CheckLAPIHealth)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/health", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
require.NoError(t, err)
|
||||
// Should be healthy via fallback
|
||||
assert.True(t, response["healthy"].(bool))
|
||||
if note, ok := response["note"].(string); ok {
|
||||
assert.Contains(t, note, "decisions endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetLAPIKey_AllEnvVars tests that getLAPIKey checks all environment variable names
|
||||
func TestGetLAPIKey_AllEnvVars(t *testing.T) {
|
||||
envVars := []string{
|
||||
"CROWDSEC_API_KEY",
|
||||
"CROWDSEC_BOUNCER_API_KEY",
|
||||
"CERBERUS_SECURITY_CROWDSEC_API_KEY",
|
||||
"CHARON_SECURITY_CROWDSEC_API_KEY",
|
||||
"CPM_SECURITY_CROWDSEC_API_KEY",
|
||||
}
|
||||
|
||||
// Clean up all env vars first
|
||||
originals := make(map[string]string)
|
||||
for _, key := range envVars {
|
||||
originals[key] = os.Getenv(key)
|
||||
_ = os.Unsetenv(key)
|
||||
}
|
||||
defer func() {
|
||||
for key, val := range originals {
|
||||
if val != "" {
|
||||
_ = os.Setenv(key, val)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Test each env var in order of priority
|
||||
for i, envVar := range envVars {
|
||||
t.Run(envVar, func(t *testing.T) {
|
||||
// Clear all vars
|
||||
for _, key := range envVars {
|
||||
_ = os.Unsetenv(key)
|
||||
}
|
||||
|
||||
// Set only this env var
|
||||
testValue := "test-key-" + envVar
|
||||
_ = os.Setenv(envVar, testValue)
|
||||
|
||||
key := getLAPIKey()
|
||||
if i == 0 || key == testValue {
|
||||
// First one should always be found, others only if earlier ones not set
|
||||
assert.Equal(t, testValue, key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user