1 #include "dbus_environment.hpp"
2 #include "helpers.hpp"
3 #include "mocks/json_storage_mock.hpp"
4 #include "mocks/trigger_factory_mock.hpp"
5 #include "mocks/trigger_mock.hpp"
6 #include "params/trigger_params.hpp"
7 #include "trigger.hpp"
8 #include "trigger_manager.hpp"
9 #include "utils/conversion_trigger.hpp"
10 #include "utils/transform.hpp"
11 
12 using namespace testing;
13 
14 class TestTriggerManager : public Test
15 {
16   public:
17     std::pair<boost::system::error_code, std::string>
18         addTrigger(const TriggerParams& params)
19     {
20         const auto sensorInfos =
21             utils::fromLabeledSensorsInfo(params.sensors());
22 
23         std::promise<std::pair<boost::system::error_code, std::string>>
24             addTriggerPromise;
25         DbusEnvironment::getBus()->async_method_call(
26             [&addTriggerPromise](boost::system::error_code ec,
27                                  const std::string& path) {
28                 addTriggerPromise.set_value({ec, path});
29             },
30             DbusEnvironment::serviceName(), TriggerManager::triggerManagerPath,
31             TriggerManager::triggerManagerIfaceName, "AddTrigger", params.id(),
32             params.name(), params.triggerActions(), sensorInfos,
33             params.reportNames(),
34             std::visit(utils::FromLabeledThresholdParamConversion(),
35                        params.thresholdParams()));
36         return DbusEnvironment::waitForFuture(addTriggerPromise.get_future());
37     }
38 
39     std::unique_ptr<TriggerManager> makeTriggerManager()
40     {
41         return std::make_unique<TriggerManager>(
42             std::move(triggerFactoryMockPtr), std::move(storageMockPtr),
43             DbusEnvironment::getObjServer());
44     }
45 
46     void SetUp() override
47     {
48         sut = makeTriggerManager();
49     }
50 
51     std::unique_ptr<StorageMock> storageMockPtr =
52         std::make_unique<NiceMock<StorageMock>>();
53     StorageMock& storageMock = *storageMockPtr;
54     std::unique_ptr<TriggerFactoryMock> triggerFactoryMockPtr =
55         std::make_unique<NiceMock<TriggerFactoryMock>>();
56     TriggerFactoryMock& triggerFactoryMock = *triggerFactoryMockPtr;
57     std::unique_ptr<TriggerMock> triggerMockPtr =
58         std::make_unique<NiceMock<TriggerMock>>(TriggerParams().id());
59     TriggerMock& triggerMock = *triggerMockPtr;
60     std::unique_ptr<TriggerManager> sut;
61     MockFunction<void(std::string)> checkPoint;
62 };
63 
64 TEST_F(TestTriggerManager, addTrigger)
65 {
66     triggerFactoryMock.expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
67         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
68 
69     auto [ec, path] = addTrigger(TriggerParams());
70     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
71     EXPECT_THAT(path, Eq(triggerMock.getPath()));
72 }
73 
74 TEST_F(TestTriggerManager, addTriggerWithDiscreteThresholds)
75 {
76     TriggerParams triggerParamsDiscrete;
77     auto thresholds = std::vector<discrete::LabeledThresholdParam>{
78         {"discrete_threshold1", discrete::Severity::ok, 10, "11.0"},
79         {"discrete_threshold2", discrete::Severity::warning, 10, "12.0"},
80         {"discrete_threshold3", discrete::Severity::critical, 10, "13.0"}};
81 
82     triggerParamsDiscrete.thresholdParams(thresholds);
83 
84     auto [ec, path] = addTrigger(triggerParamsDiscrete);
85     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
86     EXPECT_THAT(path, Eq(triggerMock.getPath()));
87 }
88 
89 TEST_F(TestTriggerManager, addDiscreteTriggerWithoutThresholds)
90 {
91     TriggerParams triggerParamsDiscrete;
92     auto thresholds = std::vector<discrete::LabeledThresholdParam>();
93 
94     triggerParamsDiscrete.thresholdParams(thresholds);
95 
96     auto [ec, path] = addTrigger(triggerParamsDiscrete);
97     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
98     EXPECT_THAT(path, Eq(triggerMock.getPath()));
99 }
100 
101 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerTwice)
102 {
103     triggerFactoryMock.expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
104         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
105 
106     addTrigger(TriggerParams());
107 
108     auto [ec, path] = addTrigger(TriggerParams());
109     EXPECT_THAT(ec.value(), Eq(boost::system::errc::file_exists));
110     EXPECT_THAT(path, Eq(std::string()));
111 }
112 
113 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithInvalidId)
114 {
115     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
116         .Times(0);
117 
118     auto [ec, path] = addTrigger(TriggerParams().id("not valid?"));
119     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
120     EXPECT_THAT(path, Eq(std::string()));
121 }
122 
123 TEST_F(TestTriggerManager, addTriggerWithoutIdAndName)
124 {
125     triggerFactoryMock
126         .expectMake(TriggerParams()
127                         .id(TriggerManager::triggerNameDefault)
128                         .name(TriggerManager::triggerNameDefault),
129                     Ref(*sut), Ref(storageMock))
130         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
131 
132     auto [ec, path] = addTrigger(TriggerParams().id("").name(""));
133     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
134     EXPECT_THAT(path, Not(Eq("")));
135 }
136 
137 TEST_F(TestTriggerManager, addTriggerWithPrefixId)
138 {
139     triggerFactoryMock
140         .expectMake(TriggerParams()
141                         .id("TelemetryService/HackyName")
142                         .name("Hacky/Name!@#$"),
143                     Ref(*sut), Ref(storageMock))
144         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
145 
146     auto [ec, path] = addTrigger(
147         TriggerParams().id("TelemetryService/").name("Hacky/Name!@#$"));
148     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
149     EXPECT_THAT(path, Not(Eq("")));
150 }
151 
152 TEST_F(TestTriggerManager, addTriggerWithoutIdTwice)
153 {
154     addTrigger(TriggerParams().id(""));
155 
156     auto [ec, path] = addTrigger(TriggerParams().id(""));
157     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
158     EXPECT_THAT(path, Not(Eq("")));
159 }
160 
161 TEST_F(TestTriggerManager, addTriggerWithoutIdAndWithLongNameTwice)
162 {
163     addTrigger(TriggerParams().id("").name(
164         std::string(2 * TriggerManager::maxTriggerIdLength, 'z')));
165 
166     auto [ec, path] = addTrigger(TriggerParams().id("").name(
167         std::string(2 * TriggerManager::maxTriggerIdLength, 'z')));
168     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
169     EXPECT_THAT(path, Not(Eq("")));
170 }
171 
172 TEST_F(TestTriggerManager, addTriggerWithMaxLengthId)
173 {
174     auto triggerId = std::string(TriggerManager::maxTriggerIdLength, 'z');
175     auto triggerParams = TriggerParams().id(triggerId);
176 
177     triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock))
178         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
179 
180     auto [ec, path] = addTrigger(triggerParams);
181 
182     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
183     EXPECT_THAT(path, Eq(triggerMock.getPath()));
184 }
185 
186 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooLongId)
187 {
188     auto triggerId = std::string(TriggerManager::maxTriggerIdLength + 1, 'z');
189     auto triggerParams = TriggerParams().id(triggerId);
190 
191     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
192         .Times(0);
193 
194     auto [ec, path] = addTrigger(triggerParams);
195 
196     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
197     EXPECT_THAT(path, Eq(std::string()));
198 }
199 
200 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWhenMaxTriggerIsReached)
201 {
202     auto triggerParams = TriggerParams();
203 
204     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
205         .Times(TriggerManager::maxTriggers);
206 
207     for (size_t i = 0; i < TriggerManager::maxTriggers; i++)
208     {
209         triggerParams.id(TriggerParams().id() + std::to_string(i));
210 
211         auto [ec, path] = addTrigger(triggerParams);
212         EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
213     }
214 
215     triggerParams.id(TriggerParams().id() +
216                      std::to_string(TriggerManager::maxTriggers));
217     auto [ec, path] = addTrigger(triggerParams);
218     EXPECT_THAT(ec.value(), Eq(boost::system::errc::too_many_files_open));
219     EXPECT_THAT(path, Eq(std::string()));
220 }
221 
222 TEST_F(TestTriggerManager, removeTrigger)
223 {
224     {
225         InSequence seq;
226         triggerFactoryMock
227             .expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
228             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
229         EXPECT_CALL(triggerMock, Die());
230         EXPECT_CALL(checkPoint, Call("end"));
231     }
232 
233     addTrigger(TriggerParams());
234     sut->removeTrigger(&triggerMock);
235     checkPoint.Call("end");
236 }
237 
238 TEST_F(TestTriggerManager, removingTriggerThatIsNotInContainerHasNoEffect)
239 {
240     {
241         InSequence seq;
242         EXPECT_CALL(checkPoint, Call("end"));
243         EXPECT_CALL(triggerMock, Die());
244     }
245 
246     sut->removeTrigger(&triggerMock);
247     checkPoint.Call("end");
248 }
249 
250 TEST_F(TestTriggerManager, removingSameTriggerTwiceHasNoSideEffect)
251 {
252     {
253         InSequence seq;
254         triggerFactoryMock
255             .expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
256             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
257         EXPECT_CALL(triggerMock, Die());
258         EXPECT_CALL(checkPoint, Call("end"));
259     }
260 
261     addTrigger(TriggerParams());
262     sut->removeTrigger(&triggerMock);
263     sut->removeTrigger(&triggerMock);
264     checkPoint.Call("end");
265 }
266 
267 class TestTriggerManagerStorage : public TestTriggerManager
268 {
269   public:
270     using FilePath = interfaces::JsonStorage::FilePath;
271     using DirectoryPath = interfaces::JsonStorage::DirectoryPath;
272 
273     void SetUp() override
274     {
275         ON_CALL(storageMock, list())
276             .WillByDefault(Return(std::vector<FilePath>{
277                 {FilePath("trigger1")}, {FilePath("trigger2")}}));
278 
279         ON_CALL(storageMock, load(FilePath("trigger1")))
280             .WillByDefault(InvokeWithoutArgs([this] { return data1; }));
281 
282         data2["Id"] = "Trigger2";
283         data2["Name"] = "Second Trigger";
284         ON_CALL(storageMock, load(FilePath("trigger2")))
285             .WillByDefault(InvokeWithoutArgs([this] { return data2; }));
286     }
287 
288     nlohmann::json data1 = nlohmann::json{
289         {"Version", Trigger::triggerVersion},
290         {"Id", TriggerParams().id()},
291         {"Name", TriggerParams().name()},
292         {"ThresholdParamsDiscriminator",
293          TriggerParams().thresholdParams().index()},
294         {"TriggerActions", TriggerParams().triggerActions()},
295         {"ThresholdParams", utils::labeledThresholdParamsToJson(
296                                 TriggerParams().thresholdParams())},
297         {"ReportNames", TriggerParams().reportNames()},
298         {"Sensors", TriggerParams().sensors()}};
299 
300     nlohmann::json data2 = data1;
301 };
302 
303 TEST_F(TestTriggerManagerStorage, triggerManagerCtorAddTriggerFromStorage)
304 {
305     triggerFactoryMock.expectMake(TriggerParams(), _, Ref(storageMock));
306     triggerFactoryMock.expectMake(
307         TriggerParams().id("Trigger2").name("Second Trigger"), _,
308         Ref(storageMock));
309     EXPECT_CALL(storageMock, remove(_)).Times(0);
310 
311     sut = makeTriggerManager();
312 }
313 
314 TEST_F(TestTriggerManagerStorage,
315        triggerManagerCtorRemoveDiscreteTriggerFromStorage)
316 {
317     LabeledTriggerThresholdParams thresholdParams =
318         std::vector<discrete::LabeledThresholdParam>{
319             {"userId1", discrete::Severity::warning, 15, "10.0"},
320             {"userId2", discrete::Severity::critical, 5, "20.0"}};
321 
322     data1["ThresholdParamsDiscriminator"] = thresholdParams.index();
323 
324     data1["ThresholdParams"] =
325         utils::labeledThresholdParamsToJson(thresholdParams);
326 
327     EXPECT_CALL(storageMock, remove(FilePath("trigger1"))).Times(0);
328 
329     sut = makeTriggerManager();
330 }
331 
332 TEST_F(TestTriggerManagerStorage,
333        triggerManagerCtorRemoveDiscreteTriggerFromStorage2)
334 {
335     data1["IsDiscrete"] = true;
336 
337     EXPECT_CALL(storageMock, remove(FilePath("trigger1"))).Times(0);
338 
339     sut = makeTriggerManager();
340 }
341 
342 TEST_F(TestTriggerManagerStorage,
343        triggerManagerCtorAddProperRemoveInvalidTriggerFromStorage)
344 {
345     data1["Version"] = Trigger::triggerVersion - 1;
346 
347     triggerFactoryMock.expectMake(
348         TriggerParams().id("Trigger2").name("Second Trigger"), _,
349         Ref(storageMock));
350     EXPECT_CALL(storageMock, remove(FilePath("trigger1")));
351 
352     sut = makeTriggerManager();
353 }
354