Skip to content

Commit fc32b03

Browse files
committed
test: adding unit tests
1 parent ba3f795 commit fc32b03

File tree

10 files changed

+736
-154
lines changed

10 files changed

+736
-154
lines changed

command/command_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package command
2+
3+
import "testing"
4+
5+
func TestDeployCommandExists(t *testing.T) {
6+
if _, ok := Commands["deploy"]; !ok {
7+
t.Fatal("Command 'deploy' should exist in Commands map")
8+
}
9+
}

command/deploy.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ import (
1010
"github.com/gnugomez/voyage/log"
1111
)
1212

13+
type Syncer interface {
14+
Sync(subDirs []string) ([]string, error)
15+
}
16+
17+
type Deployer interface {
18+
DeployCompose(targetPath string, daemonMode bool) error
19+
}
20+
1321
// stringSlice implements flag.Value interface for handling multiple string values
1422
type stringSlice []string
1523

@@ -32,7 +40,9 @@ type DeployCommandParameters struct {
3240
}
3341

3442
type deployCommand struct {
35-
params DeployCommandParameters
43+
params DeployCommandParameters
44+
syncer Syncer
45+
deployer Deployer
3646
}
3747

3848
func (d *deployCommand) GetBaseParameters() BaseParameters {
@@ -44,8 +54,6 @@ func (d *deployCommand) Handle() {
4454
return
4555
}
4656

47-
repository := git.CreateRepository(d.params.Repo, d.params.Branch, d.params.OutPath)
48-
4957
log.Debug("Running command with parameters", "repo", d.params.Repo, "branch", d.params.Branch, "remoteComposePaths", d.params.RemoteComposePaths, "out-path", d.params.OutPath)
5058

5159
// Create map of subdirectories to compose paths for change detection
@@ -66,9 +74,9 @@ func (d *deployCommand) Handle() {
6674
subDirToComposePaths[subDir] = append(subDirToComposePaths[subDir], composePath)
6775
}
6876

69-
updatedSubDirs, err := repository.Sync(subDirs)
77+
updatedSubDirs, err := d.syncer.Sync(subDirs)
7078
if err != nil {
71-
log.Fatal("Error syncing repository", "error", err)
79+
log.Error("Error syncing repository", "error", err)
7280
return
7381
}
7482

@@ -91,9 +99,9 @@ func (d *deployCommand) Handle() {
9199
// Deploy all collected compose files
92100
for _, composePath := range composePathsToDeploy {
93101
log.Info("Deploying compose file", "composePath", composePath)
94-
err := docker.DeployCompose(filepath.Join(d.params.OutPath, composePath), true)
102+
err := d.deployer.DeployCompose(filepath.Join(d.params.OutPath, composePath), true)
95103
if err != nil {
96-
log.Fatal("Error running docker-compose up", "error", err, "composePath", composePath)
104+
log.Error("Error running docker-compose up", "error", err, "composePath", composePath)
97105
return
98106
}
99107
}
@@ -104,6 +112,9 @@ func createDeployCommand() *Command {
104112

105113
d := &deployCommand{
106114
params: params,
115+
// Default implementations
116+
syncer: git.CreateRepository(params.Repo, params.Branch, params.OutPath),
117+
deployer: docker.NewDeployer(),
107118
}
108119

109120
return &Command{

command/deploy_test.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package command
2+
3+
import (
4+
"errors"
5+
"testing"
6+
)
7+
8+
// --- Mocks ---
9+
10+
type mockSyncer struct {
11+
SyncFunc func(subDirs []string) ([]string, error)
12+
}
13+
14+
func (m *mockSyncer) Sync(subDirs []string) ([]string, error) {
15+
if m.SyncFunc != nil {
16+
return m.SyncFunc(subDirs)
17+
}
18+
return nil, nil
19+
}
20+
21+
type mockDeployer struct {
22+
DeployComposeFunc func(targetPath string, daemonMode bool) error
23+
}
24+
25+
func (m *mockDeployer) DeployCompose(targetPath string, daemonMode bool) error {
26+
if m.DeployComposeFunc != nil {
27+
return m.DeployComposeFunc(targetPath, daemonMode)
28+
}
29+
return nil
30+
}
31+
32+
func TestDeployCommand_Handle(t *testing.T) {
33+
t.Run("Changes detected, should deploy", func(t *testing.T) {
34+
syncer := &mockSyncer{}
35+
deployer := &mockDeployer{}
36+
37+
dc := &deployCommand{
38+
params: DeployCommandParameters{
39+
Repo: "repo",
40+
Branch: "main",
41+
OutPath: "/tmp",
42+
RemoteComposePaths: []string{"app1/docker-compose.yml"},
43+
},
44+
syncer: syncer,
45+
deployer: deployer,
46+
}
47+
48+
syncer.SyncFunc = func(subDirs []string) ([]string, error) {
49+
return []string{"app1"}, nil
50+
}
51+
52+
deployerCalled := false
53+
deployer.DeployComposeFunc = func(targetPath string, daemonMode bool) error {
54+
deployerCalled = true
55+
return nil
56+
}
57+
58+
dc.Handle()
59+
60+
if !deployerCalled {
61+
t.Error("Deployer.DeployCompose should have been called, but it wasn't")
62+
}
63+
})
64+
65+
t.Run("No changes detected, should not deploy", func(t *testing.T) {
66+
syncer := &mockSyncer{}
67+
deployer := &mockDeployer{}
68+
69+
dc := &deployCommand{
70+
params: DeployCommandParameters{
71+
Repo: "repo",
72+
Branch: "main",
73+
OutPath: "/tmp",
74+
RemoteComposePaths: []string{"app1/docker-compose.yml"},
75+
Force: false, // Explicitly false
76+
},
77+
syncer: syncer,
78+
deployer: deployer,
79+
}
80+
81+
syncer.SyncFunc = func(subDirs []string) ([]string, error) {
82+
return []string{}, nil // No changes
83+
}
84+
85+
deployerCalled := false
86+
deployer.DeployComposeFunc = func(targetPath string, daemonMode bool) error {
87+
deployerCalled = true
88+
return nil
89+
}
90+
91+
dc.Handle()
92+
93+
if deployerCalled {
94+
t.Error("Deployer.DeployCompose should not have been called, but it was")
95+
}
96+
})
97+
98+
t.Run("No changes, but force flag is set, should deploy", func(t *testing.T) {
99+
syncer := &mockSyncer{}
100+
deployer := &mockDeployer{}
101+
102+
dc := &deployCommand{
103+
params: DeployCommandParameters{
104+
Repo: "repo",
105+
Branch: "main",
106+
OutPath: "/tmp",
107+
RemoteComposePaths: []string{"app1/docker-compose.yml"},
108+
Force: true, // Force is true
109+
},
110+
syncer: syncer,
111+
deployer: deployer,
112+
}
113+
114+
syncer.SyncFunc = func(subDirs []string) ([]string, error) {
115+
return []string{}, nil // No changes
116+
}
117+
118+
deployerCalled := false
119+
deployer.DeployComposeFunc = func(targetPath string, daemonMode bool) error {
120+
deployerCalled = true
121+
return nil
122+
}
123+
124+
dc.Handle()
125+
126+
if !deployerCalled {
127+
t.Error("Deployer.DeployCompose should have been called with force flag, but it wasn't")
128+
}
129+
})
130+
131+
t.Run("Sync fails, should not deploy", func(t *testing.T) {
132+
syncer := &mockSyncer{}
133+
deployer := &mockDeployer{}
134+
135+
dc := &deployCommand{
136+
params: DeployCommandParameters{
137+
Repo: "repo",
138+
Branch: "main",
139+
OutPath: "/tmp",
140+
RemoteComposePaths: []string{"app1/docker-compose.yml"},
141+
},
142+
syncer: syncer,
143+
deployer: deployer,
144+
}
145+
146+
syncer.SyncFunc = func(subDirs []string) ([]string, error) {
147+
return nil, errors.New("sync failed")
148+
}
149+
150+
deployerCalled := false
151+
deployer.DeployComposeFunc = func(targetPath string, daemonMode bool) error {
152+
deployerCalled = true
153+
return nil
154+
}
155+
156+
dc.Handle()
157+
158+
if deployerCalled {
159+
t.Error("Deployer.DeployCompose should not have been called when sync fails, but it was")
160+
}
161+
})
162+
}

docker/deploy.go

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,59 @@ package docker
22

33
import (
44
"fmt"
5+
"io"
56
"os"
6-
"os/exec"
77
)
88

9-
func DeployCompose(targetPath string, daemonMode bool) error {
10-
if err := checkForDockerEnv(); err != nil {
11-
return err
12-
}
13-
if err := checkTargetPath(targetPath); err != nil {
14-
return err
15-
}
9+
// Deployer handles the logic for deploying a docker-compose application.
10+
type Deployer struct {
11+
dockerService DockerService
12+
fileExists func(path string) bool
13+
stdout io.Writer
14+
stderr io.Writer
15+
}
1616

17-
args := []string{"compose", "-f", targetPath, "up"}
18-
if daemonMode {
19-
args = append(args, "-d")
17+
// NewDeployer creates a new Deployer with default dependencies.
18+
func NewDeployer() *Deployer {
19+
return &Deployer{
20+
dockerService: NewCliDockerService(),
21+
fileExists: osFileExists,
22+
stdout: os.Stdout,
23+
stderr: os.Stderr,
2024
}
21-
cmd := exec.Command("docker", args...)
25+
}
2226

23-
cmd.Stdout = os.Stdout
24-
cmd.Stderr = os.Stderr
27+
// DeployCompose checks the environment and runs 'docker compose up'.
28+
func (d *Deployer) DeployCompose(targetPath string, daemonMode bool) error {
29+
// Check docker availability
30+
if err := d.isDockerAvailable(); err != nil {
31+
return err
32+
}
2533

26-
if err := cmd.Run(); err != nil {
27-
return fmt.Errorf("failed to run docker compose: %w", err)
34+
// Check target file
35+
if !d.fileExists(targetPath) {
36+
return fmt.Errorf("target path does not exist: %s", targetPath)
2837
}
2938

30-
return nil
39+
// Run compose
40+
return d.dockerService.ComposeUp(targetPath, daemonMode, d.stdout, d.stderr)
3141
}
3242

33-
func checkTargetPath(targetPath string) error {
34-
if _, err := os.Stat(targetPath); os.IsNotExist(err) {
35-
return fmt.Errorf("target path does not exist: %v", err)
43+
func (d *Deployer) isDockerAvailable() error {
44+
daemonRunning, err := d.dockerService.IsDaemonRunning()
45+
if err != nil || !daemonRunning {
46+
return fmt.Errorf("docker daemon is not running: %w", err)
3647
}
37-
return nil
38-
}
3948

40-
func checkForDockerEnv() error {
41-
if sockRunning, err := isDockerSocketRunning(); !sockRunning && err != nil {
42-
return fmt.Errorf("docker socket is not running: %v", err)
49+
composeInstalled, err := d.dockerService.IsComposeInstalled()
50+
if err != nil || !composeInstalled {
51+
return fmt.Errorf("docker compose is not installed: %w", err)
4352
}
44-
if installed, err := isDockerComposeInstalled(); !installed && err != nil {
45-
return fmt.Errorf("docker compose is not installed: %v", err)
46-
}
47-
return nil
48-
}
4953

50-
func isDockerComposeInstalled() (bool, error) {
51-
cmd := exec.Command("docker", "compose", "--version")
52-
err := cmd.Run()
53-
if err != nil {
54-
return false, err
55-
}
56-
return true, nil
54+
return nil
5755
}
5856

59-
func isDockerSocketRunning() (bool, error) {
60-
cmd := exec.Command("docker", "info")
61-
err := cmd.Run()
62-
if err != nil {
63-
return false, err
64-
}
65-
return true, nil
57+
func osFileExists(path string) bool {
58+
_, err := os.Stat(path)
59+
return err == nil
6660
}

0 commit comments

Comments
 (0)