chore: clean .gitignore cache
This commit is contained in:
@@ -1,362 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"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"
|
||||
)
|
||||
|
||||
type fakeDockerService struct {
|
||||
called bool
|
||||
host string
|
||||
|
||||
ret []services.DockerContainer
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeDockerService) ListContainers(_ context.Context, host string) ([]services.DockerContainer, error) {
|
||||
f.called = true
|
||||
f.host = host
|
||||
return f.ret, f.err
|
||||
}
|
||||
|
||||
type fakeRemoteServerService struct {
|
||||
gotUUID string
|
||||
|
||||
server *models.RemoteServer
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeRemoteServerService) GetByUUID(uuidStr string) (*models.RemoteServer, error) {
|
||||
f.gotUUID = uuidStr
|
||||
return f.server, f.err
|
||||
}
|
||||
|
||||
func TestDockerHandler_ListContainers_InvalidHostRejected(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{}
|
||||
remoteSvc := &fakeRemoteServerService{}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers?host=tcp://127.0.0.1:2375", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.False(t, dockerSvc.called, "docker service should not be called for invalid host")
|
||||
}
|
||||
|
||||
func TestDockerHandler_ListContainers_DockerUnavailableMappedTo503(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{err: services.NewDockerUnavailableError(errors.New("no docker socket"))}
|
||||
remoteSvc := &fakeRemoteServerService{}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers?host=local", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "Docker daemon unavailable")
|
||||
// Verify the new details field is included in the response
|
||||
assert.Contains(t, w.Body.String(), "details")
|
||||
assert.Contains(t, w.Body.String(), "Docker is running")
|
||||
}
|
||||
|
||||
func TestDockerHandler_ListContainers_ServerIDResolvesToTCPHost(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{ret: []services.DockerContainer{}}
|
||||
remoteSvc := &fakeRemoteServerService{server: &models.RemoteServer{Host: "example.internal", Port: 2375}}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers?server_id=abc-123", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
require.True(t, dockerSvc.called)
|
||||
assert.Equal(t, "abc-123", remoteSvc.gotUUID)
|
||||
assert.Equal(t, "tcp://example.internal:2375", dockerSvc.host)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestDockerHandler_ListContainers_ServerIDNotFoundReturns404(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{}
|
||||
remoteSvc := &fakeRemoteServerService{err: errors.New("not found")}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers?server_id=missing", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.False(t, dockerSvc.called)
|
||||
}
|
||||
|
||||
// Phase 4.1: Additional test cases for complete coverage
|
||||
|
||||
func TestDockerHandler_ListContainers_Local(t *testing.T) {
|
||||
// Test local/default docker connection (empty host parameter)
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{
|
||||
ret: []services.DockerContainer{
|
||||
{
|
||||
ID: "abc123456789",
|
||||
Names: []string{"test-container"},
|
||||
Image: "nginx:latest",
|
||||
State: "running",
|
||||
Status: "Up 2 hours",
|
||||
Network: "bridge",
|
||||
IP: "172.17.0.2",
|
||||
Ports: []services.DockerPort{
|
||||
{PrivatePort: 80, PublicPort: 8080, Type: "tcp"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
remoteSvc := &fakeRemoteServerService{}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
require.True(t, dockerSvc.called)
|
||||
assert.Empty(t, dockerSvc.host, "local connection should have empty host")
|
||||
assert.Contains(t, w.Body.String(), "test-container")
|
||||
assert.Contains(t, w.Body.String(), "nginx:latest")
|
||||
}
|
||||
|
||||
func TestDockerHandler_ListContainers_RemoteServerSuccess(t *testing.T) {
|
||||
// Test successful remote server connection via server_id
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{
|
||||
ret: []services.DockerContainer{
|
||||
{
|
||||
ID: "remote123",
|
||||
Names: []string{"remote-nginx"},
|
||||
Image: "nginx:alpine",
|
||||
State: "running",
|
||||
Status: "Up 1 day",
|
||||
},
|
||||
},
|
||||
}
|
||||
remoteSvc := &fakeRemoteServerService{
|
||||
server: &models.RemoteServer{
|
||||
UUID: "server-uuid-123",
|
||||
Name: "Production Server",
|
||||
Host: "192.168.1.100",
|
||||
Port: 2376,
|
||||
},
|
||||
}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers?server_id=server-uuid-123", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
require.True(t, dockerSvc.called)
|
||||
assert.Equal(t, "server-uuid-123", remoteSvc.gotUUID)
|
||||
assert.Equal(t, "tcp://192.168.1.100:2376", dockerSvc.host)
|
||||
assert.Contains(t, w.Body.String(), "remote-nginx")
|
||||
}
|
||||
|
||||
func TestDockerHandler_ListContainers_RemoteServerNotFound(t *testing.T) {
|
||||
// Test server_id that doesn't exist in database
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{}
|
||||
remoteSvc := &fakeRemoteServerService{
|
||||
err: errors.New("server not found"),
|
||||
}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers?server_id=nonexistent-uuid", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.False(t, dockerSvc.called, "docker service should not be called when server not found")
|
||||
assert.Contains(t, w.Body.String(), "Remote server not found")
|
||||
}
|
||||
|
||||
func TestDockerHandler_ListContainers_InvalidHost(t *testing.T) {
|
||||
// Test SSRF protection: reject arbitrary host values
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{}
|
||||
remoteSvc := &fakeRemoteServerService{}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
hostParam string
|
||||
}{
|
||||
{"arbitrary IP", "host=10.0.0.1"},
|
||||
{"tcp URL", "host=tcp://evil.com:2375"},
|
||||
{"unix socket", "host=unix:///var/run/docker.sock"},
|
||||
{"http URL", "host=http://attacker.com/"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers?"+tt.hostParam, http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code, "should reject invalid host: %s", tt.hostParam)
|
||||
assert.Contains(t, w.Body.String(), "Invalid docker host selector")
|
||||
assert.False(t, dockerSvc.called, "docker service should not be called for invalid host")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerHandler_ListContainers_DockerUnavailable(t *testing.T) {
|
||||
// Test various Docker unavailability scenarios
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
wantCode int
|
||||
wantMsg string
|
||||
}{
|
||||
{
|
||||
name: "daemon not running",
|
||||
err: services.NewDockerUnavailableError(errors.New("cannot connect to docker daemon")),
|
||||
wantCode: http.StatusServiceUnavailable,
|
||||
wantMsg: "Docker daemon unavailable",
|
||||
},
|
||||
{
|
||||
name: "socket permission denied",
|
||||
err: services.NewDockerUnavailableError(errors.New("permission denied")),
|
||||
wantCode: http.StatusServiceUnavailable,
|
||||
wantMsg: "Docker daemon unavailable",
|
||||
},
|
||||
{
|
||||
name: "socket not found",
|
||||
err: services.NewDockerUnavailableError(errors.New("no such file or directory")),
|
||||
wantCode: http.StatusServiceUnavailable,
|
||||
wantMsg: "Docker daemon unavailable",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{err: tt.err}
|
||||
remoteSvc := &fakeRemoteServerService{}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers?host=local", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, tt.wantCode, w.Code)
|
||||
assert.Contains(t, w.Body.String(), tt.wantMsg)
|
||||
assert.True(t, dockerSvc.called)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerHandler_ListContainers_GenericError(t *testing.T) {
|
||||
// Test non-connectivity errors (should return 500)
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
wantCode int
|
||||
wantMsg string
|
||||
}{
|
||||
{
|
||||
name: "API error",
|
||||
err: errors.New("API error: invalid request"),
|
||||
wantCode: http.StatusInternalServerError,
|
||||
wantMsg: "Failed to list containers",
|
||||
},
|
||||
{
|
||||
name: "context cancelled",
|
||||
err: context.Canceled,
|
||||
wantCode: http.StatusInternalServerError,
|
||||
wantMsg: "Failed to list containers",
|
||||
},
|
||||
{
|
||||
name: "unknown error",
|
||||
err: errors.New("unexpected error occurred"),
|
||||
wantCode: http.StatusInternalServerError,
|
||||
wantMsg: "Failed to list containers",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
|
||||
dockerSvc := &fakeDockerService{err: tt.err}
|
||||
remoteSvc := &fakeRemoteServerService{}
|
||||
h := NewDockerHandler(dockerSvc, remoteSvc)
|
||||
|
||||
api := router.Group("/api/v1")
|
||||
h.RegisterRoutes(api)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/docker/containers", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, tt.wantCode, w.Code)
|
||||
assert.Contains(t, w.Body.String(), tt.wantMsg)
|
||||
assert.True(t, dockerSvc.called)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user