Skip to content

Commit ab3d408

Browse files
committed
just few unit tests
1 parent 3c8f033 commit ab3d408

7 files changed

Lines changed: 642 additions & 1 deletion

File tree

internal/controller/container.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ func init() {
2828
containerService = service.NewContainerService(cli)
2929
}
3030

31+
func setContainerServiceForTest(s service.ContainerService) { containerService = s }
32+
3133
// Container represents a container's simplified information.
3234
type Container struct {
3335
ID string
@@ -318,6 +320,10 @@ func GetContainerStats(idOrName string) (stat ContainerStat, err error) {
318320
return ContainerStat{}, err
319321
}
320322

323+
return calculateStat(s), nil
324+
}
325+
326+
func calculateStat(s statsJSON) ContainerStat {
321327
cpuDelta := float64(s.CPUStats.CPUUsage.TotalUsage) - float64(s.PreCPUStats.CPUUsage.TotalUsage)
322328
systemDelta := float64(s.CPUStats.SystemCPUUsage) - float64(s.PreCPUStats.SystemCPUUsage)
323329
numCPUs := float64(s.CPUStats.OnlineCPUs)
@@ -339,7 +345,7 @@ func GetContainerStats(idOrName string) (stat ContainerStat, err error) {
339345
CPUPercent: cpuPercent,
340346
MemUsage: memUsage,
341347
MemLimit: s.MemoryStats.Limit,
342-
}, nil
348+
}
343349
}
344350

345351
// formatCreated parses a Docker RFC3339 created timestamp into a human-readable age.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package controller
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/docker/docker/api/types/container"
8+
clientmock "github.com/rluders/berth/mocks/client"
9+
"github.com/rluders/berth/internal/service"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/mock"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestListContainers_returnsMappedContainers(t *testing.T) {
16+
mockClient := clientmock.NewMockAPIClient(t)
17+
mockClient.EXPECT().
18+
ContainerList(mock.Anything, container.ListOptions{All: true}).
19+
Return([]container.Summary{
20+
{
21+
ID: "abcdef123456789",
22+
Image: "nginx:latest",
23+
Command: "nginx -g 'daemon off;'",
24+
Names: []string{"/my-nginx"},
25+
Status: "Up 2 hours",
26+
State: "running",
27+
},
28+
}, nil)
29+
30+
setContainerServiceForTest(service.NewContainerService(mockClient))
31+
32+
result, err := ListContainers()
33+
34+
require.NoError(t, err)
35+
require.Len(t, result, 1)
36+
assert.Equal(t, "abcdef123456", result[0].ID) // truncated to 12
37+
assert.Equal(t, "nginx:latest", result[0].Image)
38+
assert.Equal(t, "my-nginx", result[0].Names) // leading / stripped
39+
assert.Equal(t, "running", result[0].State)
40+
}
41+
42+
func TestListContainers_propagatesError(t *testing.T) {
43+
mockClient := clientmock.NewMockAPIClient(t)
44+
mockClient.EXPECT().
45+
ContainerList(mock.Anything, container.ListOptions{All: true}).
46+
Return(nil, errors.New("connection refused"))
47+
48+
setContainerServiceForTest(service.NewContainerService(mockClient))
49+
50+
_, err := ListContainers()
51+
assert.ErrorContains(t, err, "connection refused")
52+
}
53+
54+
func TestStartContainer_callsServiceWithID(t *testing.T) {
55+
mockClient := clientmock.NewMockAPIClient(t)
56+
mockClient.EXPECT().
57+
ContainerStart(mock.Anything, "abc123", container.StartOptions{}).
58+
Return(nil)
59+
60+
setContainerServiceForTest(service.NewContainerService(mockClient))
61+
62+
err := StartContainer("abc123")
63+
assert.NoError(t, err)
64+
}
65+
66+
func TestStopContainer_callsServiceWithID(t *testing.T) {
67+
mockClient := clientmock.NewMockAPIClient(t)
68+
mockClient.EXPECT().
69+
ContainerStop(mock.Anything, "abc123", container.StopOptions{}).
70+
Return(nil)
71+
72+
setContainerServiceForTest(service.NewContainerService(mockClient))
73+
74+
err := StopContainer("abc123")
75+
assert.NoError(t, err)
76+
}
77+
78+
func TestRestartContainer_callsServiceWithID(t *testing.T) {
79+
mockClient := clientmock.NewMockAPIClient(t)
80+
mockClient.EXPECT().
81+
ContainerRestart(mock.Anything, "abc123", container.StopOptions{}).
82+
Return(nil)
83+
84+
setContainerServiceForTest(service.NewContainerService(mockClient))
85+
86+
err := RestartContainer("abc123")
87+
assert.NoError(t, err)
88+
}
89+
90+
func TestRemoveContainer_callsServiceWithForce(t *testing.T) {
91+
mockClient := clientmock.NewMockAPIClient(t)
92+
mockClient.EXPECT().
93+
ContainerRemove(mock.Anything, "abc123", container.RemoveOptions{Force: true}).
94+
Return(nil)
95+
96+
setContainerServiceForTest(service.NewContainerService(mockClient))
97+
98+
err := RemoveContainer("abc123")
99+
assert.NoError(t, err)
100+
}

