changeset 137:0f9a8ddee022

Server: rework storage of test_database, closes #714 Do not store any objects in maps, instead store a unique nlohmann::json object in the test_database class, update that object from any model object. Unify access to the objects with the following functions: - test_database::ref, access the JSON object, - test_database::dict, access the parent JSON object.
author David Demelier <markand@malikania.fr>
date Thu, 05 Oct 2017 08:38:47 +0200
parents 835c8ee3f9e5
children 532f259557dd
files libserver-test/malikania/server/db/test_account.cpp libserver-test/malikania/server/db/test_account.hpp libserver-test/malikania/server/db/test_character.cpp libserver-test/malikania/server/db/test_character.hpp libserver-test/malikania/server/db/test_database.cpp libserver-test/malikania/server/db/test_database.hpp libserver-test/malikania/server/db/test_spell.cpp libserver-test/malikania/server/db/test_spell.hpp tests/libserver/db/account/main.cpp
diffstat 9 files changed, 251 insertions(+), 243 deletions(-) [+]
line wrap: on
line diff
--- a/libserver-test/malikania/server/db/test_account.cpp	Fri Oct 20 11:44:57 2017 +0200
+++ b/libserver-test/malikania/server/db/test_account.cpp	Thu Oct 05 08:38:47 2017 +0200
@@ -26,47 +26,60 @@
 
 void test_account::do_save()
 {
-    assert(db_.test_account_dao().accounts().count(id_) == 0);
+    assert(!db_.exists(*this));
 
-    id_ = db_.test_account_dao().next_id();
-    db_.test_account_dao().accounts().emplace(id_, serialize());
+    id_ = db_.next_account_id();
+    db_.dict(*this)[db_.sid(*this)] = serialize();
 }
 
 void test_account::do_remove()
 {
-    assert(db_.test_account_dao().accounts().count(id_));
+    assert(db_.exists(*this));
 
-    // Do cascade deletion.
-    db_.test_account_dao().accounts().erase(id_);
-    db_.test_character_dao().remove_all(id_);
+    db_.dict(*this).erase(db_.sid(*this));
 }
 
 void test_account::do_set_password(const std::string& password)
 {
-    assert(db_.test_account_dao().accounts().count(id_));
+    assert(db_.exists(*this));
 
-    db_.test_account_dao().accounts().at(id_)["password"] = password;
+    db_.ref(*this)["password"] = password;
 }
 
 void test_account::do_set_email(const std::string& email)
 {
-    assert(db_.test_account_dao().accounts().count(id_));
+    assert(db_.exists(*this));
 
-    db_.test_account_dao().accounts().at(id_)["email"] = email;
+    db_.ref(*this)["email"] = email;
 }
 
 void test_account::do_set_firstname(const std::string& name)
 {
-    assert(db_.test_account_dao().accounts().count(id_));
+    assert(db_.exists(*this));
 
-    db_.test_account_dao().accounts().at(id_)["firstname"] = name;
+
+    db_.ref(*this)["firstname"] = name;
 }
 
 void test_account::do_set_lastname(const std::string& name)
 {
-    assert(db_.test_account_dao().accounts().count(id_));
+    assert(db_.exists(*this));
+
+    db_.ref(*this)["lastname"] = name;
+}
 
-    db_.test_account_dao().accounts().at(id_)["lastname"] = name;
+test_account::test_account(nlohmann::json json, test_database& db)
+    : test_account(json["login"], json["password"], db)
+{
+    id_ = json["id"];
+    login_ = json["login"];
+    password_ = json["password"];
+    email_ = json["email"];
+    firstname_ = json["firstname"];
+    lastname_ = json["lastname"];
+
+    for (const auto& cdata : json["characters"])
+        characters_.push_back(std::make_unique<test_character>(cdata, db_));
 }
 
 nlohmann::json test_account::serialize() const
@@ -77,32 +90,16 @@
         { "password", password_ },
         { "email", email_ },
         { "firstname", firstname_ },
-        { "lastname", lastname_ }
+        { "lastname", lastname_ },
+        { "characters", nlohmann::json::object() }
     };
 }
 
