1 #include "../common/certs.hpp"
2 #include "root_path.hpp"
3 
4 #include <pxp-agent/configuration.hpp>
5 
6 #include "horsewhisperer/horsewhisperer.h"
7 
8 #include <leatherman/util/scope_exit.hpp>
9 
10 #define LEATHERMAN_LOGGING_NAMESPACE "puppetlabs.pxp_agent.configuration_test"
11 #include <leatherman/logging/logging.hpp>
12 
13 #include <boost/filesystem/operations.hpp>
14 #include <boost/filesystem/path.hpp>
15 
16 #include <catch.hpp>
17 
18 #include <string>
19 #include <vector>
20 
21 #define ARGUMENT_COUNT(argv) (sizeof(argv)/sizeof((argv)[0]) - 1)
22 
23 using namespace PXPAgent;
24 
25 namespace fs = boost::filesystem;
26 namespace HW = HorseWhisperer;
27 namespace lth_log = leatherman::logging;
28 namespace lth_util = leatherman::util;
29 
30 static const std::string CONFIG { std::string { PXP_AGENT_ROOT_PATH }
31                                   + "/lib/tests/resources/config/empty-pxp-agent.conf" };
32 static const std::string MULTI_BROKER_CONFIG { std::string { PXP_AGENT_ROOT_PATH }
33                                                + "/lib/tests/resources/config/multi-broker.conf" };
34 static const std::string MULTI_URI_CONFIG { std::string { PXP_AGENT_ROOT_PATH }
35                                                + "/lib/tests/resources/config/multi-broker-both-uris.conf" };
36 static const std::string MASTER_URI_CONFIG { std::string { PXP_AGENT_ROOT_PATH }
37                                                + "/lib/tests/resources/config/multi-broker-master-uris.conf" };
38 static const std::string DUPLICATE_CONFIG { std::string { PXP_AGENT_ROOT_PATH }
39                                             + "/lib/tests/resources/config/duplicate.conf" };
40 static const std::string BAD_BROKER_CONFIG { std::string { PXP_AGENT_ROOT_PATH }
41                                              + "/lib/tests/resources/config/bad-broker.conf" };
42 static const std::string BAD_MASTER_CONFIG { std::string { PXP_AGENT_ROOT_PATH }
43                                              + "/lib/tests/resources/config/bad-master.conf" };
44 static const std::string INVALID_CONFIG_NAME { std::string { PXP_AGENT_ROOT_PATH }
45                                                + "/lib/tests/resources/config/foo.cfg" };
46 static const std::string UNKNOWN_CONFIG { std::string { PXP_AGENT_ROOT_PATH }
47                                           + "/lib/tests/resources/config/unknown.conf" };
48 static const std::string TEST_BROKER_WS_URI { "wss:///test_c_t_h_u_n_broker" };
49 static const std::string CA { getCaPath() };
50 static const std::string CERT { getCertPath() };
51 static const std::string KEY { getKeyPath() };
52 static const std::string CRL { getCrlPath() };
53 static const std::string MODULES_DIR { std::string { PXP_AGENT_ROOT_PATH }
54                                        + "/lib/tests/resources/modules/" };
55 static const std::string MODULES_CONFIG_DIR { std::string { PXP_AGENT_ROOT_PATH }
56                                               + "/lib/tests/resources/modules_config" };
57 static const std::string SPOOL_DIR { std::string { PXP_AGENT_ROOT_PATH }
58                                      + "/lib/tests/resources/test_spool" };
59 static const std::string TASK_CACHE_DIR { std::string { PXP_AGENT_ROOT_PATH }
60                                           + "/lib/tests/resources/test_task_cache" };
61 static const std::string DIR_PURGE_TTL { "1h" };
62 
63 static const char* ARGV[] = {
64     "test-command",
65     "--config-file", CONFIG.c_str(),
66     "--broker-ws-uri", TEST_BROKER_WS_URI.c_str(),
67     "--pcp-version=2",
68     "--ssl-ca-cert", CA.c_str(),
69     "--ssl-cert", CERT.c_str(),
70     "--ssl-key", KEY.c_str(),
71     "--ssl-crl", CRL.c_str(),
72     "--modules-dir", MODULES_DIR.c_str(),
73     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
74     "--spool-dir", SPOOL_DIR.c_str(),
75     "--spool-dir-purge-ttl", DIR_PURGE_TTL.c_str(),
76     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
77     "--task-cache-dir-purge-ttl", DIR_PURGE_TTL.c_str(),
78     "--task-download-connect-timeout", "5",
79     "--task-download-timeout", "120",
80     "--foreground=true",
81     nullptr };
82 
configureTest()83 static void configureTest() {
84     if (!fs::exists(SPOOL_DIR) && !fs::create_directories(SPOOL_DIR)) {
85         FAIL("Failed to create the results directory");
86     }
87     if (!fs::exists(TASK_CACHE_DIR) && !fs::create_directories(TASK_CACHE_DIR)) {
88         FAIL("Failed to create the task cache directory");
89     }
90     if (!fs::exists(MODULES_CONFIG_DIR) && !fs::create_directories(MODULES_CONFIG_DIR)) {
91         FAIL("Failed to create the modules configuration directory");
92     }
93     Configuration::Instance().initialize(
94         [](std::vector<std::string>) {
95             return EXIT_SUCCESS;
96         });
97 }
98 
resetTest()99 static void resetTest() {
100     if (fs::exists(SPOOL_DIR)) {
101         fs::remove_all(SPOOL_DIR);
102     }
103     if (fs::exists(TASK_CACHE_DIR)) {
104         fs::remove_all(TASK_CACHE_DIR);
105     }
106     if (fs::exists(MODULES_CONFIG_DIR)) {
107         fs::remove_all(MODULES_CONFIG_DIR);
108     }
109 }
110 
111 TEST_CASE("Configuration - metatest", "[configuration]") {
112     lth_util::scope_exit config_cleaner { resetTest };
113 
114     SECTION("Metatest - can initialize Configuration") {
115         resetTest();
116         REQUIRE_NOTHROW(configureTest());
117     }
118 
119     SECTION("Metatest - we can inject CL options into HorseWhisperer") {
120         configureTest();
121         Configuration::Instance().parseOptions(ARGUMENT_COUNT(ARGV), const_cast<char**>(ARGV));
122         REQUIRE(HW::GetFlag<std::string>("ssl-ca-cert") == CA);
123         REQUIRE(HW::GetFlag<std::string>("modules-dir") == MODULES_DIR);
124         REQUIRE(HW::GetFlag<std::string>("spool-dir") == SPOOL_DIR);
125     }
126 }
127 
128 TEST_CASE("Configuration::initialize()", "[configuration]") {
129     lth_util::scope_exit config_cleaner { resetTest };
130 
131     SECTION("No error when starting without setting a function") {
132         REQUIRE_NOTHROW(HW::Start());
133     }
134 
135     SECTION("It does set HorseWhisperer's action correctly") {
136         Configuration::Instance().initialize(
__anoncd1ded380202(std::vector<std::string> arg) 137             [] (std::vector<std::string> arg) -> int {
138                 return 42;
139             });
140         const char* args[] = { "pxp-agent", "start", nullptr };
141         HW::Parse(2, const_cast<char**>(args));
142 
143         REQUIRE(HW::Start() == 42);
144     }
145 }
146 
147 TEST_CASE("Configuration::set", "[configuration]") {
148     configureTest();
149     Configuration::Instance().parseOptions(ARGUMENT_COUNT(ARGV), const_cast<char**>(ARGV));
150     Configuration::Instance().validate();
151     lth_util::scope_exit config_cleaner { resetTest };
152 
153     SECTION("can set a known flag to a valid value") {
154         REQUIRE_NOTHROW(Configuration::Instance().set<std::string>("ssl-ca-cert",
155                                                                    "value"));
156     }
157 
158     SECTION("set a flag correctly") {
159         Configuration::Instance().set<std::string>("ssl-ca-cert", "value");
160         REQUIRE(HW::GetFlag<std::string>("ssl-ca-cert") == "value");
161     }
162 
163     SECTION("throw when setting an unknown flag") {
164         REQUIRE_THROWS_AS(Configuration::Instance()
165                                 .set<int>("tentacle_spawning_interval_s", 45),
166                           Configuration::Error);
167     }
168 
169     SECTION("throw when setting a known flag to an invalid value") {
170         HW::DefineGlobalFlag<int>("num_tentacles",
171                                   "number of spawn tentacles",
172                                   8,
__anoncd1ded380302(int v) 173                                   [](int v) {
174                                       if (v != 8) {
175                                           throw HW::flag_validation_error { "bad!" };
176                                       }
177                                   });
178 
179         // The only valid number of tentacles is 8
180         REQUIRE_THROWS_AS(Configuration::Instance().set<int>("num_tentacles", 42),
181                           Configuration::Error);
182         REQUIRE_NOTHROW(Configuration::Instance().set<int>("num_tentacles", 8));
183     }
184 }
185 
186 TEST_CASE("Configuration::get", "[configuration]") {
187     lth_util::scope_exit config_cleaner { resetTest };
188     configureTest();
189 
190     SECTION("When options were not parsed and validated yet") {
191         SECTION("can get a defined flag") {
192             REQUIRE_NOTHROW(Configuration::Instance().get<std::string>("ssl-ca-cert"));
193         }
194 
195         SECTION("return the default value correctly") {
196             REQUIRE(Configuration::Instance().get<std::string>("spool-dir")
197                     == DEFAULT_SPOOL_DIR);
198         }
199 
200         SECTION("throw a Configuration::Error if the flag is unknown") {
201             REQUIRE_THROWS_AS(
202                 Configuration::Instance().get<std::string>("dont_exist"),
203                 Configuration::Error);
204         }
205     }
206 
207     SECTION("After parsing and validating options") {
208         configureTest();
209 
210         SECTION("can get a defined flag") {
211             Configuration::Instance().parseOptions(ARGUMENT_COUNT(ARGV), const_cast<char**>(ARGV));
212             Configuration::Instance().validate();
213             REQUIRE_NOTHROW(Configuration::Instance().get<std::string>("ssl-ca-cert"));
214         }
215 
216         SECTION("return the default value if the flag was not set") {
217             // NB: ignoring --foreground in ARGV since argc is set to 19
218             Configuration::Instance().parseOptions(ARGUMENT_COUNT(ARGV) - 1, const_cast<char**>(ARGV));
219 #ifndef _WIN32
220             HW::SetFlag<std::string>("pidfile", SPOOL_DIR + "/test.pid");
221 #endif
222             Configuration::Instance().validate();
223 
224             REQUIRE_FALSE(Configuration::Instance().get<bool>("foreground"));
225         }
226 
227         SECTION("return the correct value after the flag has been set") {
228             Configuration::Instance().parseOptions(ARGUMENT_COUNT(ARGV), const_cast<char**>(ARGV));
229             Configuration::Instance().validate();
230             Configuration::Instance().set<std::string>("spool-dir", "/fake/dir");
231             REQUIRE(Configuration::Instance().get<std::string>("spool-dir")
232                     == "/fake/dir");
233         }
234 
235         SECTION("throw a Configuration::Error if the flag is unknown") {
236             Configuration::Instance().parseOptions(ARGUMENT_COUNT(ARGV), const_cast<char**>(ARGV));
237             Configuration::Instance().validate();
238             REQUIRE_THROWS_AS(
239                 Configuration::Instance().get<std::string>("still_dont_exist"),
240                 Configuration::Error);
241         }
242     }
243 }
244 
245 TEST_CASE("Configuration::validate", "[configuration]") {
246     lth_util::scope_exit config_cleaner { resetTest };
247     configureTest();
248     Configuration::Instance().parseOptions(ARGUMENT_COUNT(ARGV), const_cast<char**>(ARGV));
249 
250     SECTION("it throws an Error when the broker WebSocket URI is undefined") {
251         HW::SetFlag<std::string>("broker-ws-uri", "");
252         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
253                           Configuration::Error);
254     }
255 
256     SECTION("it throws an Error when the broker WebSocket URi is invalid") {
257         HW::SetFlag<std::string>("broker-ws-uri", "ws://");
258         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
259                           Configuration::Error);
260     }
261 
262     SECTION("it throws an Error when the PCP version is invalid") {
263         HW::SetFlag<std::string>("pcp-version", "3");
264         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
265                           Configuration::Error);
266     }
267 
268     SECTION("it throws an Error ssl-ca-cert is undefined") {
269         HW::SetFlag<std::string>("ssl-ca-cert", "");
270         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
271                           Configuration::Error);
272     }
273 
274     SECTION("it throws an Error when ssl-ca-cert file cannot be found") {
275         HW::SetFlag<std::string>("ssl-ca-cert", "/fake/file");
276         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
277                           Configuration::Error);
278     }
279 
280     SECTION("it throws an Error when ssl-ca-cert file is not "
281             "readable (is a directory)") {
282         HW::SetFlag<std::string>("ssl-ca-cert", "/fake");
283         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
284                           Configuration::Error);
285     }
286 
287     SECTION("it throws an Error when ssl-cert is undefined") {
288         HW::SetFlag<std::string>("ssl-cert", "");
289         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
290                           Configuration::Error);
291     }
292 
293     SECTION("it throws an Error when ssl-cert file cannot be found") {
294         HW::SetFlag<std::string>("ssl-cert", "/fake/file");
295         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
296                           Configuration::Error);
297     }
298 
299     SECTION("it throws an Error when ssl-cert file is not "
300             "readable (is a directory)") {
301         HW::SetFlag<std::string>("ssl-cert", "/fake");
302         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
303                           Configuration::Error);
304     }
305 
306     SECTION("it throws an Error when ssl-key is undefined") {
307         HW::SetFlag<std::string>("ssl-key", "");
308         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
309                           Configuration::Error);
310     }
311 
312     SECTION("it throws an Error when ssl-key file cannot be found") {
313         HW::SetFlag<std::string>("ssl-key", "/fake/file");
314         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
315                           Configuration::Error);
316     }
317 
318     SECTION("it throws an Error when ssl-key file is not "
319             "readable (is a directory)") {
320         HW::SetFlag<std::string>("ssl-key", "/fake");
321         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
322                           Configuration::Error);
323     }
324 
325     SECTION("it fails when --modules-dir does not exist") {
326         auto test_modules = SPOOL_DIR + "/testing_modules";
327         HW::SetFlag<std::string>("modules-dir", test_modules);
328         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
329                           Configuration::Error);
330     }
331 
332     SECTION("it fails when --modules-config-dir is not a directory") {
333         HW::SetFlag<std::string>("modules-dir", CONFIG);
334         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
335                           Configuration::Error);
336     }
337 
338     SECTION("it fails when --task-cache-dir is empty") {
339         HW::SetFlag<std::string>("task-cache-dir", "");
340         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
341                           Configuration::Error);
342     }
343 
344     SECTION("it fails when --task-cache-dir exists but is not a directory") {
345         HW::SetFlag<std::string>("task-cache-dir", CONFIG);
346         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
347                           Configuration::Error);
348     }
349 
350     SECTION("it creates --task-cache-dir when needed and with the right permissions") {
351         auto test_task_cache_dir = TASK_CACHE_DIR + "/testing_creation";
352         HW::SetFlag<std::string>("task-cache-dir", test_task_cache_dir);
353 
354         REQUIRE_FALSE(fs::exists(test_task_cache_dir));
355         REQUIRE_NOTHROW(Configuration::Instance().validate());
356         REQUIRE(fs::exists(test_task_cache_dir));
357 #ifndef _WIN32
358         REQUIRE(fs::status(test_task_cache_dir).permissions() == 0750);
359 #else
360         REQUIRE(fs::status(test_task_cache_dir).permissions() == 0666);
361 #endif
362 
363         fs::remove_all(test_task_cache_dir);
364     }
365 
366     SECTION("it fails when -task-cache-dir-purge-ttl as not a valid timestamp") {
367         HW::SetFlag<std::string>("task-cache-dir-purge-ttl", "1.0");
368         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
369                           Configuration::Error);
370     }
371 
372     SECTION("it fails when --spool-dir is empty") {
373         HW::SetFlag<std::string>("spool-dir", "");
374         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
375                           Configuration::Error);
376     }
377 
378     SECTION("it fails when --spool-dir exists but is not a directory") {
379         HW::SetFlag<std::string>("spool-dir", CONFIG);
380         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
381                           Configuration::Error);
382     }
383 
384     SECTION("it creates --spool-dir when needed") {
385         auto test_spool = SPOOL_DIR + "/testing_creation";
386         HW::SetFlag<std::string>("spool-dir", test_spool);
387 
388         REQUIRE_FALSE(fs::exists(test_spool));
389         REQUIRE_NOTHROW(Configuration::Instance().validate());
390         REQUIRE(fs::exists(test_spool));
391 
392         fs::remove_all(test_spool);
393     }
394 
395     SECTION("it fails when --spool-dir-purge-ttl as not a valid timestamp") {
396         HW::SetFlag<std::string>("spool-dir-purge-ttl", "1.0");
397         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
398                           Configuration::Error);
399     }
400 
401     SECTION("it fails when --task-download-connect-timeout is negative") {
402         HW::SetFlag<int>("task-download-connect-timeout", -1);
403         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
404                           Configuration::Error);
405     }
406 
407     SECTION("it fails when --task-download-timeout is negative") {
408         HW::SetFlag<int>("task-download-timeout", -1);
409         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
410                           Configuration::Error);
411     }
412 }
413 
414 TEST_CASE("Configuration::validate with unknown config options", "[configuration]") {
415     const char* altArgv[] = {
416     "test-command",
417     "--config-file", UNKNOWN_CONFIG.c_str(),
418     "--ssl-ca-cert", CA.c_str(),
419     "--ssl-cert", CERT.c_str(),
420     "--ssl-key", KEY.c_str(),
421     "--ssl-crl", CRL.c_str(),
422     "--modules-dir", MODULES_DIR.c_str(),
423     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
424     "--spool-dir", SPOOL_DIR.c_str(),
425     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
426     "--foreground=true",
427     nullptr };
428 
429     lth_util::scope_exit config_cleaner { resetTest };
430     configureTest();
431     Configuration::Instance().parseOptions(ARGUMENT_COUNT(altArgv), const_cast<char**>(altArgv));
432 
433     SECTION("it validates") {
434         REQUIRE_NOTHROW(Configuration::Instance().validate());
435     }
436 }
437 
438 TEST_CASE("Configuration::validate multiple brokers", "[configuration]") {
439     const char* altArgv[] = {
440     "test-command",
441     "--config-file", MULTI_BROKER_CONFIG.c_str(),
442     "--ssl-ca-cert", CA.c_str(),
443     "--ssl-cert", CERT.c_str(),
444     "--ssl-key", KEY.c_str(),
445     "--ssl-crl", CRL.c_str(),
446     "--modules-dir", MODULES_DIR.c_str(),
447     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
448     "--spool-dir", SPOOL_DIR.c_str(),
449     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
450     "--foreground=true",
451     nullptr };
452 
453     lth_util::scope_exit config_cleaner { resetTest };
454     configureTest();
455     Configuration::Instance().parseOptions(ARGUMENT_COUNT(altArgv), const_cast<char**>(altArgv));
456 
457     SECTION("it allows broker-ws-uris in place of broker-ws-uri") {
458         HW::SetFlag<std::string>("broker-ws-uri", TEST_BROKER_WS_URI);
459         REQUIRE_NOTHROW(Configuration::Instance().validate());
460         auto uris = Configuration::Instance().get_broker_ws_uris();
461         REQUIRE(uris == std::vector<std::string>({TEST_BROKER_WS_URI}));
462     }
463 
464     SECTION("it parses multiple arguments to broker-ws-uris") {
465         REQUIRE_NOTHROW(Configuration::Instance().validate());
466         auto uris = Configuration::Instance().get_broker_ws_uris();
467         REQUIRE(uris == std::vector<std::string>({"wss://test_pcp_broker", "wss://alt_pcp_broker"}));
468     }
469 
470     SECTION("it parses multiple arguments to primary-uris") {
471         REQUIRE_NOTHROW(Configuration::Instance().validate());
472         auto uris = Configuration::Instance().get_primary_uris();
473         REQUIRE(uris == std::vector<std::string>({"https://test_master:8140", "https://alt_master:8140"}));
474     }
475 }
476 
477 TEST_CASE("Configuration::validate primary-uris precedence", "[configuration]") {
478     const char* altArgv[] = {
479     "test-command",
480     "--config-file", MULTI_URI_CONFIG.c_str(),
481     "--ssl-ca-cert", CA.c_str(),
482     "--ssl-cert", CERT.c_str(),
483     "--ssl-key", KEY.c_str(),
484     "--modules-dir", MODULES_DIR.c_str(),
485     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
486     "--spool-dir", SPOOL_DIR.c_str(),
487     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
488     "--foreground=true",
489     nullptr };
490 
491     lth_util::scope_exit config_cleaner { resetTest };
492     configureTest();
493     Configuration::Instance().parseOptions(ARGUMENT_COUNT(altArgv), const_cast<char**>(altArgv));
494 
495     SECTION("primary-uris takes precedence over master-uris") {
496         REQUIRE_NOTHROW(Configuration::Instance().validate());
497         auto uris = Configuration::Instance().get_primary_uris();
498         REQUIRE(uris == std::vector<std::string>({"https://test_master:8140", "https://alt_master_one:8140", "https://alt_master_two:8140"}));
499     }
500 }
501 
502 TEST_CASE("Configuration::validate deprecated master-uris works when no primary-uris", "[configuration]") {
503     const char* altArgv[] = {
504     "test-command",
505     "--config-file", MASTER_URI_CONFIG.c_str(),
506     "--ssl-ca-cert", CA.c_str(),
507     "--ssl-cert", CERT.c_str(),
508     "--ssl-key", KEY.c_str(),
509     "--modules-dir", MODULES_DIR.c_str(),
510     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
511     "--spool-dir", SPOOL_DIR.c_str(),
512     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
513     "--foreground=true",
514     nullptr };
515 
516     lth_util::scope_exit config_cleaner { resetTest };
517     configureTest();
518     Configuration::Instance().parseOptions(ARGUMENT_COUNT(altArgv), const_cast<char**>(altArgv));
519 
520     SECTION("it parses multiple arguments to master-uris when primary-uris not present") {
521         REQUIRE_NOTHROW(Configuration::Instance().validate());
522         auto uris = Configuration::Instance().get_primary_uris();
523         REQUIRE(uris == std::vector<std::string>({"https://test_master:8140", "https://alt_master:8140"}));
524     }
525 }
526 
527 TEST_CASE("Configuration::parseOptions duplicate broker-ws-uris", "[configuration]") {
528     const char* altArgv[] = {
529     "test-command",
530     "--config-file", DUPLICATE_CONFIG.c_str(),
531     "--ssl-ca-cert", CA.c_str(),
532     "--ssl-cert", CERT.c_str(),
533     "--ssl-key", KEY.c_str(),
534     "--ssl-crl", CRL.c_str(),
535     "--modules-dir", MODULES_DIR.c_str(),
536     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
537     "--spool-dir", SPOOL_DIR.c_str(),
538     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
539     "--foreground=true",
540     nullptr };
541 
542     lth_util::scope_exit config_cleaner { resetTest };
543     configureTest();
544 
545     SECTION("it throws an Error when broker-ws-uri and broker-ws-uris are defined") {
546         REQUIRE_THROWS_AS(Configuration::Instance().parseOptions(ARGUMENT_COUNT(altArgv), const_cast<char**>(altArgv)),
547                           Configuration::Error);
548     }
549 }
550 
551 TEST_CASE("Configuration::parseOptions invalid config-file name", "[configuration]") {
552     const char* altArgv[] = {
553     "test-command",
554     "--config-file", INVALID_CONFIG_NAME.c_str(),
555     "--ssl-ca-cert", CA.c_str(),
556     "--ssl-cert", CERT.c_str(),
557     "--ssl-key", KEY.c_str(),
558     "--ssl-crl", CRL.c_str(),
559     "--modules-dir", MODULES_DIR.c_str(),
560     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
561     "--spool-dir", SPOOL_DIR.c_str(),
562     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
563     "--foreground=true",
564     nullptr };
565 
566     lth_util::scope_exit config_cleaner { resetTest };
567     configureTest();
568 
569     SECTION("it throws an Error when config-file is invalid") {
570         REQUIRE_THROWS_AS(Configuration::Instance().parseOptions(ARGUMENT_COUNT(altArgv), const_cast<char**>(altArgv)),
571                           Configuration::Error);
572     }
573 }
574 
575 TEST_CASE("Configuration::validate bad broker-ws-uris", "[configuration]") {
576     const char* altArgv[] = {
577     "test-command",
578     "--config-file", BAD_BROKER_CONFIG.c_str(),
579     "--ssl-ca-cert", CA.c_str(),
580     "--ssl-cert", CERT.c_str(),
581     "--ssl-key", KEY.c_str(),
582     "--ssl-crl", CRL.c_str(),
583     "--modules-dir", MODULES_DIR.c_str(),
584     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
585     "--spool-dir", SPOOL_DIR.c_str(),
586     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
587     "--foreground=true",
588     nullptr };
589 
590     lth_util::scope_exit config_cleaner { resetTest };
591     configureTest();
592     Configuration::Instance().parseOptions(ARGUMENT_COUNT(altArgv), const_cast<char**>(altArgv));
593 
594     SECTION("it throws an Error when broker-ws-uris is invalid") {
595         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
596                           Configuration::Error);
597     }
598 }
599 
600 TEST_CASE("Configuration::validate bad master-uris", "[configuration]") {
601     const char* altArgv[] = {
602     "test-command",
603     "--config-file", BAD_MASTER_CONFIG.c_str(),
604     "--ssl-ca-cert", CA.c_str(),
605     "--ssl-cert", CERT.c_str(),
606     "--ssl-key", KEY.c_str(),
607     "--ssl-crl", CRL.c_str(),
608     "--modules-dir", MODULES_DIR.c_str(),
609     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
610     "--spool-dir", SPOOL_DIR.c_str(),
611     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
612     "--foreground=true",
613     nullptr };
614 
615     lth_util::scope_exit config_cleaner { resetTest };
616     configureTest();
617     Configuration::Instance().parseOptions(ARGUMENT_COUNT(altArgv), const_cast<char**>(altArgv));
618 
619     SECTION("it throws an Error when master-uris is invalid") {
620         REQUIRE_THROWS_AS(Configuration::Instance().validate(),
621                           Configuration::Error);
622     }
623 }
624 
625 TEST_CASE("Configuration::setupLogging", "[configuration]") {
626     lth_util::scope_exit config_cleaner { resetTest };
627     configureTest();
628     Configuration::Instance().parseOptions(ARGUMENT_COUNT(ARGV), const_cast<char**>(ARGV));
629     Configuration::Instance().validate();
630 
631 #ifndef _WIN32
632     SECTION("it sets log level to info by default") {
633         // NOTE(ale): we cannot test this on Windows because it won't
634         // allow us to delete the SPOOL_DIR while test.log is open
635         Configuration::Instance().set<std::string>("logfile", SPOOL_DIR + "/test.log");
636         Configuration::Instance().setupLogging();
637         REQUIRE(lth_log::get_level() == lth_log::log_level::info);
638     }
639 
640     SECTION("it sets log level to none if foreground is unflagged and logfile "
641             "is set to stdout") {
642         Configuration::Instance().set<bool>("foreground", false);
643         Configuration::Instance().set<std::string>("logfile", "-");
644         Configuration::Instance().setupLogging();
645 
646         REQUIRE(lth_log::get_level() == lth_log::log_level::none);
647     }
648 #endif
649 }
650 
651 TEST_CASE("Configuration::validate without ssl-crl (is optional)", "[configuration]") {
652     const char* altArgv[] = {
653     "test-command",
654     "--config-file", UNKNOWN_CONFIG.c_str(),
655     "--ssl-ca-cert", CA.c_str(),
656     "--ssl-cert", CERT.c_str(),
657     "--ssl-key", KEY.c_str(),
658     "--modules-dir", MODULES_DIR.c_str(),
659     "--modules-config-dir", MODULES_CONFIG_DIR.c_str(),
660     "--spool-dir", SPOOL_DIR.c_str(),
661     "--task-cache-dir", TASK_CACHE_DIR.c_str(),
662     "--foreground=true",
663     nullptr };
664 
665     lth_util::scope_exit config_cleaner { resetTest };
666     configureTest();
667     Configuration::Instance().parseOptions(ARGUMENT_COUNT(altArgv), const_cast<char**>(altArgv));
668 
669     SECTION("it validates") {
670         REQUIRE_NOTHROW(Configuration::Instance().validate());
671     }
672 }
673