#include #include "CharacterXml.h" namespace { const std::string XML = R"()" R"()" R"()" R"()"; int64_t g_Next = 500; int64_t NextId() { return g_Next++; } std::optional Apply(const nlohmann::json& edit, std::string& error) { return CharacterXml::Apply(XML, edit, NextId, error); } } TEST(CharacterXmlTests, Summary) { const auto s = CharacterXml::Summary(XML); ASSERT_FALSE(s.is_null()); EXPECT_EQ(s["coins"], 449); EXPECT_EQ(s["uscore"], 110); EXPECT_EQ(s["level"], 2); EXPECT_EQ(s["missions_done"], 2); ASSERT_EQ(s["inventories"].size(), 2u); EXPECT_EQ(s["inventories"][0]["size"], 3); EXPECT_EQ(s["inventories"][0]["items"][1]["count"], 5); EXPECT_EQ(s["inventories"][0]["items"][0]["equipped"], true); EXPECT_EQ(s["inventories"][1]["items"][0]["id"], "102"); EXPECT_TRUE(CharacterXml::Summary("not xml").is_null()); } TEST(CharacterXmlTests, EditsNumbersAndItems) { std::string error; const auto xml = Apply({ {"coins", 5000}, {"level", 10}, {"counts", { {"101", 20} }}, {"remove", { "100" }} }, error); ASSERT_TRUE(xml) << error; const auto s = CharacterXml::Summary(*xml); EXPECT_EQ(s["coins"], 5000); EXPECT_EQ(s["uscore"], 110); // untouched EXPECT_EQ(s["level"], 10); ASSERT_EQ(s["inventories"][0]["items"].size(), 1u); EXPECT_EQ(s["inventories"][0]["items"][0]["count"], 20); // Everything else in the file survives EXPECT_NE(xml->find(R"()"), std::string::npos); EXPECT_NE(xml->find(""), std::string::npos); } TEST(CharacterXmlTests, AddsIntoTheFirstFreeSlot) { std::string error; const auto xml = Apply({ {"add", { { {"lot", 1727}, {"count", 3}, {"inventory", 0} } }} }, error); ASSERT_TRUE(xml) << error; const auto summary = CharacterXml::Summary(*xml); const auto& items = summary["inventories"][0]["items"]; ASSERT_EQ(items.size(), 3u); EXPECT_EQ(items[2]["lot"], 1727); EXPECT_EQ(items[2]["slot"], 1); // 0 and 2 are taken EXPECT_EQ(items[2]["count"], 3); EXPECT_EQ(items[2]["bound"], false); EXPECT_EQ(std::stoll(items[2]["id"].get()), g_Next - 1); } TEST(CharacterXmlTests, RefusesBadEdits) { std::string error; // The bag has 3 slots and 2 are used: a second new item doesn't fit EXPECT_FALSE(Apply({ {"add", { { {"lot", 1}, {"count", 1} }, { {"lot", 2}, {"count", 1} } }} }, error)); EXPECT_EQ(error, "That inventory is full"); EXPECT_FALSE(Apply({ {"remove", { "999" }} }, error)); EXPECT_NE(error.find("no item 999"), std::string::npos); EXPECT_FALSE(Apply({ {"coins", -1} }, error)); EXPECT_FALSE(Apply({ {"level", 46} }, error)); EXPECT_FALSE(Apply({ {"counts", { {"101", 0} }} }, error)); EXPECT_FALSE(Apply({ {"add", { { {"lot", 5}, {"inventory", 7} } }} }, error)); } TEST(CharacterXmlTests, PetIdsMatchPetNames) { // Saved before eCharacterVersion::PET_IDS: the game clears bit 32 at the next login, and so does the dashboard const std::string oldSave = R"(

)"; const auto oldPets = CharacterXml::Pets(oldSave); ASSERT_EQ(oldPets.size(), 1u); EXPECT_EQ(oldPets[0].id, 1152921504816087040LL); // 1152921509111054336 without bit 32 EXPECT_EQ(oldPets[0].lot, 3195); EXPECT_EQ(oldPets[0].name, "Trike"); // Up to date: ids are used as saved const std::string newSave = R"(

)"; const auto newPets = CharacterXml::Pets(newSave); ASSERT_EQ(newPets.size(), 1u); EXPECT_EQ(newPets[0].id, 1152921509111054336LL); EXPECT_TRUE(CharacterXml::Pets("").empty()); EXPECT_TRUE(CharacterXml::Pets("not xml").empty()); }