Multiple DBAAS Redis Sentinel groups
[ric-plt/sdl.git] / tst / configurationreader_test.cpp
1 /*
2    Copyright (c) 2018-2019 Nokia.
3
4    Licensed under the Apache License, Version 2.0 (the "License");
5    you may not use this file except in compliance with the License.
6    You may obtain a copy of the License at
7
8        http://www.apache.org/licenses/LICENSE-2.0
9
10    Unless required by applicable law or agreed to in writing, software
11    distributed under the License is distributed on an "AS IS" BASIS,
12    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    See the License for the specific language governing permissions and
14    limitations under the License.
15 */
16
17 /*
18  * This source code is part of the near-RT RIC (RAN Intelligent Controller)
19  * platform project (RICP).
20 */
21
22 #include <gtest/gtest.h>
23 #include <gmock/gmock.h>
24 #include <memory>
25 #include <boost/property_tree/ptree.hpp>
26 #include <boost/property_tree/json_parser.hpp>
27 #include "private/configurationreader.hpp"
28 #include "private/createlogger.hpp"
29 #include "private/logger.hpp"
30 #include "private/tst/databaseconfigurationmock.hpp"
31 #include "private/tst/gettopsrcdir.hpp"
32 #include "private/tst/namespaceconfigurationsmock.hpp"
33 #include "private/tst/systemmock.hpp"
34
35 using namespace shareddatalayer;
36 using namespace shareddatalayer::tst;
37 using namespace testing;
38
39 namespace
40 {
41     class ConfigurationReaderBaseTest: public testing::Test
42     {
43     public:
44         const std::string someKnownInputSource;
45         const Directories noDirectories;
46         DatabaseConfigurationMock databaseConfigurationMock;
47         NamespaceConfigurationsMock namespaceConfigurationsMock;
48         std::unique_ptr<ConfigurationReader> configurationReader;
49         SystemMock systemMock;
50         std::shared_ptr<Logger> logger;
51
52         ConfigurationReaderBaseTest(std::string inputSourceName):
53             someKnownInputSource(inputSourceName),
54             logger(createLogger(SDL_LOG_PREFIX))
55         {
56             EXPECT_CALL(databaseConfigurationMock, isEmpty()).WillRepeatedly(Return(true));
57             EXPECT_CALL(namespaceConfigurationsMock, isEmpty()).WillRepeatedly(Return(true));
58         }
59
60         void expectDbTypeConfigurationCheckAndApply(const std::string& type)
61         {
62             EXPECT_CALL(databaseConfigurationMock, checkAndApplyDbType(type));
63         }
64
65         void expectDBServerAddressConfigurationCheckAndApply(const std::string& address)
66         {
67             EXPECT_CALL(databaseConfigurationMock, checkAndApplyServerAddress(address));
68         }
69
70         void expectSentinelAddressConfigurationCheckAndApply(const std::string& address)
71         {
72             EXPECT_CALL(databaseConfigurationMock, checkAndApplySentinelAddress(address));
73         }
74
75         void expectSentinelMasterNameConfigurationCheckAndApply(const std::string& address)
76         {
77             EXPECT_CALL(databaseConfigurationMock, checkAndApplySentinelMasterName(address));
78         }
79
80         void expectDatabaseConfigurationIsEmpty_returnFalse()
81         {
82             EXPECT_CALL(databaseConfigurationMock, isEmpty()).
83                 WillOnce(Return(false));
84         }
85
86         void tryToReadDatabaseConfigurationToNonEmptyContainer()
87         {
88             expectDatabaseConfigurationIsEmpty_returnFalse();
89             configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
90         }
91
92         void expectNamespaceConfigurationIsEmpty_returnFalse()
93         {
94             EXPECT_CALL(namespaceConfigurationsMock, isEmpty()).
95                 WillOnce(Return(false));
96         }
97
98         void tryToReadNamespaceConfigurationToNonEmptyContainer()
99         {
100             expectNamespaceConfigurationIsEmpty_returnFalse();
101             configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
102         }
103
104         void expectAddNamespaceConfiguration(const std::string&  namespacePrefix, bool useDbBackend,
105                                              bool enableNotifications)
106         {
107             NamespaceConfiguration expectedNamespaceConfiguration{namespacePrefix, useDbBackend,
108                                                                   enableNotifications,
109                                                                   someKnownInputSource};
110             EXPECT_CALL(namespaceConfigurationsMock, addNamespaceConfiguration(expectedNamespaceConfiguration));
111         }
112
113         void expectGetEnvironmentString(const char* returnValue)
114         {
115             EXPECT_CALL(systemMock, getenv(_))
116                 .WillOnce(Return(returnValue));
117         }
118
119         void initializeReaderWithoutDirectories()
120         {
121             configurationReader.reset(new ConfigurationReader(noDirectories, systemMock, logger));
122         }
123
124         void initializeReaderWithSDLconfigFileDirectory()
125         {
126             configurationReader.reset(new ConfigurationReader({getTopSrcDir() + "/conf"}, systemMock, logger));
127         }
128     };
129
130     class ConfigurationReaderSDLConfigFileTest: public ConfigurationReaderBaseTest
131     {
132     public:
133
134         ConfigurationReaderSDLConfigFileTest():
135             ConfigurationReaderBaseTest("ConfFileFromSDLrepo")
136         {
137             expectGetEnvironmentString(nullptr);
138             initializeReaderWithSDLconfigFileDirectory();
139         }
140     };
141
142     class ConfigurationReaderInputStreamTest: public ConfigurationReaderBaseTest
143     {
144     public:
145
146         ConfigurationReaderInputStreamTest():
147             ConfigurationReaderBaseTest("<istream>")
148         {
149             expectGetEnvironmentString(nullptr);
150             initializeReaderWithoutDirectories();
151         }
152
153         void readConfigurationAndExpectJsonParserException(const std::istringstream& is)
154         {
155             const std::string expectedError("error in SDL configuration <istream> at line 7: expected ':'");
156
157             EXPECT_THROW( {
158                 try
159                 {
160                     configurationReader->readConfigurationFromInputStream(is);
161                     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
162                     configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
163                 }
164                 catch (const std::exception& e)
165                 {
166                     EXPECT_EQ(expectedError, e.what());
167                     throw;
168                 }
169             }, Exception);
170         }
171
172         void readConfigurationAndExpectBadValueException(const std::istringstream& is,
173                                                          const std::string& param)
174         {
175             std::ostringstream os;
176             os << "Configuration error in " << someKnownInputSource << ": "
177                << "invalid \"" << param << "\": \"bad-value\"";
178
179             EXPECT_THROW( {
180                 try
181                 {
182                     configurationReader->readConfigurationFromInputStream(is);
183                     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
184                     configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
185                 }
186                 catch (const std::exception& e)
187                 {
188                     EXPECT_EQ(os.str(), e.what());
189                     throw;
190                 }
191             }, Exception);
192         }
193
194         void readConfigurationAndExpectDbTypeException(const std::istringstream& is)
195         {
196             std::ostringstream os;
197             os << "Configuration error in " << someKnownInputSource << ": some error";
198
199             EXPECT_CALL(databaseConfigurationMock, checkAndApplyDbType(_))
200                 .WillOnce(Throw(Exception("some error")));
201
202             EXPECT_THROW( {
203                 try
204                 {
205                     configurationReader->readConfigurationFromInputStream(is);
206                     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
207                 }
208                 catch (const std::exception& e)
209                 {
210                     EXPECT_EQ(os.str(), e.what());
211                     throw;
212                 }
213             }, Exception);
214         }
215
216         void readConfigurationAndExpectAddressException(const std::istringstream& is,
217                                                         const std::string& addressValue)
218         {
219             std::ostringstream os;
220             os << "Configuration error in " << someKnownInputSource << ": "
221                << "invalid \"address\": \"" << addressValue << "\" some error";
222
223             EXPECT_CALL(databaseConfigurationMock, checkAndApplyServerAddress(_))
224                 .WillOnce(Throw(Exception("some error")));
225
226             EXPECT_THROW( {
227                 try
228                 {
229                     configurationReader->readConfigurationFromInputStream(is);
230                     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
231                 }
232                 catch (const std::exception& e)
233                 {
234                     EXPECT_EQ(os.str(), e.what());
235                     throw;
236                 }
237             }, Exception);
238         }
239
240         void readConfigurationAndExpectMissingParameterException(const std::istringstream& is, const std::string& param)
241         {
242             std::ostringstream os;
243             os << "Configuration error in " << someKnownInputSource << ": "
244                << "missing \"" << param << "\"";
245
246             EXPECT_THROW( {
247                 try
248                 {
249                     configurationReader->readConfigurationFromInputStream(is);
250                     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
251                     configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
252                 }
253                 catch (const std::exception& e)
254                 {
255                     EXPECT_EQ(os.str(), e.what());
256                     throw;
257                 }
258             }, Exception);
259         }
260
261         void readConfigurationAndExpectNamespacePrefixValidationException(const std::istringstream& is,
262                                                                           const std::string& namespacePrefix)
263         {
264             std::ostringstream os;
265             os << "Configuration error in " << someKnownInputSource << ": "
266                << "\"namespacePrefix\": \"" << namespacePrefix << "\""
267                << " contains some of these disallowed characters: ,{}";
268
269             EXPECT_THROW( {
270                 try
271                 {
272                     configurationReader->readConfigurationFromInputStream(is);
273                     configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
274                 }
275                 catch (const std::exception& e)
276                 {
277                     EXPECT_EQ(os.str(), e.what() );
278                     throw;
279                 }
280             }, Exception);
281         }
282
283         void readConfigurationAndExpectEnableNotificationsValidationException(const std::istringstream& is)
284         {
285             std::ostringstream os;
286             os << "Configuration error in " << someKnownInputSource << ": "
287                << "\"enableNotifications\" cannot be true, when \"useDbBackend\" is false";
288
289             EXPECT_THROW( {
290                 try
291                 {
292                     configurationReader->readConfigurationFromInputStream(is);
293                     configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
294                 }
295                 catch (const std::exception& e)
296                 {
297                     EXPECT_EQ(os.str(), e.what() );
298                     throw;
299                 }
300             }, Exception);
301         }
302
303     };
304 }
305
306 TEST_F(ConfigurationReaderInputStreamTest, CanReadJSONDatabaseConfiguration)
307 {
308     InSequence dummy;
309     std::istringstream is(R"JSON(
310         {
311             "database":
312             {
313                 "type": "redis-standalone",
314                 "servers":
315                 [
316                     {
317                         "address": "someKnownDbAddress:65535"
318                     }
319                 ]
320             }
321         })JSON");
322     expectDbTypeConfigurationCheckAndApply("redis-standalone");
323     expectDBServerAddressConfigurationCheckAndApply("someKnownDbAddress:65535");
324     configurationReader->readConfigurationFromInputStream(is);
325     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
326 }
327
328 TEST_F(ConfigurationReaderInputStreamTest, CanReadJSONDatabaseConfigurationWithMultipleServerAddresses)
329 {
330     InSequence dummy;
331     std::istringstream is(R"JSON(
332         {
333             "database":
334             {
335                 "type": "redis-cluster",
336                 "servers":
337                 [
338                     {
339                         "address": "10.20.30.40:50000"
340                     },
341                     {
342                         "address": "10.20.30.50:50001"
343                     }
344                 ]
345             }
346         })JSON");
347
348     expectDbTypeConfigurationCheckAndApply("redis-cluster");
349     expectDBServerAddressConfigurationCheckAndApply("10.20.30.40:50000");
350     expectDBServerAddressConfigurationCheckAndApply("10.20.30.50:50001");
351     configurationReader->readConfigurationFromInputStream(is);
352     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
353 }
354
355 TEST_F(ConfigurationReaderInputStreamTest, CanReadJSONDatabaseConfigurationWithMultipleReadOperations)
356 {
357     InSequence dummy;
358     std::istringstream isOne(R"JSON(
359         {
360             "database":
361             {
362                 "type": "redis-cluster",
363                 "servers":
364                 [
365                     {
366                         "address": "10.20.30.40:50000"
367                     }
368                 ]
369             }
370         })JSON");
371     std::istringstream isTwo(R"JSON(
372         {
373             "database":
374             {
375                 "type": "redis-cluster",
376                 "servers":
377                 [
378                     {
379                         "address": "10.20.30.50:50001"
380                     }
381                 ]
382             }
383         })JSON");
384
385     expectDbTypeConfigurationCheckAndApply("redis-cluster");
386     expectDBServerAddressConfigurationCheckAndApply("10.20.30.40:50000");
387     expectDbTypeConfigurationCheckAndApply("redis-cluster");
388     expectDBServerAddressConfigurationCheckAndApply("10.20.30.50:50001");
389     configurationReader->readConfigurationFromInputStream(isOne);
390     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
391     configurationReader->readConfigurationFromInputStream(isTwo);
392     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
393 }
394
395 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowMisingMandatoryDatabaseTypeParameter)
396 {
397     InSequence dummy;
398     std::istringstream is(R"JSON(
399         {
400             "database":
401             {
402                 "servers":
403                 [
404                     {
405                         "address": "10.20.30.50:50001"
406                     }
407                 ]
408             }
409         })JSON");
410
411     readConfigurationAndExpectMissingParameterException(is, "type");
412 }
413
414 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowMisingMandatoryDatabaseServersArray)
415 {
416     InSequence dummy;
417     std::istringstream is(R"JSON(
418         {
419             "database":
420             {
421                 "type": "redis-standalone"
422             }
423         })JSON");
424
425     expectDbTypeConfigurationCheckAndApply("redis-standalone");
426     readConfigurationAndExpectMissingParameterException(is, "servers");
427 }
428
429 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowMisingMandatoryDatabaseServerAddressParameter)
430 {
431     InSequence dummy;
432     std::istringstream is(R"JSON(
433         {
434             "database":
435             {
436                 "type": "redis-standalone",
437                 "servers":
438                 [
439                     {
440                     }
441                 ]
442             }
443         })JSON");
444
445     expectDbTypeConfigurationCheckAndApply("redis-standalone");
446     readConfigurationAndExpectMissingParameterException(is, "address");
447 }
448
449 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowDatabaseConfigurationDbTypeError)
450 {
451     InSequence dummy;
452     std::istringstream is(R"JSON(
453         {
454             "database":
455             {
456                 "type": "someBadType",
457                 "servers":
458                 [
459                     {
460                         "address": "10.20.30.50:50001"
461                     }
462                 ]
463             }
464         })JSON");
465
466     readConfigurationAndExpectDbTypeException(is);
467 }
468
469 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowDatabaseConfigurationAddressError)
470 {
471     InSequence dummy;
472     std::istringstream is(R"JSON(
473         {
474             "database":
475             {
476                 "type": "redis-standalone",
477                 "servers":
478                 [
479                     {
480                         "address": "someBadAddress"
481                     }
482                 ]
483             }
484         })JSON");
485
486     expectDbTypeConfigurationCheckAndApply("redis-standalone");
487     readConfigurationAndExpectAddressException(is, "someBadAddress");
488 }
489
490 TEST_F(ConfigurationReaderInputStreamTest, CanHandleJSONWithoutAnyConfiguration)
491 {
492     InSequence dummy;
493     std::istringstream is(R"JSON(
494         {
495         })JSON");
496
497     EXPECT_CALL(databaseConfigurationMock, checkAndApplyServerAddress(_))
498         .Times(0);
499     EXPECT_CALL(namespaceConfigurationsMock, addNamespaceConfiguration(_))
500         .Times(0);
501     configurationReader->readConfigurationFromInputStream(is);
502 }
503
504 TEST_F(ConfigurationReaderInputStreamTest, CanReadJSONSharedDataLayerConfiguration)
505 {
506     InSequence dummy;
507     std::istringstream is(R"JSON(
508         {
509             "sharedDataLayer":
510             [
511                 {
512                     "namespacePrefix": "someKnownNamespacePrefix",
513                     "useDbBackend": true,
514                     "enableNotifications": true
515                 },
516                 {
517                     "namespacePrefix": "anotherKnownNamespace",
518                     "useDbBackend": false,
519                     "enableNotifications": false
520                 }
521             ]
522         })JSON");
523
524     expectAddNamespaceConfiguration("anotherKnownNamespace", false, false);
525     expectAddNamespaceConfiguration("someKnownNamespacePrefix", true, true);
526     configurationReader->readConfigurationFromInputStream(is);
527     configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
528 }
529
530 TEST_F(ConfigurationReaderInputStreamTest, CanReadJSONSharedDataLayerConfigurationWithMultipleReadOperations)
531 {
532     InSequence dummy;
533     std::istringstream isOne(R"JSON(
534         {
535             "sharedDataLayer":
536             [
537                 {
538                     "namespacePrefix": "someKnownNamespacePrefix",
539                     "useDbBackend": true,
540                     "enableNotifications": true
541                 }
542             ]
543         })JSON");
544
545     std::istringstream isTwo(R"JSON(
546         {
547             "sharedDataLayer":
548             [
549                 {
550                     "namespacePrefix": "anotherKnownNamespace",
551                     "useDbBackend": false,
552                     "enableNotifications": false
553                 }
554             ]
555         })JSON");
556
557     expectAddNamespaceConfiguration("someKnownNamespacePrefix", true, true);
558     expectAddNamespaceConfiguration("anotherKnownNamespace", false, false);
559     configurationReader->readConfigurationFromInputStream(isOne);
560     configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
561     configurationReader->readConfigurationFromInputStream(isTwo);
562     configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
563 }
564
565 TEST_F(ConfigurationReaderInputStreamTest, CanReadJSONSharedDataLayerConfigurationWithEmptyNamespacePrefixValue)
566 {
567     InSequence dummy;
568     std::istringstream is(R"JSON(
569         {
570             "sharedDataLayer":
571             [
572                 {
573                     "namespacePrefix": "",
574                     "useDbBackend": false,
575                     "enableNotifications": false
576                 }
577             ]
578         })JSON");
579
580     expectAddNamespaceConfiguration("", false, false);
581     configurationReader->readConfigurationFromInputStream(is);
582     configurationReader->readNamespaceConfigurations(namespaceConfigurationsMock);
583 }
584
585 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowJSONSyntaxError)
586 {
587     InSequence dummy;
588     std::istringstream is(R"JSON(
589         {
590             "sharedDataLayer":
591             [
592                 {
593                     "abc"
594                 }
595             ]
596         })JSON");
597
598     readConfigurationAndExpectJsonParserException(is);
599 }
600
601 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowParameterUseDbBackendBadValue)
602 {
603     InSequence dummy;
604     std::istringstream is(R"JSON(
605         {
606             "sharedDataLayer":
607             [
608                 {
609                     "namespacePrefix": "someKnownNamespacePrefix",
610                     "useDbBackend": "bad-value",
611                     "enableNotifications": false
612                 }
613             ]
614         })JSON");
615
616     readConfigurationAndExpectBadValueException(is, "useDbBackend");
617 }
618
619 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowParameterEnableNotificationsBadValue)
620 {
621     InSequence dummy;
622     std::istringstream is(R"JSON(
623         {
624             "sharedDataLayer":
625             [
626                 {
627                     "namespacePrefix": "someKnownNamespacePrefix",
628                     "useDbBackend": true,
629                     "enableNotifications": "bad-value"
630                 }
631             ]
632         })JSON");
633
634     readConfigurationAndExpectBadValueException(is, "enableNotifications");
635 }
636
637 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowMisingMandatoryNamespacePrefixParameter)
638 {
639     InSequence dummy;
640     std::istringstream is(R"JSON(
641         {
642             "sharedDataLayer":
643             [
644                 {
645                     "useDbBackend": true,
646                     "enableNotifications": true
647                 }
648             ]
649         })JSON");
650
651     readConfigurationAndExpectMissingParameterException(is, "namespacePrefix");
652 }
653
654 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowMisingMandatoryUseDbBackendParameter)
655 {
656     InSequence dummy;
657     std::istringstream is(R"JSON(
658         {
659             "sharedDataLayer":
660             [
661                 {
662                     "namespacePrefix": "someKnownNamespacePrefix",
663                     "enableNotifications": true
664                 }
665             ]
666         })JSON");
667
668     readConfigurationAndExpectMissingParameterException(is, "useDbBackend");
669 }
670
671 TEST_F(ConfigurationReaderInputStreamTest, CanCatchAndThrowMisingMandatoryEnableNotificationsParameter)
672 {
673     InSequence dummy;
674     std::istringstream is(R"JSON(
675         {
676             "sharedDataLayer":
677             [
678                 {
679                     "namespacePrefix": "someKnownNamespacePrefix",
680                     "useDbBackend": true
681                 }
682             ]
683         })JSON");
684
685     readConfigurationAndExpectMissingParameterException(is, "enableNotifications");
686 }
687
688 TEST_F(ConfigurationReaderInputStreamTest, CanThrowValidationErrorForNamespacePrefixWithDisallowedCharacters)
689 {
690     InSequence dummy;
691     std::istringstream is(R"JSON(
692         {
693             "sharedDataLayer":
694             [
695                 {
696                     "namespacePrefix": "a,b{c}",
697                     "useDbBackend": true,
698                     "enableNotifications": true
699                 }
700             ]
701         })JSON");
702
703     readConfigurationAndExpectNamespacePrefixValidationException(is, "a,b{c}");
704 }
705
706 TEST_F(ConfigurationReaderInputStreamTest, CanThrowValidationErrorForEnableNotificationsWithNoDbBackend)
707 {
708     InSequence dummy;
709     std::istringstream is(R"JSON(
710         {
711             "sharedDataLayer":
712             [
713                 {
714                     "namespacePrefix": "someKnownNamespacePrefix",
715                     "useDbBackend": false,
716                     "enableNotifications": true
717                 }
718             ]
719         })JSON");
720
721     readConfigurationAndExpectEnableNotificationsValidationException(is);
722 }
723
724 TEST_F(ConfigurationReaderInputStreamTest, WillNotReadDatabaseConfigurationToNonEmptyContainer)
725 {
726     EXPECT_EXIT(tryToReadDatabaseConfigurationToNonEmptyContainer(),
727         KilledBySignal(SIGABRT), "ABORT.*configurationreader\\.cpp");
728 }
729
730 TEST_F(ConfigurationReaderInputStreamTest, WillNotReadNamespaceConfigurationToNonEmptyContainer)
731 {
732     EXPECT_EXIT(tryToReadNamespaceConfigurationToNonEmptyContainer(),
733         KilledBySignal(SIGABRT), "ABORT.*configurationreader\\.cpp");
734 }
735
736 class ConfigurationReaderEnvironmentVariableTest: public ConfigurationReaderBaseTest
737 {
738 public:
739     std::string dbHostEnvVariableValue;
740     std::string dbPortEnvVariableValue;
741     std::string sentinelPortEnvVariableValue;
742     std::string sentinelMasterNameEnvVariableValue;
743     std::string dbClusterAddrListEnvVariableValue;
744     std::istringstream is{R"JSON(
745         {
746             "database":
747             {
748                 "type": "redis-cluster",
749                 "servers":
750                 [
751                     {
752                         "address": "10.20.30.40:50000"
753                     },
754                     {
755                         "address": "10.20.30.50:50001"
756                     }
757                 ]
758             }
759         })JSON"};
760
761     ConfigurationReaderEnvironmentVariableTest():
762         ConfigurationReaderBaseTest(DB_HOST_ENV_VAR_NAME)
763     {
764     }
765
766     void readEnvironmentConfigurationAndExpectConfigurationErrorException()
767     {
768         std::ostringstream os;
769         os << "Configuration error in " << someKnownInputSource << ": some error";
770
771         EXPECT_CALL(databaseConfigurationMock, checkAndApplyDbType(_))
772             .WillOnce(Throw(Exception("some error")));
773
774         EXPECT_THROW( {
775             try
776             {
777                 EXPECT_CALL(systemMock, getenv(_))
778                     .Times(5)
779                     .WillOnce(Return(dbHostEnvVariableValue.c_str()))
780                     .WillOnce(Return(nullptr))
781                     .WillOnce(Return(nullptr))
782                     .WillOnce(Return(nullptr))
783                     .WillOnce(Return(nullptr));
784                 initializeReaderWithoutDirectories();
785                 configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
786             }
787             catch (const std::exception& e)
788             {
789                 EXPECT_EQ(os.str(), e.what());
790                 throw;
791             }
792         }, Exception);
793     }
794 };
795
796 TEST_F(ConfigurationReaderEnvironmentVariableTest, EnvironmentConfigurationCanOverrideJSONDatabaseConfiguration)
797 {
798     InSequence dummy;
799     dbHostEnvVariableValue = "unknownAddress.local";
800     expectGetEnvironmentString(dbHostEnvVariableValue.c_str());
801     dbPortEnvVariableValue = "12345";
802     expectGetEnvironmentString(dbPortEnvVariableValue.c_str());
803     expectGetEnvironmentString(nullptr); //SENTINEL_PORT_ENV_VAR_NAME
804     expectGetEnvironmentString(nullptr); //SENTINEL_MASTER_NAME_ENV_VAR_NAME
805     expectGetEnvironmentString(nullptr); //DB_CLUSTER_ENV_VAR_NAME
806
807     expectDbTypeConfigurationCheckAndApply("redis-standalone");
808     expectDBServerAddressConfigurationCheckAndApply("unknownAddress.local:12345");
809     initializeReaderWithSDLconfigFileDirectory();
810     configurationReader->readConfigurationFromInputStream(is);
811     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
812 }
813
814 TEST_F(ConfigurationReaderEnvironmentVariableTest, EnvironmentConfigurationWithoutPortIsAccepted)
815 {
816     InSequence dummy;
817     dbHostEnvVariableValue = "server.local";
818     expectGetEnvironmentString(dbHostEnvVariableValue.c_str());
819     expectGetEnvironmentString(nullptr); //DB_PORT_ENV_VAR_NAME
820     expectGetEnvironmentString(nullptr); //SENTINEL_PORT_ENV_VAR_NAME
821     expectGetEnvironmentString(nullptr); //SENTINEL_MASTER_NAME_ENV_VAR_NAME
822     expectGetEnvironmentString(nullptr); //DB_CLUSTER_ENV_VAR_NAME
823
824     expectDbTypeConfigurationCheckAndApply("redis-standalone");
825     expectDBServerAddressConfigurationCheckAndApply("server.local");
826     initializeReaderWithoutDirectories();
827     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
828 }
829
830 TEST_F(ConfigurationReaderEnvironmentVariableTest, EmptyEnvironmentVariableThrows)
831 {
832     dbHostEnvVariableValue = "";
833     readEnvironmentConfigurationAndExpectConfigurationErrorException();
834 }
835
836 TEST_F(ConfigurationReaderEnvironmentVariableTest, IllegalCharacterInEnvironmentVariableThrows)
837 {
838     dbHostEnvVariableValue = "@";
839     readEnvironmentConfigurationAndExpectConfigurationErrorException();
840 }
841
842 TEST_F(ConfigurationReaderEnvironmentVariableTest, EnvironmentConfigurationAcceptIPv6Address)
843 {
844     InSequence dummy;
845     dbHostEnvVariableValue = "[2001::123]:12345";
846     expectGetEnvironmentString(dbHostEnvVariableValue.c_str());
847     expectGetEnvironmentString(nullptr); //DB_PORT_ENV_VAR_NAME
848     expectGetEnvironmentString(nullptr); //SENTINEL_PORT_ENV_VAR_NAME
849     expectGetEnvironmentString(nullptr); //SENTINEL_MASTER_NAME_ENV_VAR_NAME
850     expectGetEnvironmentString(nullptr); //DB_CLUSTER_ENV_VAR_NAME
851
852     expectDbTypeConfigurationCheckAndApply("redis-standalone");
853     expectDBServerAddressConfigurationCheckAndApply("[2001::123]:12345");
854     initializeReaderWithoutDirectories();
855     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
856 }
857
858 TEST_F(ConfigurationReaderEnvironmentVariableTest, EnvironmentConfigurationWithSentinel)
859 {
860     InSequence dummy;
861     dbHostEnvVariableValue = "sentinelAddress.local";
862     expectGetEnvironmentString(dbHostEnvVariableValue.c_str());
863     dbPortEnvVariableValue = "1111";
864     expectGetEnvironmentString(dbPortEnvVariableValue.c_str());
865     sentinelPortEnvVariableValue = "2222";
866     expectGetEnvironmentString(sentinelPortEnvVariableValue.c_str());
867     sentinelMasterNameEnvVariableValue = "mymaster";
868     expectGetEnvironmentString(sentinelMasterNameEnvVariableValue.c_str());
869     expectGetEnvironmentString(nullptr); //DB_CLUSTER_ENV_VAR_NAME
870
871     expectDbTypeConfigurationCheckAndApply("redis-sentinel");
872     expectSentinelAddressConfigurationCheckAndApply("sentinelAddress.local:2222");
873     expectSentinelMasterNameConfigurationCheckAndApply(sentinelMasterNameEnvVariableValue);
874     initializeReaderWithoutDirectories();
875     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
876 }
877
878 TEST_F(ConfigurationReaderEnvironmentVariableTest, EnvironmentConfigurationWithSentinelAndClusterConfiguration)
879 {
880     InSequence dummy;
881     dbHostEnvVariableValue = "address-0.local";
882     expectGetEnvironmentString(dbHostEnvVariableValue.c_str());
883     dbPortEnvVariableValue = "1111";
884     expectGetEnvironmentString(dbPortEnvVariableValue.c_str());
885     sentinelPortEnvVariableValue = "2222";
886     expectGetEnvironmentString(sentinelPortEnvVariableValue.c_str());
887     sentinelMasterNameEnvVariableValue = "mymaster";
888     expectGetEnvironmentString(sentinelMasterNameEnvVariableValue.c_str());
889     dbClusterAddrListEnvVariableValue = "address-0.local,address-1.local,address-2.local";
890     expectGetEnvironmentString(dbClusterAddrListEnvVariableValue.c_str());
891
892     expectDbTypeConfigurationCheckAndApply("sdl-cluster");
893     expectDBServerAddressConfigurationCheckAndApply("address-0.local");
894     expectDBServerAddressConfigurationCheckAndApply("address-1.local");
895     expectDBServerAddressConfigurationCheckAndApply("address-2.local");
896     expectSentinelAddressConfigurationCheckAndApply("address-0.local:2222");
897     expectSentinelMasterNameConfigurationCheckAndApply(sentinelMasterNameEnvVariableValue);
898     initializeReaderWithoutDirectories();
899     configurationReader->readDatabaseConfiguration(databaseConfigurationMock);
900 }