Skip to content

Commit

Permalink
refactor: commands (#23)
Browse files Browse the repository at this point in the history
* refactor: main.go and separate commands.go

* refactor: separate loadConfig & newDockerService

* chore: remove print in tests

* refactor: use a deployer

* refactor: use variable for docker service creator function

* refactor: make it more testable

* test: defaultConfigLoader

* refactor: remove unused vars

* refactor: separate config loader

* refactor: separate docker_service

* test: error conditions in commands.go

* test: cover more error conditions

* chore: ignore coverage.html

* style: format code with Go fmt

This commit fixes the style issues introduced in b494663 according to the output
from Go fmt.

Details: #23

* fix: RVV-B0013

* fix: Unused parameter in function

* fix: Types of function parameters can be combined

* feat: test main.go

* style: Unused method receiver

* style: fix Unused parameter in function

---------

Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
  • Loading branch information
scmmishra and deepsource-autofix[bot] authored Aug 8, 2024
1 parent c44ae9f commit 4e03379
Show file tree
Hide file tree
Showing 10 changed files with 687 additions and 119 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ tmp/
dist/

.env

coverage.html
69 changes: 69 additions & 0 deletions cmd/slick/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main

import (
"fmt"

"github.com/scmmishra/slick-deploy/internal/caddy"
"github.com/scmmishra/slick-deploy/internal/config"
"github.com/scmmishra/slick-deploy/internal/deploy"
"github.com/spf13/cobra"
)

type Deployer interface {
Deploy(cfg config.DeploymentConfig) error
}

type DefaultDeployer struct{}

func (DefaultDeployer) Deploy(cfg config.DeploymentConfig) error {
return deploy.Deploy(cfg)
}

var defaultDeployer Deployer = DefaultDeployer{}

func runDeploy(cmd *cobra.Command, deployer Deployer, configLoader ConfigLoader) error {
cfg, err := configLoader(cmd)
if err != nil {
return err
}
return deployer.Deploy(cfg)
}

func runStatus() error {
dockerService, err := dockerServiceCreator()
if err != nil {
return err
}
return dockerService.GetStatus()
}

func runLogs(cmd *cobra.Command, configLoader ConfigLoader) error {
cfg, err := configLoader(cmd)
if err != nil {
return err
}

dockerService, err := dockerServiceCreator()
if err != nil {
return err
}

container := dockerService.FindContainer(cfg.App.ImageName)
if container == nil {
return fmt.Errorf("no container found")
}

tail, _ := cmd.Flags().GetString("tail")
return dockerService.StreamLogs(container.ID, tail)
}

func runCaddyInspect(cmd *cobra.Command, configLoader ConfigLoader) error {
cfg, err := configLoader(cmd)
if err != nil {
return err
}

caddyConfig := caddy.ConvertToCaddyfile(cfg.Caddy, 0) // Use 0 as port since we're just inspecting
fmt.Println(caddyConfig)
return nil
}
276 changes: 276 additions & 0 deletions cmd/slick/commands_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
package main

import (
"bytes"
"errors"
"os"
"testing"

"github.com/scmmishra/slick-deploy/internal/config"
"github.com/scmmishra/slick-deploy/internal/docker"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

var mockLoadConfig func(*cobra.Command) (config.DeploymentConfig, error)

type MockDeployer struct {
mock.Mock
}

func (m *MockDeployer) Deploy(cfg config.DeploymentConfig) error {
args := m.Called(cfg)
return args.Error(0)
}

type MockDockerService struct {
mock.Mock
}

func (m *MockDockerService) GetStatus() error {
args := m.Called()
return args.Error(0)
}

func (m *MockDockerService) FindContainer(imageName string) *docker.Container {
args := m.Called(imageName)
if args.Get(0) == nil {
return nil
}
return args.Get(0).(*docker.Container)
}

func (m *MockDockerService) StreamLogs(containerID, tail string) error {
args := m.Called(containerID, tail)
return args.Error(0)
}

// Helper function to create a cobra command for testing
func createTestCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "test",
}
cmd.Flags().String("config", "", "Path to the configuration file")
cmd.Flags().String("env", "", "Path to the env file")
return cmd
}

