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, expandLocationCode("P1-C40", 0))
869         .Times(3)
870         .WillRepeatedly(Return("Ufcs-P1-C40"));
871 
872     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
873         .Times(3)
874         .WillRepeatedly(Return("Ufcs-P1"));
875 
876     EXPECT_CALL(dataIface, expandLocationCode("P1-C15", 0))
877         .Times(3)
878         .WillRepeatedly(Return("Ufcs-P1-C15"));
879 
880     EXPECT_CALL(
881         dataIface,
882         getHWCalloutFields(
883             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
884         .Times(3)
885         .WillRepeatedly(DoAll(SetArgReferee<1>("1234567"),
886                               SetArgReferee<2>("CCCC"),
887                               SetArgReferee<3>("123456789ABC")));
888     EXPECT_CALL(
889         dataIface,
890         getHWCalloutFields("/xyz/openbmc_project/inventory/chassis/motherboard",
891                            _, _, _))
892         .Times(3)
893         .WillRepeatedly(DoAll(SetArgReferee<1>("7654321"),
894                               SetArgReferee<2>("MMMM"),
895                               SetArgReferee<3>("CBA987654321")));
896     EXPECT_CALL(
897         dataIface,
898         getHWCalloutFields(
899             "/xyz/openbmc_project/inventory/chassis/motherboard/bmc", _, _, _))
900         .Times(3)
901         .WillRepeatedly(DoAll(SetArgReferee<1>("7123456"),
902                               SetArgReferee<2>("BBBB"),
903                               SetArgReferee<3>("C123456789AB")));
904 
905     // Call this below with different AdditionalData values that
906     // result in the same callouts.
907     auto checkCallouts = [&entry, &dataIface](const auto& items) {
908         AdditionalData ad{items};
909         SRC src{entry, ad, dataIface};
910 
911         ASSERT_TRUE(src.callouts());
912         auto& callouts = src.callouts()->callouts();
913 
914         ASSERT_EQ(callouts.size(), 3);
915 
916         {
917             EXPECT_EQ(callouts[0]->priority(), 'H');
918             EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P1-C40");
919 
920             auto& fru = callouts[0]->fruIdentity();
921             EXPECT_EQ(fru->getPN().value(), "1234567");
922             EXPECT_EQ(fru->getCCIN().value(), "CCCC");
923             EXPECT_EQ(fru->getSN().value(), "123456789ABC");
924         }
925         {
926             EXPECT_EQ(callouts[1]->priority(), 'M');
927             EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P1");
928 
929             auto& fru = callouts[1]->fruIdentity();
930             EXPECT_EQ(fru->getPN().value(), "7654321");
931             EXPECT_EQ(fru->getCCIN().value(), "MMMM");
932             EXPECT_EQ(fru->getSN().value(), "CBA987654321");
933         }
934         {
935             EXPECT_EQ(callouts[2]->priority(), 'L');
936             EXPECT_EQ(callouts[2]->locationCode(), "Ufcs-P1-C15");
937 
938             auto& fru = callouts[2]->fruIdentity();
939             EXPECT_EQ(fru->getPN().value(), "7123456");
940             EXPECT_EQ(fru->getCCIN().value(), "BBBB");
941             EXPECT_EQ(fru->getSN().value(), "C123456789AB");
942         }
943     };
944 
945     {
946         // Callouts based on the device path
947         std::vector<std::string> items{
948             "CALLOUT_ERRNO=5",
949             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
950             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
951 
952         checkCallouts(items);
953     }
954 
955     {
956         // Callouts based on the I2C bus and address
957         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
958                                        "CALLOUT_IIC_ADDR=0x72"};
959         checkCallouts(items);
960     }
961 
962     {
963         // Also based on I2C bus and address, but with bus = /dev/i2c-14
964         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
965                                        "CALLOUT_IIC_ADDR=0x72"};
966         checkCallouts(items);
967     }
968 
969     {
970         // Callout not found
971         std::vector<std::string> items{
972             "CALLOUT_ERRNO=5",
973             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
974             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-24/24-0012"};
975 
976         AdditionalData ad{items};
977         SRC src{entry, ad, dataIface};
978 
979         EXPECT_FALSE(src.callouts());
980         ASSERT_EQ(src.getDebugData().size(), 1);
981         EXPECT_EQ(src.getDebugData()[0],
982                   "Problem looking up I2C callouts on 24 18: "
983                   "[json.exception.out_of_range.403] key '24' not found");
984     }
985 
986     {
987         // Callout not found
988         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=22",
989                                        "CALLOUT_IIC_ADDR=0x99"};
990         AdditionalData ad{items};
991         SRC src{entry, ad, dataIface};
992 
993         EXPECT_FALSE(src.callouts());
994         ASSERT_EQ(src.getDebugData().size(), 1);
995         EXPECT_EQ(src.getDebugData()[0],
996                   "Problem looking up I2C callouts on 22 153: "
997                   "[json.exception.out_of_range.403] key '22' not found");
998     }
999 
1000     fs::remove_all(dataPath);
1001 }
1002 
1003 // Test when callouts are passed in via JSON
1004 TEST_F(SRCTest, JsonCalloutsTest)
1005 {
1006     const auto jsonCallouts = R"(
1007         [
1008             {
1009                 "LocationCode": "P0-C1",
1010                 "Priority": "H",
1011                 "MRUs": [
1012                     {
1013                         "ID": 42,
1014                         "Priority": "H"
1015                     },
1016                     {
1017                         "ID": 43,
1018                         "Priority": "M"
1019                     }
1020                 ]
1021             },
1022             {
1023                 "InventoryPath": "/inv/system/chassis/motherboard/cpu0",
1024                 "Priority": "M",
1025                 "Guarded": true,
1026                 "Deconfigured": true
1027             },
1028             {
1029                 "Procedure": "PROCEDU",
1030                 "Priority": "A"
1031             },
1032             {
1033                 "SymbolicFRU": "TRUSTED",
1034                 "Priority": "B",
1035                 "TrustedLocationCode": true,
1036                 "LocationCode": "P1-C23"
1037             },
1038             {
1039                 "SymbolicFRU": "FRUTST1",
1040                 "Priority": "C",
1041                 "LocationCode": "P1-C24"
1042             },
1043             {
1044                 "SymbolicFRU": "FRUTST2LONG",
1045                 "Priority": "L"
1046             }
1047         ]
1048     )"_json;
1049 
1050     message::Entry entry;
1051     entry.src.type = 0xBD;
1052     entry.src.reasonCode = 0xABCD;
1053     entry.subsystem = 0x42;
1054     entry.src.powerFault = false;
1055 
1056     AdditionalData ad;
1057     NiceMock<MockDataInterface> dataIface;
1058 
1059     // Callout 0 mock calls
1060     {
1061         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1062             .Times(1)
1063             .WillOnce(Return("UXXX-P0-C1"));
1064         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1065             .Times(1)
1066             .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
1067         EXPECT_CALL(
1068             dataIface,
1069             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1070             .Times(1)
1071             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1072                             SetArgReferee<2>("CCCC"),
1073                             SetArgReferee<3>("123456789ABC")));
1074     }
1075     // Callout 1 mock calls
1076     {
1077         EXPECT_CALL(dataIface,
1078                     getLocationCode("/inv/system/chassis/motherboard/cpu0"))
1079             .WillOnce(Return("UYYY-P5"));
1080         EXPECT_CALL(
1081             dataIface,
1082             getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _))
1083             .Times(1)
1084             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
1085                             SetArgReferee<2>("DDDD"),
1086                             SetArgReferee<3>("23456789ABCD")));
1087     }
1088     // Callout 3 mock calls
1089     {
1090         EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0))
1091             .Times(1)
1092             .WillOnce(Return("UXXX-P1-C23"));
1093     }
1094     // Callout 4 mock calls
1095     {
1096         EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0))
1097             .Times(1)
1098             .WillOnce(Return("UXXX-P1-C24"));
1099     }
1100 
1101     SRC src{entry, ad, jsonCallouts, dataIface};
1102     ASSERT_TRUE(src.callouts());
1103 
1104     // Check the guarded and deconfigured flags
1105     EXPECT_TRUE(src.hexwordData()[3] & 0x03000000);
1106 
1107     const auto& callouts = src.callouts()->callouts();
1108     ASSERT_EQ(callouts.size(), 6);
1109 
1110     // Check callout 0
1111     {
1112         EXPECT_EQ(callouts[0]->priority(), 'H');
1113         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1114 
1115         auto& fru = callouts[0]->fruIdentity();
1116         EXPECT_EQ(fru->getPN().value(), "1234567");
1117         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1118         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1119         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1120 
1121         auto& mruCallouts = callouts[0]->mru();
1122         ASSERT_TRUE(mruCallouts);
1123         auto& mrus = mruCallouts->mrus();
1124         ASSERT_EQ(mrus.size(), 2);
1125         EXPECT_EQ(mrus[0].id, 42);
1126         EXPECT_EQ(mrus[0].priority, 'H');
1127         EXPECT_EQ(mrus[1].id, 43);
1128         EXPECT_EQ(mrus[1].priority, 'M');
1129     }
1130 
1131     // Check callout 1
1132     {
1133         EXPECT_EQ(callouts[1]->priority(), 'M');
1134         EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5");
1135 
1136         auto& fru = callouts[1]->fruIdentity();
1137         EXPECT_EQ(fru->getPN().value(), "2345678");
1138         EXPECT_EQ(fru->getCCIN().value(), "DDDD");
1139         EXPECT_EQ(fru->getSN().value(), "23456789ABCD");
1140         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1141     }
1142 
1143     // Check callout 2
1144     {
1145         EXPECT_EQ(callouts[2]->priority(), 'A');
1146         EXPECT_EQ(callouts[2]->locationCode(), "");
1147 
1148         auto& fru = callouts[2]->fruIdentity();
1149         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1150         EXPECT_EQ(fru->failingComponentType(),
1151                   src::FRUIdentity::maintenanceProc);
1152     }
1153 
1154     // Check callout 3
1155     {
1156         EXPECT_EQ(callouts[3]->priority(), 'B');
1157         EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23");
1158 
1159         auto& fru = callouts[3]->fruIdentity();
1160         EXPECT_EQ(fru->getPN().value(), "TRUSTED");
1161         EXPECT_EQ(fru->failingComponentType(),
1162                   src::FRUIdentity::symbolicFRUTrustedLocCode);
1163     }
1164 
1165     // Check callout 4
1166     {
1167         EXPECT_EQ(callouts[4]->priority(), 'C');
1168         EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24");
1169 
1170         auto& fru = callouts[4]->fruIdentity();
1171         EXPECT_EQ(fru->getPN().value(), "FRUTST1");
1172         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1173     }
1174 
1175     // Check callout 5
1176     {
1177         EXPECT_EQ(callouts[5]->priority(), 'L');
1178         EXPECT_EQ(callouts[5]->locationCode(), "");
1179 
1180         auto& fru = callouts[5]->fruIdentity();
1181         EXPECT_EQ(fru->getPN().value(), "FRUTST2");
1182         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1183     }
1184 
1185     // Check that it didn't find any errors
1186     const auto& data = src.getDebugData();
1187     EXPECT_TRUE(data.empty());
1188 }
1189 
1190 TEST_F(SRCTest, JsonBadCalloutsTest)
1191 {
1192     // The first call will have a Throw in a mock call.
1193     // The second will have a different Throw in a mock call.
1194     // The others have issues with the Priority field.
1195     const auto jsonCallouts = R"(
1196         [
1197             {
1198                 "LocationCode": "P0-C1",
1199                 "Priority": "H"
1200             },
1201             {
1202                 "LocationCode": "P0-C2",
1203                 "Priority": "H"
1204             },
1205             {
1206                 "LocationCode": "P0-C3"
1207             },
1208             {
1209                 "LocationCode": "P0-C4",
1210                 "Priority": "X"
1211             }
1212         ]
1213     )"_json;
1214 
1215     message::Entry entry;
1216     entry.src.type = 0xBD;
1217     entry.src.reasonCode = 0xABCD;
1218     entry.subsystem = 0x42;
1219     entry.src.powerFault = false;
1220 
1221     AdditionalData ad;
1222     NiceMock<MockDataInterface> dataIface;
1223 
1224     // Callout 0 mock calls
1225     // Expand location code will fail, so the unexpanded location
1226     // code should show up in the callout instead.
1227     {
1228         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1229             .WillOnce(Throw(std::runtime_error("Fail")));
1230 
1231         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1232             .Times(1)
1233             .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
1234         EXPECT_CALL(
1235             dataIface,
1236             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1237             .Times(1)
1238             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1239                             SetArgReferee<2>("CCCC"),
1240                             SetArgReferee<3>("123456789ABC")));
1241     }
1242 
1243     // Callout 1 mock calls
1244     // getInventoryFromLocCode will fail
1245     {
1246         EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0))
1247             .Times(1)
1248             .WillOnce(Return("UXXX-P0-C2"));
1249 
1250         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false))
1251             .Times(1)
1252             .WillOnce(Throw(std::runtime_error("Fail")));
1253     }
1254 
1255     SRC src{entry, ad, jsonCallouts, dataIface};
1256 
1257     ASSERT_TRUE(src.callouts());
1258 
1259     const auto& callouts = src.callouts()->callouts();
1260 
1261     // Only the first callout was successful
1262     ASSERT_EQ(callouts.size(), 1);
1263 
1264     {
1265         EXPECT_EQ(callouts[0]->priority(), 'H');
1266         EXPECT_EQ(callouts[0]->locationCode(), "P0-C1");
1267 
1268         auto& fru = callouts[0]->fruIdentity();
1269         EXPECT_EQ(fru->getPN().value(), "1234567");
1270         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1271         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1272         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1273     }
1274 
1275     const auto& data = src.getDebugData();
1276     ASSERT_EQ(data.size(), 4);
1277     EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail");
1278     EXPECT_STREQ(data[1].c_str(),
1279                  "Failed extracting callout data from JSON: Unable to "
1280                  "get inventory path from location code: P0-C2: Fail");
1281     EXPECT_STREQ(data[2].c_str(),
1282                  "Failed extracting callout data from JSON: "
1283                  "[json.exception.out_of_range.403] key 'Priority' not found");
1284     EXPECT_STREQ(data[3].c_str(),
1285                  "Failed extracting callout data from JSON: Invalid "
1286                  "priority 'X' found in JSON callout");
1287 }
1288 
1289 // Test that an inventory path callout can have
1290 // a different priority than H.
1291 TEST_F(SRCTest, InventoryCalloutTestPriority)
1292 {
1293     message::Entry entry;
1294     entry.src.type = 0xBD;
1295     entry.src.reasonCode = 0xABCD;
1296     entry.subsystem = 0x42;
1297     entry.src.powerFault = false;
1298 
1299     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard",
1300                                     "CALLOUT_PRIORITY=M"};
1301     AdditionalData ad{adData};
1302     NiceMock<MockDataInterface> dataIface;
1303 
1304     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
1305         .WillOnce(Return("UTMS-P1"));
1306 
1307     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
1308         .Times(1)
1309         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1310                         SetArgReferee<3>("123456789ABC")));
1311 
1312     SRC src{entry, ad, dataIface};
1313     EXPECT_TRUE(src.valid());
1314 
1315     ASSERT_TRUE(src.callouts());
1316 
1317     EXPECT_EQ(src.callouts()->callouts().size(), 1);
1318 
1319     auto& callout = src.callouts()->callouts().front();
1320 
1321     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
1322     EXPECT_EQ(callout->priority(), 'M');
1323 }
1324