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