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 // no callout is added.
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_FALSE(src.callouts());
424 
425     // flatten and unflatten
426     std::vector<uint8_t> data;
427     Stream stream{data};
428     src.flatten(stream);
429 
430     stream.offset(0);
431     SRC newSRC{stream};
432     EXPECT_TRUE(newSRC.valid());
433     ASSERT_FALSE(src.callouts());
434 }
435 
436 // Test that when the VPD can't be obtained that
437 // a callout is still created.
438 TEST_F(SRCTest, InventoryCalloutNoVPDTest)
439 {
440     message::Entry entry;
441     entry.src.type = 0xBD;
442     entry.src.reasonCode = 0xABCD;
443     entry.subsystem = 0x42;
444     entry.src.powerFault = false;
445 
446     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
447     AdditionalData ad{adData};
448     NiceMock<MockDataInterface> dataIface;
449 
450     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
451         .Times(1)
452         .WillOnce(Return("UTMS-P10"));
453 
454     auto func = []() { throw sdbusplus::exception::SdBusError(5, "Error"); };
455 
456     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
457         .Times(1)
458         .WillOnce(InvokeWithoutArgs(func));
459 
460     SRC src{entry, ad, dataIface};
461     EXPECT_TRUE(src.valid());
462     ASSERT_TRUE(src.callouts());
463     EXPECT_EQ(src.callouts()->callouts().size(), 1);
464 
465     auto& callout = src.callouts()->callouts().front();
466     EXPECT_EQ(callout->locationCode(), "UTMS-P10");
467     EXPECT_EQ(callout->priority(), 'H');
468 
469     auto& fru = callout->fruIdentity();
470 
471     EXPECT_EQ(fru->getPN(), "");
472     EXPECT_EQ(fru->getCCIN(), "");
473     EXPECT_EQ(fru->getSN(), "");
474     EXPECT_FALSE(fru->getMaintProc());
475 
476     // flatten and unflatten
477     std::vector<uint8_t> data;
478     Stream stream{data};
479     src.flatten(stream);
480 
481     stream.offset(0);
482     SRC newSRC{stream};
483     EXPECT_TRUE(newSRC.valid());
484     ASSERT_TRUE(src.callouts());
485     EXPECT_EQ(src.callouts()->callouts().size(), 1);
486 }
487 
488 TEST_F(SRCTest, RegistryCalloutTest)
489 {
490     message::Entry entry;
491     entry.src.type = 0xBD;
492     entry.src.reasonCode = 0xABCD;
493     entry.subsystem = 0x42;
494     entry.src.powerFault = false;
495 
496     entry.callouts = R"(
497         [
498         {
499             "System": "systemA",
500             "CalloutList":
501             [
502                 {
503                     "Priority": "high",
504                     "SymbolicFRU": "service_docs"
505                 },
506                 {
507                     "Priority": "medium",
508                     "Procedure": "bmc_code"
509                 }
510             ]
511         },
512         {
513             "System": "systemB",
514             "CalloutList":
515             [
516                 {
517                     "Priority": "high",
518                     "LocCode": "P0-C8",
519                     "SymbolicFRUTrusted": "service_docs"
520                 },
521                 {
522                     "Priority": "medium",
523                     "SymbolicFRUTrusted": "service_docs"
524                 }
525             ]
526         },
527         {
528             "System": "systemC",
529             "CalloutList":
530             [
531                 {
532                     "Priority": "high",
533                     "LocCode": "P0-C8"
534                 },
535                 {
536                     "Priority": "medium",
537                     "LocCode": "P0-C9"
538                 }
539             ]
540         }
541         ])"_json;
542 
543     {
544         // Call out a symbolic FRU and a procedure
545         AdditionalData ad;
546         NiceMock<MockDataInterface> dataIface;
547         std::vector<std::string> names{"systemA"};
548 
549         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
550 
551         SRC src{entry, ad, dataIface};
552 
553         auto& callouts = src.callouts()->callouts();
554         ASSERT_EQ(callouts.size(), 2);
555 
556         EXPECT_EQ(callouts[0]->locationCodeSize(), 0);
557         EXPECT_EQ(callouts[0]->priority(), 'H');
558 
559         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
560         EXPECT_EQ(callouts[1]->priority(), 'M');
561 
562         auto& fru1 = callouts[0]->fruIdentity();
563         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
564         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
565         EXPECT_FALSE(fru1->getMaintProc());
566         EXPECT_FALSE(fru1->getSN());
567         EXPECT_FALSE(fru1->getCCIN());
568 
569         auto& fru2 = callouts[1]->fruIdentity();
570         EXPECT_EQ(fru2->getMaintProc().value(), "BMC0001");
571         EXPECT_EQ(fru2->failingComponentType(),
572                   src::FRUIdentity::maintenanceProc);
573         EXPECT_FALSE(fru2->getPN());
574         EXPECT_FALSE(fru2->getSN());
575         EXPECT_FALSE(fru2->getCCIN());
576     }
577 
578     {
579         // Call out a trusted symbolic FRU with a location code, and
580         // another one without.
581         AdditionalData ad;
582         NiceMock<MockDataInterface> dataIface;
583         std::vector<std::string> names{"systemB"};
584 
585         EXPECT_CALL(dataIface, expandLocationCode).WillOnce(Return("P0-C8"));
586         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
587 
588         SRC src{entry, ad, dataIface};
589 
590         auto& callouts = src.callouts()->callouts();
591         EXPECT_EQ(callouts.size(), 2);
592 
593         EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
594         EXPECT_EQ(callouts[0]->priority(), 'H');
595 
596         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
597         EXPECT_EQ(callouts[1]->priority(), 'M');
598 
599         auto& fru1 = callouts[0]->fruIdentity();
600         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
601         EXPECT_EQ(fru1->failingComponentType(),
602                   src::FRUIdentity::symbolicFRUTrustedLocCode);
603         EXPECT_FALSE(fru1->getMaintProc());
604         EXPECT_FALSE(fru1->getSN());
605         EXPECT_FALSE(fru1->getCCIN());
606 
607         // It asked for a trusted symbolic FRU, but no location code
608         // was provided so it is switched back to a normal one
609         auto& fru2 = callouts[1]->fruIdentity();
610         EXPECT_EQ(fru2->getPN().value(), "SVCDOCS");
611         EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::symbolicFRU);
612         EXPECT_FALSE(fru2->getMaintProc());
613         EXPECT_FALSE(fru2->getSN());
614         EXPECT_FALSE(fru2->getCCIN());
615     }
616 
617     {
618         // Two hardware callouts
619         AdditionalData ad;
620         NiceMock<MockDataInterface> dataIface;
621         std::vector<std::string> names{"systemC"};
622 
623         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
624 
625         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
626             .WillOnce(Return("UXXX-P0-C8"));
627 
628         EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0))
629             .WillOnce(Return("UXXX-P0-C9"));
630 
631         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C8", 0, false))
632             .WillOnce(Return(
633                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
634 
635         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C9", 0, false))
636             .WillOnce(Return(
637                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1"));
638 
639         EXPECT_CALL(
640             dataIface,
641             getHWCalloutFields(
642                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _,
643                 _))
644             .Times(1)
645             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
646                             SetArgReferee<2>("CCCC"),
647                             SetArgReferee<3>("123456789ABC")));
648 
649         EXPECT_CALL(
650             dataIface,
651             getHWCalloutFields(
652                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1", _, _,
653                 _))
654             .Times(1)
655             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
656                             SetArgReferee<2>("DDDD"),
657                             SetArgReferee<3>("23456789ABCD")));
658 
659         SRC src{entry, ad, dataIface};
660 
661         auto& callouts = src.callouts()->callouts();
662         EXPECT_EQ(callouts.size(), 2);
663 
664         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C8");
665         EXPECT_EQ(callouts[0]->priority(), 'H');
666 
667         auto& fru1 = callouts[0]->fruIdentity();
668         EXPECT_EQ(fru1->getPN().value(), "1234567");
669         EXPECT_EQ(fru1->getCCIN().value(), "CCCC");
670         EXPECT_EQ(fru1->getSN().value(), "123456789ABC");
671 
672         EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C9");
673         EXPECT_EQ(callouts[1]->priority(), 'M');
674 
675         auto& fru2 = callouts[1]->fruIdentity();
676         EXPECT_EQ(fru2->getPN().value(), "2345678");
677         EXPECT_EQ(fru2->getCCIN().value(), "DDDD");
678         EXPECT_EQ(fru2->getSN().value(), "23456789ABCD");
679     }
680 }
681 
682 // Test that a symbolic FRU with a trusted location code callout
683 // from the registry can get its location from the
684 // CALLOUT_INVENTORY_PATH AdditionalData entry.
685 TEST_F(SRCTest, SymbolicFRUWithInvPathTest)
686 {
687     message::Entry entry;
688     entry.src.type = 0xBD;
689     entry.src.reasonCode = 0xABCD;
690     entry.subsystem = 0x42;
691     entry.src.powerFault = false;
692 
693     entry.callouts = R"(
694         [{
695             "CalloutList":
696             [
697                 {
698                     "Priority": "high",
699                     "SymbolicFRUTrusted": "service_docs",
700                     "UseInventoryLocCode": true
701                 },
702                 {
703                     "Priority": "medium",
704                     "LocCode": "P0-C8",
705                     "SymbolicFRUTrusted": "pwrsply"
706                 }
707             ]
708         }])"_json;
709 
710     {
711         // The location code for the first symbolic FRU callout will
712         // come from this inventory path since UseInventoryLocCode is set.
713         // In this case there will be no normal FRU callout for the motherboard.
714         std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
715         AdditionalData ad{adData};
716         NiceMock<MockDataInterface> dataIface;
717         std::vector<std::string> names{"systemA"};
718 
719         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
720 
721         EXPECT_CALL(dataIface, getLocationCode("motherboard"))
722             .Times(1)
723             .WillOnce(Return("Ufcs-P10"));
724 
725         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
726             .WillOnce(Return("Ufcs-P0-C8"));
727 
728         SRC src{entry, ad, dataIface};
729 
730         auto& callouts = src.callouts()->callouts();
731         EXPECT_EQ(callouts.size(), 2);
732 
733         // The location code for the first symbolic FRU callout with a
734         // trusted location code comes from the motherboard.
735         EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P10");
736         EXPECT_EQ(callouts[0]->priority(), 'H');
737         auto& fru1 = callouts[0]->fruIdentity();
738         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
739         EXPECT_EQ(fru1->failingComponentType(),
740                   src::FRUIdentity::symbolicFRUTrustedLocCode);
741 
742         // The second trusted symbolic FRU callouts uses the location
743         // code in the registry as usual.
744         EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P0-C8");
745         EXPECT_EQ(callouts[1]->priority(), 'M');
746         auto& fru2 = callouts[1]->fruIdentity();
747         EXPECT_EQ(fru2->getPN().value(), "PWRSPLY");
748         EXPECT_EQ(fru2->failingComponentType(),
749                   src::FRUIdentity::symbolicFRUTrustedLocCode);
750     }
751 
752     {
753         // This time say we want to use the location code from
754         // the inventory, but don't pass it in and the callout should
755         // end up a regular symbolic FRU
756         entry.callouts = R"(
757         [{
758             "CalloutList":
759             [
760                 {
761                     "Priority": "high",
762                     "SymbolicFRUTrusted": "service_docs",
763                     "UseInventoryLocCode": true
764                 }
765             ]
766         }])"_json;
767 
768         AdditionalData ad;
769         NiceMock<MockDataInterface> dataIface;
770         std::vector<std::string> names{"systemA"};
771 
772         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
773 
774         SRC src{entry, ad, dataIface};
775 
776         auto& callouts = src.callouts()->callouts();
777         EXPECT_EQ(callouts.size(), 1);
778 
779         EXPECT_EQ(callouts[0]->locationCode(), "");
780         EXPECT_EQ(callouts[0]->priority(), 'H');
781         auto& fru1 = callouts[0]->fruIdentity();
782         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
783         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
784     }
785 }
786 
787 // Test looking up device path fails in the callout jSON.
788 TEST_F(SRCTest, DevicePathCalloutTest)
789 {
790     message::Entry entry;
791     entry.src.type = 0xBD;
792     entry.src.reasonCode = 0xABCD;
793     entry.subsystem = 0x42;
794     entry.src.powerFault = false;
795 
796     const auto calloutJSON = R"(
797     {
798         "I2C":
799         {
800             "14":
801             {
802                 "114":
803                 {
804                     "Callouts":[
805                     {
806                         "Name": "/chassis/motherboard/cpu0",
807                         "LocationCode": "P1-C40",
808                         "Priority": "H"
809                     },
810                     {
811                         "Name": "/chassis/motherboard",
812                         "LocationCode": "P1",
813                         "Priority": "M"
814                     },
815                     {
816                         "Name": "/chassis/motherboard/bmc",
817                         "LocationCode": "P1-C15",
818                         "Priority": "L"
819                     }
820                     ],
821                     "Dest": "proc 0 target"
822                 }
823             }
824         }
825     })";
826 
827     auto dataPath = getPELReadOnlyDataPath();
828     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
829     file << calloutJSON;
830     file.close();
831 
832     NiceMock<MockDataInterface> dataIface;
833     std::vector<std::string> names{"systemA"};
834 
835     EXPECT_CALL(dataIface, getSystemNames)
836         .Times(5)
837         .WillRepeatedly(Return(names));
838 
839     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
840         .Times(3)
841         .WillRepeatedly(
842             Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
843 
844     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
845         .Times(3)
846         .WillRepeatedly(
847             Return("/xyz/openbmc_project/inventory/chassis/motherboard"));
848 
849     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C15", 0, false))
850         .Times(3)
851         .WillRepeatedly(
852             Return("/xyz/openbmc_project/inventory/chassis/motherboard/bmc"));
853 
854     EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0))
855         .Times(3)
856         .WillRepeatedly(Return("Ufcs-P1-C40"));
857 
858     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
859         .Times(3)
860         .WillRepeatedly(Return("Ufcs-P1"));
861 
862     EXPECT_CALL(dataIface, expandLocationCode("P1-C15", 0))
863         .Times(3)
864         .WillRepeatedly(Return("Ufcs-P1-C15"));
865 
866     EXPECT_CALL(
867         dataIface,
868         getHWCalloutFields(
869             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
870         .Times(3)
871         .WillRepeatedly(DoAll(SetArgReferee<1>("1234567"),
872                               SetArgReferee<2>("CCCC"),
873                               SetArgReferee<3>("123456789ABC")));
874     EXPECT_CALL(
875         dataIface,
876         getHWCalloutFields("/xyz/openbmc_project/inventory/chassis/motherboard",
877                            _, _, _))
878         .Times(3)
879         .WillRepeatedly(DoAll(SetArgReferee<1>("7654321"),
880                               SetArgReferee<2>("MMMM"),
881                               SetArgReferee<3>("CBA987654321")));
882     EXPECT_CALL(
883         dataIface,
884         getHWCalloutFields(
885             "/xyz/openbmc_project/inventory/chassis/motherboard/bmc", _, _, _))
886         .Times(3)
887         .WillRepeatedly(DoAll(SetArgReferee<1>("7123456"),
888                               SetArgReferee<2>("BBBB"),
889                               SetArgReferee<3>("C123456789AB")));
890 
891     // Call this below with different AdditionalData values that
892     // result in the same callouts.
893     auto checkCallouts = [&entry, &dataIface](const auto& items) {
894         AdditionalData ad{items};
895         SRC src{entry, ad, dataIface};
896 
897         ASSERT_TRUE(src.callouts());
898         auto& callouts = src.callouts()->callouts();
899 
900         ASSERT_EQ(callouts.size(), 3);
901 
902         {
903             EXPECT_EQ(callouts[0]->priority(), 'H');
904             EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P1-C40");
905 
906             auto& fru = callouts[0]->fruIdentity();
907             EXPECT_EQ(fru->getPN().value(), "1234567");
908             EXPECT_EQ(fru->getCCIN().value(), "CCCC");
909             EXPECT_EQ(fru->getSN().value(), "123456789ABC");
910         }
911         {
912             EXPECT_EQ(callouts[1]->priority(), 'M');
913             EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P1");
914 
915             auto& fru = callouts[1]->fruIdentity();
916             EXPECT_EQ(fru->getPN().value(), "7654321");
917             EXPECT_EQ(fru->getCCIN().value(), "MMMM");
918             EXPECT_EQ(fru->getSN().value(), "CBA987654321");
919         }
920         {
921             EXPECT_EQ(callouts[2]->priority(), 'L');
922             EXPECT_EQ(callouts[2]->locationCode(), "Ufcs-P1-C15");
923 
924             auto& fru = callouts[2]->fruIdentity();
925             EXPECT_EQ(fru->getPN().value(), "7123456");
926             EXPECT_EQ(fru->getCCIN().value(), "BBBB");
927             EXPECT_EQ(fru->getSN().value(), "C123456789AB");
928         }
929     };
930 
931     {
932         // Callouts based on the device path
933         std::vector<std::string> items{
934             "CALLOUT_ERRNO=5",
935             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
936             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
937 
938         checkCallouts(items);
939     }
940 
941     {
942         // Callouts based on the I2C bus and address
943         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
944                                        "CALLOUT_IIC_ADDR=0x72"};
945         checkCallouts(items);
946     }
947 
948     {
949         // Also based on I2C bus and address, but with bus = /dev/i2c-14
950         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
951                                        "CALLOUT_IIC_ADDR=0x72"};
952         checkCallouts(items);
953     }
954 
955     {
956         // Callout not found
957         std::vector<std::string> items{
958             "CALLOUT_ERRNO=5",
959             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
960             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-24/24-0012"};
961 
962         AdditionalData ad{items};
963         SRC src{entry, ad, dataIface};
964 
965         EXPECT_FALSE(src.callouts());
966         ASSERT_EQ(src.getDebugData().size(), 1);
967         EXPECT_EQ(src.getDebugData()[0],
968                   "Problem looking up I2C callouts on 24 18: "
969                   "[json.exception.out_of_range.403] key '24' not found");
970     }
971 
972     {
973         // Callout not found
974         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=22",
975                                        "CALLOUT_IIC_ADDR=0x99"};
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 22 153: "
983                   "[json.exception.out_of_range.403] key '22' not found");
984     }
985 
986     fs::remove_all(dataPath);
987 }
988 
989 // Test when callouts are passed in via JSON
990 TEST_F(SRCTest, JsonCalloutsTest)
991 {
992     const auto jsonCallouts = R"(
993         [
994             {
995                 "LocationCode": "P0-C1",
996                 "Priority": "H",
997                 "MRUs": [
998                     {
999                         "ID": 42,
1000                         "Priority": "H"
1001                     },
1002                     {
1003                         "ID": 43,
1004                         "Priority": "M"
1005                     }
1006                 ]
1007             },
1008             {
1009                 "InventoryPath": "/inv/system/chassis/motherboard/cpu0",
1010                 "Priority": "M",
1011                 "Guarded": true,
1012                 "Deconfigured": true
1013             },
1014             {
1015                 "Procedure": "PROCEDU",
1016                 "Priority": "A"
1017             },
1018             {
1019                 "SymbolicFRU": "TRUSTED",
1020                 "Priority": "B",
1021                 "TrustedLocationCode": true,
1022                 "LocationCode": "P1-C23"
1023             },
1024             {
1025                 "SymbolicFRU": "FRUTST1",
1026                 "Priority": "C",
1027                 "LocationCode": "P1-C24"
1028             },
1029             {
1030                 "SymbolicFRU": "FRUTST2LONG",
1031                 "Priority": "L"
1032             }
1033         ]
1034     )"_json;
1035 
1036     message::Entry entry;
1037     entry.src.type = 0xBD;
1038     entry.src.reasonCode = 0xABCD;
1039     entry.subsystem = 0x42;
1040     entry.src.powerFault = false;
1041 
1042     AdditionalData ad;
1043     NiceMock<MockDataInterface> dataIface;
1044 
1045     // Callout 0 mock calls
1046     {
1047         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1048             .Times(1)
1049             .WillOnce(Return("UXXX-P0-C1"));
1050         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1051             .Times(1)
1052             .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
1053         EXPECT_CALL(
1054             dataIface,
1055             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1056             .Times(1)
1057             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1058                             SetArgReferee<2>("CCCC"),
1059                             SetArgReferee<3>("123456789ABC")));
1060     }
1061     // Callout 1 mock calls
1062     {
1063         EXPECT_CALL(dataIface,
1064                     getLocationCode("/inv/system/chassis/motherboard/cpu0"))
1065             .WillOnce(Return("UYYY-P5"));
1066         EXPECT_CALL(
1067             dataIface,
1068             getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _))
1069             .Times(1)
1070             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
1071                             SetArgReferee<2>("DDDD"),
1072                             SetArgReferee<3>("23456789ABCD")));
1073     }
1074     // Callout 3 mock calls
1075     {
1076         EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0))
1077             .Times(1)
1078             .WillOnce(Return("UXXX-P1-C23"));
1079     }
1080     // Callout 4 mock calls
1081     {
1082         EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0))
1083             .Times(1)
1084             .WillOnce(Return("UXXX-P1-C24"));
1085     }
1086 
1087     SRC src{entry, ad, jsonCallouts, dataIface};
1088     ASSERT_TRUE(src.callouts());
1089 
1090     // Check the guarded and deconfigured flags
1091     EXPECT_TRUE(src.hexwordData()[3] & 0x03000000);
1092 
1093     const auto& callouts = src.callouts()->callouts();
1094     ASSERT_EQ(callouts.size(), 6);
1095 
1096     // Check callout 0
1097     {
1098         EXPECT_EQ(callouts[0]->priority(), 'H');
1099         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1100 
1101         auto& fru = callouts[0]->fruIdentity();
1102         EXPECT_EQ(fru->getPN().value(), "1234567");
1103         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1104         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1105         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1106 
1107         auto& mruCallouts = callouts[0]->mru();
1108         ASSERT_TRUE(mruCallouts);
1109         auto& mrus = mruCallouts->mrus();
1110         ASSERT_EQ(mrus.size(), 2);
1111         EXPECT_EQ(mrus[0].id, 42);
1112         EXPECT_EQ(mrus[0].priority, 'H');
1113         EXPECT_EQ(mrus[1].id, 43);
1114         EXPECT_EQ(mrus[1].priority, 'M');
1115     }
1116 
1117     // Check callout 1
1118     {
1119         EXPECT_EQ(callouts[1]->priority(), 'M');
1120         EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5");
1121 
1122         auto& fru = callouts[1]->fruIdentity();
1123         EXPECT_EQ(fru->getPN().value(), "2345678");
1124         EXPECT_EQ(fru->getCCIN().value(), "DDDD");
1125         EXPECT_EQ(fru->getSN().value(), "23456789ABCD");
1126         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1127     }
1128 
1129     // Check callout 2
1130     {
1131         EXPECT_EQ(callouts[2]->priority(), 'A');
1132         EXPECT_EQ(callouts[2]->locationCode(), "");
1133 
1134         auto& fru = callouts[2]->fruIdentity();
1135         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1136         EXPECT_EQ(fru->failingComponentType(),
1137                   src::FRUIdentity::maintenanceProc);
1138     }
1139 
1140     // Check callout 3
1141     {
1142         EXPECT_EQ(callouts[3]->priority(), 'B');
1143         EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23");
1144 
1145         auto& fru = callouts[3]->fruIdentity();
1146         EXPECT_EQ(fru->getPN().value(), "TRUSTED");
1147         EXPECT_EQ(fru->failingComponentType(),
1148                   src::FRUIdentity::symbolicFRUTrustedLocCode);
1149     }
1150 
1151     // Check callout 4
1152     {
1153         EXPECT_EQ(callouts[4]->priority(), 'C');
1154         EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24");
1155 
1156         auto& fru = callouts[4]->fruIdentity();
1157         EXPECT_EQ(fru->getPN().value(), "FRUTST1");
1158         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1159     }
1160 
1161     // Check callout 5
1162     {
1163         EXPECT_EQ(callouts[5]->priority(), 'L');
1164         EXPECT_EQ(callouts[5]->locationCode(), "");
1165 
1166         auto& fru = callouts[5]->fruIdentity();
1167         EXPECT_EQ(fru->getPN().value(), "FRUTST2");
1168         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1169     }
1170 
1171     // Check that it didn't find any errors
1172     const auto& data = src.getDebugData();
1173     EXPECT_TRUE(data.empty());
1174 }
1175 
1176 TEST_F(SRCTest, JsonBadCalloutsTest)
1177 {
1178     // The first call will have a Throw in a mock call.
1179     // The second will have a different Throw in a mock call.
1180     // The others have issues with the Priority field.
1181     const auto jsonCallouts = R"(
1182         [
1183             {
1184                 "LocationCode": "P0-C1",
1185                 "Priority": "H"
1186             },
1187             {
1188                 "LocationCode": "P0-C2",
1189                 "Priority": "H"
1190             },
1191             {
1192                 "LocationCode": "P0-C3"
1193             },
1194             {
1195                 "LocationCode": "P0-C4",
1196                 "Priority": "X"
1197             }
1198         ]
1199     )"_json;
1200 
1201     message::Entry entry;
1202     entry.src.type = 0xBD;
1203     entry.src.reasonCode = 0xABCD;
1204     entry.subsystem = 0x42;
1205     entry.src.powerFault = false;
1206 
1207     AdditionalData ad;
1208     NiceMock<MockDataInterface> dataIface;
1209 
1210     // Callout 0 mock calls
1211     // Expand location code will fail, so the unexpanded location
1212     // code should show up in the callout instead.
1213     {
1214         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1215             .WillOnce(Throw(std::runtime_error("Fail")));
1216 
1217         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1218             .Times(1)
1219             .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
1220         EXPECT_CALL(
1221             dataIface,
1222             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1223             .Times(1)
1224             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1225                             SetArgReferee<2>("CCCC"),
1226                             SetArgReferee<3>("123456789ABC")));
1227     }
1228 
1229     // Callout 1 mock calls
1230     // getInventoryFromLocCode will fail
1231     {
1232         EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0))
1233             .Times(1)
1234             .WillOnce(Return("UXXX-P0-C2"));
1235 
1236         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false))
1237             .Times(1)
1238             .WillOnce(Throw(std::runtime_error("Fail")));
1239     }
1240 
1241     SRC src{entry, ad, jsonCallouts, dataIface};
1242 
1243     ASSERT_TRUE(src.callouts());
1244 
1245     const auto& callouts = src.callouts()->callouts();
1246 
1247     // Only the first callout was successful
1248     ASSERT_EQ(callouts.size(), 1);
1249 
1250     {
1251         EXPECT_EQ(callouts[0]->priority(), 'H');
1252         EXPECT_EQ(callouts[0]->locationCode(), "P0-C1");
1253 
1254         auto& fru = callouts[0]->fruIdentity();
1255         EXPECT_EQ(fru->getPN().value(), "1234567");
1256         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1257         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1258         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1259     }
1260 
1261     const auto& data = src.getDebugData();
1262     ASSERT_EQ(data.size(), 4);
1263     EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail");
1264     EXPECT_STREQ(data[1].c_str(),
1265                  "Failed extracting callout data from JSON: Unable to "
1266                  "get inventory path from location code: P0-C2: Fail");
1267     EXPECT_STREQ(data[2].c_str(),
1268                  "Failed extracting callout data from JSON: "
1269                  "[json.exception.out_of_range.403] key 'Priority' not found");
1270     EXPECT_STREQ(data[3].c_str(),
1271                  "Failed extracting callout data from JSON: Invalid "
1272                  "priority 'X' found in JSON callout");
1273 }
1274 
1275 // Test that an inventory path callout can have
1276 // a different priority than H.
1277 TEST_F(SRCTest, InventoryCalloutTestPriority)
1278 {
1279     message::Entry entry;
1280     entry.src.type = 0xBD;
1281     entry.src.reasonCode = 0xABCD;
1282     entry.subsystem = 0x42;
1283     entry.src.powerFault = false;
1284 
1285     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard",
1286                                     "CALLOUT_PRIORITY=M"};
1287     AdditionalData ad{adData};
1288     NiceMock<MockDataInterface> dataIface;
1289 
1290     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
1291         .WillOnce(Return("UTMS-P1"));
1292 
1293     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
1294         .Times(1)
1295         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1296                         SetArgReferee<3>("123456789ABC")));
1297 
1298     SRC src{entry, ad, dataIface};
1299     EXPECT_TRUE(src.valid());
1300 
1301     ASSERT_TRUE(src.callouts());
1302 
1303     EXPECT_EQ(src.callouts()->callouts().size(), 1);
1304 
1305     auto& callout = src.callouts()->callouts().front();
1306 
1307     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
1308     EXPECT_EQ(callout->priority(), 'M');
1309 }
1310