text: /**
   * Copyright © 2019 IBM Corporation
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  #include "extensions/openpower-pels/registry.hpp"
  
  #include <filesystem>
  #include <fstream>
  #include <nlohmann/json.hpp>
  
  #include <gtest/gtest.h>
  
  using namespace openpower::pels::message;
  using namespace openpower::pels;
  namespace fs = std::filesystem;
  
  const auto registryData = R"(
  {
      "PELs":
      [
          {
              "Name": "xyz.openbmc_project.Power.Fault",
              "Subsystem": "power_supply",
  
              "SRC":
              {
                  "ReasonCode": "0x2030"
              },
  
              "Documentation":
              {
                  "Description": "A PGOOD Fault",
                  "Message": "PS had a PGOOD Fault"
              }
          },
  
          {
              "Name": "xyz.openbmc_project.Power.OverVoltage",
              "Subsystem": "power_control_hw",
              "Severity":
              [
                  {
                      "System": "systemA",
                      "SevValue": "unrecoverable"
                  },
                  {
                      "System": "systemB",
                      "SevValue": "recovered"
                  },
                  {
                      "SevValue": "predictive"
                  }
              ],
              "MfgSeverity": "non_error",
              "ActionFlags": ["service_action", "report", "call_home"],
              "MfgActionFlags": ["hidden"],
  
              "SRC":
              {
                  "ReasonCode": "0x2333",
                  "Type": "BD",
                  "SymptomIDFields": ["SRCWord5", "SRCWord6", "SRCWord7"],
                  "PowerFault": true,
                  "Words6To9":
                  {
                      "6":
                      {
                          "Description": "Failing unit number",
                          "AdditionalDataPropSource": "PS_NUM"
                      },
  
                      "7":
                      {
                          "Description": "bad voltage",
                          "AdditionalDataPropSource": "VOLTAGE"
                      }
                  }
              },
  
              "Documentation":
              {
                  "Description": "A PGOOD Fault",
                  "Message": "PS %1 had a PGOOD Fault",
                  "MessageArgSources":
                  [
                      "SRCWord6"
                  ],
                  "Notes": [
                      "In the UserData section there is a JSON",
                      "dump that provides debug information."
                  ]
              }
          }
      ]
  }
  )";
  
  class RegistryTest : public ::testing::Test
  {
    protected:
      static void SetUpTestCase()
      {
          char path[] = "/tmp/regtestXXXXXX";
          regDir = mkdtemp(path);
      }
  
      static void TearDownTestCase()
      {
          fs::remove_all(regDir);
      }
  
      static std::string writeData(const char* data)
      {
          fs::path path = regDir / "registry.json";
          std::ofstream stream{path};
          stream << data;
          return path;
      }
  
      static fs::path regDir;
  };
  
  fs::path RegistryTest::regDir{};
  
  TEST_F(RegistryTest, TestNoEntry)
  {
      auto path = RegistryTest::writeData(registryData);
      Registry registry{path};
  
      auto entry = registry.lookup("foo", LookupType::name);
      EXPECT_FALSE(entry);
  }
  
  TEST_F(RegistryTest, TestFindEntry)
  {
      auto path = RegistryTest::writeData(registryData);
      Registry registry{path};
  
      auto entry = registry.lookup("xyz.openbmc_project.Power.OverVoltage",
                                   LookupType::name);
      ASSERT_TRUE(entry);
      EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage");
      EXPECT_EQ(entry->subsystem, 0x62);
  
      ASSERT_EQ(entry->severity->size(), 3);
      EXPECT_EQ((*entry->severity)[0].severity, 0x40);
      EXPECT_EQ((*entry->severity)[0].system, "systemA");
      EXPECT_EQ((*entry->severity)[1].severity, 0x10);
      EXPECT_EQ((*entry->severity)[1].system, "systemB");
      EXPECT_EQ((*entry->severity)[2].severity, 0x20);
      EXPECT_EQ((*entry->severity)[2].system, "");
  
      EXPECT_EQ(entry->mfgSeverity->size(), 1);
      EXPECT_EQ((*entry->mfgSeverity)[0].severity, 0x00);
  
      EXPECT_EQ(*(entry->actionFlags), 0xA800);
      EXPECT_EQ(*(entry->mfgActionFlags), 0x4000);
      EXPECT_EQ(entry->componentID, 0x2300);
      EXPECT_FALSE(entry->eventType);
      EXPECT_FALSE(entry->eventScope);
  
      EXPECT_EQ(entry->src.type, 0xBD);
      EXPECT_EQ(entry->src.reasonCode, 0x2333);
      EXPECT_EQ(*(entry->src.powerFault), true);
  
      auto& hexwords = entry->src.hexwordADFields;
      EXPECT_TRUE(hexwords);
      EXPECT_EQ((*hexwords).size(), 2);
  
      auto word = (*hexwords).find(6);
      EXPECT_NE(word, (*hexwords).end());
      EXPECT_EQ(std::get<0>(word->second), "PS_NUM");
  
      word = (*hexwords).find(7);
      EXPECT_NE(word, (*hexwords).end());
      EXPECT_EQ(std::get<0>(word->second), "VOLTAGE");
  
      auto& sid = entry->src.symptomID;
      EXPECT_TRUE(sid);
      EXPECT_EQ((*sid).size(), 3);
      EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 5), (*sid).end());
      EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 6), (*sid).end());
      EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 7), (*sid).end());
  
      EXPECT_EQ(entry->doc.description, "A PGOOD Fault");
      EXPECT_EQ(entry->doc.message, "PS %1 had a PGOOD Fault");
      auto& hexwordSource = entry->doc.messageArgSources;
      EXPECT_TRUE(hexwordSource);
      EXPECT_EQ((*hexwordSource).size(), 1);
      EXPECT_EQ((*hexwordSource).front(), "SRCWord6");
  
      entry = registry.lookup("0x2333", LookupType::reasonCode);
      ASSERT_TRUE(entry);
      EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage");
  }
  
  // Check the entry that mostly uses defaults
  TEST_F(RegistryTest, TestFindEntryMinimal)
  {
      auto path = RegistryTest::writeData(registryData);
      Registry registry{path};
  
      auto entry =
          registry.lookup("xyz.openbmc_project.Power.Fault", LookupType::name);
      ASSERT_TRUE(entry);
      EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.Fault");
      EXPECT_EQ(entry->subsystem, 0x61);
      EXPECT_FALSE(entry->severity);
      EXPECT_FALSE(entry->mfgSeverity);
      EXPECT_FALSE(entry->mfgActionFlags);
      EXPECT_FALSE(entry->actionFlags);
      EXPECT_EQ(entry->componentID, 0x2000);
      EXPECT_FALSE(entry->eventType);
      EXPECT_FALSE(entry->eventScope);
  
      EXPECT_EQ(entry->src.reasonCode, 0x2030);
      EXPECT_EQ(entry->src.type, 0xBD);
      EXPECT_FALSE(entry->src.powerFault);
      EXPECT_FALSE(entry->src.hexwordADFields);
      EXPECT_FALSE(entry->src.symptomID);
  }
  
  TEST_F(RegistryTest, TestBadJSON)
  {
      auto path = RegistryTest::writeData("bad {} json");
  
      Registry registry{path};
  
      EXPECT_FALSE(registry.lookup("foo", LookupType::name));
  }
  
  // Test the helper functions the use the pel_values data.
  TEST_F(RegistryTest, TestHelperFunctions)
  {
      using namespace openpower::pels::message::helper;
      EXPECT_EQ(getSubsystem("input_power_source"), 0xA1);
      EXPECT_THROW(getSubsystem("foo"), std::runtime_error);
  
      EXPECT_EQ(getSeverity("symptom_recovered"), 0x71);
      EXPECT_THROW(getSeverity("foo"), std::runtime_error);
  
      EXPECT_EQ(getEventType("dump_notification"), 0x08);
      EXPECT_THROW(getEventType("foo"), std::runtime_error);
  
      EXPECT_EQ(getEventScope("possibly_multiple_platforms"), 0x04);
      EXPECT_THROW(getEventScope("foo"), std::runtime_error);
  
      std::vector<std::string> flags{"service_action", "dont_report",
                                     "termination"};
      EXPECT_EQ(getActionFlags(flags), 0x9100);
  
      flags.clear();
      flags.push_back("foo");
      EXPECT_THROW(getActionFlags(flags), std::runtime_error);
  }
  
  TEST_F(RegistryTest, TestGetSRCReasonCode)
  {
      using namespace openpower::pels::message::helper;
      EXPECT_EQ(getSRCReasonCode(R"({"ReasonCode": "0x5555"})"_json, "foo"),
                0x5555);
  
      EXPECT_THROW(getSRCReasonCode(R"({"ReasonCode": "ZZZZ"})"_json, "foo"),
                   std::runtime_error);
  }
  
  TEST_F(RegistryTest, TestGetSRCType)
  {
      using namespace openpower::pels::message::helper;
      EXPECT_EQ(getSRCType(R"({"Type": "11"})"_json, "foo"), 0x11);
      EXPECT_EQ(getSRCType(R"({"Type": "BF"})"_json, "foo"), 0xBF);
  
      EXPECT_THROW(getSRCType(R"({"Type": "1"})"_json, "foo"),
                   std::runtime_error);
  
      EXPECT_THROW(getSRCType(R"({"Type": "111"})"_json, "foo"),
                   std::runtime_error);
  }
  
  TEST_F(RegistryTest, TestGetSRCHexwordFields)
  {
      using namespace openpower::pels::message::helper;
      const auto hexwords = R"(
      {"Words6To9":
        {
          "8":
          {
              "Description": "TEST",
              "AdditionalDataPropSource": "TEST"
          }
        }
      })"_json;
  
      auto fields = getSRCHexwordFields(hexwords, "foo");
      EXPECT_TRUE(fields);
      auto word = fields->find(8);
      EXPECT_NE(word, fields->end());
  
      const auto theInvalidRWord = R"(
      {"Words6To9":
        {
          "R":
          {
              "Description": "TEST",
              "AdditionalDataPropSource": "TEST"
          }
        }
      })"_json;
  
      EXPECT_THROW(getSRCHexwordFields(theInvalidRWord, "foo"),
                   std::runtime_error);
  }
  
  TEST_F(RegistryTest, TestGetSRCSymptomIDFields)
  {
      using namespace openpower::pels::message::helper;
      const auto sID = R"(
      {
          "SymptomIDFields": ["SRCWord3", "SRCWord4", "SRCWord5"]
      })"_json;
  
      auto fields = getSRCSymptomIDFields(sID, "foo");
      EXPECT_NE(std::find(fields->begin(), fields->end(), 3), fields->end());
      EXPECT_NE(std::find(fields->begin(), fields->end(), 4), fields->end());
      EXPECT_NE(std::find(fields->begin(), fields->end(), 5), fields->end());
  
      const auto badField = R"(
      {
          "SymptomIDFields": ["SRCWord3", "SRCWord4", "SRCWord"]
      })"_json;
  
      EXPECT_THROW(getSRCSymptomIDFields(badField, "foo"), std::runtime_error);
  }
  
  TEST_F(RegistryTest, TestGetComponentID)
  {
      using namespace openpower::pels::message::helper;
  
      // Get it from the JSON
      auto id =
          getComponentID(0xBD, 0x4200, R"({"ComponentID":"0x4200"})"_json, "foo");
      EXPECT_EQ(id, 0x4200);
  
      // Get it from the reason code on a 0xBD SRC
      id = getComponentID(0xBD, 0x6700, R"({})"_json, "foo");
      EXPECT_EQ(id, 0x6700);
  
      // Not present on a 0x11 SRC
      EXPECT_THROW(getComponentID(0x11, 0x8800, R"({})"_json, "foo"),
                   std::runtime_error);
  }
  
  // Test when callouts are in the JSON.
  TEST_F(RegistryTest, TestGetCallouts)
  {
      std::vector<std::string> names;
  
      {
          // Callouts without AD, that depend on system type,
          // where there is a default entry without a system type.
          auto json = R"(
          [
          {
              "System": "system1",
              "CalloutList":
              [
                  {
                      "Priority": "high",
                      "LocCode": "P1-C1"
                  },
                  {
                      "Priority": "low",
                      "LocCode": "P1"
                  },
                  {
                      "Priority": "low",
                      "SymbolicFRU": "service_docs"
                  },
                  {
                      "Priority": "low",
                      "SymbolicFRUTrusted": "air_mover",
                      "UseInventoryLocCode": true
                  }
              ]
          },
          {
              "CalloutList":
              [
                  {
                      "Priority": "medium",
                      "Procedure": "no_vpd_for_fru"
                  },
                  {
                      "Priority": "low",
                      "LocCode": "P3-C8",
                      "SymbolicFRUTrusted": "service_docs"
                  }
              ]
  
          }
          ])"_json;
  
          AdditionalData ad;
          names.push_back("system1");
  
          auto callouts = Registry::getCallouts(json, names, ad);
          EXPECT_EQ(callouts.size(), 4);
          EXPECT_EQ(callouts[0].priority, "high");
          EXPECT_EQ(callouts[0].locCode, "P1-C1");
          EXPECT_EQ(callouts[0].procedure, "");
          EXPECT_EQ(callouts[0].symbolicFRU, "");
          EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
          EXPECT_EQ(callouts[1].priority, "low");
          EXPECT_EQ(callouts[1].locCode, "P1");
          EXPECT_EQ(callouts[1].procedure, "");
          EXPECT_EQ(callouts[1].symbolicFRU, "");
          EXPECT_EQ(callouts[1].symbolicFRUTrusted, "");
          EXPECT_EQ(callouts[2].priority, "low");
          EXPECT_EQ(callouts[2].locCode, "");
          EXPECT_EQ(callouts[2].procedure, "");
          EXPECT_EQ(callouts[2].symbolicFRU, "service_docs");
          EXPECT_EQ(callouts[2].symbolicFRUTrusted, "");
          EXPECT_EQ(callouts[3].priority, "low");
          EXPECT_EQ(callouts[3].locCode, "");
          EXPECT_EQ(callouts[3].procedure, "");
          EXPECT_EQ(callouts[3].symbolicFRU, "");
          EXPECT_EQ(callouts[3].symbolicFRUTrusted, "air_mover");
          EXPECT_EQ(callouts[3].useInventoryLocCode, true);
  
          // system2 isn't in the JSON, so it will pick the default one
          names[0] = "system2";
          callouts = Registry::getCallouts(json, names, ad);
          EXPECT_EQ(callouts.size(), 2);
          EXPECT_EQ(callouts[0].priority, "medium");
          EXPECT_EQ(callouts[0].locCode, "");
          EXPECT_EQ(callouts[0].procedure, "no_vpd_for_fru");
          EXPECT_EQ(callouts[0].symbolicFRU, "");
          EXPECT_EQ(callouts[1].priority, "low");
          EXPECT_EQ(callouts[1].locCode, "P3-C8");
          EXPECT_EQ(callouts[1].procedure, "");
          EXPECT_EQ(callouts[1].symbolicFRU, "");
          EXPECT_EQ(callouts[1].symbolicFRUTrusted, "service_docs");
          EXPECT_EQ(callouts[1].useInventoryLocCode, false);
      }
  
      // Empty JSON array (treated as an error)
      {
          auto json = R"([])"_json;
          AdditionalData ad;
          names[0] = "system1";
          EXPECT_THROW(Registry::getCallouts(json, names, ad),
                       std::runtime_error);
      }
  
      {
          // Callouts without AD, that depend on system type,
          // where there isn't a default entry without a system type.
          auto json = R"(
          [
          {
              "System": "system1",
              "CalloutList":
              [
                  {
                      "Priority": "high",
                      "LocCode": "P1-C1"
                  },
                  {
                      "Priority": "low",
                      "LocCode": "P1",
                      "SymbolicFRU": "1234567"
                  }
              ]
          },
          {
              "System": "system2",
              "CalloutList":
              [
                  {
                      "Priority": "medium",
                      "LocCode": "P7",
                      "CalloutType": "tool_fru"
                  }
              ]
  
          }
          ])"_json;
  
          AdditionalData ad;
          names[0] = "system1";
  
          auto callouts = Registry::getCallouts(json, names, ad);
          EXPECT_EQ(callouts.size(), 2);
          EXPECT_EQ(callouts[0].priority, "high");
          EXPECT_EQ(callouts[0].locCode, "P1-C1");
          EXPECT_EQ(callouts[0].procedure, "");
          EXPECT_EQ(callouts[0].symbolicFRU, "");
          EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
          EXPECT_EQ(callouts[1].priority, "low");
          EXPECT_EQ(callouts[1].locCode, "P1");
          EXPECT_EQ(callouts[1].procedure, "");
          EXPECT_EQ(callouts[1].symbolicFRU, "1234567");
          EXPECT_EQ(callouts[1].symbolicFRUTrusted, "");
  
          names[0] = "system2";
          callouts = Registry::getCallouts(json, names, ad);
          EXPECT_EQ(callouts.size(), 1);
          EXPECT_EQ(callouts[0].priority, "medium");
          EXPECT_EQ(callouts[0].locCode, "P7");
          EXPECT_EQ(callouts[0].procedure, "");
          EXPECT_EQ(callouts[0].symbolicFRU, "");
          EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
  
          // There is no entry for system3 or a default system,
          // so this should fail.
          names[0] = "system3";
          EXPECT_THROW(Registry::getCallouts(json, names, ad),
                       std::runtime_error);
      }
  
      {
          // Callouts that use the AdditionalData key PROC_NUM
          // as an index into them, along with a system type.
          // It supports PROC_NUMs 0 and 1.
          auto json = R"(
          {
              "ADName": "PROC_NUM",
              "CalloutsWithTheirADValues":
              [
                  {
                      "ADValue": "0",
                      "Callouts":
                      [
                          {
                              "System": "system3",
                              "CalloutList":
                              [
                                  {
                                      "Priority": "high",
                                      "LocCode": "P1-C5"
                                  },
                                  {
                                      "Priority": "medium",
                                      "LocCode": "P1-C6",
                                      "SymbolicFRU": "1234567"
                                  },
                                  {
                                      "Priority": "low",
                                      "Procedure": "no_vpd_for_fru",
                                      "CalloutType": "config_procedure"
                                  }
                              ]
                          },
                          {
                              "CalloutList":
                              [
                                  {
                                      "Priority": "low",
                                      "LocCode": "P55"
                                  }
                              ]
                          }
                      ]
                  },
                  {
                      "ADValue": "1",
                      "Callouts":
                      [
                          {
                              "CalloutList":
                              [
                                  {
                                      "Priority": "high",
                                      "LocCode": "P1-C6",
                                      "CalloutType": "external_fru"
                                  }
                              ]
                          }
                      ]
                  }
              ]
          })"_json;
  
          {
              // Find callouts for PROC_NUM 0 on system3
              std::vector<std::string> adData{"PROC_NUM=0"};
              AdditionalData ad{adData};
              names[0] = "system3";
  
              auto callouts = Registry::getCallouts(json, names, ad);
              EXPECT_EQ(callouts.size(), 3);
              EXPECT_EQ(callouts[0].priority, "high");
              EXPECT_EQ(callouts[0].locCode, "P1-C5");
              EXPECT_EQ(callouts[0].procedure, "");
              EXPECT_EQ(callouts[0].symbolicFRU, "");
              EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
              EXPECT_EQ(callouts[1].priority, "medium");
              EXPECT_EQ(callouts[1].locCode, "P1-C6");
              EXPECT_EQ(callouts[1].procedure, "");
              EXPECT_EQ(callouts[1].symbolicFRU, "1234567");
              EXPECT_EQ(callouts[1].symbolicFRUTrusted, "");
              EXPECT_EQ(callouts[2].priority, "low");
              EXPECT_EQ(callouts[2].locCode, "");
              EXPECT_EQ(callouts[2].procedure, "no_vpd_for_fru");
              EXPECT_EQ(callouts[2].symbolicFRU, "");
              EXPECT_EQ(callouts[2].symbolicFRUTrusted, "");
  
              // Find callouts for PROC_NUM 0 that uses the default system entry.
              names[0] = "system99";
  
              callouts = Registry::getCallouts(json, names, ad);
              EXPECT_EQ(callouts.size(), 1);
              EXPECT_EQ(callouts[0].priority, "low");
              EXPECT_EQ(callouts[0].locCode, "P55");
              EXPECT_EQ(callouts[0].procedure, "");
              EXPECT_EQ(callouts[0].symbolicFRU, "");
              EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
          }
          {
              // Find callouts for PROC_NUM 1 that uses a default system entry.
              std::vector<std::string> adData{"PROC_NUM=1"};
              AdditionalData ad{adData};
              names[0] = "system1";
  
              auto callouts = Registry::getCallouts(json, names, ad);
              EXPECT_EQ(callouts.size(), 1);
              EXPECT_EQ(callouts[0].priority, "high");
              EXPECT_EQ(callouts[0].locCode, "P1-C6");
              EXPECT_EQ(callouts[0].procedure, "");
              EXPECT_EQ(callouts[0].symbolicFRU, "");
              EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
          }
          {
              // There is no entry for PROC_NUM 2, so no callouts
              std::vector<std::string> adData{"PROC_NUM=2"};
              AdditionalData ad{adData};
  
              auto callouts = Registry::getCallouts(json, names, ad);
              EXPECT_TRUE(callouts.empty());
          }
      }
  }
  