chore: git cache cleanup
This commit is contained in:
360
backend/internal/api/handlers/crowdsec_state_sync_test.go
Normal file
360
backend/internal/api/handlers/crowdsec_state_sync_test.go
Normal file
@@ -0,0 +1,360 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Wikid82/charon/backend/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestStartSyncsSettingsTable verifies that Start() updates the settings table.
|
||||
func TestStartSyncsSettingsTable(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
|
||||
// Migrate both SecurityConfig and Setting tables
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))
|
||||
|
||||
// Mock LAPI server for testKeyAgainstLAPI (returns 200 OK for any key)
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"new": [], "deleted": []}`))
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
// Create SecurityConfig with mock LAPI URL so testKeyAgainstLAPI uses it
|
||||
secCfg := models.SecurityConfig{
|
||||
UUID: "test-uuid",
|
||||
Name: "default",
|
||||
CrowdSecAPIURL: mockLAPI.URL,
|
||||
}
|
||||
require.NoError(t, db.Create(&secCfg).Error)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
fe := &fakeExec{}
|
||||
h := newTestCrowdsecHandler(t, db, fe, "/bin/false", tmpDir)
|
||||
|
||||
// Replace CmdExec to prevent LAPI wait loop - simulate LAPI ready
|
||||
h.CmdExec = &mockCommandExecutor{
|
||||
output: []byte("lapi is running"),
|
||||
err: nil,
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
g := r.Group("/api/v1")
|
||||
h.RegisterRoutes(g)
|
||||
|
||||
// Verify settings table is initially empty
|
||||
var initialSetting models.Setting
|
||||
err := db.Where("key = ?", "security.crowdsec.enabled").First(&initialSetting).Error
|
||||
require.Error(t, err, "expected setting to not exist initially")
|
||||
|
||||
// Start CrowdSec
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// Verify setting was created/updated to "true"
|
||||
var setting models.Setting
|
||||
err = db.Where("key = ?", "security.crowdsec.enabled").First(&setting).Error
|
||||
require.NoError(t, err, "expected setting to be created after Start")
|
||||
require.Equal(t, "true", setting.Value)
|
||||
require.Equal(t, "security", setting.Category)
|
||||
require.Equal(t, "bool", setting.Type)
|
||||
|
||||
// Also verify SecurityConfig was updated
|
||||
var cfg models.SecurityConfig
|
||||
err = db.First(&cfg).Error
|
||||
require.NoError(t, err, "expected SecurityConfig to exist")
|
||||
require.Equal(t, "local", cfg.CrowdSecMode)
|
||||
require.True(t, cfg.Enabled)
|
||||
}
|
||||
|
||||
// TestStopSyncsSettingsTable verifies that Stop() updates the settings table.
|
||||
func TestStopSyncsSettingsTable(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
|
||||
// Migrate both SecurityConfig and Setting tables
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))
|
||||
|
||||
// Mock LAPI server for testKeyAgainstLAPI (returns 200 OK for any key)
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"new": [], "deleted": []}`))
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
// Create SecurityConfig with mock LAPI URL so testKeyAgainstLAPI uses it
|
||||
secCfg := models.SecurityConfig{
|
||||
UUID: "test-uuid",
|
||||
Name: "default",
|
||||
CrowdSecAPIURL: mockLAPI.URL,
|
||||
}
|
||||
require.NoError(t, db.Create(&secCfg).Error)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
fe := &fakeExec{}
|
||||
h := newTestCrowdsecHandler(t, db, fe, "/bin/false", tmpDir)
|
||||
|
||||
// Replace CmdExec to prevent LAPI wait loop - simulate LAPI ready
|
||||
h.CmdExec = &mockCommandExecutor{
|
||||
output: []byte("lapi is running"),
|
||||
err: nil,
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
g := r.Group("/api/v1")
|
||||
h.RegisterRoutes(g)
|
||||
|
||||
// First start CrowdSec to create the settings
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// Verify setting is "true" after start
|
||||
var settingAfterStart models.Setting
|
||||
err := db.Where("key = ?", "security.crowdsec.enabled").First(&settingAfterStart).Error
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "true", settingAfterStart.Value)
|
||||
|
||||
// Now stop CrowdSec
|
||||
w2 := httptest.NewRecorder()
|
||||
req2 := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/stop", http.NoBody)
|
||||
r.ServeHTTP(w2, req2)
|
||||
require.Equal(t, http.StatusOK, w2.Code)
|
||||
|
||||
// Verify setting was updated to "false"
|
||||
var settingAfterStop models.Setting
|
||||
err = db.Where("key = ?", "security.crowdsec.enabled").First(&settingAfterStop).Error
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "false", settingAfterStop.Value)
|
||||
|
||||
// Also verify SecurityConfig was updated
|
||||
var cfg models.SecurityConfig
|
||||
err = db.First(&cfg).Error
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "disabled", cfg.CrowdSecMode)
|
||||
require.False(t, cfg.Enabled)
|
||||
}
|
||||
|
||||
// TestStartAndStopStateConsistency verifies consistent state across Start/Stop cycles.
|
||||
func TestStartAndStopStateConsistency(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))
|
||||
|
||||
// Mock LAPI server for testKeyAgainstLAPI (returns 200 OK for any key)
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"new": [], "deleted": []}`))
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
// Create SecurityConfig with mock LAPI URL so testKeyAgainstLAPI uses it
|
||||
secCfg := models.SecurityConfig{
|
||||
UUID: "test-uuid",
|
||||
Name: "default",
|
||||
CrowdSecAPIURL: mockLAPI.URL,
|
||||
}
|
||||
require.NoError(t, db.Create(&secCfg).Error)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
fe := &fakeExec{}
|
||||
h := newTestCrowdsecHandler(t, db, fe, "/bin/false", tmpDir)
|
||||
|
||||
// Replace CmdExec to simulate LAPI ready immediately (for cscli bouncers list)
|
||||
h.CmdExec = &mockCommandExecutor{
|
||||
output: []byte("lapi is running"),
|
||||
err: nil,
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
g := r.Group("/api/v1")
|
||||
h.RegisterRoutes(g)
|
||||
|
||||
// Perform multiple start/stop cycles
|
||||
for i := 0; i < 3; i++ {
|
||||
// Start
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code, "cycle %d start", i)
|
||||
|
||||
// Verify both tables are in sync
|
||||
var setting models.Setting
|
||||
err := db.Where("key = ?", "security.crowdsec.enabled").First(&setting).Error
|
||||
require.NoError(t, err, "cycle %d: setting should exist after start", i)
|
||||
require.Equal(t, "true", setting.Value, "cycle %d: setting should be true after start", i)
|
||||
|
||||
var cfg models.SecurityConfig
|
||||
err = db.First(&cfg).Error
|
||||
require.NoError(t, err, "cycle %d: config should exist after start", i)
|
||||
require.Equal(t, "local", cfg.CrowdSecMode, "cycle %d: mode should be local after start", i)
|
||||
|
||||
// Stop
|
||||
w2 := httptest.NewRecorder()
|
||||
req2 := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/stop", http.NoBody)
|
||||
r.ServeHTTP(w2, req2)
|
||||
require.Equal(t, http.StatusOK, w2.Code, "cycle %d stop", i)
|
||||
|
||||
// Verify both tables are in sync
|
||||
err = db.Where("key = ?", "security.crowdsec.enabled").First(&setting).Error
|
||||
require.NoError(t, err, "cycle %d: setting should exist after stop", i)
|
||||
require.Equal(t, "false", setting.Value, "cycle %d: setting should be false after stop", i)
|
||||
|
||||
err = db.First(&cfg).Error
|
||||
require.NoError(t, err, "cycle %d: config should exist after stop", i)
|
||||
require.Equal(t, "disabled", cfg.CrowdSecMode, "cycle %d: mode should be disabled after stop", i)
|
||||
}
|
||||
}
|
||||
|
||||
// TestExistingSettingIsUpdated verifies that an existing setting is updated, not duplicated.
|
||||
func TestExistingSettingIsUpdated(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))
|
||||
|
||||
// Mock LAPI server for testKeyAgainstLAPI (returns 200 OK for any key)
|
||||
mockLAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"new": [], "deleted": []}`))
|
||||
}))
|
||||
defer mockLAPI.Close()
|
||||
|
||||
// Create SecurityConfig with mock LAPI URL so testKeyAgainstLAPI uses it
|
||||
secCfg := models.SecurityConfig{
|
||||
UUID: "test-uuid",
|
||||
Name: "default",
|
||||
CrowdSecAPIURL: mockLAPI.URL,
|
||||
}
|
||||
require.NoError(t, db.Create(&secCfg).Error)
|
||||
|
||||
// Pre-create a setting with a different value
|
||||
existingSetting := models.Setting{
|
||||
Key: "security.crowdsec.enabled",
|
||||
Value: "false",
|
||||
Category: "security",
|
||||
Type: "bool",
|
||||
}
|
||||
require.NoError(t, db.Create(&existingSetting).Error)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
fe := &fakeExec{}
|
||||
h := newTestCrowdsecHandler(t, db, fe, "/bin/false", tmpDir)
|
||||
|
||||
// Replace CmdExec to prevent LAPI wait loop - simulate LAPI ready
|
||||
h.CmdExec = &mockCommandExecutor{
|
||||
output: []byte("lapi is running"),
|
||||
err: nil,
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
g := r.Group("/api/v1")
|
||||
h.RegisterRoutes(g)
|
||||
|
||||
// Start CrowdSec
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// Verify the existing setting was updated (not duplicated)
|
||||
var settings []models.Setting
|
||||
err := db.Where("key = ?", "security.crowdsec.enabled").Find(&settings).Error
|
||||
require.NoError(t, err)
|
||||
require.Len(t, settings, 1, "should not create duplicate settings")
|
||||
require.Equal(t, "true", settings[0].Value, "setting should be updated to true")
|
||||
}
|
||||
|
||||
// fakeFailingExec simulates an executor that fails on Start.
|
||||
type fakeFailingExec struct{}
|
||||
|
||||
func (f *fakeFailingExec) Start(ctx context.Context, binPath, configDir string) (int, error) {
|
||||
return 0, http.ErrAbortHandler
|
||||
}
|
||||
|
||||
func (f *fakeFailingExec) Stop(ctx context.Context, configDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeFailingExec) Status(ctx context.Context, configDir string) (running bool, pid int, err error) {
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
// TestStartFailureRevertsSettings verifies that a failed Start reverts the settings.
|
||||
func TestStartFailureRevertsSettings(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
fe := &fakeFailingExec{}
|
||||
h := newTestCrowdsecHandler(t, db, fe, "/bin/false", tmpDir)
|
||||
|
||||
r := gin.New()
|
||||
g := r.Group("/api/v1")
|
||||
h.RegisterRoutes(g)
|
||||
|
||||
// Pre-create a setting with "false" to verify it's reverted
|
||||
existingSetting := models.Setting{
|
||||
Key: "security.crowdsec.enabled",
|
||||
Value: "false",
|
||||
Category: "security",
|
||||
Type: "bool",
|
||||
}
|
||||
require.NoError(t, db.Create(&existingSetting).Error)
|
||||
|
||||
// Try to start CrowdSec (this will fail)
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/crowdsec/start", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
|
||||
// Verify the setting was reverted to "false"
|
||||
var setting models.Setting
|
||||
err := db.Where("key = ?", "security.crowdsec.enabled").First(&setting).Error
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "false", setting.Value, "setting should be reverted to false on failure")
|
||||
}
|
||||
|
||||
// TestStatusResponseFormat verifies the status endpoint response format.
|
||||
func TestStatusResponseFormat(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := OpenTestDB(t)
|
||||
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.Setting{}))
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
fe := &fakeExec{}
|
||||
h := newTestCrowdsecHandler(t, db, fe, "/bin/false", tmpDir)
|
||||
|
||||
r := gin.New()
|
||||
g := r.Group("/api/v1")
|
||||
h.RegisterRoutes(g)
|
||||
|
||||
// Get status
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/crowdsec/status", http.NoBody)
|
||||
r.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify response contains expected fields
|
||||
require.Contains(t, resp, "running")
|
||||
require.Contains(t, resp, "pid")
|
||||
require.Contains(t, resp, "lapi_ready")
|
||||
}
|
||||
Reference in New Issue
Block a user