-void test_account::unserialize(const nlohmann::json& input)
-{
-    id_ = input["id"];
-    login_ = input["login"];
-    password_ = input["password"];
-    email_ = input["email"];
-    firstname_ = input["firstname"];
-    lastname_ = input["lastname"];
-}
-
 std::unique_ptr<account> test_dao::find_by_login(const std::string& login)
 {
-    for (const auto& pair : accounts_) {
-        if (pair.second["login"] == login) {
-            auto a = std::make_unique<test_account>(pair.second["login"], pair.second["password"], db_);
-
-            a->unserialize(pair.second);
-            a->characters_ = db_.test_character_dao().characters_for_account(a->id_);
-
-            return std::move(a);
-        }
-    }
+    for (const auto& v : db_.data()["accounts"])
+        if (v["login"].get<std::string>() == login)
+            return std::make_unique<test_account>(v, db_);
 
     return nullptr;
 }
--- a/libserver-test/malikania/server/db/test_account.hpp	Fri Oct 20 11:44:57 2017 +0200
+++ b/libserver-test/malikania/server/db/test_account.hpp	Thu Oct 05 08:38:47 2017 +0200
@@ -50,6 +50,17 @@
 
 public:
     /**
+     * Parse the JSON as account.
+     *
+     * This also load recursively characters.
+     *
+     * \warning json input is not verified
+     * \param json the JSON input
+     * \param db the database
+     */
+    test_account(nlohmann::json json, test_database& db);
+
+    /**
      * Construct a test_account.
      *
      * \param login the login
@@ -63,6 +74,16 @@
     }
 
     /**
+     * Path where accounts are saved in JSON.
+     *
+     * \return the path as JSON pointer
+     */
+    inline std::string path() const noexcept
+    {
+        return "/accounts";
+    }
+
+    /**
      * Dump this account as json.
      *
      * \return the json
@@ -70,14 +91,6 @@
     nlohmann::json serialize() const;
 
     /**
-     * Fill this account with json input.
-     *
-     * \warning json input must be valid, no checks are performed
-     * \param input the json input
-     */
-    void unserialize(const nlohmann::json& json);
-
-    /**
      * \copydoc account::do_save
      */
     void do_save() override;
@@ -116,7 +129,6 @@
  */
 class test_account::test_dao : public account::dao {
 private:
-    std::unordered_map<std::int64_t, nlohmann::json> accounts_;
     std::int64_t sequence_{0};
     test_database& db_;
 
@@ -132,36 +144,6 @@
     }
 
     /**
-     * Get the in memory accounts.
-     *
-     * \return the account map
-     */
-    inline const std::unordered_map<std::int64_t, nlohmann::json>& accounts() const noexcept
-    {
-        return accounts_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the account map
-     */
-    inline std::unordered_map<std::int64_t, nlohmann::json>& accounts() noexcept
-    {
-        return accounts_;
-    }
-
-    /**
-     * Compute the next id from the internal sequence.
-     *
-     * \return the next id
-     */
-    inline std::int64_t next_id() noexcept
-    {
-        return ++sequence_;
-    }
-
-    /**
      * \copydoc dao::find_by_login
      */
     std::unique_ptr<account> find_by_login(const std::string& login) override;
--- a/libserver-test/malikania/server/db/test_character.cpp	Fri Oct 20 11:44:57 2017 +0200
+++ b/libserver-test/malikania/server/db/test_character.cpp	Thu Oct 05 08:38:47 2017 +0200
@@ -26,21 +26,19 @@
 
 void test_character::do_save(std::int64_t account_id)
 {
-    id_ = db_.test_character_dao().next_id();
+    id_ = db_.next_character_id();
     account_id_ = account_id;
-    db_.test_character_dao().characters().emplace(id_, serialize());
+    db_.dict(*this)[db_.sid(*this)] = serialize();
 }
 
 void test_character::do_remove()
 {
-    db_.test_character_dao().characters().erase(id_);
+    db_.dict(*this).erase(db_.sid(*this));
 }
 
 void test_character::do_set_level(std::uint16_t level)
 {
-    assert(db_.test_character_dao().characters().count(id_));
-
-    db_.test_character_dao().characters().at(id_)["level"] = level;
+    db_.ref(*this)["level"] = level;
 }
 
 nlohmann::json test_character::serialize() const
@@ -54,7 +52,8 @@
     };
 }
 
-void test_character::unserialize(const nlohmann::json& json)
+test_character::test_character(nlohmann::json json, test_database& db)
+    : test_character(json["nickname"], json["classname"], db)
 {
     id_ = json["id"];
     account_id_ = json["account_id"];
@@ -63,37 +62,6 @@
     level_ = json["level"];
 }
 
