- Implemented SystemPermissionsHandler to check and repair file permissions. - Added endpoints for retrieving and repairing permissions. - Introduced utility functions for permission checks and error mapping. - Created tests for the new handler and utility functions. - Updated routes to include the new permissions endpoints. - Enhanced configuration to support new logging and plugin directories.
189 lines
5.0 KiB
Go
189 lines
5.0 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.Use(func(c *gin.Context) {
|
|
setAdminContext(c)
|
|
c.Next()
|
|
})
|
|
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.Use(func(c *gin.Context) {
|
|
setAdminContext(c)
|
|
c.Next()
|
|
})
|
|
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.Use(func(c *gin.Context) {
|
|
setAdminContext(c)
|
|
c.Next()
|
|
})
|
|
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")
|
|
}
|