1 #include "activation.hpp"
2 #include "mocked_activation_listener.hpp"
3 #include "mocked_association_interface.hpp"
4 #include "mocked_utils.hpp"
5 
6 #include <sdbusplus/test/sdbus_mock.hpp>
7 
8 #include <gmock/gmock.h>
9 #include <gtest/gtest.h>
10 
11 using namespace phosphor::software::updater;
12 
13 using ::testing::_;
14 using ::testing::Return;
15 using ::testing::StrEq;
16 
17 using std::any;
18 
19 class TestActivation : public ::testing::Test
20 {
21   public:
22     using PropertyType = utils::UtilsInterface::PropertyType;
23     using Status = Activation::Status;
24     using RequestedStatus = Activation::RequestedActivations;
25 
26     TestActivation(const TestActivation&) = delete;
27     TestActivation& operator=(const TestActivation&) = delete;
28     TestActivation(TestActivation&&) = delete;
29     TestActivation& operator=(TestActivation&&) = delete;
30 
31     TestActivation() :
32         mockedUtils(
33             reinterpret_cast<const utils::MockedUtils&>(utils::getUtils()))
34     {
35         // By default make PSU present
36         ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(PRESENT)))
37             .WillByDefault(Return(any(PropertyType(true))));
38 
39         // By default make PSU compatible with the test software
40         ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER)))
41             .WillByDefault(Return(any(PropertyType(std::string("TestManu")))));
42         ON_CALL(mockedUtils, getModel(_))
43             .WillByDefault(Return(std::string("TestModel")));
44         ON_CALL(mockedUtils, isAssociated(_, _)).WillByDefault(Return(false));
45     }
46     ~TestActivation() override
47     {
48         utils::freeUtils();
49     }
50 
51     void onUpdateDone() const
52     {
53         activation->onUpdateDone();
54     }
55     void onUpdateFailed() const
56     {
57         activation->onUpdateFailed();
58     }
59     int getProgress() const
60     {
61         return activation->activationProgress->progress();
62     }
63     const auto& getPsuQueue() const
64     {
65         return activation->psuQueue;
66     }
67     std::string getUpdateService(const std::string& psuInventoryPath) const
68     {
69         return activation->getUpdateService(psuInventoryPath);
70     }
71 
72     sdbusplus::SdBusMock sdbusMock;
73     sdbusplus::bus_t mockedBus = sdbusplus::get_mocked_new(&sdbusMock);
74     const utils::MockedUtils& mockedUtils;
75     MockedAssociationInterface mockedAssociationInterface;
76     MockedActivationListener mockedActivationListener;
77     std::unique_ptr<Activation> activation;
78     std::string versionId = "abcdefgh";
79     std::string extVersion = "manufacturer=TestManu,model=TestModel";
80     std::string filePath = "/tmp/images/abcdefgh";
81     std::string dBusPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
82     Status status = Status::Ready;
83     AssociationList associations;
84 };
85 
86 TEST_F(TestActivation, ctordtor)
87 {
88     activation = std::make_unique<Activation>(
89         mockedBus, dBusPath, versionId, extVersion, status, associations,
90         filePath, &mockedAssociationInterface, &mockedActivationListener);
91 }
92 
93 TEST_F(TestActivation, ctorWithInvalidExtVersion)
94 {
95     extVersion = "invalid text";
96     activation = std::make_unique<Activation>(
97         mockedBus, dBusPath, versionId, extVersion, status, associations,
98         filePath, &mockedAssociationInterface, &mockedActivationListener);
99 }
100 
101 TEST_F(TestActivation, getUpdateService)
102 {
103     std::string psuInventoryPath = "/com/example/inventory/powersupply1";
104     std::string toCompare = "psu-update@-com-example-inventory-"
105                             "powersupply1\\x20-tmp-images-12345678.service";
106     versionId = "12345678";
107     filePath = "/tmp/images/12345678";
108 
109     activation = std::make_unique<Activation>(
110         mockedBus, dBusPath, versionId, extVersion, status, associations,
111         filePath, &mockedAssociationInterface, &mockedActivationListener);
112 
113     auto service = getUpdateService(psuInventoryPath);
114     EXPECT_EQ(toCompare, service);
115 }
116 
117 TEST_F(TestActivation, doUpdateWhenNoPSU)
118 {
119     activation = std::make_unique<Activation>(
120         mockedBus, dBusPath, versionId, extVersion, status, associations,
121         filePath, &mockedAssociationInterface, &mockedActivationListener);
122     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
123         .WillByDefault(
124             Return(std::vector<std::string>({}))); // No PSU inventory
125     activation->requestedActivation(RequestedStatus::Active);
126 
127     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
128         .Times(0);
129     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
130         .Times(0);
131     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
132         .Times(0);
133     EXPECT_CALL(mockedActivationListener, onUpdateDone(_, _)).Times(0);
134     EXPECT_EQ(Status::Failed, activation->activation());
135 }
136 
137 TEST_F(TestActivation, doUpdateOnePSUOK)
138 {
139     constexpr auto psu0 = "/com/example/inventory/psu0";
140     activation = std::make_unique<Activation>(
141         mockedBus, dBusPath, versionId, extVersion, status, associations,
142         filePath, &mockedAssociationInterface, &mockedActivationListener);
143     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
144         .WillByDefault(
145             Return(std::vector<std::string>({psu0}))); // One PSU inventory
146     activation->requestedActivation(RequestedStatus::Active);
147 
148     EXPECT_EQ(Status::Activating, activation->activation());
149 
150     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
151         .Times(1);
152     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
153         .Times(1);
154     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
155         .Times(1);
156     EXPECT_CALL(mockedActivationListener,
157                 onUpdateDone(StrEq(versionId), StrEq(psu0)))
158         .Times(1);
159     onUpdateDone();
160     EXPECT_EQ(Status::Active, activation->activation());
161 }
162 
163 TEST_F(TestActivation, doUpdateFourPSUsOK)
164 {
165     constexpr auto psu0 = "/com/example/inventory/psu0";
166     constexpr auto psu1 = "/com/example/inventory/psu1";
167     constexpr auto psu2 = "/com/example/inventory/psu2";
168     constexpr auto psu3 = "/com/example/inventory/psu3";
169     activation = std::make_unique<Activation>(
170         mockedBus, dBusPath, versionId, extVersion, status, associations,
171         filePath, &mockedAssociationInterface, &mockedActivationListener);
172     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
173         .WillByDefault(Return(
174             std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
175     activation->requestedActivation(RequestedStatus::Active);
176 
177     EXPECT_EQ(Status::Activating, activation->activation());
178     EXPECT_EQ(10, getProgress());
179 
180     EXPECT_CALL(mockedActivationListener,
181                 onUpdateDone(StrEq(versionId), StrEq(psu0)))
182         .Times(1);
183     onUpdateDone();
184     EXPECT_EQ(Status::Activating, activation->activation());
185     EXPECT_EQ(30, getProgress());
186 
187     EXPECT_CALL(mockedActivationListener,
188                 onUpdateDone(StrEq(versionId), StrEq(psu1)))
189         .Times(1);
190     onUpdateDone();
191     EXPECT_EQ(Status::Activating, activation->activation());
192     EXPECT_EQ(50, getProgress());
193 
194     EXPECT_CALL(mockedActivationListener,
195                 onUpdateDone(StrEq(versionId), StrEq(psu2)))
196         .Times(1);
197     onUpdateDone();
198     EXPECT_EQ(Status::Activating, activation->activation());
199     EXPECT_EQ(70, getProgress());
200 
201     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
202         .Times(1);
203     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
204         .Times(1);
205     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
206         .Times(1);
207 
208     EXPECT_CALL(mockedActivationListener,
209                 onUpdateDone(StrEq(versionId), StrEq(psu3)))
210         .Times(1);
211     onUpdateDone();
212     EXPECT_EQ(Status::Active, activation->activation());
213 }
214 
215 TEST_F(TestActivation, doUpdateFourPSUsFailonSecond)
216 {
217     constexpr auto psu0 = "/com/example/inventory/psu0";
218     constexpr auto psu1 = "/com/example/inventory/psu1";
219     constexpr auto psu2 = "/com/example/inventory/psu2";
220     constexpr auto psu3 = "/com/example/inventory/psu3";
221     activation = std::make_unique<Activation>(
222         mockedBus, dBusPath, versionId, extVersion, status, associations,
223         filePath, &mockedAssociationInterface, &mockedActivationListener);
224     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
225         .WillByDefault(Return(
226             std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
227     activation->requestedActivation(RequestedStatus::Active);
228 
229     EXPECT_EQ(Status::Activating, activation->activation());
230     EXPECT_EQ(10, getProgress());
231 
232     EXPECT_CALL(mockedActivationListener,
233                 onUpdateDone(StrEq(versionId), StrEq(psu0)))
234         .Times(1);
235     onUpdateDone();
236     EXPECT_EQ(Status::Activating, activation->activation());
237     EXPECT_EQ(30, getProgress());
238 
239     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
240         .Times(0);
241     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
242         .Times(0);
243     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
244         .Times(0);
245     EXPECT_CALL(mockedActivationListener, onUpdateDone(_, _)).Times(0);
246     onUpdateFailed();
247     EXPECT_EQ(Status::Failed, activation->activation());
248 }
249 
250 TEST_F(TestActivation, doUpdateOnExceptionFromDbus)
251 {
252     constexpr auto psu0 = "/com/example/inventory/psu0";
253     activation = std::make_unique<Activation>(
254         mockedBus, dBusPath, versionId, extVersion, status, associations,
255         filePath, &mockedAssociationInterface, &mockedActivationListener);
256     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
257         .WillByDefault(
258             Return(std::vector<std::string>({psu0}))); // One PSU inventory
259     ON_CALL(sdbusMock, sd_bus_call(_, _, _, _, nullptr))
260         .WillByDefault(Return(-1)); // Make sdbus call failure
261     activation->requestedActivation(RequestedStatus::Active);
262 
263     EXPECT_EQ(Status::Failed, activation->activation());
264 }
265 
266 TEST_F(TestActivation, doUpdateOnePSUNotPresent)
267 {
268     constexpr auto psu0 = "/com/example/inventory/psu0";
269     activation = std::make_unique<Activation>(
270         mockedBus, dBusPath, versionId, extVersion, status, associations,
271         filePath, &mockedAssociationInterface, &mockedActivationListener);
272     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
273         .WillByDefault(Return(std::vector<std::string>({psu0})));
274     EXPECT_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(PRESENT)))
275         .WillOnce(Return(any(PropertyType(false)))); // not present
276     activation->requestedActivation(RequestedStatus::Active);
277 
278     EXPECT_EQ(Status::Ready, activation->activation());
279 }
280 
281 TEST_F(TestActivation, doUpdateOnePSUModelNotCompatible)
282 {
283     constexpr auto psu0 = "/com/example/inventory/psu0";
284     extVersion = "manufacturer=TestManu,model=DifferentModel";
285     activation = std::make_unique<Activation>(
286         mockedBus, dBusPath, versionId, extVersion, status, associations,
287         filePath, &mockedAssociationInterface, &mockedActivationListener);
288     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
289         .WillByDefault(Return(std::vector<std::string>({psu0})));
290     activation->requestedActivation(RequestedStatus::Active);
291 
292     EXPECT_EQ(Status::Ready, activation->activation());
293 }
294 
295 TEST_F(TestActivation, doUpdateOnePSUManufactureNotCompatible)
296 {
297     constexpr auto psu0 = "/com/example/inventory/psu0";
298     extVersion = "manufacturer=DifferentManu,model=TestModel";
299     activation = std::make_unique<Activation>(
300         mockedBus, dBusPath, versionId, extVersion, status, associations,
301         filePath, &mockedAssociationInterface, &mockedActivationListener);
302     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
303         .WillByDefault(Return(std::vector<std::string>({psu0})));
304     activation->requestedActivation(RequestedStatus::Active);
305 
306     EXPECT_EQ(Status::Ready, activation->activation());
307 }
308 
309 TEST_F(TestActivation, doUpdateOnePSUSelfManufactureIsEmpty)
310 {
311     ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER)))
312         .WillByDefault(Return(any(PropertyType(std::string("")))));
313     extVersion = "manufacturer=AnyManu,model=TestModel";
314     // Below is the same as doUpdateOnePSUOK case
315     constexpr auto psu0 = "/com/example/inventory/psu0";
316     activation = std::make_unique<Activation>(
317         mockedBus, dBusPath, versionId, extVersion, status, associations,
318         filePath, &mockedAssociationInterface, &mockedActivationListener);
319     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
320         .WillByDefault(
321             Return(std::vector<std::string>({psu0}))); // One PSU inventory
322     activation->requestedActivation(RequestedStatus::Active);
323 
324     EXPECT_EQ(Status::Activating, activation->activation());
325 
326     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
327         .Times(1);
328     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
329         .Times(1);
330     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
331         .Times(1);
332     onUpdateDone();
333     EXPECT_EQ(Status::Active, activation->activation());
334 }
335 
336 TEST_F(TestActivation, doUpdateFourPSUsSecondPSUNotCompatible)
337 {
338     constexpr auto psu0 = "/com/example/inventory/psu0";
339     constexpr auto psu1 = "/com/example/inventory/psu1";
340     constexpr auto psu2 = "/com/example/inventory/psu2";
341     constexpr auto psu3 = "/com/example/inventory/psu3";
342     ON_CALL(mockedUtils, getModel(StrEq(psu1)))
343         .WillByDefault(Return(std::string("DifferentModel")));
344     activation = std::make_unique<Activation>(
345         mockedBus, dBusPath, versionId, extVersion, status, associations,
346         filePath, &mockedAssociationInterface, &mockedActivationListener);
347     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
348         .WillByDefault(Return(
349             std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
350     activation->requestedActivation(RequestedStatus::Active);
351 
352     const auto& psuQueue = getPsuQueue();
353     EXPECT_EQ(3U, psuQueue.size());
354 
355     // Only 3 PSUs shall be updated, and psu1 shall be skipped
356     EXPECT_EQ(Status::Activating, activation->activation());
357     EXPECT_EQ(10, getProgress());
358 
359     onUpdateDone();
360     EXPECT_EQ(Status::Activating, activation->activation());
361     EXPECT_EQ(36, getProgress());
362 
363     onUpdateDone();
364     EXPECT_EQ(Status::Activating, activation->activation());
365     EXPECT_EQ(62, getProgress());
366 
367     EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath))
368         .Times(1);
369     EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath))
370         .Times(1);
371     EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath))
372         .Times(1);
373 
374     onUpdateDone();
375     EXPECT_EQ(Status::Active, activation->activation());
376 }
377 
378 TEST_F(TestActivation, doUpdateWhenNoFilePathInActiveState)
379 {
380     filePath = "";
381     status = Status::Active; // Typically, a running PSU software is active
382                              // without file path
383     constexpr auto psu0 = "/com/example/inventory/psu0";
384     activation = std::make_unique<Activation>(
385         mockedBus, dBusPath, versionId, extVersion, status, associations,
386         filePath, &mockedAssociationInterface, &mockedActivationListener);
387 
388     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
389         .WillByDefault(
390             Return(std::vector<std::string>({psu0}))); // One PSU inventory
391 
392     // There shall be no DBus call to start update service
393     EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
394                                                           StrEq("StartUnit")))
395         .Times(0);
396 
397     activation->requestedActivation(RequestedStatus::Active);
398     EXPECT_EQ(Status::Active, activation->activation());
399 }
400 
401 TEST_F(TestActivation, doUpdateWhenNoFilePathInReadyState)
402 {
403     filePath = "";
404     status = Status::Ready; // Usually a Ready activation should have file path,
405                             // but we are testing this case as well
406     constexpr auto psu0 = "/com/example/inventory/psu0";
407     activation = std::make_unique<Activation>(
408         mockedBus, dBusPath, versionId, extVersion, status, associations,
409         filePath, &mockedAssociationInterface, &mockedActivationListener);
410 
411     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
412         .WillByDefault(
413             Return(std::vector<std::string>({psu0}))); // One PSU inventory
414 
415     // There shall be no DBus call to start update service
416     EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
417                                                           StrEq("StartUnit")))
418         .Times(0);
419 
420     activation->requestedActivation(RequestedStatus::Active);
421     EXPECT_EQ(Status::Ready, activation->activation());
422 }
423 
424 TEST_F(TestActivation, doUpdateWhenPSUIsAssociated)
425 {
426     constexpr auto psu0 = "/com/example/inventory/psu0";
427     status = Status::Active; // Typically, a running PSU software is associated
428     activation = std::make_unique<Activation>(
429         mockedBus, dBusPath, versionId, extVersion, status, associations,
430         filePath, &mockedAssociationInterface, &mockedActivationListener);
431 
432     ON_CALL(mockedUtils, getPSUInventoryPaths(_))
433         .WillByDefault(
434             Return(std::vector<std::string>({psu0}))); // One PSU inventory
435 
436     // When PSU is already associated, there shall be no DBus call to start
437     // update service
438     EXPECT_CALL(mockedUtils, isAssociated(StrEq(psu0), _))
439         .WillOnce(Return(true));
440     EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _,
441                                                           StrEq("StartUnit")))
442         .Times(0);
443 
444     activation->requestedActivation(RequestedStatus::Active);
445     EXPECT_EQ(Status::Active, activation->activation());
446 }
447