-character_list test_dao::characters_for_account(std::int64_t account_id)
-{
-    character_list set;
-
-    for (const auto& pair : characters_) {
-        if (pair.second["account_id"].get<int>() == account_id) {
-            auto c = std::make_unique<test_character>(
-                pair.second["nickname"],
-                pair.second["classname"],
-                db_
-            );
-
-            c->unserialize(pair.second);
-            set.push_back(std::move(c));
-        }
-    }
-
-    return set;
-}
-
-void test_dao::remove_all(std::int64_t account_id)
-{
-    for (auto it = characters_.begin(); it != characters_.end(); ) {
-        if (it->second["account_id"].get<int>() == account_id) {
-            db_.test_character_dao().remove_all(it->second["id"]);
-            it = characters_.erase(it);
-        } else
-            it++;
-    }
-}
-
 } // !server
 
 } // !mlk
--- a/libserver-test/malikania/server/db/test_character.hpp	Fri Oct 20 11:44:57 2017 +0200
+++ b/libserver-test/malikania/server/db/test_character.hpp	Thu Oct 05 08:38:47 2017 +0200
@@ -25,6 +25,7 @@
  */
 
 #include <unordered_map>
+#include <sstream>
 
 #include <json.hpp>
 
@@ -64,6 +65,17 @@
 
 public:
     /**
+     * Parse the JSON as character.
+     *
+     * This also load recursively spells.
+     *
+     * \warning json input is not verified
+     * \param json the JSON input
+     * \param db the database
+     */
+    test_character(nlohmann::json json, test_database& db);
+
+    /**
      * Construct a test account.
      *
      * \param nickname the nickname
@@ -77,80 +89,28 @@
     }
 
     /**
+     * Path where characters are saved in JSON.
+     *
+     * \return the path as JSON pointer
+     */
+    inline std::string path() const
+    {
+        assert(account_id_ > 0);
+
+        std::ostringstream oss;
+
+        oss << "/accounts/" << account_id_;
+        oss << "/characters";
+
+        return oss.str();
+    }
+
+    /**
      * Dump this account as json.
      *
      * \return the json
      */
     nlohmann::json serialize() const;
-
-    /**
-     * Fill this account with json input.
-     *
-     * \warning json input must be valid, no checks are performed
-     * \param input the json input
-     */
-    void unserialize(const nlohmann::json& json);
-};
-
-/**
- * \brief Character dao for test_character.
- *
- * This class saves characters in memory, thus once deleted the no characters
- * are available anymore.
- */
-class test_character::test_dao {
-private:
-    std::unordered_map<std::int64_t, nlohmann::json> characters_;
-    std::int64_t sequence_{0};
-    test_database& db_;
-
-public:
-    inline test_dao(test_database& db) noexcept
-        : db_(db)
-    {
-    }
-
-    /**
-     * Get the in memory characters.
-     *
-     * \return the character map
-     */
-    inline const std::unordered_map<std::int64_t, nlohmann::json>& characters() const noexcept
-    {
-        return characters_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the character map
-     */
-    inline std::unordered_map<std::int64_t, nlohmann::json>& characters() noexcept
-    {
-        return characters_;
-    }
-
-    /**
-     * Compute the next id from the internal sequence.
-     *
-     * \return the next id
-     */
-    inline std::int64_t next_id() noexcept
-    {
-        return ++sequence_;
-    }
-
-    /**
-     * Get the set of characters for the given account.
-     */
-    character_list characters_for_account(std::int64_t account_id);
-
-    /**
-     * Remove all characters for the specified account.
-     *
-     * \param account_id the account id
-     */
-    void remove_all(std::int64_t account_id);
 };
 
 } // !server
