1package docker
2
3import (
4 "context"
5 "encoding/json"
6 "math"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/moby/moby/client"
14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/require"
16)
17
18func TestRuntime_PruneImages_DanglingOnlyFilter(t *testing.T) {
19 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20 assert.Equal(t, http.MethodPost, r.Method)
21 assert.Equal(t, "/v1.41/images/prune", r.URL.Path)
22
23 parsedFilters := client.Filters{}
24 require.NoError(t, json.Unmarshal([]byte(r.URL.Query().Get("filters")), &parsedFilters))
25 assert.True(t, parsedFilters["dangling"]["true"])
26
27 w.Header().Set("Content-Type", "application/json")
28 _, _ = w.Write([]byte(`{"ImagesDeleted":[{"Deleted":"sha256:abc"},{"Untagged":"example:old"}],"SpaceReclaimed":1234}`))
29 }))
30 defer server.Close()
31
32 runtime := newRuntimeForHTTPServer(t, server)
33 report, err := runtime.PruneImages(context.Background(), true)
34
35 require.NoError(t, err)
36 assert.Equal(t, []string{"sha256:abc", "example:old"}, report.DeletedIDs)
37 assert.EqualValues(t, 1234, report.SpaceReclaimed)
38}
39
40func TestRuntime_PruneImages_FullUnusedHasNoDanglingFilter(t *testing.T) {
41 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
42 assert.Equal(t, http.MethodPost, r.Method)
43 assert.Equal(t, "/v1.41/images/prune", r.URL.Path)
44
45 parsedFilters := client.Filters{}
46 require.NoError(t, json.Unmarshal([]byte(r.URL.Query().Get("filters")), &parsedFilters))
47 assert.Empty(t, parsedFilters["dangling"])
48
49 w.Header().Set("Content-Type", "application/json")
50 _, _ = w.Write([]byte(`{"ImagesDeleted":[{"Deleted":"sha256:def"}],"SpaceReclaimed":5678}`))
51 }))
52 defer server.Close()
53
54 runtime := newRuntimeForHTTPServer(t, server)
55 report, err := runtime.PruneImages(context.Background(), false)
56
57 require.NoError(t, err)
58 assert.Equal(t, []string{"sha256:def"}, report.DeletedIDs)
59 assert.EqualValues(t, 5678, report.SpaceReclaimed)
60}
61
62func TestRuntime_PruneImages_ReturnsErrorOnNon2xxResponse(t *testing.T) {
63 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
64 assert.Equal(t, http.MethodPost, r.Method)
65 assert.Equal(t, "/v1.41/images/prune", r.URL.Path)
66
67 w.WriteHeader(http.StatusInternalServerError)
68 _, _ = w.Write([]byte(`{"message":"internal error"}`))
69 }))
70 defer server.Close()
71
72 runtime := newRuntimeForHTTPServer(t, server)
73 _, err := runtime.PruneImages(context.Background(), true)
74
75 require.Error(t, err)
76}
77
78func TestRuntime_PruneImages_ReturnsErrorOnInvalidPayload(t *testing.T) {
79 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
80 assert.Equal(t, http.MethodPost, r.Method)
81 assert.Equal(t, "/v1.41/images/prune", r.URL.Path)
82
83 w.Header().Set("Content-Type", "application/json")
84 _, _ = w.Write([]byte(`not-json`))
85 }))
86 defer server.Close()
87
88 runtime := newRuntimeForHTTPServer(t, server)
89 _, err := runtime.PruneImages(context.Background(), true)
90
91 require.Error(t, err)
92}
93
94func TestRuntime_PruneImages_CapsSpaceReclaimedOverflow(t *testing.T) {
95 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
96 assert.Equal(t, http.MethodPost, r.Method)
97 assert.Equal(t, "/v1.41/images/prune", r.URL.Path)
98
99 w.Header().Set("Content-Type", "application/json")
100 _, _ = w.Write([]byte(`{"ImagesDeleted":[],"SpaceReclaimed":9223372036854775808}`))
101 }))
102 defer server.Close()
103
104 runtime := newRuntimeForHTTPServer(t, server)
105 report, err := runtime.PruneImages(context.Background(), true)
106
107 require.NoError(t, err)
108 assert.EqualValues(t, math.MaxInt64, report.SpaceReclaimed)
109}
110
111func TestRuntime_ListImagesDetailed_MapsImageSummary(t *testing.T) {
112 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
113 assert.Equal(t, http.MethodGet, r.Method)
114 assert.Equal(t, "/v1.41/images/json", r.URL.Path)
115 assert.Equal(t, "1", r.URL.Query().Get("all"))
116
117 w.Header().Set("Content-Type", "application/json")
118 _, _ = w.Write([]byte(`[{"Id":"sha256:img1","RepoTags":["alpine:latest"],"Size":321,"Created":1700000000}]`))
119 }))
120 defer server.Close()
121
122 runtime := newRuntimeForHTTPServer(t, server)
123 images, err := runtime.ListImagesDetailed(context.Background())
124
125 require.NoError(t, err)
126 require.Len(t, images, 1)
127 assert.Equal(t, "sha256:img1", images[0].ID)
128 assert.Equal(t, []string{"alpine:latest"}, images[0].RepoTags)
129 assert.EqualValues(t, 321, images[0].Size)
130 assert.Equal(t, time.Unix(1700000000, 0), images[0].Created)
131}
132
133func TestRuntime_ListImagesDetailed_ReturnsErrorOnNon2xxResponse(t *testing.T) {
134 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
135 assert.Equal(t, http.MethodGet, r.Method)
136 assert.Equal(t, "/v1.41/images/json", r.URL.Path)
137
138 w.WriteHeader(http.StatusBadGateway)
139 _, _ = w.Write([]byte(`{"message":"upstream error"}`))
140 }))
141 defer server.Close()
142
143 runtime := newRuntimeForHTTPServer(t, server)
144 _, err := runtime.ListImagesDetailed(context.Background())
145
146 require.Error(t, err)
147}
148
149func TestRuntime_ListImagesDetailed_ReturnsErrorOnInvalidPayload(t *testing.T) {
150 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
151 assert.Equal(t, http.MethodGet, r.Method)
152 assert.Equal(t, "/v1.41/images/json", r.URL.Path)
153
154 w.Header().Set("Content-Type", "application/json")
155 _, _ = w.Write([]byte(`{"not":`))
156 }))
157 defer server.Close()
158
159 runtime := newRuntimeForHTTPServer(t, server)
160 _, err := runtime.ListImagesDetailed(context.Background())
161
162 require.Error(t, err)
163}
164
165func TestRuntime_PruneImages_UsesMaxInt64Boundary(t *testing.T) {
166 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
167 assert.Equal(t, http.MethodPost, r.Method)
168 assert.Equal(t, "/v1.41/images/prune", r.URL.Path)
169
170 w.Header().Set("Content-Type", "application/json")
171 _, _ = w.Write([]byte(`{"ImagesDeleted":[],"SpaceReclaimed":9223372036854775807}`))
172 }))
173 defer server.Close()
174
175 runtime := newRuntimeForHTTPServer(t, server)
176 report, err := runtime.PruneImages(context.Background(), true)
177
178 require.NoError(t, err)
179 assert.EqualValues(t, math.MaxInt64, report.SpaceReclaimed)
180}
181
182func newRuntimeForHTTPServer(t *testing.T, server *httptest.Server) *Runtime {
183 t.Helper()
184
185 host := strings.TrimPrefix(server.URL, "http://")
186 cli, err := client.New(client.WithHost("tcp://"+host), client.WithAPIVersion("1.41"), client.WithHTTPClient(server.Client()))
187 require.NoError(t, err)
188
189 return NewRuntimeWithClient(cli)
190}