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