func TestRunDeploy(t *testing.T) {
mockDeployer := new(MockDeployer)
mockDeployer.On("Deploy", mock.Anything).Return(nil)

mockConfigLoader := func(*cobra.Command) (config.DeploymentConfig, error) {
return config.DeploymentConfig{}, nil
}

cmd := createTestCommand()
err := runDeploy(cmd, mockDeployer, mockConfigLoader)

assert.NoError(t, err)
mockDeployer.AssertExpectations(t)
}

func TestRunDeploy_ConfigLoaderError(t *testing.T) {
mockDeployer := new(MockDeployer)
mockConfigLoader := func(*cobra.Command) (config.DeploymentConfig, error) {
return config.DeploymentConfig{}, errors.New("config load error")
}

cmd := createTestCommand()
err := runDeploy(cmd, mockDeployer, mockConfigLoader)

assert.Error(t, err)
assert.Contains(t, err.Error(), "config load error")
mockDeployer.AssertNotCalled(t, "Deploy")
}

func TestRunLogs(t *testing.T) {
mockDockerService := new(MockDockerService)
mockDockerService.On("FindContainer", mock.Anything).Return(&docker.Container{ID: "test-container"})
mockDockerService.On("StreamLogs", "test-container", "all").Return(nil)

mockConfigLoader := func(*cobra.Command) (config.DeploymentConfig, error) {
return config.DeploymentConfig{
App: config.App{ImageName: "test-image"},
}, nil
}

originalDockerServiceCreator := dockerServiceCreator
dockerServiceCreator = func() (DockerService, error) {
return mockDockerService, nil
}
defer func() { dockerServiceCreator = originalDockerServiceCreator }()

cmd := createTestCommand()
cmd.Flags().String("tail", "all", "")
err := runLogs(cmd, mockConfigLoader)

assert.NoError(t, err)
mockDockerService.AssertExpectations(t)
}

func TestRunLogs_NoContainer(t *testing.T) {
mockDockerService := new(MockDockerService)
mockDockerService.On("FindContainer", mock.Anything).Return(nil)

mockConfigLoader := func(*cobra.Command) (config.DeploymentConfig, error) {
return config.DeploymentConfig{
App: config.App{ImageName: "test-image"},
}, nil
}

originalDockerServiceCreator := dockerServiceCreator
dockerServiceCreator = func() (DockerService, error) {
return mockDockerService, nil
}
defer func() { dockerServiceCreator = originalDockerServiceCreator }()

cmd := createTestCommand()
cmd.Flags().String("tail", "all", "")
err := runLogs(cmd, mockConfigLoader)

assert.Error(t, err)
assert.Contains(t, err.Error(), "no container found")
mockDockerService.AssertExpectations(t)
}

func TestRunLogs_DockerServiceCreatorFails(t *testing.T) {
mockConfigLoader := func(*cobra.Command) (config.DeploymentConfig, error) {
return config.DeploymentConfig{
App: config.App{ImageName: "test-image"},
}, nil
}

originalDockerServiceCreator := dockerServiceCreator
dockerServiceCreator = func() (DockerService, error) {
return nil, errors.New("failed to create Docker service")
}
defer func() { dockerServiceCreator = originalDockerServiceCreator }()

cmd := createTestCommand()
cmd.Flags().String("tail", "all", "")
err := runLogs(cmd, mockConfigLoader)

assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to create Docker service")
}

