diff --git a/internal/docker/client.go b/internal/docker/client.go index eaec2882..0e5d9466 100644 --- a/internal/docker/client.go +++ b/internal/docker/client.go @@ -149,14 +149,14 @@ func (d *Client) FindContainer(id string) (Container, error) { return container, nil } -func (d *Client) ContainerActions(action string, id string) error { +func (d *Client) ContainerActions(action string, containerID string) error { switch action { case "start": - return d.cli.ContainerStart(context.Background(), id, types.ContainerStartOptions{}) + return d.cli.ContainerStart(context.Background(), containerID, types.ContainerStartOptions{}) case "stop": - return d.cli.ContainerStop(context.Background(), id, container.StopOptions{}) + return d.cli.ContainerStop(context.Background(), containerID, container.StopOptions{}) case "restart": - return d.cli.ContainerRestart(context.Background(), id, container.StopOptions{}) + return d.cli.ContainerRestart(context.Background(), containerID, container.StopOptions{}) default: return fmt.Errorf("unknown action: %s", action) } diff --git a/internal/docker/client_test.go b/internal/docker/client_test.go index 291abfad..5c32eb48 100644 --- a/internal/docker/client_test.go +++ b/internal/docker/client_test.go @@ -50,6 +50,42 @@ func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, st return types.ContainerStats{}, nil } +func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error { + + args := m.Called(ctx, containerID, options) + err := args.Get(0) + + if err != nil { + return args.Error(0) + } + + return nil +} + +func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error { + + args := m.Called(ctx, containerID, options) + err := args.Get(0) + + if err != nil { + return args.Error(0) + } + + return nil +} + +func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error { + + args := m.Called(ctx, containerID, options) + err := args.Get(0) + + if err != nil { + return args.Error(0) + } + + return nil +} + func Test_dockerClient_ListContainers_null(t *testing.T) { proxy := new(mockedProxy) proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil) @@ -202,3 +238,78 @@ func Test_dockerClient_FindContainer_error(t *testing.T) { proxy.AssertExpectations(t) } + +func Test_dockerClient_ContainerActions_happy(t *testing.T) { + containers := []types.Container{ + { + ID: "abcdefghijklmnopqrst", + Names: []string{"/z_test_container"}, + }, + { + ID: "1234567890_abcxyzdef", + Names: []string{"/a_test_container"}, + }, + } + + proxy := new(mockedProxy) + client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} + json := types.ContainerJSON{Config: &container.Config{Tty: false}} + proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) + proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil) + proxy.On("ContainerStart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil) + proxy.On("ContainerStop", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil) + proxy.On("ContainerRestart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil) + + container, err := client.FindContainer("abcdefghijkl") + require.NoError(t, err, "error should not be thrown") + + assert.Equal(t, container, Container{ + ID: "abcdefghijkl", + Name: "z_test_container", + Names: []string{"/z_test_container"}, + Host: "localhost", + Tty: false, + }) + + actions := []string{"start", "stop", "restart"} + for _, action := range actions { + err := client.ContainerActions(action, container.ID) + require.NoError(t, err, "error should not be thrown") + assert.Equal(t, err, nil) + } + + proxy.AssertExpectations(t) +} + +func Test_dockerClient_ContainerActions_error(t *testing.T) { + containers := []types.Container{ + { + ID: "abcdefghijklmnopqrst", + Names: []string{"/z_test_container"}, + }, + { + ID: "1234567890_abcxyzdef", + Names: []string{"/a_test_container"}, + }, + } + + proxy := new(mockedProxy) + client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}} + + proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil) + proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) + proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) + proxy.On("ContainerRestart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test")) + + container, err := client.FindContainer("random-id") + require.Error(t, err, "error should be thrown") + + actions := []string{"start", "stop", "restart"} + for _, action := range actions { + err := client.ContainerActions(action, container.ID) + require.Error(t, err, "error should be thrown") + assert.Error(t, err, "error should have been returned") + } + + proxy.AssertExpectations(t) +} diff --git a/internal/web/routes.go b/internal/web/routes.go index 8044e33e..9ad3fefe 100644 --- a/internal/web/routes.go +++ b/internal/web/routes.go @@ -64,7 +64,7 @@ type DockerClient interface { ContainerStats(context.Context, string, chan<- docker.ContainerStat) error Ping(context.Context) (types.Ping, error) Host() *docker.Host - ContainerActions(action string, id string) error + ContainerActions(action string, containerID string) error } func CreateServer(clients map[string]DockerClient, content fs.FS, config Config) *http.Server { diff --git a/internal/web/routes_actions_test.go b/internal/web/routes_actions_test.go new file mode 100644 index 00000000..099c54ce --- /dev/null +++ b/internal/web/routes_actions_test.go @@ -0,0 +1,91 @@ +package web + +import ( + "errors" + "net/http" + "net/http/httptest" + + "testing" + + "github.com/amir20/dozzle/internal/docker" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func get_mocked_client() *MockedClient { + mockedClient := new(MockedClient) + container := docker.Container{ID: "123"} + + mockedClient.On("FindContainer", "123").Return(container, nil) + mockedClient.On("FindContainer", "456").Return(docker.Container{}, errors.New("container not found")) + + mockedClient.On("ContainerActions", "start", container.ID).Return(nil) + mockedClient.On("ContainerActions", "stop", container.ID).Return(nil) + mockedClient.On("ContainerActions", "restart", container.ID).Return(nil) + mockedClient.On("ContainerActions", "something-else", container.ID).Return(errors.New("unknown action")) + + mockedClient.On("ContainerActions", "start", mock.Anything).Return(errors.New("container not found")) + + return mockedClient +} + +func Test_handler_containerActions_stop(t *testing.T) { + mockedClient := get_mocked_client() + + handler := createHandler(mockedClient, nil, Config{Base: "/", EnableActions: true, Authorization: Authorization{Provider: NONE}}) + req, err := http.NewRequest("POST", "/api/actions/stop/localhost/123", nil) + require.NoError(t, err, "Request should not return an error.") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + assert.Equal(t, rr.Code, 200) +} + +func Test_handler_containerActions_restart(t *testing.T) { + mockedClient := get_mocked_client() + + handler := createHandler(mockedClient, nil, Config{Base: "/", EnableActions: true, Authorization: Authorization{Provider: NONE}}) + req, err := http.NewRequest("POST", "/api/actions/restart/localhost/123", nil) + require.NoError(t, err, "Request should not return an error.") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + assert.Equal(t, rr.Code, 200) +} + +func Test_handler_containerActions_unknown_action(t *testing.T) { + mockedClient := get_mocked_client() + + handler := createHandler(mockedClient, nil, Config{Base: "/", EnableActions: true, Authorization: Authorization{Provider: NONE}}) + req, err := http.NewRequest("POST", "/api/actions/something-else/localhost/123", nil) + require.NoError(t, err, "Request should not return an error.") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + assert.Equal(t, rr.Code, 500) +} + +func Test_handler_containerActions_unknown_container(t *testing.T) { + mockedClient := get_mocked_client() + + handler := createHandler(mockedClient, nil, Config{Base: "/", EnableActions: true, Authorization: Authorization{Provider: NONE}}) + req, err := http.NewRequest("POST", "/api/actions/start/localhost/456", nil) + require.NoError(t, err, "Request should not return an error.") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + assert.Equal(t, rr.Code, 404) +} + +func Test_handler_containerActions_start(t *testing.T) { + mockedClient := get_mocked_client() + + handler := createHandler(mockedClient, nil, Config{Base: "/", EnableActions: true, Authorization: Authorization{Provider: NONE}}) + req, err := http.NewRequest("POST", "/api/actions/start/localhost/123", nil) + require.NoError(t, err, "Request should not return an error.") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + assert.Equal(t, rr.Code, 200) +} diff --git a/internal/web/routes_test.go b/internal/web/routes_test.go index 24d5b1e1..e86d4bb4 100644 --- a/internal/web/routes_test.go +++ b/internal/web/routes_test.go @@ -25,6 +25,11 @@ func (m *MockedClient) FindContainer(id string) (docker.Container, error) { return args.Get(0).(docker.Container), args.Error(1) } +func (m *MockedClient) ContainerActions(action string, containerID string) error { + args := m.Called(action, containerID) + return args.Error(0) +} + func (m *MockedClient) ListContainers() ([]docker.Container, error) { args := m.Called() return args.Get(0).([]docker.Container), args.Error(1)