master
cpp 373 lines 14.8 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCE2ENetworkListTests.cpp
8
9 Abstract:
10
11 This file contains end-to-end tests for WSLC.
12 --*/
13
14 #include "precomp.h"
15 #include "windows/Common.h"
16 #include "WSLCExecutor.h"
17 #include "WSLCE2EHelpers.h"
18
19 namespace WSLCE2ETests {
20 using namespace wsl::shared;
21 using namespace wsl::shared::string;
22
23 class WSLCE2ENetworkListTests
24 {
25 WSLC_TEST_CLASS(WSLCE2ENetworkListTests)
26
27 TEST_METHOD_SETUP(MethodSetup)
28 {
29 EnsureNetworkDoesNotExist(TestNetworkName);
30 EnsureNetworkDoesNotExist(TestNetworkName2);
31 for (const auto& name : FilterTestNetworkNames)
32 {
33 EnsureNetworkDoesNotExist(name);
34 }
35 return true;
36 }
37
38 TEST_CLASS_CLEANUP(ClassCleanup)
39 {
40 EnsureNetworkDoesNotExist(TestNetworkName);
41 EnsureNetworkDoesNotExist(TestNetworkName2);
42 for (const auto& name : FilterTestNetworkNames)
43 {
44 EnsureNetworkDoesNotExist(name);
45 }
46 return true;
47 }
48
49 WSLC_TEST_METHOD(WSLCE2E_Network_List_HelpCommand)
50 {
51 auto result = RunWslc(L"network list --help");
52 result.Verify({.Stderr = L"", .ExitCode = 0});
53 VERIFY_IS_FALSE(result.Stdout.value().empty());
54 }
55
56 WSLC_TEST_METHOD(WSLCE2E_Network_List_InvalidFormatOption)
57 {
58 auto result = RunWslc(L"network list --format invalid");
59 result.Verify({.Stdout = L"", .ExitCode = 1});
60 VERIFY_IS_TRUE(result.StderrContainsSubstring(
61 L"Invalid format value: invalid is not a recognized format type. Supported format types are: json, table."));
62 }
63
64 WSLC_TEST_METHOD(WSLCE2E_Network_List_QuietOption_OutputsIdsOnly)
65 {
66 auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
67 result.Verify({.Stderr = L"", .ExitCode = 0});
68 result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName2));
69 result.Verify({.Stderr = L"", .ExitCode = 0});
70
71 result = RunWslc(L"network list --format json");
72 result.Verify({.Stderr = L"", .ExitCode = 0});
73
74 std::vector<std::wstring> expectedIds;
75 for (const auto& network : ParseNdjsonOutput(result))
76 {
77 expectedIds.push_back(MultiByteToWide(network.at("ID").get<std::string>()));
78 }
79
80 result = RunWslc(L"network list --quiet");
81 result.Verify({.Stderr = L"", .ExitCode = 0});
82
83 auto lines = result.GetStdoutLines();
84 VERIFY_ARE_EQUAL(expectedIds.size(), lines.size());
85 for (const auto& id : expectedIds)
86 {
87 VerifyIdOutput(id, true);
88 VERIFY_ARE_NOT_EQUAL(lines.end(), std::find(lines.begin(), lines.end(), id));
89 }
90
91 VERIFY_ARE_EQUAL(lines.end(), std::find(lines.begin(), lines.end(), TestNetworkName));
92 VERIFY_ARE_EQUAL(lines.end(), std::find(lines.begin(), lines.end(), TestNetworkName2));
93
94 result = RunWslc(L"network list --format json --no-trunc");
95 result.Verify({.Stderr = L"", .ExitCode = 0});
96
97 expectedIds.clear();
98 for (const auto& network : ParseNdjsonOutput(result))
99 {
100 expectedIds.push_back(MultiByteToWide(network.at("ID").get<std::string>()));
101 }
102
103 result = RunWslc(L"network list --quiet --no-trunc");
104 result.Verify({.Stderr = L"", .ExitCode = 0});
105
106 lines = result.GetStdoutLines();
107 VERIFY_ARE_EQUAL(expectedIds.size(), lines.size());
108 for (const auto& id : expectedIds)
109 {
110 VerifyIdOutput(id, false);
111 VERIFY_ARE_NOT_EQUAL(lines.end(), std::find(lines.begin(), lines.end(), id));
112 }
113 }
114
115 WSLC_TEST_METHOD(WSLCE2E_Network_List_JsonFormat)
116 {
117 auto result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName));
118 result.Verify({.Stderr = L"", .ExitCode = 0});
119 result = RunWslc(std::format(L"network create --driver bridge {}", TestNetworkName2));
120 result.Verify({.Stderr = L"", .ExitCode = 0});
121
122 result = RunWslc(L"network list --format json");
123 result.Verify({.Stderr = L"", .ExitCode = 0});
124 auto networks = ParseNdjsonOutput(result);
125
126 std::vector<std::string> names;
127 names.reserve(networks.size());
128 for (const auto& network : networks)
129 {
130 VERIFY_ARE_EQUAL(9u, network.size());
131 VERIFY_IS_TRUE(network.contains("ID"));
132 VERIFY_IS_FALSE(network.contains("Id"));
133 VERIFY_IS_TRUE(network.contains("Name"));
134 VERIFY_IS_TRUE(network.contains("Driver"));
135 VERIFY_IS_TRUE(network.contains("Scope"));
136 VERIFY_IS_TRUE(network.contains("CreatedAt"));
137 VERIFY_IS_TRUE(network.contains("IPv4"));
138 VERIFY_IS_TRUE(network.contains("IPv6"));
139 VERIFY_IS_TRUE(network.contains("Internal"));
140 VERIFY_IS_TRUE(network.contains("Labels"));
141 VERIFY_IS_TRUE(network.at("CreatedAt").get<std::string>().ends_with(" +0000 UTC"));
142 VerifyIdOutput(MultiByteToWide(network.at("ID").get<std::string>()), true);
143 names.push_back(network.at("Name").get<std::string>());
144 }
145
146 VERIFY_ARE_EQUAL(1u, static_cast<size_t>(std::count(names.begin(), names.end(), WideToMultiByte(TestNetworkName))));
147 VERIFY_ARE_EQUAL(1u, static_cast<size_t>(std::count(names.begin(), names.end(), WideToMultiByte(TestNetworkName2))));
148
149 auto quietResult = RunWslc(L"network list --format json --quiet");
150 quietResult.Verify({.Stderr = L"", .ExitCode = 0});
151 VERIFY_ARE_EQUAL(result.Stdout.value(), quietResult.Stdout.value());
152
153 auto fullResult = RunWslc(L"network list --format json --no-trunc");
154 fullResult.Verify({.Stderr = L"", .ExitCode = 0});
155 for (const auto& network : ParseNdjsonOutput(fullResult))
156 {
157 VerifyIdOutput(MultiByteToWide(network.at("ID").get<std::string>()), false);
158 }
159
160 auto fullQuietResult = RunWslc(L"network list --format json --quiet --no-trunc");
161 fullQuietResult.Verify({.Stderr = L"", .ExitCode = 0});
162 VERIFY_ARE_EQUAL(fullResult.Stdout.value(), fullQuietResult.Stdout.value());
163
164 auto fullTableResult = RunWslc(L"network list --no-trunc");
165 fullTableResult.Verify({.Stderr = L"", .ExitCode = 0});
166 for (const auto& network : ParseNdjsonOutput(fullResult))
167 {
168 const auto id = MultiByteToWide(network.at("ID").get<std::string>());
169 VERIFY_IS_TRUE(fullTableResult.StdoutContainsSubstring(id));
170 }
171 }
172
173 WSLC_TEST_METHOD(WSLCE2E_Network_List_IncludesPredefinedNetworks)
174 {
175 // The predefined networks are not created by wslc but are still reported by list.
176 auto result = RunWslc(L"network list --format json");
177 result.Verify({.Stderr = L"", .ExitCode = 0});
178
179 std::set<std::string> names;
180 for (const auto& network : ParseNdjsonOutputAs<NetworkListOutput>(result))
181 {
182 names.insert(network.Name);
183 }
184
185 VERIFY_IS_TRUE(names.contains("bridge"));
186 VERIFY_IS_TRUE(names.contains("host"));
187 VERIFY_IS_TRUE(names.contains("none"));
188
189 result = RunWslc(L"network list");
190 result.Verify({.Stderr = L"", .ExitCode = 0});
191 VERIFY_IS_TRUE(result.StdoutContainsSubstring(L"NETWORK ID"));
192 VERIFY_IS_TRUE(result.StdoutContainsSubstring(L"SCOPE"));
193 }
194
195 WSLC_TEST_METHOD(WSLCE2E_Network_List_Filter_MalformedValue)
196 {
197 // Filter values must be of the form key=value; bare keys are rejected by the CLI.
198 const auto result = RunWslc(L"network list --filter label");
199 result.Verify({.Stdout = L"", .ExitCode = 1});
200 VERIFY_IS_TRUE(result.StderrContainsSubstring(Localization::WSLCCLI_InvalidFilterError(L"label")));
201 }
202
203 WSLC_TEST_METHOD(WSLCE2E_Network_List_Filter_InvalidKey)
204 {
205 // Unknown filter keys are rejected by the Docker daemon.
206 const auto result = RunWslc(L"network list --filter color=blue");
207 VERIFY_ARE_EQUAL(1, result.ExitCode);
208 VERIFY_IS_TRUE(result.Stderr.has_value());
209 VERIFY_ARE_NOT_EQUAL(std::wstring::npos, result.Stderr->find(L"invalid filter"));
210 }
211
212 WSLC_TEST_METHOD(WSLCE2E_Network_List_Filter_Driver)
213 {
214 const std::wstring alpha = L"wslc-flt-list-driver-alpha";
215 const std::wstring beta = L"wslc-flt-list-driver-beta";
216 auto cleanup = wil::scope_exit([&]() {
217 EnsureNetworkDoesNotExist(alpha);
218 EnsureNetworkDoesNotExist(beta);
219 });
220
221 auto result = RunWslc(std::format(L"network create --driver bridge {}", alpha));
222 result.Verify({.Stderr = L"", .ExitCode = 0});
223 result = RunWslc(std::format(L"network create --driver bridge {}", beta));
224 result.Verify({.Stderr = L"", .ExitCode = 0});
225
226 auto listNames = [&](const std::wstring& filterArgs) {
227 auto r = RunWslc(std::format(L"network list --format json {}", filterArgs));
228 r.Verify({.Stderr = L"", .ExitCode = 0});
229 const auto networks = ParseNdjsonOutputAs<NetworkListOutput>(r);
230 std::set<std::string> names;
231 for (const auto& n : networks)
232 {
233 names.insert(n.Name);
234 }
235 return names;
236 };
237
238 {
239 const auto names = listNames(L"--filter driver=bridge");
240 VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
241 VERIFY_IS_TRUE(names.contains(WideToMultiByte(beta)));
242 }
243
244 // overlay networks require swarm mode; none exist in the test session.
245 {
246 const auto names = listNames(L"--filter driver=overlay");
247 VERIFY_IS_FALSE(names.contains(WideToMultiByte(alpha)));
248 VERIFY_IS_FALSE(names.contains(WideToMultiByte(beta)));
249 }
250 }
251
252 WSLC_TEST_METHOD(WSLCE2E_Network_List_Filter_Label)
253 {
254 const std::wstring alpha = L"wslc-flt-list-label-alpha";
255 const std::wstring beta = L"wslc-flt-list-label-beta";
256 const std::wstring scopeKey = L"wslc.e2e.list_filter_label";
257 const std::wstring scopeValue = L"1";
258
259 auto cleanup = wil::scope_exit([&]() {
260 EnsureNetworkDoesNotExist(alpha);
261 EnsureNetworkDoesNotExist(beta);
262 });
263
264 // alpha carries both scope-key=1 and env=prod; beta carries only scope-key=1.
265 auto result =
266 RunWslc(std::format(L"network create --driver bridge --label {}={} --label env=prod {}", scopeKey, scopeValue, alpha));
267 result.Verify({.Stderr = L"", .ExitCode = 0});
268
269 result = RunWslc(std::format(L"network create --driver bridge --label {}={} {}", scopeKey, scopeValue, beta));
270 result.Verify({.Stderr = L"", .ExitCode = 0});
271
272 auto listNames = [&](const std::wstring& filterArgs) {
273 auto r = RunWslc(std::format(L"network list --format json {}", filterArgs));
274 r.Verify({.Stderr = L"", .ExitCode = 0});
275 const auto networks = ParseNdjsonOutputAs<NetworkListOutput>(r);
276 std::set<std::string> names;
277 for (const auto& n : networks)
278 {
279 names.insert(n.Name);
280 }
281 return names;
282 };
283
284 // label=<key> (key-only) matches any value.
285 {
286 const auto names = listNames(std::format(L"--filter label={}", scopeKey));
287 VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
288 VERIFY_IS_TRUE(names.contains(WideToMultiByte(beta)));
289 }
290
291 // label=<key>=<value> narrows to alpha.
292 {
293 const auto names = listNames(std::format(L"--filter label={}={} --filter label=env=prod", scopeKey, scopeValue));
294 VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
295 VERIFY_IS_FALSE(names.contains(WideToMultiByte(beta)));
296 }
297
298 // Multiple --filter label= entries are AND'd.
299 {
300 const auto names = listNames(std::format(L"--filter label={} --filter label=env=prod", scopeKey));
301 VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
302 VERIFY_IS_FALSE(names.contains(WideToMultiByte(beta)));
303 }
304 }
305
306 WSLC_TEST_METHOD(WSLCE2E_Network_List_Filter_JsonEmptyIsExactlyEmpty)
307 {
308 const std::wstring alpha = L"wslc-flt-list-empty-alpha";
309 auto cleanup = wil::scope_exit([&]() { EnsureNetworkDoesNotExist(alpha); });
310
311 auto result = RunWslc(std::format(L"network create --driver bridge {}", alpha));
312 result.Verify({.Stderr = L"", .ExitCode = 0});
313
314 // NDJSON with zero rows must be exactly empty stdout — not "[]", not "\n".
315 result = RunWslc(L"network list --format json --filter name=wslc-flt-list-no-such-network-zzz");
316 result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
317 }
318
319 WSLC_TEST_METHOD(WSLCE2E_Network_List_Filter_Name)
320 {
321 const std::wstring alpha = L"wslc-flt-list-name-alpha";
322 const std::wstring beta = L"wslc-flt-list-name-beta";
323 auto cleanup = wil::scope_exit([&]() {
324 EnsureNetworkDoesNotExist(alpha);
325 EnsureNetworkDoesNotExist(beta);
326 });
327
328 auto result = RunWslc(std::format(L"network create --driver bridge {}", alpha));
329 result.Verify({.Stderr = L"", .ExitCode = 0});
330 result = RunWslc(std::format(L"network create --driver bridge {}", beta));
331 result.Verify({.Stderr = L"", .ExitCode = 0});
332
333 auto listNames = [&](const std::wstring& filterArgs) {
334 auto r = RunWslc(std::format(L"network list --format json {}", filterArgs));
335 r.Verify({.Stderr = L"", .ExitCode = 0});
336 const auto networks = ParseNdjsonOutputAs<NetworkListOutput>(r);
337 std::set<std::string> names;
338 for (const auto& n : networks)
339 {
340 names.insert(n.Name);
341 }
342 return names;
343 };
344
345 // Docker's `name` filter is a substring match; the shared prefix picks up both networks.
346 {
347 const auto names = listNames(L"--filter name=wslc-flt-list-name-");
348 VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
349 VERIFY_IS_TRUE(names.contains(WideToMultiByte(beta)));
350 }
351
352 // A narrower substring selects only the matching one.
353 {
354 const auto names = listNames(L"--filter name=name-alpha");
355 VERIFY_IS_TRUE(names.contains(WideToMultiByte(alpha)));
356 VERIFY_IS_FALSE(names.contains(WideToMultiByte(beta)));
357 }
358 }
359
360 private:
361 const std::wstring TestNetworkName = L"wslc-e2e-network-list";
362 const std::wstring TestNetworkName2 = L"wslc-e2e-network-list-2";
363 const std::vector<std::wstring> FilterTestNetworkNames = {
364 L"wslc-flt-list-driver-alpha",
365 L"wslc-flt-list-driver-beta",
366 L"wslc-flt-list-label-alpha",
367 L"wslc-flt-list-label-beta",
368 L"wslc-flt-list-empty-alpha",
369 L"wslc-flt-list-name-alpha",
370 L"wslc-flt-list-name-beta",
371 };
372 };
373 } // namespace WSLCE2ETests