func TestRunLogs_ConfigLoaderFails(t *testing.T) {
mockConfigLoader := func(*cobra.Command) (config.DeploymentConfig, error) {
return config.DeploymentConfig{}, errors.New("config loading failed")
}

mockDockerService := new(MockDockerService)

originalDockerServiceCreator := dockerServiceCreator
dockerServiceCreator = func() (DockerService, error) {
return mockDockerService, nil
}
defer func() { dockerServiceCreator = originalDockerServiceCreator }()

cmd := createTestCommand()
cmd.Flags().String("tail", "all", "")
err := runLogs(cmd, mockConfigLoader)

assert.Error(t, err)
assert.Contains(t, err.Error(), "config loading failed")

// Ensure that no methods on mockDockerService were called
mockDockerService.AssertNotCalled(t, "FindContainer")
mockDockerService.AssertNotCalled(t, "StreamLogs")
}

func TestRunCaddyInspect(t *testing.T) {
mockConfigLoader := func(*cobra.Command) (config.DeploymentConfig, error) {
return config.DeploymentConfig{
Caddy: config.CaddyConfig{
Rules: []config.Rule{
{Match: "http://example.com"},
},
},
}, nil
}

old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

cmd := createTestCommand()
err := runCaddyInspect(cmd, mockConfigLoader)

w.Close()
os.Stdout = old

var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
output := buf.String()

assert.NoError(t, err)
assert.Contains(t, output, "http://")
}

func TestRunCaddyInspect_ConfigError(t *testing.T) {
mockConfigLoader := func(*cobra.Command) (config.DeploymentConfig, error) {
return config.DeploymentConfig{}, errors.New("config load error")
}

cmd := createTestCommand()
err := runCaddyInspect(cmd, mockConfigLoader)

assert.Error(t, err)
assert.Contains(t, err.Error(), "config load error")
}

func TestRunStatus(t *testing.T) {
mockDockerService := new(MockDockerService)
mockDockerService.On("GetStatus").Return(nil)

// Save the original dockerServiceCreator
originalDockerServiceCreator := dockerServiceCreator

// Replace dockerServiceCreator with a mock version
dockerServiceCreator = func() (DockerService, error) {
return mockDockerService, nil
}

// Restore the original dockerServiceCreator after the test
defer func() {
dockerServiceCreator = originalDockerServiceCreator
}()

err := runStatus()

assert.NoError(t, err)
mockDockerService.AssertExpectations(t)
}

func TestRunStatus_Error(t *testing.T) {
mockDockerService := new(MockDockerService)
mockDockerService.On("GetStatus").Return(errors.New("status error"))

originalDockerServiceCreator := dockerServiceCreator
dockerServiceCreator = func() (DockerService, error) {
return mockDockerService, nil
}
defer func() { dockerServiceCreator = originalDockerServiceCreator }()

err := runStatus()

assert.Error(t, err)
assert.Contains(t, err.Error(), "status error")
mockDockerService.AssertExpectations(t)
}

func TestRunStatus_DockerServiceCreatorFails(t *testing.T) {
originalDockerServiceCreator := dockerServiceCreator
dockerServiceCreator = func() (DockerService, error) {
return nil, errors.New("failed to create Docker service")
}
defer func() { dockerServiceCreator = originalDockerServiceCreator }()

err := runStatus()

assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to create Docker service")
}
27 changes: 27 additions & 0 deletions cmd/slick/config_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"fmt"

"github.com/joho/godotenv"
"github.com/scmmishra/slick-deploy/internal/config"
"github.com/spf13/cobra"
)

type ConfigLoader func(*cobra.Command) (config.DeploymentConfig, error)

func defaultConfigLoader(cmd *cobra.Command) (config.DeploymentConfig, error) {
cfgPath, _ := cmd.Flags().GetString("config")
envPath, _ := cmd.Flags().GetString("env")

if err := godotenv.Load(envPath); err != nil {
return config.DeploymentConfig{}, fmt.Errorf("failed to load env file: %w", err)
}

cfg, err := config.LoadConfig(cfgPath)
if err != nil {
return config.DeploymentConfig{}, fmt.Errorf("failed to load config: %w", err)
}

return cfg, nil
}
Loading

0 comments on commit 4e03379

Please sign in to comment.