--- a/libserver-test/malikania/server/db/test_database.cpp	Fri Oct 20 11:44:57 2017 +0200
+++ b/libserver-test/malikania/server/db/test_database.cpp	Thu Oct 05 08:38:47 2017 +0200
@@ -23,6 +23,19 @@
 
 namespace server {
 
+/*
+ * test_database::test_database
+ * ------------------------------------------------------------------
+ *
+ * We can't use the in-class initialization because the JSON object will be
+ * constructed as an array.
+ */
+test_database::test_database()
+    : data_(nlohmann::json::value_t::object)
+{
+    data_["accounts"] = nlohmann::json::object();
+}
+
 std::unique_ptr<account> test_database::account_draft(std::string login, std::string password)
 {
     return std::make_unique<test_account>(std::move(login), std::move(password), *this);
--- a/libserver-test/malikania/server/db/test_database.hpp	Fri Oct 20 11:44:57 2017 +0200
+++ b/libserver-test/malikania/server/db/test_database.hpp	Thu Oct 05 08:38:47 2017 +0200
@@ -27,6 +27,8 @@
 
 #include <malikania/server/db/database.hpp>
 
+#include <json.hpp>
+
 #include "test_account.hpp"
 #include "test_character.hpp"
 #include "test_spell.hpp"
@@ -40,12 +42,59 @@
  */
 class test_database : public database {
 private:
+    nlohmann::json data_;
     test_spell::test_dao spell_dao_{*this};
-    test_character::test_dao character_dao_{*this};
     test_account::test_dao account_dao_{*this};
+    std::uint64_t seq_account_id_{1};
+    std::uint64_t seq_character_id_{1};
 
 public:
     /**
+     * Construct the database and initialize the JSON tree.
+     */
+    test_database();
+
+    /**
+     * Get the next account id.
+     *
+     * \return the next id
+     */
+    inline std::int64_t next_account_id() noexcept
+    {
+        return seq_account_id_ ++;
+    }
+
+    /**
+     * Get the next character id.
+     *
+     * \return the next character id
+     */
+    inline std::int64_t next_character_id() noexcept
+    {
+        return seq_character_id_ ++;
+    }
+
+    /**
+     * Access the underlying data.
+     *
+     * \return the data
+     */
+    inline const nlohmann::json& data() const noexcept
+    {
+        return data_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the data
+     */
+    inline nlohmann::json& data() noexcept
+    {
+        return data_;
+    }
+
+    /**
      * Get the real underlying test_dao type for accounts.
      *
      * \return the test_dao instance
@@ -56,16 +105,6 @@
     }
 
     /**
-     * Get the real underlying test_dao type for characters.
-     *
-     * \return the test_character::test_dao instance
-     */
-    inline test_character::test_dao& test_character_dao() noexcept
-    {
-        return character_dao_;
-    }
-
-    /**
      * Get the real underlying test_dao type for spells.
      *
      * \return the test_spell::test_dao instance
@@ -89,6 +128,86 @@
      * \copydoc database::character_draft
      */
     std::unique_ptr<character> character_draft(std::string nickname, std::string classname) override;
+
+    /**
+     * Convert the model id into a string to be used as JSON keys.
+     *
+     * \return the string id
+     */
+    inline std::string sid(const model& m) const noexcept
+    {
+        return std::to_string(m.id());
+    }
+
+    /**
+     * Overload that takes an id.
+     *
+     * \return the id
+     */
+    inline std::string sid(std::int64_t id) const noexcept
+    {
+        return std::to_string(id);
+    }
+
+    /**
+     * Check if the specified object exists in JSON database.
+     *
+     * \param obj the object
+     * \return true if exists
+     */
+    template <typename Object>
+    inline bool exists(const Object& obj) const noexcept
+    {
+        return data_[nlohmann::json::json_pointer(obj.path())].count(sid(obj)) > 0;
+    }
+
+    /**
+     * Get the parent JSON object.
+     *
+     * \param obj the object
+     * \return the parent JSON object
+     */
+    template <typename Object>
+    inline const nlohmann::json& dict(Object& obj) const noexcept
+    {
+        return data_[nlohmann::json::json_pointer(obj.path())];
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \param obj the object
+     * \return the parent JSON object
+     */
+    template <typename Object>
+    inline nlohmann::json& dict(Object& obj) noexcept
+    {
+        return data_[nlohmann::json::json_pointer(obj.path())];
+    }
+
+    /**
+     * Get the reference to the object in JSON database.
+     *
+     * \param obj the object
+     * \return the reference to the object
+     */
+    template <typename Object>
+    inline nlohmann::json& ref(Object& obj) noexcept
+    {
+        return data_[nlohmann::json::json_pointer(obj.path() + std::string("/") + sid(obj))];
+    }
+
+    /**
+     * Overloaded function
+     *
+     * \param obj the object
+     * \return the reference to the object
+     */
+    template <typename Object>
+    inline const nlohmann::json& ref(const Object& obj) noexcept
+    {
+        return data_[nlohmann::json::json_pointer(obj.path() + std::string("/") + sid(obj))];
+    }
 };
 
 } // !server
--- a/libserver-test/malikania/server/db/test_spell.cpp	Fri Oct 20 11:44:57 2017 +0200
+++ b/libserver-test/malikania/server/db/test_spell.cpp	Thu Oct 05 08:38:47 2017 +0200
@@ -10,19 +10,14 @@
 {
     id_ = db_.test_spell_dao().next_id();
     character_id_ = character_id;
-    db_.test_spell_dao().spells().emplace(id_, serialize());
 }
 
 void test_spell::do_remove()
 {
-    db_.test_spell_dao().spells().erase(id_);
 }
 
-void test_spell::do_set_level(std::uint8_t level)
+void test_spell::do_set_level(std::uint8_t)
 {
-    assert(db_.test_spell_dao().spells().count(id_));
-
-    db_.test_spell_dao().spells().at(id_)["level"] = level;
 }
 
 nlohmann::json test_spell::serialize() const
@@ -45,6 +40,7 @@
 
 spell_list test_dao::spells_for_charater(std::int64_t character_id)
 {
+#if 0
     spell_list list;
 
     for (const auto& pair : spells_) {
@@ -57,16 +53,25 @@
     }
 
     return list;
+#endif
+    spell_list list;
+
+    (void)character_id;
+
+    return list;
 }
 
 void test_dao::remove_all(std::int64_t character_id)
 {
+#if 0
     for (auto it = spells_.begin(); it != spells_.end(); ) {
         if (it->second["character_id"].get<int>() == character_id)
             it = spells_.erase(it);
         else
             it++;
     }
+#endif
+    (void)character_id;
 }
 
 } // !server
--- a/libserver-test/malikania/server/db/test_spell.hpp	Fri Oct 20 11:44:57 2017 +0200
+++ b/libserver-test/malikania/server/db/test_spell.hpp	Thu Oct 05 08:38:47 2017 +0200
@@ -76,7 +76,6 @@
 
 class test_spell::test_dao {
 private:
-    std::unordered_map<std::int64_t, nlohmann::json> spells_;
     std::int64_t sequence_{0};
     test_database& db_;
 
@@ -87,26 +86,6 @@
     }
 
     /**
-     * Get the in memory spells.
-     *
-     * \return the spell map
-     */
-    inline const std::unordered_map<std::int64_t, nlohmann::json>& spells() const noexcept
-    {
-        return spells_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the spell map
-     */
-    inline std::unordered_map<std::int64_t, nlohmann::json>& spells() noexcept
-    {
-        return spells_;
-    }
-
-    /**
      * Compute the next id from the internal sequence.
      *
      * \return the next id
--- a/tests/libserver/db/account/main.cpp	Fri Oct 20 11:44:57 2017 +0200
+++ b/tests/libserver/db/account/main.cpp	Thu Oct 05 08:38:47 2017 +0200
@@ -30,31 +30,16 @@
 
 class account_fixture {
 protected:
-    std::shared_ptr<test_database> db_{new test_database};
+    test_database db_;
 
-    inline const std::unordered_map<std::int64_t, nlohmann::json> accounts() const noexcept
-    {
-        return db_->test_account_dao().accounts();
-    }
-
-    inline std::unordered_map<std::int64_t, nlohmann::json> accounts() noexcept
+    const nlohmann::json& accounts() const noexcept
     {
-        return db_->test_account_dao().accounts();
-    }
-
-    inline const std::unordered_map<std::int64_t, nlohmann::json> characters() const noexcept
-    {
-        return db_->test_character_dao().characters();
-    }
-
-    inline std::unordered_map<std::int64_t, nlohmann::json> characters() noexcept
-    {
-        return db_->test_character_dao().characters();
+        return db_.data()["accounts"];
     }
 
     const nlohmann::json& get(std::int64_t id) const noexcept
     {
-        return db_->test_account_dao().accounts().at(id);
+        return accounts().at(std::to_string(id));
     }
 };
 
@@ -70,7 +55,7 @@
  */
 BOOST_AUTO_TEST_CASE(set_password)
 {
-    auto ac = db_->account_draft("markand", "nopassword");
+    auto ac = db_.account_draft("markand", "nopassword");
 
     ac->set_password("temporarypassword");
 
@@ -83,7 +68,7 @@
  */
 BOOST_AUTO_TEST_CASE(set_email)
 {
-    auto ac = db_->account_draft("markand", "nopassword");
+    auto ac = db_.account_draft("markand", "nopassword");
 
     ac->set_email("fake@malikania.fr");
 
@@ -96,7 +81,7 @@
  */
 BOOST_AUTO_TEST_CASE(set_firstname)
 {
-    auto ac = db_->account_draft("markand", "nopassword");
+    auto ac = db_.account_draft("markand", "nopassword");
 
     ac->set_firstname("Jean");
 
@@ -109,7 +94,7 @@
  */
 BOOST_AUTO_TEST_CASE(set_lastname)
 {
-    auto ac = db_->account_draft("markand", "nopassword");
+    auto ac = db_.account_draft("markand", "nopassword");
 
     ac->set_lastname("Bertrand");