|
1 | 1 | package completion
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "context" |
| 5 | + "errors" |
| 6 | + "sort" |
4 | 7 | "testing"
|
5 | 8 |
|
| 9 | + "github.com/docker/docker/api/types/container" |
| 10 | + "github.com/docker/docker/api/types/filters" |
| 11 | + "github.com/docker/docker/api/types/image" |
| 12 | + "github.com/docker/docker/api/types/network" |
| 13 | + "github.com/docker/docker/api/types/volume" |
| 14 | + "github.com/docker/docker/client" |
| 15 | + "github.com/google/go-cmp/cmp/cmpopts" |
6 | 16 | "github.com/spf13/cobra"
|
7 | 17 | "gotest.tools/v3/assert"
|
8 | 18 | is "gotest.tools/v3/assert/cmp"
|
| 19 | + "gotest.tools/v3/env" |
9 | 20 | )
|
10 | 21 |
|
| 22 | +type fakeCLI struct { |
| 23 | + *fakeClient |
| 24 | +} |
| 25 | + |
| 26 | +// Client implements [APIClientProvider]. |
| 27 | +func (c fakeCLI) Client() client.APIClient { |
| 28 | + return c.fakeClient |
| 29 | +} |
| 30 | + |
| 31 | +type fakeClient struct { |
| 32 | + client.Client |
| 33 | + containerListFunc func(options container.ListOptions) ([]container.Summary, error) |
| 34 | + imageListFunc func(options image.ListOptions) ([]image.Summary, error) |
| 35 | + networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) |
| 36 | + volumeListFunc func(filter filters.Args) (volume.ListResponse, error) |
| 37 | +} |
| 38 | + |
| 39 | +func (c *fakeClient) ContainerList(_ context.Context, options container.ListOptions) ([]container.Summary, error) { |
| 40 | + if c.containerListFunc != nil { |
| 41 | + return c.containerListFunc(options) |
| 42 | + } |
| 43 | + return []container.Summary{}, nil |
| 44 | +} |
| 45 | + |
| 46 | +func (c *fakeClient) ImageList(_ context.Context, options image.ListOptions) ([]image.Summary, error) { |
| 47 | + if c.imageListFunc != nil { |
| 48 | + return c.imageListFunc(options) |
| 49 | + } |
| 50 | + return []image.Summary{}, nil |
| 51 | +} |
| 52 | + |
| 53 | +func (c *fakeClient) NetworkList(ctx context.Context, options network.ListOptions) ([]network.Summary, error) { |
| 54 | + if c.networkListFunc != nil { |
| 55 | + return c.networkListFunc(ctx, options) |
| 56 | + } |
| 57 | + return []network.Inspect{}, nil |
| 58 | +} |
| 59 | + |
| 60 | +func (c *fakeClient) VolumeList(_ context.Context, options volume.ListOptions) (volume.ListResponse, error) { |
| 61 | + if c.volumeListFunc != nil { |
| 62 | + return c.volumeListFunc(options.Filters) |
| 63 | + } |
| 64 | + return volume.ListResponse{}, nil |
| 65 | +} |
| 66 | + |
| 67 | +func TestCompleteContainerNames(t *testing.T) { |
| 68 | + tests := []struct { |
| 69 | + doc string |
| 70 | + showAll, showIDs bool |
| 71 | + filters []func(container.Summary) bool |
| 72 | + containers []container.Summary |
| 73 | + expOut []string |
| 74 | + expOpts container.ListOptions |
| 75 | + expDirective cobra.ShellCompDirective |
| 76 | + }{ |
| 77 | + { |
| 78 | + doc: "no results", |
| 79 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 80 | + }, |
| 81 | + { |
| 82 | + doc: "all containers", |
| 83 | + showAll: true, |
| 84 | + containers: []container.Summary{ |
| 85 | + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, |
| 86 | + {ID: "id-b", State: "created", Names: []string{"/container-b"}}, |
| 87 | + {ID: "id-a", State: "exited", Names: []string{"/container-a"}}, |
| 88 | + }, |
| 89 | + expOut: []string{"container-c", "container-c/link-b", "container-b", "container-a"}, |
| 90 | + expOpts: container.ListOptions{All: true}, |
| 91 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 92 | + }, |
| 93 | + { |
| 94 | + doc: "all containers with ids", |
| 95 | + showAll: true, |
| 96 | + showIDs: true, |
| 97 | + containers: []container.Summary{ |
| 98 | + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, |
| 99 | + {ID: "id-b", State: "created", Names: []string{"/container-b"}}, |
| 100 | + {ID: "id-a", State: "exited", Names: []string{"/container-a"}}, |
| 101 | + }, |
| 102 | + expOut: []string{"id-c", "container-c", "container-c/link-b", "id-b", "container-b", "id-a", "container-a"}, |
| 103 | + expOpts: container.ListOptions{All: true}, |
| 104 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 105 | + }, |
| 106 | + { |
| 107 | + doc: "only running containers", |
| 108 | + showAll: false, |
| 109 | + containers: []container.Summary{ |
| 110 | + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, |
| 111 | + }, |
| 112 | + expOut: []string{"container-c", "container-c/link-b"}, |
| 113 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 114 | + }, |
| 115 | + { |
| 116 | + doc: "with filter", |
| 117 | + showAll: true, |
| 118 | + filters: []func(container.Summary) bool{ |
| 119 | + func(container container.Summary) bool { return container.State == "created" }, |
| 120 | + }, |
| 121 | + containers: []container.Summary{ |
| 122 | + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, |
| 123 | + {ID: "id-b", State: "created", Names: []string{"/container-b"}}, |
| 124 | + {ID: "id-a", State: "exited", Names: []string{"/container-a"}}, |
| 125 | + }, |
| 126 | + expOut: []string{"container-b"}, |
| 127 | + expOpts: container.ListOptions{All: true}, |
| 128 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 129 | + }, |
| 130 | + { |
| 131 | + doc: "multiple filters", |
| 132 | + showAll: true, |
| 133 | + filters: []func(container.Summary) bool{ |
| 134 | + func(container container.Summary) bool { return container.ID == "id-a" }, |
| 135 | + func(container container.Summary) bool { return container.State == "created" }, |
| 136 | + }, |
| 137 | + containers: []container.Summary{ |
| 138 | + {ID: "id-c", State: "running", Names: []string{"/container-c", "/container-c/link-b"}}, |
| 139 | + {ID: "id-b", State: "created", Names: []string{"/container-b"}}, |
| 140 | + {ID: "id-a", State: "created", Names: []string{"/container-a"}}, |
| 141 | + }, |
| 142 | + expOut: []string{"container-a"}, |
| 143 | + expOpts: container.ListOptions{All: true}, |
| 144 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 145 | + }, |
| 146 | + { |
| 147 | + doc: "with error", |
| 148 | + expDirective: cobra.ShellCompDirectiveError, |
| 149 | + }, |
| 150 | + } |
| 151 | + |
| 152 | + for _, tc := range tests { |
| 153 | + tc := tc |
| 154 | + t.Run(tc.doc, func(t *testing.T) { |
| 155 | + if tc.showIDs { |
| 156 | + t.Setenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS", "yes") |
| 157 | + } |
| 158 | + comp := ContainerNames(fakeCLI{&fakeClient{ |
| 159 | + containerListFunc: func(opts container.ListOptions) ([]container.Summary, error) { |
| 160 | + assert.Check(t, is.DeepEqual(opts, tc.expOpts, cmpopts.IgnoreUnexported(container.ListOptions{}, filters.Args{}))) |
| 161 | + if tc.expDirective == cobra.ShellCompDirectiveError { |
| 162 | + return nil, errors.New("some error occurred") |
| 163 | + } |
| 164 | + return tc.containers, nil |
| 165 | + }, |
| 166 | + }}, tc.showAll, tc.filters...) |
| 167 | + |
| 168 | + containers, directives := comp(&cobra.Command{}, nil, "") |
| 169 | + assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective)) |
| 170 | + assert.Check(t, is.DeepEqual(containers, tc.expOut)) |
| 171 | + }) |
| 172 | + } |
| 173 | +} |
| 174 | + |
| 175 | +func TestCompleteEnvVarNames(t *testing.T) { |
| 176 | + env.PatchAll(t, map[string]string{ |
| 177 | + "ENV_A": "hello-a", |
| 178 | + "ENV_B": "hello-b", |
| 179 | + }) |
| 180 | + values, directives := EnvVarNames(nil, nil, "") |
| 181 | + assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") |
| 182 | + |
| 183 | + sort.Strings(values) |
| 184 | + expected := []string{"ENV_A", "ENV_B"} |
| 185 | + assert.Check(t, is.DeepEqual(values, expected)) |
| 186 | +} |
| 187 | + |
| 188 | +func TestCompleteFileNames(t *testing.T) { |
| 189 | + values, directives := FileNames(nil, nil, "") |
| 190 | + assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveDefault)) |
| 191 | + assert.Check(t, is.Len(values, 0)) |
| 192 | +} |
| 193 | + |
| 194 | +func TestCompleteFromList(t *testing.T) { |
| 195 | + expected := []string{"one", "two", "three"} |
| 196 | + |
| 197 | + values, directives := FromList(expected...)(nil, nil, "") |
| 198 | + assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") |
| 199 | + assert.Check(t, is.DeepEqual(values, expected)) |
| 200 | +} |
| 201 | + |
| 202 | +func TestCompleteImageNames(t *testing.T) { |
| 203 | + tests := []struct { |
| 204 | + doc string |
| 205 | + images []image.Summary |
| 206 | + expOut []string |
| 207 | + expDirective cobra.ShellCompDirective |
| 208 | + }{ |
| 209 | + { |
| 210 | + doc: "no results", |
| 211 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 212 | + }, |
| 213 | + { |
| 214 | + doc: "with results", |
| 215 | + images: []image.Summary{ |
| 216 | + {RepoTags: []string{"image-c:latest", "image-c:other"}}, |
| 217 | + {RepoTags: []string{"image-b:latest", "image-b:other"}}, |
| 218 | + {RepoTags: []string{"image-a:latest", "image-a:other"}}, |
| 219 | + }, |
| 220 | + expOut: []string{"image-c:latest", "image-c:other", "image-b:latest", "image-b:other", "image-a:latest", "image-a:other"}, |
| 221 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 222 | + }, |
| 223 | + { |
| 224 | + doc: "with error", |
| 225 | + expDirective: cobra.ShellCompDirectiveError, |
| 226 | + }, |
| 227 | + } |
| 228 | + |
| 229 | + for _, tc := range tests { |
| 230 | + tc := tc |
| 231 | + t.Run(tc.doc, func(t *testing.T) { |
| 232 | + comp := ImageNames(fakeCLI{&fakeClient{ |
| 233 | + imageListFunc: func(options image.ListOptions) ([]image.Summary, error) { |
| 234 | + if tc.expDirective == cobra.ShellCompDirectiveError { |
| 235 | + return nil, errors.New("some error occurred") |
| 236 | + } |
| 237 | + return tc.images, nil |
| 238 | + }, |
| 239 | + }}) |
| 240 | + |
| 241 | + volumes, directives := comp(&cobra.Command{}, nil, "") |
| 242 | + assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective)) |
| 243 | + assert.Check(t, is.DeepEqual(volumes, tc.expOut)) |
| 244 | + }) |
| 245 | + } |
| 246 | +} |
| 247 | + |
| 248 | +func TestCompleteNetworkNames(t *testing.T) { |
| 249 | + tests := []struct { |
| 250 | + doc string |
| 251 | + networks []network.Summary |
| 252 | + expOut []string |
| 253 | + expDirective cobra.ShellCompDirective |
| 254 | + }{ |
| 255 | + { |
| 256 | + doc: "no results", |
| 257 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 258 | + }, |
| 259 | + { |
| 260 | + doc: "with results", |
| 261 | + networks: []network.Summary{ |
| 262 | + {ID: "nw-c", Name: "network-c"}, |
| 263 | + {ID: "nw-b", Name: "network-b"}, |
| 264 | + {ID: "nw-a", Name: "network-a"}, |
| 265 | + }, |
| 266 | + expOut: []string{"network-c", "network-b", "network-a"}, |
| 267 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 268 | + }, |
| 269 | + { |
| 270 | + doc: "with error", |
| 271 | + expDirective: cobra.ShellCompDirectiveError, |
| 272 | + }, |
| 273 | + } |
| 274 | + |
| 275 | + for _, tc := range tests { |
| 276 | + tc := tc |
| 277 | + t.Run(tc.doc, func(t *testing.T) { |
| 278 | + comp := NetworkNames(fakeCLI{&fakeClient{ |
| 279 | + networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) { |
| 280 | + if tc.expDirective == cobra.ShellCompDirectiveError { |
| 281 | + return nil, errors.New("some error occurred") |
| 282 | + } |
| 283 | + return tc.networks, nil |
| 284 | + }, |
| 285 | + }}) |
| 286 | + |
| 287 | + volumes, directives := comp(&cobra.Command{}, nil, "") |
| 288 | + assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective)) |
| 289 | + assert.Check(t, is.DeepEqual(volumes, tc.expOut)) |
| 290 | + }) |
| 291 | + } |
| 292 | +} |
| 293 | + |
| 294 | +func TestCompleteNoComplete(t *testing.T) { |
| 295 | + values, directives := NoComplete(nil, nil, "") |
| 296 | + assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp)) |
| 297 | + assert.Check(t, is.Len(values, 0)) |
| 298 | +} |
| 299 | + |
11 | 300 | func TestCompletePlatforms(t *testing.T) {
|
12 | 301 | values, directives := Platforms(nil, nil, "")
|
13 | 302 | assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
14 | 303 | assert.Check(t, is.DeepEqual(values, commonPlatforms))
|
15 | 304 | }
|
| 305 | + |
| 306 | +func TestCompleteVolumeNames(t *testing.T) { |
| 307 | + tests := []struct { |
| 308 | + doc string |
| 309 | + volumes []*volume.Volume |
| 310 | + expOut []string |
| 311 | + expDirective cobra.ShellCompDirective |
| 312 | + }{ |
| 313 | + { |
| 314 | + doc: "no results", |
| 315 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 316 | + }, |
| 317 | + { |
| 318 | + doc: "with results", |
| 319 | + volumes: []*volume.Volume{ |
| 320 | + {Name: "volume-c"}, |
| 321 | + {Name: "volume-b"}, |
| 322 | + {Name: "volume-a"}, |
| 323 | + }, |
| 324 | + expOut: []string{"volume-c", "volume-b", "volume-a"}, |
| 325 | + expDirective: cobra.ShellCompDirectiveNoFileComp, |
| 326 | + }, |
| 327 | + { |
| 328 | + doc: "with error", |
| 329 | + expDirective: cobra.ShellCompDirectiveError, |
| 330 | + }, |
| 331 | + } |
| 332 | + |
| 333 | + for _, tc := range tests { |
| 334 | + tc := tc |
| 335 | + t.Run(tc.doc, func(t *testing.T) { |
| 336 | + comp := VolumeNames(fakeCLI{&fakeClient{ |
| 337 | + volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) { |
| 338 | + if tc.expDirective == cobra.ShellCompDirectiveError { |
| 339 | + return volume.ListResponse{}, errors.New("some error occurred") |
| 340 | + } |
| 341 | + return volume.ListResponse{Volumes: tc.volumes}, nil |
| 342 | + }, |
| 343 | + }}) |
| 344 | + |
| 345 | + volumes, directives := comp(&cobra.Command{}, nil, "") |
| 346 | + assert.Check(t, is.Equal(directives&tc.expDirective, tc.expDirective)) |
| 347 | + assert.Check(t, is.DeepEqual(volumes, tc.expOut)) |
| 348 | + }) |
| 349 | + } |
| 350 | +} |
0 commit comments