internal/controller/format_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package controller
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
"github.com/docker/docker/api/types/container"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestFormatPorts(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
ports []container.Port
16+
want string
17+
}{
18+
{"empty", nil, ""},
19+
{"single exposed port no binding", []container.Port{{PrivatePort: 80, Type: "tcp"}}, "80/tcp"},
20+
{"single port with public binding", []container.Port{{PrivatePort: 80, PublicPort: 8080, Type: "tcp"}}, "8080->80/tcp"},
21+
{"duplicate ports deduplicated", []container.Port{
22+
{PrivatePort: 80, Type: "tcp"},
23+
{PrivatePort: 80, Type: "tcp"},
24+
}, "80/tcp"},
25+
{"multiple distinct ports", []container.Port{
26+
{PrivatePort: 80, Type: "tcp"},
27+
{PrivatePort: 443, Type: "tcp"},
28+
}, "80/tcp, 443/tcp"},
29+
}
30+
31+
for _, tt := range tests {
32+
t.Run(tt.name, func(t *testing.T) {
33+
got := formatPorts(tt.ports)
34+
assert.Equal(t, tt.want, got)
35+
})
36+
}
37+
}
38+
39+
func TestFormatCreated(t *testing.T) {
40+
t.Run("invalid string returned as-is", func(t *testing.T) {
41+
got := formatCreated("not-a-timestamp")
42+
assert.Equal(t, "not-a-timestamp", got)
43+
})
44+
45+
t.Run("30 seconds ago", func(t *testing.T) {
46+
ts := time.Now().Add(-30 * time.Second).Format(time.RFC3339Nano)
47+
got := formatCreated(ts)
48+
assert.Equal(t, "30s ago", got)
49+
})
50+
51+
t.Run("5 minutes ago", func(t *testing.T) {
52+
ts := time.Now().Add(-5 * time.Minute).Format(time.RFC3339Nano)
53+
got := formatCreated(ts)
54+
assert.Equal(t, "5m ago", got)
55+
})
56+
57+
t.Run("3 hours ago", func(t *testing.T) {
58+
ts := time.Now().Add(-3 * time.Hour).Format(time.RFC3339Nano)
59+
got := formatCreated(ts)
60+
assert.Equal(t, "3h ago", got)
61+
})
62+
63+
t.Run("2 days ago", func(t *testing.T) {
64+
ts := time.Now().Add(-48 * time.Hour).Format(time.RFC3339Nano)
65+
got := formatCreated(ts)
66+
assert.Equal(t, fmt.Sprintf("%dd ago", 48/24), got)
67+
})
68+
}

0 commit comments

Comments
 (0)