1 /**
2  * Copyright © 2019 IBM Corporation
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 #include "extensions/openpower-pels/src.hpp"
17 #include "mocks.hpp"
18 #include "pel_utils.hpp"
19 
20 #include <fstream>
21 
22 #include <gtest/gtest.h>
23 
24 using namespace openpower::pels;
25 using ::testing::_;
26 using ::testing::DoAll;
27 using ::testing::InvokeWithoutArgs;
28 using ::testing::NiceMock;
29 using ::testing::Return;
30 using ::testing::SetArgReferee;
31 using ::testing::Throw;
32 namespace fs = std::filesystem;
33 
34 const auto testRegistry = R"(
35 {
36 "PELs":
37 [
38     {
39         "Name": "xyz.openbmc_project.Error.Test",
40         "Subsystem": "bmc_firmware",
41         "SRC":
42         {
43             "ReasonCode": "0xABCD",
44             "Words6To9":
45             {
46                 "6":
47                 {
48                     "Description": "Component ID",
49                     "AdditionalDataPropSource": "COMPID"
50                 },
51                 "7":
52                 {
53                     "Description": "Failure count",
54                     "AdditionalDataPropSource": "FREQUENCY"
55                 },
56                 "8":
57                 {
58                     "Description": "Time period",
59                     "AdditionalDataPropSource": "DURATION"
60                 },
61                 "9":
62                 {
63                     "Description": "Error code",
64                     "AdditionalDataPropSource": "ERRORCODE"
65                 }
66             }
67         },
68         "Documentation":
69         {
70             "Description": "A Component Fault",
71             "Message": "Comp %1 failed %2 times over %3 secs with ErrorCode %4",
72             "MessageArgSources":
73             [
74                 "SRCWord6", "SRCWord7", "SRCWord8", "SRCWord9"
75             ]
76         }
77     }
78 ]
79 }
80 )";
81 
82 class SRCTest : public ::testing::Test
83 {
84   protected:
85     static void SetUpTestCase()
86     {
87         char path[] = "/tmp/srctestXXXXXX";
88         regDir = mkdtemp(path);
89     }
90 
91     static void TearDownTestCase()
92     {
93         fs::remove_all(regDir);
94     }
95 
96     static std::string writeData(const char* data)
97     {
98         fs::path path = regDir / "registry.json";
99         std::ofstream stream{path};
100         stream << data;
101         return path;
102     }
103 
104     static fs::path regDir;
105 };
106 
107 fs::path SRCTest::regDir{};
108 
109 TEST_F(SRCTest, UnflattenFlattenTestNoCallouts)
110 {
111     auto data = pelDataFactory(TestPELType::primarySRCSection);
112 
113     Stream stream{data};
114     SRC src{stream};
115 
116     EXPECT_TRUE(src.valid());
117 
118     EXPECT_EQ(src.header().id, 0x5053);
119     EXPECT_EQ(src.header().size, 0x50);
120     EXPECT_EQ(src.header().version, 0x01);
121     EXPECT_EQ(src.header().subType, 0x01);
122     EXPECT_EQ(src.header().componentID, 0x0202);
123 
124     EXPECT_EQ(src.version(), 0x02);
125     EXPECT_EQ(src.flags(), 0x00);
126     EXPECT_EQ(src.hexWordCount(), 9);
127     EXPECT_EQ(src.size(), 0x48);
128 
129     const auto& hexwords = src.hexwordData();
130     EXPECT_EQ(0x02020255, hexwords[0]);
131     EXPECT_EQ(0x03030310, hexwords[1]);
132     EXPECT_EQ(0x04040404, hexwords[2]);
133     EXPECT_EQ(0x05050505, hexwords[3]);
134     EXPECT_EQ(0x06060606, hexwords[4]);
135     EXPECT_EQ(0x07070707, hexwords[5]);
136     EXPECT_EQ(0x08080808, hexwords[6]);
137     EXPECT_EQ(0x09090909, hexwords[7]);
138 
139     EXPECT_EQ(src.asciiString(), "BD8D5678                        ");
140     EXPECT_FALSE(src.callouts());
141 
142     // Flatten
143     std::vector<uint8_t> newData;
144     Stream newStream{newData};
145 
146     src.flatten(newStream);
147     EXPECT_EQ(data, newData);
148 }
149 
150 TEST_F(SRCTest, UnflattenFlattenTest2Callouts)
151 {
152     auto data = pelDataFactory(TestPELType::primarySRCSection2Callouts);
153 
154     Stream stream{data};
155     SRC src{stream};
156 
157     EXPECT_TRUE(src.valid());
158     EXPECT_EQ(src.flags(), 0x01); // Additional sections within the SRC.
159 
160     // Spot check the SRC fields, but they're the same as above
161     EXPECT_EQ(src.asciiString(), "BD8D5678                        ");
162 
163     // There should be 2 callouts
164     const auto& calloutsSection = src.callouts();
165     ASSERT_TRUE(calloutsSection);
166     const auto& callouts = calloutsSection->callouts();
167     EXPECT_EQ(callouts.size(), 2);
168 
169     // spot check that each callout has the right substructures
170     EXPECT_TRUE(callouts.front()->fruIdentity());
171     EXPECT_FALSE(callouts.front()->pceIdentity());
172     EXPECT_FALSE(callouts.front()->mru());
173 
174     EXPECT_TRUE(callouts.back()->fruIdentity());
175     EXPECT_TRUE(callouts.back()->pceIdentity());
176     EXPECT_TRUE(callouts.back()->mru());
177 
178     // Flatten
179     std::vector<uint8_t> newData;
180     Stream newStream{newData};
181 
182     src.flatten(newStream);
183     EXPECT_EQ(data, newData);
184 }
185 
186 // Create an SRC from the message registry
187 TEST_F(SRCTest, CreateTestNoCallouts)
188 {
189     message::Entry entry;
190     entry.src.type = 0xBD;
191     entry.src.reasonCode = 0xABCD;
192     entry.subsystem = 0x42;
193     entry.src.powerFault = true;
194     entry.src.hexwordADFields = {
195         {5, {"TEST1", "DESCR1"}}, // Not a user defined word
196         {6, {"TEST1", "DESCR1"}},
197         {7, {"TEST2", "DESCR2"}},
198         {8, {"TEST3", "DESCR3"}},
199         {9, {"TEST4", "DESCR4"}}};
200 
201     // Values for the SRC words pointed to above
202     std::vector<std::string> adData{"TEST1=0x12345678", "TEST2=12345678",
203                                     "TEST3=0XDEF", "TEST4=Z"};
204     AdditionalData ad{adData};
205     NiceMock<MockDataInterface> dataIface;
206 
207     EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
208 
209     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
210                                       "system/entry"};
211     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
212         .WillOnce(Return(std::vector<bool>{false, false, false}));
213 
214     SRC src{entry, ad, dataIface};
215 
216     EXPECT_TRUE(src.valid());
217     EXPECT_TRUE(src.isPowerFaultEvent());
218     EXPECT_EQ(src.size(), baseSRCSize);
219 
220     const auto& hexwords = src.hexwordData();
221 
222     // The spec always refers to SRC words 2 - 9, and as the hexwordData()
223     // array index starts at 0 use the math in the [] below to make it easier
224     // to tell what is being accessed.
225     EXPECT_EQ(hexwords[2 - 2] & 0xF0000000, 0);    // Partition dump status
226     EXPECT_EQ(hexwords[2 - 2] & 0x00F00000, 0);    // Partition boot type
227     EXPECT_EQ(hexwords[2 - 2] & 0x000000FF, 0x55); // SRC format
228     EXPECT_EQ(hexwords[3 - 2] & 0x000000FF, 0x10); // BMC position
229     EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0xABCD0000); // Motherboard CCIN
230 
231     // Validate more fields here as the code starts filling them in.
232 
233     // Ensure hex word 5 wasn't allowed to be set to TEST1's contents
234     EXPECT_EQ(hexwords[5 - 2], 0);
235 
236     // The user defined hex word fields specifed in the additional data.
237     EXPECT_EQ(hexwords[6 - 2], 0x12345678); // TEST1
238     EXPECT_EQ(hexwords[7 - 2], 12345678);   // TEST2
239     EXPECT_EQ(hexwords[8 - 2], 0xdef);      // TEST3
240     EXPECT_EQ(hexwords[9 - 2], 0);          // TEST4, but can't convert a 'Z'
241 
242     EXPECT_EQ(src.asciiString(), "BD42ABCD                        ");
243 
244     // No callouts
245     EXPECT_FALSE(src.callouts());
246 
247     // May as well spot check the flatten/unflatten
248     std::vector<uint8_t> data;
249     Stream stream{data};
250     src.flatten(stream);
251 
252     stream.offset(0);
253     SRC newSRC{stream};
254 
255     EXPECT_TRUE(newSRC.valid());
256     EXPECT_EQ(newSRC.isPowerFaultEvent(), src.isPowerFaultEvent());
257     EXPECT_EQ(newSRC.asciiString(), src.asciiString());
258     EXPECT_FALSE(newSRC.callouts());
259 }
260 
261 // Create an SRC to test POWER_THERMAL_CRITICAL_FAULT set to TRUE
262 // sets the power fault bit in SRC
263 TEST_F(SRCTest, PowerFaultTest)
264 {
265     message::Entry entry;
266     entry.src.type = 0xBD;
267     entry.src.reasonCode = 0xABCD;
268     entry.subsystem = 0x42;
269     entry.src.powerFault = false;
270 
271     // Values for the SRC words pointed to above
272     std::vector<std::string> adData{"POWER_THERMAL_CRITICAL_FAULT=TRUE",
273                                     "TEST2=12345678", "TEST3=0XDEF", "TEST4=Z"};
274     AdditionalData ad{adData};
275     NiceMock<MockDataInterface> dataIface;
276 
277     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
278                                       "system/entry"};
279     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
280         .WillOnce(Return(std::vector<bool>{false, false, false}));
281 
282     SRC src{entry, ad, dataIface};
283 
284     EXPECT_TRUE(src.valid());
285     EXPECT_TRUE(src.isPowerFaultEvent());
286     EXPECT_EQ(src.size(), baseSRCSize);
287 }
288 
289 // Test when the CCIN string isn't a 4 character number
290 TEST_F(SRCTest, BadCCINTest)
291 {
292     message::Entry entry;
293     entry.src.type = 0xBD;
294     entry.src.reasonCode = 0xABCD;
295     entry.subsystem = 0x42;
296     entry.src.powerFault = false;
297 
298     std::vector<std::string> adData{};
299     AdditionalData ad{adData};
300     NiceMock<MockDataInterface> dataIface;
301 
302     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
303                                       "system/entry"};
304     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
305         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
306 
307     // First it isn't a number, then it is too long,
308     // then it is empty.
309     EXPECT_CALL(dataIface, getMotherboardCCIN)
310         .WillOnce(Return("X"))
311         .WillOnce(Return("12345"))
312         .WillOnce(Return(""));
313 
314     // The CCIN in the first half should still be 0 each time.
315     {
316         SRC src{entry, ad, dataIface};
317         EXPECT_TRUE(src.valid());
318         const auto& hexwords = src.hexwordData();
319         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
320     }
321 
322     {
323         SRC src{entry, ad, dataIface};
324         EXPECT_TRUE(src.valid());
325         const auto& hexwords = src.hexwordData();
326         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
327     }
328 
329     {
330         SRC src{entry, ad, dataIface};
331         EXPECT_TRUE(src.valid());
332         const auto& hexwords = src.hexwordData();
333         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
334     }
335 }
336 
337 // Test the getErrorDetails function
338 TEST_F(SRCTest, MessageSubstitutionTest)
339 {
340     auto path = SRCTest::writeData(testRegistry);
341     message::Registry registry{path};
342     auto entry = registry.lookup("0xABCD", message::LookupType::reasonCode);
343 
344     std::vector<std::string> adData{"COMPID=0x1", "FREQUENCY=0x4",
345                                     "DURATION=30", "ERRORCODE=0x01ABCDEF"};
346     AdditionalData ad{adData};
347     NiceMock<MockDataInterface> dataIface;
348 
349     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
350                                       "system/entry"};
351     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
352         .WillOnce(Return(std::vector<bool>{false, false, false}));
353 
354     SRC src{*entry, ad, dataIface};
355     EXPECT_TRUE(src.valid());
356 
357     auto errorDetails = src.getErrorDetails(registry, DetailLevel::message);
358     ASSERT_TRUE(errorDetails);
359     EXPECT_EQ(
360         errorDetails.value(),
361         "Comp 0x1 failed 0x4 times over 0x1E secs with ErrorCode 0x1ABCDEF");
362 }
363 // Test that an inventory path callout string is
364 // converted into the appropriate FRU callout.
365 TEST_F(SRCTest, InventoryCalloutTest)
366 {
367     message::Entry entry;
368     entry.src.type = 0xBD;
369     entry.src.reasonCode = 0xABCD;
370     entry.subsystem = 0x42;
371     entry.src.powerFault = false;
372 
373     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
374     AdditionalData ad{adData};
375     NiceMock<MockDataInterface> dataIface;
376 
377     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
378         .WillOnce(Return("UTMS-P1"));
379 
380     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
381         .Times(1)
382         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
383                         SetArgReferee<3>("123456789ABC")));
384 
385     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
386                                       "system/entry"};
387     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
388         .WillOnce(Return(std::vector<bool>{false, false, false}));
389 
390     SRC src{entry, ad, dataIface};
391     EXPECT_TRUE(src.valid());
392 
393     ASSERT_TRUE(src.callouts());
394 
395     EXPECT_EQ(src.callouts()->callouts().size(), 1);
396 
397     auto& callout = src.callouts()->callouts().front();
398 
399     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
400     EXPECT_EQ(callout->priority(), 'H');
401 
402     auto& fru = callout->fruIdentity();
403 
404     EXPECT_EQ(fru->getPN().value(), "1234567");
405     EXPECT_EQ(fru->getCCIN().value(), "CCCC");
406     EXPECT_EQ(fru->getSN().value(), "123456789ABC");
407 
408     // flatten and unflatten
409     std::vector<uint8_t> data;
410     Stream stream{data};
411     src.flatten(stream);
412 
413     stream.offset(0);
414     SRC newSRC{stream};
415     EXPECT_TRUE(newSRC.valid());
416     ASSERT_TRUE(src.callouts());
417     EXPECT_EQ(src.callouts()->callouts().size(), 1);
418 }
419 
420 // Test that when the location code can't be obtained that
421 // no callout is added.
422 TEST_F(SRCTest, InventoryCalloutNoLocCodeTest)
423 {
424     message::Entry entry;
425     entry.src.type = 0xBD;
426     entry.src.reasonCode = 0xABCD;
427     entry.subsystem = 0x42;
428     entry.src.powerFault = false;
429 
430     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
431     AdditionalData ad{adData};
432     NiceMock<MockDataInterface> dataIface;
433 
434     auto func = []() {
435         throw sdbusplus::exception::SdBusError(5, "Error");
436         return std::string{};
437     };
438 
439     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
440         .Times(1)
441         .WillOnce(InvokeWithoutArgs(func));
442 
443     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
444                                       "system/entry"};
445     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
446         .WillOnce(Return(std::vector<bool>{false, false, false}));
447 
448     EXPECT_CALL(dataIface, getHWCalloutFields(_, _, _, _)).Times(0);
449 
450     SRC src{entry, ad, dataIface};
451     EXPECT_TRUE(src.valid());
452 
453     ASSERT_FALSE(src.callouts());
454 
455     // flatten and unflatten
456     std::vector<uint8_t> data;
457     Stream stream{data};
458     src.flatten(stream);
459 
460     stream.offset(0);
461     SRC newSRC{stream};
462     EXPECT_TRUE(newSRC.valid());
463     ASSERT_FALSE(src.callouts());
464 }
465 
466 // Test that when the VPD can't be obtained that
467 // a callout is still created.
468 TEST_F(SRCTest, InventoryCalloutNoVPDTest)
469 {
470     message::Entry entry;
471     entry.src.type = 0xBD;
472     entry.src.reasonCode = 0xABCD;
473     entry.subsystem = 0x42;
474     entry.src.powerFault = false;
475 
476     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
477     AdditionalData ad{adData};
478     NiceMock<MockDataInterface> dataIface;
479 
480     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
481         .Times(1)
482         .WillOnce(Return("UTMS-P10"));
483 
484     auto func = []() { throw sdbusplus::exception::SdBusError(5, "Error"); };
485 
486     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
487         .Times(1)
488         .WillOnce(InvokeWithoutArgs(func));
489 
490     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
491                                       "system/entry"};
492     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
493         .WillOnce(Return(std::vector<bool>{false, false, false}));
494 
495     SRC src{entry, ad, dataIface};
496     EXPECT_TRUE(src.valid());
497     ASSERT_TRUE(src.callouts());
498     EXPECT_EQ(src.callouts()->callouts().size(), 1);
499 
500     auto& callout = src.callouts()->callouts().front();
501     EXPECT_EQ(callout->locationCode(), "UTMS-P10");
502     EXPECT_EQ(callout->priority(), 'H');
503 
504     auto& fru = callout->fruIdentity();
505 
506     EXPECT_EQ(fru->getPN(), "");
507     EXPECT_EQ(fru->getCCIN(), "");
508     EXPECT_EQ(fru->getSN(), "");
509     EXPECT_FALSE(fru->getMaintProc());
510 
511     // flatten and unflatten
512     std::vector<uint8_t> data;
513     Stream stream{data};
514     src.flatten(stream);
515 
516     stream.offset(0);
517     SRC newSRC{stream};
518     EXPECT_TRUE(newSRC.valid());
519     ASSERT_TRUE(src.callouts());
520     EXPECT_EQ(src.callouts()->callouts().size(), 1);
521 }
522 
523 TEST_F(SRCTest, RegistryCalloutTest)
524 {
525     message::Entry entry;
526     entry.src.type = 0xBD;
527     entry.src.reasonCode = 0xABCD;
528     entry.subsystem = 0x42;
529     entry.src.powerFault = false;
530 
531     entry.callouts = R"(
532         [
533         {
534             "System": "systemA",
535             "CalloutList":
536             [
537                 {
538                     "Priority": "high",
539                     "SymbolicFRU": "service_docs"
540                 },
541                 {
542                     "Priority": "medium",
543                     "Procedure": "bmc_code"
544                 }
545             ]
546         },
547         {
548             "System": "systemB",
549             "CalloutList":
550             [
551                 {
552                     "Priority": "high",
553                     "LocCode": "P0-C8",
554                     "SymbolicFRUTrusted": "service_docs"
555                 },
556                 {
557                     "Priority": "medium",
558                     "SymbolicFRUTrusted": "service_docs"
559                 }
560             ]
561         },
562         {
563             "System": "systemC",
564             "CalloutList":
565             [
566                 {
567                     "Priority": "high",
568                     "LocCode": "P0-C8"
569                 },
570                 {
571                     "Priority": "medium",
572                     "LocCode": "P0-C9"
573                 }
574             ]
575         }
576         ])"_json;
577 
578     {
579         // Call out a symbolic FRU and a procedure
580         AdditionalData ad;
581         NiceMock<MockDataInterface> dataIface;
582         std::vector<std::string> names{"systemA"};
583 
584         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
585 
586         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
587                                           "system/entry"};
588         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
589             .WillOnce(Return(std::vector<bool>{false, false, false}));
590 
591         SRC src{entry, ad, dataIface};
592 
593         auto& callouts = src.callouts()->callouts();
594         ASSERT_EQ(callouts.size(), 2);
595 
596         EXPECT_EQ(callouts[0]->locationCodeSize(), 0);
597         EXPECT_EQ(callouts[0]->priority(), 'H');
598 
599         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
600         EXPECT_EQ(callouts[1]->priority(), 'M');
601 
602         auto& fru1 = callouts[0]->fruIdentity();
603         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
604         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
605         EXPECT_FALSE(fru1->getMaintProc());
606         EXPECT_FALSE(fru1->getSN());
607         EXPECT_FALSE(fru1->getCCIN());
608 
609         auto& fru2 = callouts[1]->fruIdentity();
610         EXPECT_EQ(fru2->getMaintProc().value(), "BMC0001");
611         EXPECT_EQ(fru2->failingComponentType(),
612                   src::FRUIdentity::maintenanceProc);
613         EXPECT_FALSE(fru2->getPN());
614         EXPECT_FALSE(fru2->getSN());
615         EXPECT_FALSE(fru2->getCCIN());
616     }
617 
618     {
619         // Call out a trusted symbolic FRU with a location code, and
620         // another one without.
621         AdditionalData ad;
622         NiceMock<MockDataInterface> dataIface;
623         std::vector<std::string> names{"systemB"};
624 
625         EXPECT_CALL(dataIface, expandLocationCode).WillOnce(Return("P0-C8"));
626         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
627 
628         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
629                                           "system/entry"};
630         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
631             .WillOnce(Return(std::vector<bool>{false, false, false}));
632 
633         SRC src{entry, ad, dataIface};
634 
635         auto& callouts = src.callouts()->callouts();
636         EXPECT_EQ(callouts.size(), 2);
637 
638         EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
639         EXPECT_EQ(callouts[0]->priority(), 'H');
640 
641         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
642         EXPECT_EQ(callouts[1]->priority(), 'M');
643 
644         auto& fru1 = callouts[0]->fruIdentity();
645         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
646         EXPECT_EQ(fru1->failingComponentType(),
647                   src::FRUIdentity::symbolicFRUTrustedLocCode);
648         EXPECT_FALSE(fru1->getMaintProc());
649         EXPECT_FALSE(fru1->getSN());
650         EXPECT_FALSE(fru1->getCCIN());
651 
652         // It asked for a trusted symbolic FRU, but no location code
653         // was provided so it is switched back to a normal one
654         auto& fru2 = callouts[1]->fruIdentity();
655         EXPECT_EQ(fru2->getPN().value(), "SVCDOCS");
656         EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::symbolicFRU);
657         EXPECT_FALSE(fru2->getMaintProc());
658         EXPECT_FALSE(fru2->getSN());
659         EXPECT_FALSE(fru2->getCCIN());
660     }
661 
662     {
663         // Two hardware callouts
664         AdditionalData ad;
665         NiceMock<MockDataInterface> dataIface;
666         std::vector<std::string> names{"systemC"};
667 
668         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
669                                           "system/entry"};
670         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
671             .WillOnce(Return(std::vector<bool>{false, false, false}));
672 
673         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
674 
675         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
676             .WillOnce(Return("UXXX-P0-C8"));
677 
678         EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0))
679             .WillOnce(Return("UXXX-P0-C9"));
680 
681         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C8", 0, false))
682             .WillOnce(Return(
683                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
684 
685         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C9", 0, false))
686             .WillOnce(Return(
687                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1"));
688 
689         EXPECT_CALL(
690             dataIface,
691             getHWCalloutFields(
692                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _,
693                 _))
694             .Times(1)
695             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
696                             SetArgReferee<2>("CCCC"),
697                             SetArgReferee<3>("123456789ABC")));
698 
699         EXPECT_CALL(
700             dataIface,
701             getHWCalloutFields(
702                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1", _, _,
703                 _))
704             .Times(1)
705             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
706                             SetArgReferee<2>("DDDD"),
707                             SetArgReferee<3>("23456789ABCD")));
708 
709         SRC src{entry, ad, dataIface};
710 
711         auto& callouts = src.callouts()->callouts();
712         EXPECT_EQ(callouts.size(), 2);
713 
714         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C8");
715         EXPECT_EQ(callouts[0]->priority(), 'H');
716 
717         auto& fru1 = callouts[0]->fruIdentity();
718         EXPECT_EQ(fru1->getPN().value(), "1234567");
719         EXPECT_EQ(fru1->getCCIN().value(), "CCCC");
720         EXPECT_EQ(fru1->getSN().value(), "123456789ABC");
721 
722         EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C9");
723         EXPECT_EQ(callouts[1]->priority(), 'M');
724 
725         auto& fru2 = callouts[1]->fruIdentity();
726         EXPECT_EQ(fru2->getPN().value(), "2345678");
727         EXPECT_EQ(fru2->getCCIN().value(), "DDDD");
728         EXPECT_EQ(fru2->getSN().value(), "23456789ABCD");
729     }
730 }
731 
732 // Test that a symbolic FRU with a trusted location code callout
733 // from the registry can get its location from the
734 // CALLOUT_INVENTORY_PATH AdditionalData entry.
735 TEST_F(SRCTest, SymbolicFRUWithInvPathTest)
736 {
737     message::Entry entry;
738     entry.src.type = 0xBD;
739     entry.src.reasonCode = 0xABCD;
740     entry.subsystem = 0x42;
741     entry.src.powerFault = false;
742 
743     entry.callouts = R"(
744         [{
745             "CalloutList":
746             [
747                 {
748                     "Priority": "high",
749                     "SymbolicFRUTrusted": "service_docs",
750                     "UseInventoryLocCode": true
751                 },
752                 {
753                     "Priority": "medium",
754                     "LocCode": "P0-C8",
755                     "SymbolicFRUTrusted": "pwrsply"
756                 }
757             ]
758         }])"_json;
759 
760     {
761         // The location code for the first symbolic FRU callout will
762         // come from this inventory path since UseInventoryLocCode is set.
763         // In this case there will be no normal FRU callout for the motherboard.
764         std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
765         AdditionalData ad{adData};
766         NiceMock<MockDataInterface> dataIface;
767         std::vector<std::string> names{"systemA"};
768 
769         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
770                                           "system/entry"};
771         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
772             .WillOnce(Return(std::vector<bool>{false, false, false}));
773 
774         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
775 
776         EXPECT_CALL(dataIface, getLocationCode("motherboard"))
777             .Times(1)
778             .WillOnce(Return("Ufcs-P10"));
779 
780         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
781             .WillOnce(Return("Ufcs-P0-C8"));
782 
783         SRC src{entry, ad, dataIface};
784 
785         auto& callouts = src.callouts()->callouts();
786         EXPECT_EQ(callouts.size(), 2);
787 
788         // The location code for the first symbolic FRU callout with a
789         // trusted location code comes from the motherboard.
790         EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P10");
791         EXPECT_EQ(callouts[0]->priority(), 'H');
792         auto& fru1 = callouts[0]->fruIdentity();
793         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
794         EXPECT_EQ(fru1->failingComponentType(),
795                   src::FRUIdentity::symbolicFRUTrustedLocCode);
796 
797         // The second trusted symbolic FRU callouts uses the location
798         // code in the registry as usual.
799         EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P0-C8");
800         EXPECT_EQ(callouts[1]->priority(), 'M');
801         auto& fru2 = callouts[1]->fruIdentity();
802         EXPECT_EQ(fru2->getPN().value(), "PWRSPLY");
803         EXPECT_EQ(fru2->failingComponentType(),
804                   src::FRUIdentity::symbolicFRUTrustedLocCode);
805     }
806 
807     {
808         // This time say we want to use the location code from
809         // the inventory, but don't pass it in and the callout should
810         // end up a regular symbolic FRU
811         entry.callouts = R"(
812         [{
813             "CalloutList":
814             [
815                 {
816                     "Priority": "high",
817                     "SymbolicFRUTrusted": "service_docs",
818                     "UseInventoryLocCode": true
819                 }
820             ]
821         }])"_json;
822 
823         AdditionalData ad;
824         NiceMock<MockDataInterface> dataIface;
825         std::vector<std::string> names{"systemA"};
826 
827         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
828                                           "system/entry"};
829         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
830             .WillOnce(Return(std::vector<bool>{false, false, false}));
831 
832         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
833 
834         SRC src{entry, ad, dataIface};
835 
836         auto& callouts = src.callouts()->callouts();
837         EXPECT_EQ(callouts.size(), 1);
838 
839         EXPECT_EQ(callouts[0]->locationCode(), "");
840         EXPECT_EQ(callouts[0]->priority(), 'H');
841         auto& fru1 = callouts[0]->fruIdentity();
842         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
843         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
844     }
845 }
846 
847 // Test looking up device path fails in the callout jSON.
848 TEST_F(SRCTest, DevicePathCalloutTest)
849 {
850     message::Entry entry;
851     entry.src.type = 0xBD;
852     entry.src.reasonCode = 0xABCD;
853     entry.subsystem = 0x42;
854     entry.src.powerFault = false;
855 
856     const auto calloutJSON = R"(
857     {
858         "I2C":
859         {
860             "14":
861             {
862                 "114":
863                 {
864                     "Callouts":[
865                     {
866                         "Name": "/chassis/motherboard/cpu0",
867                         "LocationCode": "P1-C40",
868                         "Priority": "H"
869                     },
870                     {
871                         "Name": "/chassis/motherboard",
872                         "LocationCode": "P1",
873                         "Priority": "M"
874                     },
875                     {
876                         "Name": "/chassis/motherboard/bmc",
877                         "LocationCode": "P1-C15",
878                         "Priority": "L"
879                     }
880                     ],
881                     "Dest": "proc 0 target"
882                 }
883             }
884         }
885     })";
886 
887     auto dataPath = getPELReadOnlyDataPath();
888     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
889     file << calloutJSON;
890     file.close();
891 
892     NiceMock<MockDataInterface> dataIface;
893     std::vector<std::string> names{"systemA"};
894 
895     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
896                                       "system/entry"};
897     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
898         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
899 
900     EXPECT_CALL(dataIface, getSystemNames)
901         .Times(5)
902         .WillRepeatedly(Return(names));
903 
904     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
905         .Times(3)
906         .WillRepeatedly(
907             Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
908 
909     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
910         .Times(3)
911         .WillRepeatedly(
912             Return("/xyz/openbmc_project/inventory/chassis/motherboard"));
913 
914     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C15", 0, false))
915         .Times(3)
916         .WillRepeatedly(
917             Return("/xyz/openbmc_project/inventory/chassis/motherboard/bmc"));
918 
919     EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0))
920         .Times(3)
921         .WillRepeatedly(Return("Ufcs-P1-C40"));
922 
923     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
924         .Times(3)
925         .WillRepeatedly(Return("Ufcs-P1"));
926 
927     EXPECT_CALL(dataIface, expandLocationCode("P1-C15", 0))
928         .Times(3)
929         .WillRepeatedly(Return("Ufcs-P1-C15"));
930 
931     EXPECT_CALL(
932         dataIface,
933         getHWCalloutFields(
934             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
935         .Times(3)
936         .WillRepeatedly(DoAll(SetArgReferee<1>("1234567"),
937                               SetArgReferee<2>("CCCC"),
938                               SetArgReferee<3>("123456789ABC")));
939     EXPECT_CALL(
940         dataIface,
941         getHWCalloutFields("/xyz/openbmc_project/inventory/chassis/motherboard",
942                            _, _, _))
943         .Times(3)
944         .WillRepeatedly(DoAll(SetArgReferee<1>("7654321"),
945                               SetArgReferee<2>("MMMM"),
946                               SetArgReferee<3>("CBA987654321")));
947     EXPECT_CALL(
948         dataIface,
949         getHWCalloutFields(
950             "/xyz/openbmc_project/inventory/chassis/motherboard/bmc", _, _, _))
951         .Times(3)
952         .WillRepeatedly(DoAll(SetArgReferee<1>("7123456"),
953                               SetArgReferee<2>("BBBB"),
954                               SetArgReferee<3>("C123456789AB")));
955 
956     // Call this below with different AdditionalData values that
957     // result in the same callouts.
958     auto checkCallouts = [&entry, &dataIface](const auto& items) {
959         AdditionalData ad{items};
960         SRC src{entry, ad, dataIface};
961 
962         ASSERT_TRUE(src.callouts());
963         auto& callouts = src.callouts()->callouts();
964 
965         ASSERT_EQ(callouts.size(), 3);
966 
967         {
968             EXPECT_EQ(callouts[0]->priority(), 'H');
969             EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P1-C40");
970 
971             auto& fru = callouts[0]->fruIdentity();
972             EXPECT_EQ(fru->getPN().value(), "1234567");
973             EXPECT_EQ(fru->getCCIN().value(), "CCCC");
974             EXPECT_EQ(fru->getSN().value(), "123456789ABC");
975         }
976         {
977             EXPECT_EQ(callouts[1]->priority(), 'M');
978             EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P1");
979 
980             auto& fru = callouts[1]->fruIdentity();
981             EXPECT_EQ(fru->getPN().value(), "7654321");
982             EXPECT_EQ(fru->getCCIN().value(), "MMMM");
983             EXPECT_EQ(fru->getSN().value(), "CBA987654321");
984         }
985         {
986             EXPECT_EQ(callouts[2]->priority(), 'L');
987             EXPECT_EQ(callouts[2]->locationCode(), "Ufcs-P1-C15");
988 
989             auto& fru = callouts[2]->fruIdentity();
990             EXPECT_EQ(fru->getPN().value(), "7123456");
991             EXPECT_EQ(fru->getCCIN().value(), "BBBB");
992             EXPECT_EQ(fru->getSN().value(), "C123456789AB");
993         }
994     };
995 
996     {
997         // Callouts based on the device path
998         std::vector<std::string> items{
999             "CALLOUT_ERRNO=5",
1000             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
1001             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
1002 
1003         checkCallouts(items);
1004     }
1005 
1006     {
1007         // Callouts based on the I2C bus and address
1008         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
1009                                        "CALLOUT_IIC_ADDR=0x72"};
1010         checkCallouts(items);
1011     }
1012 
1013     {
1014         // Also based on I2C bus and address, but with bus = /dev/i2c-14
1015         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
1016                                        "CALLOUT_IIC_ADDR=0x72"};
1017         checkCallouts(items);
1018     }
1019 
1020     {
1021         // Callout not found
1022         std::vector<std::string> items{
1023             "CALLOUT_ERRNO=5",
1024             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
1025             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-24/24-0012"};
1026 
1027         AdditionalData ad{items};
1028         SRC src{entry, ad, dataIface};
1029 
1030         EXPECT_FALSE(src.callouts());
1031         ASSERT_EQ(src.getDebugData().size(), 1);
1032         EXPECT_EQ(src.getDebugData()[0],
1033                   "Problem looking up I2C callouts on 24 18: "
1034                   "[json.exception.out_of_range.403] key '24' not found");
1035     }
1036 
1037     {
1038         // Callout not found
1039         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=22",
1040                                        "CALLOUT_IIC_ADDR=0x99"};
1041         AdditionalData ad{items};
1042         SRC src{entry, ad, dataIface};
1043 
1044         EXPECT_FALSE(src.callouts());
1045         ASSERT_EQ(src.getDebugData().size(), 1);
1046         EXPECT_EQ(src.getDebugData()[0],
1047                   "Problem looking up I2C callouts on 22 153: "
1048                   "[json.exception.out_of_range.403] key '22' not found");
1049     }
1050 
1051     fs::remove_all(dataPath);
1052 }
1053 
1054 // Test when callouts are passed in via JSON
1055 TEST_F(SRCTest, JsonCalloutsTest)
1056 {
1057     const auto jsonCallouts = R"(
1058         [
1059             {
1060                 "LocationCode": "P0-C1",
1061                 "Priority": "H",
1062                 "MRUs": [
1063                     {
1064                         "ID": 42,
1065                         "Priority": "H"
1066                     },
1067                     {
1068                         "ID": 43,
1069                         "Priority": "M"
1070                     }
1071                 ]
1072             },
1073             {
1074                 "InventoryPath": "/inv/system/chassis/motherboard/cpu0",
1075                 "Priority": "M",
1076                 "Guarded": true,
1077                 "Deconfigured": true
1078             },
1079             {
1080                 "Procedure": "PROCEDU",
1081                 "Priority": "A"
1082             },
1083             {
1084                 "SymbolicFRU": "TRUSTED",
1085                 "Priority": "B",
1086                 "TrustedLocationCode": true,
1087                 "LocationCode": "P1-C23"
1088             },
1089             {
1090                 "SymbolicFRU": "FRUTST1",
1091                 "Priority": "C",
1092                 "LocationCode": "P1-C24"
1093             },
1094             {
1095                 "SymbolicFRU": "FRUTST2LONG",
1096                 "Priority": "L"
1097             }
1098         ]
1099     )"_json;
1100 
1101     message::Entry entry;
1102     entry.src.type = 0xBD;
1103     entry.src.reasonCode = 0xABCD;
1104     entry.subsystem = 0x42;
1105     entry.src.powerFault = false;
1106 
1107     AdditionalData ad;
1108     NiceMock<MockDataInterface> dataIface;
1109 
1110     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1111                                       "system/entry"};
1112     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1113         .WillOnce(Return(std::vector<bool>{false, false, false}));
1114 
1115     // Callout 0 mock calls
1116     {
1117         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1118             .Times(1)
1119             .WillOnce(Return("UXXX-P0-C1"));
1120         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1121             .Times(1)
1122             .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
1123         EXPECT_CALL(
1124             dataIface,
1125             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1126             .Times(1)
1127             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1128                             SetArgReferee<2>("CCCC"),
1129                             SetArgReferee<3>("123456789ABC")));
1130     }
1131     // Callout 1 mock calls
1132     {
1133         EXPECT_CALL(dataIface,
1134                     getLocationCode("/inv/system/chassis/motherboard/cpu0"))
1135             .WillOnce(Return("UYYY-P5"));
1136         EXPECT_CALL(
1137             dataIface,
1138             getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _))
1139             .Times(1)
1140             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
1141                             SetArgReferee<2>("DDDD"),
1142                             SetArgReferee<3>("23456789ABCD")));
1143     }
1144     // Callout 3 mock calls
1145     {
1146         EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0))
1147             .Times(1)
1148             .WillOnce(Return("UXXX-P1-C23"));
1149     }
1150     // Callout 4 mock calls
1151     {
1152         EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0))
1153             .Times(1)
1154             .WillOnce(Return("UXXX-P1-C24"));
1155     }
1156 
1157     SRC src{entry, ad, jsonCallouts, dataIface};
1158     ASSERT_TRUE(src.callouts());
1159 
1160     // Check the guarded and deconfigured flags
1161     EXPECT_TRUE(src.hexwordData()[3] & 0x03000000);
1162 
1163     const auto& callouts = src.callouts()->callouts();
1164     ASSERT_EQ(callouts.size(), 6);
1165 
1166     // Check callout 0
1167     {
1168         EXPECT_EQ(callouts[0]->priority(), 'H');
1169         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1170 
1171         auto& fru = callouts[0]->fruIdentity();
1172         EXPECT_EQ(fru->getPN().value(), "1234567");
1173         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1174         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1175         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1176 
1177         auto& mruCallouts = callouts[0]->mru();
1178         ASSERT_TRUE(mruCallouts);
1179         auto& mrus = mruCallouts->mrus();
1180         ASSERT_EQ(mrus.size(), 2);
1181         EXPECT_EQ(mrus[0].id, 42);
1182         EXPECT_EQ(mrus[0].priority, 'H');
1183         EXPECT_EQ(mrus[1].id, 43);
1184         EXPECT_EQ(mrus[1].priority, 'M');
1185     }
1186 
1187     // Check callout 1
1188     {
1189         EXPECT_EQ(callouts[1]->priority(), 'M');
1190         EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5");
1191 
1192         auto& fru = callouts[1]->fruIdentity();
1193         EXPECT_EQ(fru->getPN().value(), "2345678");
1194         EXPECT_EQ(fru->getCCIN().value(), "DDDD");
1195         EXPECT_EQ(fru->getSN().value(), "23456789ABCD");
1196         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1197     }
1198 
1199     // Check callout 2
1200     {
1201         EXPECT_EQ(callouts[2]->priority(), 'A');
1202         EXPECT_EQ(callouts[2]->locationCode(), "");
1203 
1204         auto& fru = callouts[2]->fruIdentity();
1205         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1206         EXPECT_EQ(fru->failingComponentType(),
1207                   src::FRUIdentity::maintenanceProc);
1208     }
1209 
1210     // Check callout 3
1211     {
1212         EXPECT_EQ(callouts[3]->priority(), 'B');
1213         EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23");
1214 
1215         auto& fru = callouts[3]->fruIdentity();
1216         EXPECT_EQ(fru->getPN().value(), "TRUSTED");
1217         EXPECT_EQ(fru->failingComponentType(),
1218                   src::FRUIdentity::symbolicFRUTrustedLocCode);
1219     }
1220 
1221     // Check callout 4
1222     {
1223         EXPECT_EQ(callouts[4]->priority(), 'C');
1224         EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24");
1225 
1226         auto& fru = callouts[4]->fruIdentity();
1227         EXPECT_EQ(fru->getPN().value(), "FRUTST1");
1228         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1229     }
1230 
1231     // Check callout 5
1232     {
1233         EXPECT_EQ(callouts[5]->priority(), 'L');
1234         EXPECT_EQ(callouts[5]->locationCode(), "");
1235 
1236         auto& fru = callouts[5]->fruIdentity();
1237         EXPECT_EQ(fru->getPN().value(), "FRUTST2");
1238         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1239     }
1240 
1241     // Check that it didn't find any errors
1242     const auto& data = src.getDebugData();
1243     EXPECT_TRUE(data.empty());
1244 }
1245 
1246 TEST_F(SRCTest, JsonBadCalloutsTest)
1247 {
1248     // The first call will have a Throw in a mock call.
1249     // The second will have a different Throw in a mock call.
1250     // The others have issues with the Priority field.
1251     const auto jsonCallouts = R"(
1252         [
1253             {
1254                 "LocationCode": "P0-C1",
1255                 "Priority": "H"
1256             },
1257             {
1258                 "LocationCode": "P0-C2",
1259                 "Priority": "H"
1260             },
1261             {
1262                 "LocationCode": "P0-C3"
1263             },
1264             {
1265                 "LocationCode": "P0-C4",
1266                 "Priority": "X"
1267             }
1268         ]
1269     )"_json;
1270 
1271     message::Entry entry;
1272     entry.src.type = 0xBD;
1273     entry.src.reasonCode = 0xABCD;
1274     entry.subsystem = 0x42;
1275     entry.src.powerFault = false;
1276 
1277     AdditionalData ad;
1278     NiceMock<MockDataInterface> dataIface;
1279 
1280     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1281                                       "system/entry"};
1282     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1283         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
1284 
1285     // Callout 0 mock calls
1286     // Expand location code will fail, so the unexpanded location
1287     // code should show up in the callout instead.
1288     {
1289         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1290             .WillOnce(Throw(std::runtime_error("Fail")));
1291 
1292         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1293             .Times(1)
1294             .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
1295         EXPECT_CALL(
1296             dataIface,
1297             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1298             .Times(1)
1299             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1300                             SetArgReferee<2>("CCCC"),
1301                             SetArgReferee<3>("123456789ABC")));
1302     }
1303 
1304     // Callout 1 mock calls
1305     // getInventoryFromLocCode will fail
1306     {
1307         EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0))
1308             .Times(1)
1309             .WillOnce(Return("UXXX-P0-C2"));
1310 
1311         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false))
1312             .Times(1)
1313             .WillOnce(Throw(std::runtime_error("Fail")));
1314     }
1315 
1316     SRC src{entry, ad, jsonCallouts, dataIface};
1317 
1318     ASSERT_TRUE(src.callouts());
1319 
1320     const auto& callouts = src.callouts()->callouts();
1321 
1322     // Only the first callout was successful
1323     ASSERT_EQ(callouts.size(), 1);
1324 
1325     {
1326         EXPECT_EQ(callouts[0]->priority(), 'H');
1327         EXPECT_EQ(callouts[0]->locationCode(), "P0-C1");
1328 
1329         auto& fru = callouts[0]->fruIdentity();
1330         EXPECT_EQ(fru->getPN().value(), "1234567");
1331         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1332         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1333         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1334     }
1335 
1336     const auto& data = src.getDebugData();
1337     ASSERT_EQ(data.size(), 4);
1338     EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail");
1339     EXPECT_STREQ(data[1].c_str(),
1340                  "Failed extracting callout data from JSON: Unable to "
1341                  "get inventory path from location code: P0-C2: Fail");
1342     EXPECT_STREQ(data[2].c_str(),
1343                  "Failed extracting callout data from JSON: "
1344                  "[json.exception.out_of_range.403] key 'Priority' not found");
1345     EXPECT_STREQ(data[3].c_str(),
1346                  "Failed extracting callout data from JSON: Invalid "
1347                  "priority 'X' found in JSON callout");
1348 }
1349 
1350 // Test that an inventory path callout can have
1351 // a different priority than H.
1352 TEST_F(SRCTest, InventoryCalloutTestPriority)
1353 {
1354     message::Entry entry;
1355     entry.src.type = 0xBD;
1356     entry.src.reasonCode = 0xABCD;
1357     entry.subsystem = 0x42;
1358     entry.src.powerFault = false;
1359 
1360     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard",
1361                                     "CALLOUT_PRIORITY=M"};
1362     AdditionalData ad{adData};
1363     NiceMock<MockDataInterface> dataIface;
1364 
1365     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1366                                       "system/entry"};
1367     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1368         .WillOnce(Return(std::vector<bool>{false, false, false}));
1369 
1370     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
1371         .WillOnce(Return("UTMS-P1"));
1372 
1373     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
1374         .Times(1)
1375         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1376                         SetArgReferee<3>("123456789ABC")));
1377 
1378     SRC src{entry, ad, dataIface};
1379     EXPECT_TRUE(src.valid());
1380 
1381     ASSERT_TRUE(src.callouts());
1382 
1383     EXPECT_EQ(src.callouts()->callouts().size(), 1);
1384 
1385     auto& callout = src.callouts()->callouts().front();
1386 
1387     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
1388     EXPECT_EQ(callout->priority(), 'M');
1389 }
1390 
1391 // Test for bmc & platform dump status bits
1392 TEST_F(SRCTest, DumpStatusBitsCheck)
1393 {
1394     message::Entry entry;
1395     entry.src.type = 0xBD;
1396     entry.src.reasonCode = 0xABCD;
1397     entry.subsystem = 0x42;
1398     entry.src.powerFault = false;
1399 
1400     AdditionalData ad;
1401     NiceMock<MockDataInterface> dataIface;
1402     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1403                                       "system/entry"};
1404 
1405     {
1406         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1407             .WillOnce(Return(std::vector<bool>{true, false, false}));
1408 
1409         SRC src{entry, ad, dataIface};
1410         EXPECT_TRUE(src.valid());
1411 
1412         const auto& hexwords = src.hexwordData();
1413         EXPECT_EQ(0x00080055, hexwords[0]);
1414     }
1415 
1416     {
1417         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1418             .WillOnce(Return(std::vector<bool>{false, true, false}));
1419 
1420         SRC src{entry, ad, dataIface};
1421         EXPECT_TRUE(src.valid());
1422 
1423         const auto& hexwords = src.hexwordData();
1424         EXPECT_EQ(0x00000255, hexwords[0]);
1425     }
1426 
1427     {
1428         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1429             .WillOnce(Return(std::vector<bool>{false, false, true}));
1430 
1431         SRC src{entry, ad, dataIface};
1432         EXPECT_TRUE(src.valid());
1433 
1434         const auto& hexwords = src.hexwordData();
1435         EXPECT_EQ(0x00000455, hexwords[0]);
1436     }
1437 
1438     {
1439         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1440             .WillOnce(Return(std::vector<bool>{true, true, true}));
1441 
1442         SRC src{entry, ad, dataIface};
1443         EXPECT_TRUE(src.valid());
1444 
1445         const auto& hexwords = src.hexwordData();
1446         EXPECT_EQ(0x00080655, hexwords[0]);
1447     }
1448 }
1449