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     SRC src{entry, ad, dataIface};
210 
211     EXPECT_TRUE(src.valid());
212     EXPECT_TRUE(src.isPowerFaultEvent());
213     EXPECT_EQ(src.size(), baseSRCSize);
214 
215     const auto& hexwords = src.hexwordData();
216 
217     // The spec always refers to SRC words 2 - 9, and as the hexwordData()
218     // array index starts at 0 use the math in the [] below to make it easier
219     // to tell what is being accessed.
220     EXPECT_EQ(hexwords[2 - 2] & 0xF0000000, 0);    // Partition dump status
221     EXPECT_EQ(hexwords[2 - 2] & 0x00F00000, 0);    // Partition boot type
222     EXPECT_EQ(hexwords[2 - 2] & 0x000000FF, 0x55); // SRC format
223     EXPECT_EQ(hexwords[3 - 2] & 0x000000FF, 0x10); // BMC position
224     EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0xABCD0000); // Motherboard CCIN
225 
226     // Validate more fields here as the code starts filling them in.
227 
228     // Ensure hex word 5 wasn't allowed to be set to TEST1's contents
229     EXPECT_EQ(hexwords[5 - 2], 0);
230 
231     // The user defined hex word fields specifed in the additional data.
232     EXPECT_EQ(hexwords[6 - 2], 0x12345678); // TEST1
233     EXPECT_EQ(hexwords[7 - 2], 12345678);   // TEST2
234     EXPECT_EQ(hexwords[8 - 2], 0xdef);      // TEST3
235     EXPECT_EQ(hexwords[9 - 2], 0);          // TEST4, but can't convert a 'Z'
236 
237     EXPECT_EQ(src.asciiString(), "BD42ABCD                        ");
238 
239     // No callouts
240     EXPECT_FALSE(src.callouts());
241 
242     // May as well spot check the flatten/unflatten
243     std::vector<uint8_t> data;
244     Stream stream{data};
245     src.flatten(stream);
246 
247     stream.offset(0);
248     SRC newSRC{stream};
249 
250     EXPECT_TRUE(newSRC.valid());
251     EXPECT_EQ(newSRC.isPowerFaultEvent(), src.isPowerFaultEvent());
252     EXPECT_EQ(newSRC.asciiString(), src.asciiString());
253     EXPECT_FALSE(newSRC.callouts());
254 }
255 
256 // Create an SRC to test POWER_THERMAL_CRITICAL_FAULT set to TRUE
257 // sets the power fault bit in SRC
258 TEST_F(SRCTest, PowerFaultTest)
259 {
260     message::Entry entry;
261     entry.src.type = 0xBD;
262     entry.src.reasonCode = 0xABCD;
263     entry.subsystem = 0x42;
264     entry.src.powerFault = false;
265 
266     // Values for the SRC words pointed to above
267     std::vector<std::string> adData{"POWER_THERMAL_CRITICAL_FAULT=TRUE",
268                                     "TEST2=12345678", "TEST3=0XDEF", "TEST4=Z"};
269     AdditionalData ad{adData};
270     NiceMock<MockDataInterface> dataIface;
271 
272     SRC src{entry, ad, dataIface};
273 
274     EXPECT_TRUE(src.valid());
275     EXPECT_TRUE(src.isPowerFaultEvent());
276     EXPECT_EQ(src.size(), baseSRCSize);
277 }
278 
279 // Test when the CCIN string isn't a 4 character number
280 TEST_F(SRCTest, BadCCINTest)
281 {
282     message::Entry entry;
283     entry.src.type = 0xBD;
284     entry.src.reasonCode = 0xABCD;
285     entry.subsystem = 0x42;
286     entry.src.powerFault = false;
287 
288     std::vector<std::string> adData{};
289     AdditionalData ad{adData};
290     NiceMock<MockDataInterface> dataIface;
291 
292     // First it isn't a number, then it is too long,
293     // then it is empty.
294     EXPECT_CALL(dataIface, getMotherboardCCIN)
295         .WillOnce(Return("X"))
296         .WillOnce(Return("12345"))
297         .WillOnce(Return(""));
298 
299     // The CCIN in the first half should still be 0 each time.
300     {
301         SRC src{entry, ad, dataIface};
302         EXPECT_TRUE(src.valid());
303         const auto& hexwords = src.hexwordData();
304         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
305     }
306 
307     {
308         SRC src{entry, ad, dataIface};
309         EXPECT_TRUE(src.valid());
310         const auto& hexwords = src.hexwordData();
311         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
312     }
313 
314     {
315         SRC src{entry, ad, dataIface};
316         EXPECT_TRUE(src.valid());
317         const auto& hexwords = src.hexwordData();
318         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
319     }
320 }
321 
322 // Test the getErrorDetails function
323 TEST_F(SRCTest, MessageSubstitutionTest)
324 {
325     auto path = SRCTest::writeData(testRegistry);
326     message::Registry registry{path};
327     auto entry = registry.lookup("0xABCD", message::LookupType::reasonCode);
328 
329     std::vector<std::string> adData{"COMPID=0x1", "FREQUENCY=0x4",
330                                     "DURATION=30", "ERRORCODE=0x01ABCDEF"};
331     AdditionalData ad{adData};
332     NiceMock<MockDataInterface> dataIface;
333 
334     SRC src{*entry, ad, dataIface};
335     EXPECT_TRUE(src.valid());
336 
337     auto errorDetails = src.getErrorDetails(registry, DetailLevel::message);
338     ASSERT_TRUE(errorDetails);
339     EXPECT_EQ(
340         errorDetails.value(),
341         "Comp 0x1 failed 0x4 times over 0x1E secs with ErrorCode 0x1ABCDEF");
342 }
343 // Test that an inventory path callout string is
344 // converted into the appropriate FRU callout.
345 TEST_F(SRCTest, InventoryCalloutTest)
346 {
347     message::Entry entry;
348     entry.src.type = 0xBD;
349     entry.src.reasonCode = 0xABCD;
350     entry.subsystem = 0x42;
351     entry.src.powerFault = false;
352 
353     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
354     AdditionalData ad{adData};
355     NiceMock<MockDataInterface> dataIface;
356 
357     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
358         .WillOnce(Return("UTMS-P1"));
359 
360     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
361         .Times(1)
362         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
363                         SetArgReferee<3>("123456789ABC")));
364 
365     SRC src{entry, ad, dataIface};
366     EXPECT_TRUE(src.valid());
367 
368     ASSERT_TRUE(src.callouts());
369 
370     EXPECT_EQ(src.callouts()->callouts().size(), 1);
371 
372     auto& callout = src.callouts()->callouts().front();
373 
374     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
375     EXPECT_EQ(callout->priority(), 'H');
376 
377     auto& fru = callout->fruIdentity();
378 
379     EXPECT_EQ(fru->getPN().value(), "1234567");
380     EXPECT_EQ(fru->getCCIN().value(), "CCCC");
381     EXPECT_EQ(fru->getSN().value(), "123456789ABC");
382 
383     // flatten and unflatten
384     std::vector<uint8_t> data;
385     Stream stream{data};
386     src.flatten(stream);
387 
388     stream.offset(0);
389     SRC newSRC{stream};
390     EXPECT_TRUE(newSRC.valid());
391     ASSERT_TRUE(src.callouts());
392     EXPECT_EQ(src.callouts()->callouts().size(), 1);
393 }
394 
395 // Test that when the location code can't be obtained that
396 // a procedure callout is used.
397 TEST_F(SRCTest, InventoryCalloutNoLocCodeTest)
398 {
399     message::Entry entry;
400     entry.src.type = 0xBD;
401     entry.src.reasonCode = 0xABCD;
402     entry.subsystem = 0x42;
403     entry.src.powerFault = false;
404 
405     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
406     AdditionalData ad{adData};
407     NiceMock<MockDataInterface> dataIface;
408 
409     auto func = []() {
410         throw sdbusplus::exception::SdBusError(5, "Error");
411         return std::string{};
412     };
413 
414     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
415         .Times(1)
416         .WillOnce(InvokeWithoutArgs(func));
417 
418     EXPECT_CALL(dataIface, getHWCalloutFields(_, _, _, _)).Times(0);
419 
420     SRC src{entry, ad, dataIface};
421     EXPECT_TRUE(src.valid());
422 
423     ASSERT_TRUE(src.callouts());
424 
425     EXPECT_EQ(src.callouts()->callouts().size(), 1);
426 
427     auto& callout = src.callouts()->callouts().front();
428     EXPECT_EQ(callout->locationCodeSize(), 0);
429     EXPECT_EQ(callout->priority(), 'H');
430 
431     auto& fru = callout->fruIdentity();
432 
433     EXPECT_EQ(fru->getMaintProc().value(), "BMCSP01");
434     EXPECT_FALSE(fru->getPN());
435     EXPECT_FALSE(fru->getSN());
436     EXPECT_FALSE(fru->getCCIN());
437 
438     // flatten and unflatten
439     std::vector<uint8_t> data;
440     Stream stream{data};
441     src.flatten(stream);
442 
443     stream.offset(0);
444     SRC newSRC{stream};
445     EXPECT_TRUE(newSRC.valid());
446     ASSERT_TRUE(src.callouts());
447     EXPECT_EQ(src.callouts()->callouts().size(), 1);
448 }
449 
450 // Test that when the VPD can't be obtained that
451 // a callout is still created.
452 TEST_F(SRCTest, InventoryCalloutNoVPDTest)
453 {
454     message::Entry entry;
455     entry.src.type = 0xBD;
456     entry.src.reasonCode = 0xABCD;
457     entry.subsystem = 0x42;
458     entry.src.powerFault = false;
459 
460     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
461     AdditionalData ad{adData};
462     NiceMock<MockDataInterface> dataIface;
463 
464     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
465         .Times(1)
466         .WillOnce(Return("UTMS-P10"));
467 
468     auto func = []() { throw sdbusplus::exception::SdBusError(5, "Error"); };
469 
470     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
471         .Times(1)
472         .WillOnce(InvokeWithoutArgs(func));
473 
474     SRC src{entry, ad, dataIface};
475     EXPECT_TRUE(src.valid());
476     ASSERT_TRUE(src.callouts());
477     EXPECT_EQ(src.callouts()->callouts().size(), 1);
478 
479     auto& callout = src.callouts()->callouts().front();
480     EXPECT_EQ(callout->locationCode(), "UTMS-P10");
481     EXPECT_EQ(callout->priority(), 'H');
482 
483     auto& fru = callout->fruIdentity();
484 
485     EXPECT_EQ(fru->getPN(), "");
486     EXPECT_EQ(fru->getCCIN(), "");
487     EXPECT_EQ(fru->getSN(), "");
488     EXPECT_FALSE(fru->getMaintProc());
489 
490     // flatten and unflatten
491     std::vector<uint8_t> data;
492     Stream stream{data};
493     src.flatten(stream);
494 
495     stream.offset(0);
496     SRC newSRC{stream};
497     EXPECT_TRUE(newSRC.valid());
498     ASSERT_TRUE(src.callouts());
499     EXPECT_EQ(src.callouts()->callouts().size(), 1);
500 }
501 
502 TEST_F(SRCTest, RegistryCalloutTest)
503 {
504     message::Entry entry;
505     entry.src.type = 0xBD;
506     entry.src.reasonCode = 0xABCD;
507     entry.subsystem = 0x42;
508     entry.src.powerFault = false;
509 
510     entry.callouts = R"(
511         [
512         {
513             "System": "systemA",
514             "CalloutList":
515             [
516                 {
517                     "Priority": "high",
518                     "SymbolicFRU": "service_docs"
519                 },
520                 {
521                     "Priority": "medium",
522                     "Procedure": "no_vpd_for_fru"
523                 }
524             ]
525         },
526         {
527             "System": "systemB",
528             "CalloutList":
529             [
530                 {
531                     "Priority": "high",
532                     "LocCode": "P0-C8",
533                     "SymbolicFRUTrusted": "service_docs"
534                 },
535                 {
536                     "Priority": "medium",
537                     "SymbolicFRUTrusted": "service_docs"
538                 }
539             ]
540         },
541         {
542             "System": "systemC",
543             "CalloutList":
544             [
545                 {
546                     "Priority": "high",
547                     "LocCode": "P0-C8"
548                 },
549                 {
550                     "Priority": "medium",
551                     "LocCode": "P0-C9"
552                 }
553             ]
554         }
555         ])"_json;
556 
557     {
558         // Call out a symbolic FRU and a procedure
559         AdditionalData ad;
560         NiceMock<MockDataInterface> dataIface;
561         std::vector<std::string> names{"systemA"};
562 
563         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
564 
565         SRC src{entry, ad, dataIface};
566 
567         auto& callouts = src.callouts()->callouts();
568         ASSERT_EQ(callouts.size(), 2);
569 
570         EXPECT_EQ(callouts[0]->locationCodeSize(), 0);
571         EXPECT_EQ(callouts[0]->priority(), 'H');
572 
573         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
574         EXPECT_EQ(callouts[1]->priority(), 'M');
575 
576         auto& fru1 = callouts[0]->fruIdentity();
577         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
578         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
579         EXPECT_FALSE(fru1->getMaintProc());
580         EXPECT_FALSE(fru1->getSN());
581         EXPECT_FALSE(fru1->getCCIN());
582 
583         auto& fru2 = callouts[1]->fruIdentity();
584         EXPECT_EQ(fru2->getMaintProc().value(), "BMCSP01");
585         EXPECT_EQ(fru2->failingComponentType(),
586                   src::FRUIdentity::maintenanceProc);
587         EXPECT_FALSE(fru2->getPN());
588         EXPECT_FALSE(fru2->getSN());
589         EXPECT_FALSE(fru2->getCCIN());
590     }
591 
592     {
593         // Call out a trusted symbolic FRU with a location code, and
594         // another one without.
595         AdditionalData ad;
596         NiceMock<MockDataInterface> dataIface;
597         std::vector<std::string> names{"systemB"};
598 
599         EXPECT_CALL(dataIface, expandLocationCode).WillOnce(Return("P0-C8"));
600         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
601 
602         SRC src{entry, ad, dataIface};
603 
604         auto& callouts = src.callouts()->callouts();
605         EXPECT_EQ(callouts.size(), 2);
606 
607         EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
608         EXPECT_EQ(callouts[0]->priority(), 'H');
609 
610         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
611         EXPECT_EQ(callouts[1]->priority(), 'M');
612 
613         auto& fru1 = callouts[0]->fruIdentity();
614         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
615         EXPECT_EQ(fru1->failingComponentType(),
616                   src::FRUIdentity::symbolicFRUTrustedLocCode);
617         EXPECT_FALSE(fru1->getMaintProc());
618         EXPECT_FALSE(fru1->getSN());
619         EXPECT_FALSE(fru1->getCCIN());
620 
621         // It asked for a trusted symbolic FRU, but no location code
622         // was provided so it is switched back to a normal one
623         auto& fru2 = callouts[1]->fruIdentity();
624         EXPECT_EQ(fru2->getPN().value(), "SVCDOCS");
625         EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::symbolicFRU);
626         EXPECT_FALSE(fru2->getMaintProc());
627         EXPECT_FALSE(fru2->getSN());
628         EXPECT_FALSE(fru2->getCCIN());
629     }
630 
631     {
632         // Two hardware callouts
633         AdditionalData ad;
634         NiceMock<MockDataInterface> dataIface;
635         std::vector<std::string> names{"systemC"};
636 
637         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
638 
639         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
640             .WillOnce(Return("UXXX-P0-C8"));
641 
642         EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0))
643             .WillOnce(Return("UXXX-P0-C9"));
644 
645         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C8", 0, false))
646             .WillOnce(Return(
647                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
648 
649         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C9", 0, false))
650             .WillOnce(Return(
651                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1"));
652 
653         EXPECT_CALL(
654             dataIface,
655             getHWCalloutFields(
656                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _,
657                 _))
658             .Times(1)
659             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
660                             SetArgReferee<2>("CCCC"),
661                             SetArgReferee<3>("123456789ABC")));
662 
663         EXPECT_CALL(
664             dataIface,
665             getHWCalloutFields(
666                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1", _, _,
667                 _))
668             .Times(1)
669             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
670                             SetArgReferee<2>("DDDD"),
671                             SetArgReferee<3>("23456789ABCD")));
672 
673         SRC src{entry, ad, dataIface};
674 
675         auto& callouts = src.callouts()->callouts();
676         EXPECT_EQ(callouts.size(), 2);
677 
678         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C8");
679         EXPECT_EQ(callouts[0]->priority(), 'H');
680 
681         auto& fru1 = callouts[0]->fruIdentity();
682         EXPECT_EQ(fru1->getPN().value(), "1234567");
683         EXPECT_EQ(fru1->getCCIN().value(), "CCCC");
684         EXPECT_EQ(fru1->getSN().value(), "123456789ABC");
685 
686         EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C9");
687         EXPECT_EQ(callouts[1]->priority(), 'M');
688 
689         auto& fru2 = callouts[1]->fruIdentity();
690         EXPECT_EQ(fru2->getPN().value(), "2345678");
691         EXPECT_EQ(fru2->getCCIN().value(), "DDDD");
692         EXPECT_EQ(fru2->getSN().value(), "23456789ABCD");
693     }
694 }
695 
696 // Test that a symbolic FRU with a trusted location code callout
697 // from the registry can get its location from the
698 // CALLOUT_INVENTORY_PATH AdditionalData entry.
699 TEST_F(SRCTest, SymbolicFRUWithInvPathTest)
700 {
701     message::Entry entry;
702     entry.src.type = 0xBD;
703     entry.src.reasonCode = 0xABCD;
704     entry.subsystem = 0x42;
705     entry.src.powerFault = false;
706 
707     entry.callouts = R"(
708         [{
709             "CalloutList":
710             [
711                 {
712                     "Priority": "high",
713                     "SymbolicFRUTrusted": "service_docs",
714                     "UseInventoryLocCode": true
715                 },
716                 {
717                     "Priority": "medium",
718                     "LocCode": "P0-C8",
719                     "SymbolicFRUTrusted": "pwrsply"
720                 }
721             ]
722         }])"_json;
723 
724     {
725         // The location code for the first symbolic FRU callout will
726         // come from this inventory path since UseInventoryLocCode is set.
727         // In this case there will be no normal FRU callout for the motherboard.
728         std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
729         AdditionalData ad{adData};
730         NiceMock<MockDataInterface> dataIface;
731         std::vector<std::string> names{"systemA"};
732 
733         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
734 
735         EXPECT_CALL(dataIface, getLocationCode("motherboard"))
736             .Times(1)
737             .WillOnce(Return("Ufcs-P10"));
738 
739         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
740             .WillOnce(Return("Ufcs-P0-C8"));
741 
742         SRC src{entry, ad, dataIface};
743 
744         auto& callouts = src.callouts()->callouts();
745         EXPECT_EQ(callouts.size(), 2);
746 
747         // The location code for the first symbolic FRU callout with a
748         // trusted location code comes from the motherboard.
749         EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P10");
750         EXPECT_EQ(callouts[0]->priority(), 'H');
751         auto& fru1 = callouts[0]->fruIdentity();
752         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
753         EXPECT_EQ(fru1->failingComponentType(),
754                   src::FRUIdentity::symbolicFRUTrustedLocCode);
755 
756         // The second trusted symbolic FRU callouts uses the location
757         // code in the registry as usual.
758         EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P0-C8");
759         EXPECT_EQ(callouts[1]->priority(), 'M');
760         auto& fru2 = callouts[1]->fruIdentity();
761         EXPECT_EQ(fru2->getPN().value(), "PWRSPLY");
762         EXPECT_EQ(fru2->failingComponentType(),
763                   src::FRUIdentity::symbolicFRUTrustedLocCode);
764     }
765 
766     {
767         // This time say we want to use the location code from
768         // the inventory, but don't pass it in and the callout should
769         // end up a regular symbolic FRU
770         entry.callouts = R"(
771         [{
772             "CalloutList":
773             [
774                 {
775                     "Priority": "high",
776                     "SymbolicFRUTrusted": "service_docs",
777                     "UseInventoryLocCode": true
778                 }
779             ]
780         }])"_json;
781 
782         AdditionalData ad;
783         NiceMock<MockDataInterface> dataIface;
784         std::vector<std::string> names{"systemA"};
785 
786         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
787 
788         SRC src{entry, ad, dataIface};
789 
790         auto& callouts = src.callouts()->callouts();
791         EXPECT_EQ(callouts.size(), 1);
792 
793         EXPECT_EQ(callouts[0]->locationCode(), "");
794         EXPECT_EQ(callouts[0]->priority(), 'H');
795         auto& fru1 = callouts[0]->fruIdentity();
796         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
797         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
798     }
799 }
800 
801 // Test looking up device path fails in the callout jSON.
802 TEST_F(SRCTest, DevicePathCalloutTest)
803 {
804     message::Entry entry;
805     entry.src.type = 0xBD;
806     entry.src.reasonCode = 0xABCD;
807     entry.subsystem = 0x42;
808     entry.src.powerFault = false;
809 
810     const auto calloutJSON = R"(
811     {
812         "I2C":
813         {
814             "14":
815             {
816                 "114":
817                 {
818                     "Callouts":[
819                     {
820                         "Name": "/chassis/motherboard/cpu0",
821                         "LocationCode": "P1-C40",
822                         "Priority": "H"
823                     },
824                     {
825                         "Name": "/chassis/motherboard",
826                         "LocationCode": "P1",
827                         "Priority": "M"
828                     },
829                     {
830                         "Name": "/chassis/motherboard/bmc",
831                         "LocationCode": "P1-C15",
832                         "Priority": "L"
833                     }
834                     ],
835                     "Dest": "proc 0 target"
836                 }
837             }
838         }
839     })";
840 
841     auto dataPath = getPELReadOnlyDataPath();
842     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
843     file << calloutJSON;
844     file.close();
845 
846     NiceMock<MockDataInterface> dataIface;
847     std::vector<std::string> names{"systemA"};
848 
849     EXPECT_CALL(dataIface, getSystemNames)
850         .Times(5)
851         .WillRepeatedly(Return(names));
852 
853     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
854         .Times(3)
855         .WillRepeatedly(
856             Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
857 
858     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
859         .Times(3)
860         .WillRepeatedly(
861             Return("/xyz/openbmc_project/inventory/chassis/motherboard"));
862 
863     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C15", 0, false))
864         .Times(3)
865         .WillRepeatedly(
866             Return("/xyz/openbmc_project/inventory/chassis/motherboard/bmc"));
867 
868     EXPECT_CALL(dataIface,
869                 getLocationCode(
870                     "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"))
871         .Times(3)
872         .WillRepeatedly(Return("Ufcs-P1-C40"));
873     EXPECT_CALL(
874         dataIface,
875         getLocationCode("/xyz/openbmc_project/inventory/chassis/motherboard"))
876         .Times(3)
877         .WillRepeatedly(Return("Ufcs-P1"));
878     EXPECT_CALL(dataIface,
879                 getLocationCode(
880                     "/xyz/openbmc_project/inventory/chassis/motherboard/bmc"))
881         .Times(3)
882         .WillRepeatedly(Return("Ufcs-P1-C15"));
883 
884     EXPECT_CALL(
885         dataIface,
886         getHWCalloutFields(
887             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
888         .Times(3)
889         .WillRepeatedly(DoAll(SetArgReferee<1>("1234567"),
890                               SetArgReferee<2>("CCCC"),
891                               SetArgReferee<3>("123456789ABC")));
892     EXPECT_CALL(
893         dataIface,
894         getHWCalloutFields("/xyz/openbmc_project/inventory/chassis/motherboard",
895                            _, _, _))
896         .Times(3)
897         .WillRepeatedly(DoAll(SetArgReferee<1>("7654321"),
898                               SetArgReferee<2>("MMMM"),
899                               SetArgReferee<3>("CBA987654321")));
900     EXPECT_CALL(
901         dataIface,
902         getHWCalloutFields(
903             "/xyz/openbmc_project/inventory/chassis/motherboard/bmc", _, _, _))
904         .Times(3)
905         .WillRepeatedly(DoAll(SetArgReferee<1>("7123456"),
906                               SetArgReferee<2>("BBBB"),
907                               SetArgReferee<3>("C123456789AB")));
908 
909     // Call this below with different AdditionalData values that
910     // result in the same callouts.
911     auto checkCallouts = [&entry, &dataIface](const auto& items) {
912         AdditionalData ad{items};
913         SRC src{entry, ad, dataIface};
914 
915         ASSERT_TRUE(src.callouts());
916         auto& callouts = src.callouts()->callouts();
917 
918         ASSERT_EQ(callouts.size(), 3);
919 
920         {
921             EXPECT_EQ(callouts[0]->priority(), 'H');
922             EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P1-C40");
923 
924             auto& fru = callouts[0]->fruIdentity();
925             EXPECT_EQ(fru->getPN().value(), "1234567");
926             EXPECT_EQ(fru->getCCIN().value(), "CCCC");
927             EXPECT_EQ(fru->getSN().value(), "123456789ABC");
928         }
929         {
930             EXPECT_EQ(callouts[1]->priority(), 'M');
931             EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P1");
932 
933             auto& fru = callouts[1]->fruIdentity();
934             EXPECT_EQ(fru->getPN().value(), "7654321");
935             EXPECT_EQ(fru->getCCIN().value(), "MMMM");
936             EXPECT_EQ(fru->getSN().value(), "CBA987654321");
937         }
938         {
939             EXPECT_EQ(callouts[2]->priority(), 'L');
940             EXPECT_EQ(callouts[2]->locationCode(), "Ufcs-P1-C15");
941 
942             auto& fru = callouts[2]->fruIdentity();
943             EXPECT_EQ(fru->getPN().value(), "7123456");
944             EXPECT_EQ(fru->getCCIN().value(), "BBBB");
945             EXPECT_EQ(fru->getSN().value(), "C123456789AB");
946         }
947     };
948 
949     {
950         // Callouts based on the device path
951         std::vector<std::string> items{
952             "CALLOUT_ERRNO=5",
953             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
954             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
955 
956         checkCallouts(items);
957     }
958 
959     {
960         // Callouts based on the I2C bus and address
961         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
962                                        "CALLOUT_IIC_ADDR=0x72"};
963         checkCallouts(items);
964     }
965 
966     {
967         // Also based on I2C bus and address, but with bus = /dev/i2c-14
968         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
969                                        "CALLOUT_IIC_ADDR=0x72"};
970         checkCallouts(items);
971     }
972 
973     {
974         // Callout not found
975         std::vector<std::string> items{
976             "CALLOUT_ERRNO=5",
977             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
978             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-24/24-0012"};
979 
980         AdditionalData ad{items};
981         SRC src{entry, ad, dataIface};
982 
983         EXPECT_FALSE(src.callouts());
984         ASSERT_EQ(src.getDebugData().size(), 1);
985         EXPECT_EQ(src.getDebugData()[0],
986                   "Problem looking up I2C callouts on 24 18: "
987                   "[json.exception.out_of_range.403] key '24' not found");
988     }
989 
990     {
991         // Callout not found
992         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=22",
993                                        "CALLOUT_IIC_ADDR=0x99"};
994         AdditionalData ad{items};
995         SRC src{entry, ad, dataIface};
996 
997         EXPECT_FALSE(src.callouts());
998         ASSERT_EQ(src.getDebugData().size(), 1);
999         EXPECT_EQ(src.getDebugData()[0],
1000                   "Problem looking up I2C callouts on 22 153: "
1001                   "[json.exception.out_of_range.403] key '22' not found");
1002     }
1003 
1004     fs::remove_all(dataPath);
1005 }
1006 
1007 // Test when callouts are passed in via JSON
1008 TEST_F(SRCTest, JsonCalloutsTest)
1009 {
1010     const auto jsonCallouts = R"(
1011         [
1012             {
1013                 "LocationCode": "P0-C1",
1014                 "Priority": "H",
1015                 "MRUs": [
1016                     {
1017                         "ID": 42,
1018                         "Priority": "H"
1019                     },
1020                     {
1021                         "ID": 43,
1022                         "Priority": "M"
1023                     }
1024                 ]
1025             },
1026             {
1027                 "InventoryPath": "/inv/system/chassis/motherboard/cpu0",
1028                 "Priority": "M",
1029                 "Guarded": true,
1030                 "Deconfigured": true
1031             },
1032             {
1033                 "Procedure": "PROCEDU",
1034                 "Priority": "A"
1035             },
1036             {
1037                 "SymbolicFRU": "TRUSTED",
1038                 "Priority": "B",
1039                 "TrustedLocationCode": true,
1040                 "LocationCode": "P1-C23"
1041             },
1042             {
1043                 "SymbolicFRU": "FRUTST1",
1044                 "Priority": "C",
1045                 "LocationCode": "P1-C24"
1046             },
1047             {
1048                 "SymbolicFRU": "FRUTST2LONG",
1049                 "Priority": "L"
1050             }
1051         ]
1052     )"_json;
1053 
1054     message::Entry entry;
1055     entry.src.type = 0xBD;
1056     entry.src.reasonCode = 0xABCD;
1057     entry.subsystem = 0x42;
1058     entry.src.powerFault = false;
1059 
1060     AdditionalData ad;
1061     NiceMock<MockDataInterface> dataIface;
1062 
1063     // Callout 0 mock calls
1064     {
1065         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1066             .Times(1)
1067             .WillOnce(Return("UXXX-P0-C1"));
1068         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1069             .Times(1)
1070             .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
1071         EXPECT_CALL(
1072             dataIface,
1073             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1074             .Times(1)
1075             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1076                             SetArgReferee<2>("CCCC"),
1077                             SetArgReferee<3>("123456789ABC")));
1078     }
1079     // Callout 1 mock calls
1080     {
1081         EXPECT_CALL(dataIface,
1082                     getLocationCode("/inv/system/chassis/motherboard/cpu0"))
1083             .WillOnce(Return("UYYY-P5"));
1084         EXPECT_CALL(
1085             dataIface,
1086             getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _))
1087             .Times(1)
1088             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
1089                             SetArgReferee<2>("DDDD"),
1090                             SetArgReferee<3>("23456789ABCD")));
1091     }
1092     // Callout 3 mock calls
1093     {
1094         EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0))
1095             .Times(1)
1096             .WillOnce(Return("UXXX-P1-C23"));
1097     }
1098     // Callout 4 mock calls
1099     {
1100         EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0))
1101             .Times(1)
1102             .WillOnce(Return("UXXX-P1-C24"));
1103     }
1104 
1105     SRC src{entry, ad, jsonCallouts, dataIface};
1106     ASSERT_TRUE(src.callouts());
1107 
1108     // Check the guarded and deconfigured flags
1109     EXPECT_TRUE(src.hexwordData()[3] & 0x03000000);
1110 
1111     const auto& callouts = src.callouts()->callouts();
1112     ASSERT_EQ(callouts.size(), 6);
1113 
1114     // Check callout 0
1115     {
1116         EXPECT_EQ(callouts[0]->priority(), 'H');
1117         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1118 
1119         auto& fru = callouts[0]->fruIdentity();
1120         EXPECT_EQ(fru->getPN().value(), "1234567");
1121         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1122         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1123         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1124 
1125         auto& mruCallouts = callouts[0]->mru();
1126         ASSERT_TRUE(mruCallouts);
1127         auto& mrus = mruCallouts->mrus();
1128         ASSERT_EQ(mrus.size(), 2);
1129         EXPECT_EQ(mrus[0].id, 42);
1130         EXPECT_EQ(mrus[0].priority, 'H');
1131         EXPECT_EQ(mrus[1].id, 43);
1132         EXPECT_EQ(mrus[1].priority, 'M');
1133     }
1134 
1135     // Check callout 1
1136     {
1137         EXPECT_EQ(callouts[1]->priority(), 'M');
1138         EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5");
1139 
1140         auto& fru = callouts[1]->fruIdentity();
1141         EXPECT_EQ(fru->getPN().value(), "2345678");
1142         EXPECT_EQ(fru->getCCIN().value(), "DDDD");
1143         EXPECT_EQ(fru->getSN().value(), "23456789ABCD");
1144         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1145     }
1146 
1147     // Check callout 2
1148     {
1149         EXPECT_EQ(callouts[2]->priority(), 'A');
1150         EXPECT_EQ(callouts[2]->locationCode(), "");
1151 
1152         auto& fru = callouts[2]->fruIdentity();
1153         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1154         EXPECT_EQ(fru->failingComponentType(),
1155                   src::FRUIdentity::maintenanceProc);
1156     }
1157 
1158     // Check callout 3
1159     {
1160         EXPECT_EQ(callouts[3]->priority(), 'B');
1161         EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23");
1162 
1163         auto& fru = callouts[3]->fruIdentity();
1164         EXPECT_EQ(fru->getPN().value(), "TRUSTED");
1165         EXPECT_EQ(fru->failingComponentType(),
1166                   src::FRUIdentity::symbolicFRUTrustedLocCode);
1167     }
1168 
1169     // Check callout 4
1170     {
1171         EXPECT_EQ(callouts[4]->priority(), 'C');
1172         EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24");
1173 
1174         auto& fru = callouts[4]->fruIdentity();
1175         EXPECT_EQ(fru->getPN().value(), "FRUTST1");
1176         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1177     }
1178 
1179     // Check callout 5
1180     {
1181         EXPECT_EQ(callouts[5]->priority(), 'L');
1182         EXPECT_EQ(callouts[5]->locationCode(), "");
1183 
1184         auto& fru = callouts[5]->fruIdentity();
1185         EXPECT_EQ(fru->getPN().value(), "FRUTST2");
1186         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1187     }
1188 
1189     // Check that it didn't find any errors
1190     const auto& data = src.getDebugData();
1191     EXPECT_TRUE(data.empty());
1192 }
1193 
1194 TEST_F(SRCTest, JsonBadCalloutsTest)
1195 {
1196     // The first call will have a Throw in a mock call.
1197     // The second will have a different Throw in a mock call.
1198     // The others have issues with the Priority field.
1199     const auto jsonCallouts = R"(
1200         [
1201             {
1202                 "LocationCode": "P0-C1",
1203                 "Priority": "H"
1204             },
1205             {
1206                 "LocationCode": "P0-C2",
1207                 "Priority": "H"
1208             },
1209             {
1210                 "LocationCode": "P0-C3"
1211             },
1212             {
1213                 "LocationCode": "P0-C4",
1214                 "Priority": "X"
1215             }
1216         ]
1217     )"_json;
1218 
1219     message::Entry entry;
1220     entry.src.type = 0xBD;
1221     entry.src.reasonCode = 0xABCD;
1222     entry.subsystem = 0x42;
1223     entry.src.powerFault = false;
1224 
1225     AdditionalData ad;
1226     NiceMock<MockDataInterface> dataIface;
1227 
1228     // Callout 0 mock calls
1229     // Expand location code will fail, so the unexpanded location
1230     // code should show up in the callout instead.
1231     {
1232         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1233             .WillOnce(Throw(std::runtime_error("Fail")));
1234 
1235         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1236             .Times(1)
1237             .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
1238         EXPECT_CALL(
1239             dataIface,
1240             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1241             .Times(1)
1242             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1243                             SetArgReferee<2>("CCCC"),
1244                             SetArgReferee<3>("123456789ABC")));
1245     }
1246 
1247     // Callout 1 mock calls
1248     // getInventoryFromLocCode will fail
1249     {
1250         EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0))
1251             .Times(1)
1252             .WillOnce(Return("UXXX-P0-C2"));
1253 
1254         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false))
1255             .Times(1)
1256             .WillOnce(Throw(std::runtime_error("Fail")));
1257     }
1258 
1259     SRC src{entry, ad, jsonCallouts, dataIface};
1260 
1261     ASSERT_TRUE(src.callouts());
1262 
1263     const auto& callouts = src.callouts()->callouts();
1264 
1265     // Only the first callout was successful
1266     ASSERT_EQ(callouts.size(), 1);
1267 
1268     {
1269         EXPECT_EQ(callouts[0]->priority(), 'H');
1270         EXPECT_EQ(callouts[0]->locationCode(), "P0-C1");
1271 
1272         auto& fru = callouts[0]->fruIdentity();
1273         EXPECT_EQ(fru->getPN().value(), "1234567");
1274         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1275         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1276         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1277     }
1278 
1279     const auto& data = src.getDebugData();
1280     ASSERT_EQ(data.size(), 4);
1281     EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail");
1282     EXPECT_STREQ(data[1].c_str(),
1283                  "Failed extracting callout data from JSON: Unable to "
1284                  "get inventory path from location code: P0-C2: Fail");
1285     EXPECT_STREQ(data[2].c_str(),
1286                  "Failed extracting callout data from JSON: "
1287                  "[json.exception.out_of_range.403] key 'Priority' not found");
1288     EXPECT_STREQ(data[3].c_str(),
1289                  "Failed extracting callout data from JSON: Invalid "
1290                  "priority 'X' found in JSON callout");
1291 }
1292 
1293 // Test that an inventory path callout can have
1294 // a different priority than H.
1295 TEST_F(SRCTest, InventoryCalloutTestPriority)
1296 {
1297     message::Entry entry;
1298     entry.src.type = 0xBD;
1299     entry.src.reasonCode = 0xABCD;
1300     entry.subsystem = 0x42;
1301     entry.src.powerFault = false;
1302 
1303     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard",
1304                                     "CALLOUT_PRIORITY=M"};
1305     AdditionalData ad{adData};
1306     NiceMock<MockDataInterface> dataIface;
1307 
1308     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
1309         .WillOnce(Return("UTMS-P1"));
1310 
1311     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
1312         .Times(1)
1313         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1314                         SetArgReferee<3>("123456789ABC")));
1315 
1316     SRC src{entry, ad, dataIface};
1317     EXPECT_TRUE(src.valid());
1318 
1319     ASSERT_TRUE(src.callouts());
1320 
1321     EXPECT_EQ(src.callouts()->callouts().size(), 1);
1322 
1323     auto& callout = src.callouts()->callouts().front();
1324 
1325     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
1326     EXPECT_EQ(callout->priority(), 'M');
1327 }
1328