Files
Charon/backend/internal/api/handlers/import_handler_coverage_test.go
GitHub Actions 719c340735 fix(ci): security toggles tests, CrowdSec response data, and coverage improvement documentation
- Implemented comprehensive tests for security toggle handlers in `security_toggles_test.go`, covering enable/disable functionality for ACL, WAF, Cerberus, CrowdSec, and RateLimit.
- Added sample JSON response for CrowdSec decisions in `lapi_decisions_response.json`.
- Created aggressive preset configuration for CrowdSec in `preset_aggressive.json`.
- Documented backend coverage, security fixes, and E2E testing improvements in `2026-02-02_backend_coverage_security_fix.md`.
- Developed a detailed backend test coverage restoration plan in `current_spec.md` to address existing gaps and improve overall test coverage to 86%+.
2026-02-02 11:55:55 +00:00

177 lines
4.8 KiB
Go

package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/Wikid82/charon/backend/internal/caddy"
)
func setupImportCoverageTestDB(t *testing.T) *gorm.DB {
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatalf("failed to connect database: %v", err)
}
return db
}
// MockImporterService implements handlers.ImporterService
type MockImporterService struct {
mock.Mock
}
func (m *MockImporterService) NormalizeCaddyfile(content string) (string, error) {
args := m.Called(content)
return args.String(0), args.Error(1)
}
func (m *MockImporterService) ParseCaddyfile(path string) ([]byte, error) {
args := m.Called(path)
return args.Get(0).([]byte), args.Error(1)
}
func (m *MockImporterService) ImportFile(path string) (*caddy.ImportResult, error) {
args := m.Called(path)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*caddy.ImportResult), args.Error(1)
}
func (m *MockImporterService) ExtractHosts(caddyJSON []byte) (*caddy.ImportResult, error) {
args := m.Called(caddyJSON)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*caddy.ImportResult), args.Error(1)
}
func (m *MockImporterService) ValidateCaddyBinary() error {
args := m.Called()
return args.Error(0)
}
// TestUploadMulti_EmptyList covers the manual check for len(Files) == 0
func TestUploadMulti_EmptyList(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupImportCoverageTestDB(t)
mockSvc := new(MockImporterService)
h := NewImportHandler(db, "caddy", "/tmp", "/tmp")
h.importerservice = mockSvc
w := httptest.NewRecorder()
_, r := gin.CreateTestContext(w)
r.POST("/upload-multi", h.UploadMulti)
// Create JSON with empty files list
req := map[string]interface{}{
"files": []interface{}{},
}
body, _ := json.Marshal(req)
request, _ := http.NewRequest("POST", "/upload-multi", bytes.NewBuffer(body))
request.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, request)
assert.Equal(t, http.StatusBadRequest, w.Code)
// Matched Gin validation error
assert.Contains(t, w.Body.String(), "Error:Field validation for 'Files' failed on the 'min' tag")
}
// TestUploadMulti_FileServerDetected covers the logic where parsable routes trigger a warning
// because they contain file_server but no valid reverse_proxy hosts
func TestUploadMulti_FileServerDetected(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupImportCoverageTestDB(t)
mockSvc := new(MockImporterService)
// Return a result that has empty Forward host/port (not importable)
// AND contains a "file_server" warning
mockResult := &caddy.ImportResult{
Hosts: []caddy.ParsedHost{
{
DomainNames: "files.example.com",
Warnings: []string{"directive 'file_server' detected"},
},
},
}
mockSvc.On("ImportFile", mock.AnythingOfType("string")).Return(mockResult, nil)
h := NewImportHandler(db, "caddy", "/tmp", "/tmp")
h.importerservice = mockSvc
// Override import dir to temp
h.importDir = t.TempDir()
w := httptest.NewRecorder()
_, r := gin.CreateTestContext(w)
r.POST("/upload-multi", h.UploadMulti)
req := map[string]interface{}{
"files": []interface{}{
map[string]string{
"filename": "Caddyfile",
"content": "files.example.com { file_server }",
},
},
}
body, _ := json.Marshal(req)
request, _ := http.NewRequest("POST", "/upload-multi", bytes.NewBuffer(body))
request.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, request)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "File server directives are not supported")
}
// TestUploadMulti_NoSitesParsed covers successfull parsing but 0 result hosts
func TestUploadMulti_NoSitesParsed(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupImportCoverageTestDB(t)
mockSvc := new(MockImporterService)
// Return empty result
mockResult := &caddy.ImportResult{
Hosts: []caddy.ParsedHost{},
}
mockSvc.On("ImportFile", mock.AnythingOfType("string")).Return(mockResult, nil)
h := NewImportHandler(db, "caddy", "/tmp", "/tmp")
h.importerservice = mockSvc
h.importDir = t.TempDir()
w := httptest.NewRecorder()
_, r := gin.CreateTestContext(w)
r.POST("/upload-multi", h.UploadMulti)
req := map[string]interface{}{
"files": []interface{}{
map[string]string{
"filename": "Caddyfile",
"content": "# just a comment",
},
},
}
body, _ := json.Marshal(req)
request, _ := http.NewRequest("POST", "/upload-multi", bytes.NewBuffer(body))
request.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, request)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "no sites parsed")
}