Mercurial > malikania
changeset 116:d7025649d85c
Server: add database account
Implement accounts using a abstract factory mechanism, the database object
creates abstract account which are implemented differently depending on the
backend.
See:
- test_database,
- test_account
- broken_account
Refs #687, #682
line wrap: on
line diff
--- a/libserver-test/CMakeLists.txt Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver-test/CMakeLists.txt Mon Sep 11 13:18:43 2017 +0200 @@ -21,15 +21,21 @@ set( HEADERS ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_account.hpp + ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_character.hpp ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_account.hpp + ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_character.hpp ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_database.hpp + ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_spell.hpp ) set( SOURCES ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_account.cpp + ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/broken_character.cpp ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_account.cpp + ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_character.cpp ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_database.cpp + ${libmlk-server-test_SOURCE_DIR}/malikania/server/db/test_spell.cpp ) malikania_define_library(
--- a/libserver-test/malikania/server/db/broken_account.cpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver-test/malikania/server/db/broken_account.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -32,6 +32,12 @@ id_ = 1; } +void broken_account::do_remove() +{ + if (!bool(allow_& allow_flags::remove)) + throw std::runtime_error("broken do_remove"); +} + void broken_account::do_set_password(const std::string&) { if (!bool(allow_ & allow_flags::set_password))
--- a/libserver-test/malikania/server/db/broken_account.hpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver-test/malikania/server/db/broken_account.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -41,10 +41,11 @@ enum class allow_flags { none = 0, //!< everything is broken save = (1 << 0), //!< allow do_save - set_password = (1 << 1), //!< allow do_set_password - set_email = (1 << 2), //!< allow do_set_email - set_firstname = (1 << 3), //!< allow do_set_firstname - set_lastname = (1 << 4) //!< allow do_set_lastname + remove = (1 << 1), //!< allow do_remove + set_password = (1 << 2), //!< allow do_set_password + set_email = (1 << 3), //!< allow do_set_email + set_firstname = (1 << 4), //!< allow do_set_firstname + set_lastname = (1 << 5) //!< allow do_set_lastname }; private: @@ -57,6 +58,11 @@ void do_save() override; /** + * \copydoc account::do_remove + */ + void do_remove() override; + + /** * \copydoc account::do_set_password */ void do_set_password(const std::string& password) override; @@ -82,7 +88,7 @@ * * \param login the login * \param password the password - * \param flags the flags + * \param flags the flags for a less broken account */ inline broken_account(std::string login, std::string password,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver-test/malikania/server/db/broken_character.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -0,0 +1,49 @@ +/* + * broken_character.cpp -- database character object (broken implementation) + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "broken_character.hpp" + +namespace mlk { + +namespace server { + +void broken_character::do_save(std::int64_t account_id) +{ + if (!bool(allow_ & allow_flags::save)) + throw std::runtime_error("broken do_save"); + + id_ = 1; +} + +void broken_character::do_remove() +{ + if (!bool(allow_ & allow_flags::remove)) + throw std::runtime_error("broken do_remove"); + + id_ = -1; +} + +void broken_character::do_set_level(std::uint16_t) +{ + if (!bool(allow_ & allow_flags::set_level)) + throw std::runtime_error("broken do_set_level"); +} + +} // !server + +} // !mlk
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver-test/malikania/server/db/broken_character.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -0,0 +1,131 @@ +/* + * broken_character.hpp -- database character object (broken implementation) + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MALIKANIA_SERVER_BROKEN_CHARACTER_HPP +#define MALIKANIA_SERVER_BROKEN_CHARACTER_HPP + +/** + * \file broken_character.hpp + * \brief Database character object (broken implementation). + */ + +#include <malikania/server/db/character.hpp> + +namespace mlk { + +namespace server { + +/** + * \brief Database character object (broken implementation). + */ +class broken_character : public character { +public: + /** + * \brief Define which function should not be broken. + */ + enum class allow_flags : unsigned { + none = 0, //!< everything is broken + save = (1 << 0), //!< allow do_save + remove = (1 << 1), //!< allow do_remove + set_level = (1 << 2), //!< allow do_set_level + }; + +private: + allow_flags allow_; + +protected: + /** + * Called by account::add_character. + */ + void do_save(std::int64_t account_id) override; + + /** + * Called by account::remove_character. + */ + void do_remove() override; + + /** + * Set the character level. + */ + void do_set_level(std::uint16_t level) override; + +public: + /** + * Construct a broken character. + * + * \param login the login + * \param password the password + * \param flags the flags for a less broken account + */ + inline broken_character(std::string nickname, + std::string classname, + allow_flags flags = allow_flags::none) noexcept + : character(std::move(nickname), std::move(classname)) + , allow_(flags) + { + } +}; + +/** + * \cond ENUM_HIDDEN_SYMBOLS + */ + +inline broken_character::allow_flags operator^(broken_character::allow_flags v1, broken_character::allow_flags v2) noexcept +{ + return static_cast<broken_character::allow_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2)); +} + +inline broken_character::allow_flags operator&(broken_character::allow_flags v1, broken_character::allow_flags v2) noexcept +{ + return static_cast<broken_character::allow_flags>(static_cast<unsigned>(v1) & static_cast<unsigned>(v2)); +} + +inline broken_character::allow_flags operator|(broken_character::allow_flags v1, broken_character::allow_flags v2) noexcept +{ + return static_cast<broken_character::allow_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2)); +} + +inline broken_character::allow_flags operator~(broken_character::allow_flags v) noexcept +{ + return static_cast<broken_character::allow_flags>(~static_cast<unsigned>(v)); +} + +inline broken_character::allow_flags& operator|=(broken_character::allow_flags& v1, broken_character::allow_flags v2) noexcept +{ + return v1 = v1 | v2; +} + +inline broken_character::allow_flags& operator&=(broken_character::allow_flags& v1, broken_character::allow_flags v2) noexcept +{ + return v1 = v1 & v2; +} + +inline broken_character::allow_flags& operator^=(broken_character::allow_flags& v1, broken_character::allow_flags v2) noexcept +{ + return v1 = v1 ^ v2; +} + +/** + * \endcond + */ + +} // !server + +} // !mlk + +#endif // !MALIKANIA_SERVER_BROKEN_CHARACTER_HPP
--- a/libserver-test/malikania/server/db/test_account.cpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver-test/malikania/server/db/test_account.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -16,7 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "test_account.hpp" +#include "test_database.hpp" namespace mlk { @@ -24,59 +24,81 @@ using test_dao = test_account::test_dao; -test_account::test_account(std::shared_ptr<test_dao> dao, - std::string login, - std::string password) noexcept - : account(std::move(login), std::move(password)) - , dao_(std::move(dao)) +void test_account::do_save() { + assert(db_.test_account_dao().accounts().count(id_) == 0); + + id_ = db_.test_account_dao().next_id(); + db_.test_account_dao().accounts().emplace(id_, serialize()); } -void test_account::do_save() +void test_account::do_remove() { - id_ = dao_->next_id(); - dao_->accounts().emplace(id_, *this); + assert(db_.test_account_dao().accounts().count(id_)); + + // Do cascade deletion. + db_.test_account_dao().accounts().erase(id_); + db_.test_character_dao().remove_all(id_); } void test_account::do_set_password(const std::string& password) { - assert(dao_->accounts().count(id_)); + assert(db_.test_account_dao().accounts().count(id_)); - dao_->accounts().at(id_).password_ = password; + db_.test_account_dao().accounts().at(id_)["password"] = password; } void test_account::do_set_email(const std::string& email) { - assert(dao_->accounts().count(id_)); + assert(db_.test_account_dao().accounts().count(id_)); - dao_->accounts().at(id_).email_ = email; + db_.test_account_dao().accounts().at(id_)["email"] = email; } void test_account::do_set_firstname(const std::string& name) { - assert(dao_->accounts().count(id_)); + assert(db_.test_account_dao().accounts().count(id_)); - dao_->accounts().at(id_).firstname_ = name; + db_.test_account_dao().accounts().at(id_)["firstname"] = name; } void test_account::do_set_lastname(const std::string& name) { - assert(dao_->accounts().count(id_)); + assert(db_.test_account_dao().accounts().count(id_)); - dao_->accounts().at(id_).lastname_ = name; + db_.test_account_dao().accounts().at(id_)["lastname"] = name; } -std::shared_ptr<account> test_dao::do_find_by_login(const std::string& login) +nlohmann::json test_account::serialize() const +{ + return { + { "id", id_ }, + { "login", login_ }, + { "password", password_ }, + { "email", email_ }, + { "firstname", firstname_ }, + { "lastname", lastname_ } + }; +} + +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_shared<test_account>(shared_from_this(), - pair.second.login(), pair.second.password()); + if (pair.second["login"] == login) { + auto a = std::make_unique<test_account>(pair.second["login"], pair.second["password"], db_); - a->id_ = pair.first; - a->email_ = pair.second.email_; - a->firstname_ = pair.second.firstname_; - a->lastname_ = pair.second.lastname_; + a->unserialize(pair.second); + a->characters_ = db_.test_character_dao().characters_for_account(a->id_); return a; }
--- a/libserver-test/malikania/server/db/test_account.hpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver-test/malikania/server/db/test_account.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -26,12 +26,18 @@ #include <unordered_map> +#include <json.hpp> + #include <malikania/server/db/account.hpp> +#include "test_character.hpp" + namespace mlk { namespace server { +class test_database; + /** * \brief Database account object (memory implementation) */ @@ -40,19 +46,36 @@ class test_dao; private: - std::shared_ptr<test_dao> dao_; + test_database& db_; public: /** * Construct a test_account. * - * \param dao the dao owner * \param login the login * \param password the password + * \param db the test_database */ - test_account(std::shared_ptr<test_dao> dao, - std::string login, - std::string password) noexcept; + inline test_account(std::string login, std::string password, test_database& db) noexcept + : account(std::move(login), std::move(password)) + , db_(db) + { + } + + /** + * 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); /** * \copydoc account::do_save @@ -60,6 +83,11 @@ void do_save() override; /** + * \copydoc account::do_remove + */ + void do_remove() override; + + /** * \copydoc account::do_set_password */ void do_set_password(const std::string& password) override; @@ -86,20 +114,29 @@ * This class saves accounts in memory, thus once deleted the no accounts are * available anymore. */ -class test_account::test_dao - : public account::dao - , public std::enable_shared_from_this<test_account::test_dao> { +class test_account::test_dao : public account::dao { private: - std::unordered_map<std::int64_t, test_account> accounts_; + std::unordered_map<std::int64_t, nlohmann::json> accounts_; std::int64_t sequence_{0}; + test_database& db_; public: /** + * Construct the dao, requires character_dao to load characters. + * + * \param db the test databasse + */ + inline test_dao(test_database& db) noexcept + : db_(db) + { + } + + /** * Get the in memory accounts. * * \return the account map */ - inline const std::unordered_map<std::int64_t, test_account>& accounts() const noexcept + inline const std::unordered_map<std::int64_t, nlohmann::json>& accounts() const noexcept { return accounts_; } @@ -109,7 +146,7 @@ * * \return the account map */ - inline std::unordered_map<std::int64_t, test_account>& accounts() noexcept + inline std::unordered_map<std::int64_t, nlohmann::json>& accounts() noexcept { return accounts_; } @@ -125,9 +162,9 @@ } /** - * \copydoc dao::do_find_by_login + * \copydoc dao::find_by_login */ - std::shared_ptr<account> do_find_by_login(const std::string& login) override; + std::unique_ptr<account> find_by_login(const std::string& login) override; }; } // !server
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver-test/malikania/server/db/test_character.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -0,0 +1,99 @@ +/* + * test_character.cpp -- database account object (memory implementation) + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "test_database.hpp" + +namespace mlk { + +namespace server { + +using test_dao = test_character::test_dao; + +void test_character::do_save(std::int64_t account_id) +{ + id_ = db_.test_character_dao().next_id(); + account_id_ = account_id; + db_.test_character_dao().characters().emplace(id_, serialize()); +} + +void test_character::do_remove() +{ + db_.test_character_dao().characters().erase(id_); +} + +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; +} + +nlohmann::json test_character::serialize() const +{ + return { + { "id", id_ }, + { "account_id", account_id_ }, + { "nickname", nickname_ }, + { "classname", classname_ }, + { "level", level_ } + }; +} + +void test_character::unserialize(const nlohmann::json& json) +{ + id_ = json["id"]; + account_id_ = json["account_id"]; + nickname_ = json["nickname"]; + classname_ = json["classname"]; + level_ = json["level"]; +} + +character_set test_dao::characters_for_account(std::int64_t account_id) +{ + character_set set; + + for (const auto& pair : characters_) { + if (pair.second["account_id"] == account_id) { + auto c = std::make_unique<test_character>( + pair.second["nickname"], + pair.second["classname"], + db_ + ); + + c->unserialize(pair.second); + set.insert(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"] == account_id) { + db_.test_character_dao().remove_all(it->second["id"]); + it = characters_.erase(it); + } else + it++; + } +} + +} // !server + +} // !mlk
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver-test/malikania/server/db/test_character.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -0,0 +1,160 @@ +/* + * test_character.hpp -- database account object (memory implementation) + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MALIKANIA_SERVER_TEST_CHARACTER_HPP +#define MALIKANIA_SERVER_TEST_CHARACTER_HPP + +/** + * \file test_account.hpp + * \brief Database account object (memory implementation) + */ + +#include <unordered_map> + +#include <json.hpp> + +#include <malikania/server/db/character.hpp> + +namespace mlk { + +namespace server { + +class test_database; + +/** + * \brief Database account object (memory implementation) + */ +class test_character : public character { +public: + class test_dao; + +private: + test_database& db_; + +protected: + /** + * \copydoc character::do_save + */ + void do_save(std::int64_t account_id) override; + + /** + * \copydoc character::do_remove + */ + void do_remove() override; + + /** + * \copydoc character::do_set_level + */ + void do_set_level(std::uint16_t level) override; + +public: + /** + * Construct a test account. + * + * \param nickname the nickname + * \param classname the classname + * \param db reference to test_database + */ + inline test_character(std::string nickname, std::string classname, test_database& db) noexcept + : character(std::move(nickname), std::move(classname)) + , db_(db) + { + } + + /** + * 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_set 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 + +} // !mlk + +#endif // !MALIKANIA_SERVER_TEST_CHARACTER_HPP
--- a/libserver-test/malikania/server/db/test_database.cpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver-test/malikania/server/db/test_database.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -23,19 +23,19 @@ namespace server { -std::shared_ptr<account> test_database::account_draft(std::string login, std::string password) +std::unique_ptr<account> test_database::account_draft(std::string login, std::string password) { - return std::make_shared<test_account>(account_dao_, std::move(login), std::move(password)); + return std::make_unique<test_account>(std::move(login), std::move(password), *this); } -std::shared_ptr<account::dao> test_database::account_dao() +account::dao& test_database::account_dao() { return account_dao_; } -std::shared_ptr<character> test_database::character_draft() +std::unique_ptr<character> test_database::character_draft(std::string nickname, std::string classname) { - return nullptr; + return std::make_unique<test_character>(std::move(nickname), std::move(classname), *this); } } // !server
--- a/libserver-test/malikania/server/db/test_database.hpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver-test/malikania/server/db/test_database.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -28,6 +28,8 @@ #include <malikania/server/db/database.hpp> #include "test_account.hpp" +#include "test_character.hpp" +#include "test_spell.hpp" namespace mlk { @@ -38,33 +40,55 @@ */ class test_database : public database { private: - std::shared_ptr<test_account::test_dao> account_dao_{new test_account::test_dao}; + test_spell::test_dao spell_dao_{*this}; + test_character::test_dao character_dao_{*this}; + test_account::test_dao account_dao_{*this}; public: /** - * Get the real underlying test_dao type. + * Get the real underlying test_dao type for accounts. * * \return the test_dao instance */ - inline std::shared_ptr<test_account::test_dao> test_account_dao() noexcept + inline test_account::test_dao& test_account_dao() noexcept { return account_dao_; } /** + * 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 + */ + inline test_spell::test_dao& test_spell_dao() noexcept + { + return spell_dao_; + } + + /** * \copydoc database::account_draft */ - std::shared_ptr<account> account_draft(std::string login, std::string password) override; + std::unique_ptr<account> account_draft(std::string login, std::string password) override; /** * \copydoc database::account_dao */ - std::shared_ptr<account::dao> account_dao() override; + account::dao& account_dao() override; /** * \copydoc database::character_draft */ - std::shared_ptr<character> character_draft() override; + std::unique_ptr<character> character_draft(std::string nickname, std::string classname) override; }; } // !server
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver-test/malikania/server/db/test_spell.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -0,0 +1,74 @@ +#include "test_database.hpp" + +namespace mlk { + +namespace server { + +using test_dao = test_spell::test_dao; + +void test_spell::do_save(std::int64_t character_id) +{ + 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) +{ + assert(db_.test_spell_dao().spells().count(id_)); + + db_.test_spell_dao().spells().at(id_)["level"] = level; +} + +nlohmann::json test_spell::serialize() const +{ + return { + { "id", id_ }, + { "character_id", character_id_ }, + { "classname", classname_ }, + { "level", level_ } + }; +} + +void test_spell::unserialize(const nlohmann::json& json) +{ + id_ = json["id"]; + character_id_ = json["character_id"]; + classname_ = json["classname"]; + level_ = json["level"]; +} + +spell_set test_dao::spells_for_charater(std::int64_t character_id) +{ + spell_set set; + + for (const auto& pair : spells_) { + if (pair.second["character_id"] == character_id) { + auto s = std::make_unique<test_spell>(pair.second["classname"], db_); + + s->unserialize(pair.second); + set.insert(std::move(s)); + } + } + + return set; +} + +void test_dao::remove_all(std::int64_t character_id) +{ + for (auto it = spells_.begin(); it != spells_.end(); ) { + if (it->second["character_id"] == character_id) + it = spells_.erase(it); + else + it++; + } +} + +} // !server + +} // !mlk
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libserver-test/malikania/server/db/test_spell.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -0,0 +1,136 @@ +/* + * test_spell.hpp -- database spell object (memory implementation) + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MALIKANIA_SERVER_TEST_SPELL_HPP +#define MALIKANIA_SERVER_TEST_SPELL_HPP + +/** + * \file test_spell.hpp + * \brief Database spell object (memory implementation). + */ + +#include <unordered_map> + +#include <malikania/server/db/spell.hpp> + +#include <json.hpp> + +namespace mlk { + +namespace server { + +class test_database; + +/** + * \brief Database spell object (memory implementation). + */ +class test_spell : public spell { +public: + class test_dao; + +private: + test_database& db_; + +protected: + void do_save(std::int64_t character_id) override; + void do_remove() override; + void do_set_level(std::uint8_t level) override; + +public: + inline test_spell(std::string classname, test_database& db) noexcept + : spell(std::move(classname)) + , db_(db) + { + } + + /** + * Dump this character as json. + * + * \return the json + */ + nlohmann::json serialize() const; + + /** + * Fill this character with json input. + * + * \warning json input must be valid, no checks are performed + * \param input the json input + */ + void unserialize(const nlohmann::json& json); +}; + +class test_spell::test_dao { +private: + std::unordered_map<std::int64_t, nlohmann::json> spells_; + std::int64_t sequence_{0}; + test_database& db_; + +public: + inline test_dao(test_database& db) noexcept + : db_(db) + { + } + + /** + * 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 + */ + inline std::int64_t next_id() noexcept + { + return ++sequence_; + } + + /** + * Get the set of spells for the given character. + */ + spell_set spells_for_charater(std::int64_t character_id); + + /** + * Remove all characters for the specified character. + * + * \param character_id the character id + */ + void remove_all(std::int64_t character_id); +}; + +} // !server + +} // !mlk + +#endif // !MALIKANIA_SERVER_TEST_SPELL_HPP
--- a/libserver/CMakeLists.txt Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/CMakeLists.txt Mon Sep 11 13:18:43 2017 +0200 @@ -21,10 +21,8 @@ set( HEADERS ${libmlk-server_SOURCE_DIR}/malikania/server/db/account.hpp - ${libmlk-server_SOURCE_DIR}/malikania/server/db/account_dao.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/character.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/database.hpp - ${libmlk-server_SOURCE_DIR}/malikania/server/db/dynlib_database.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/spell.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/model.hpp ${libmlk-server_SOURCE_DIR}/malikania/server/net/auth_handler.hpp @@ -36,8 +34,6 @@ SOURCES ${libmlk-server_SOURCE_DIR}/malikania/server/db/account.cpp ${libmlk-server_SOURCE_DIR}/malikania/server/db/character.cpp - ${libmlk-server_SOURCE_DIR}/malikania/server/db/dynlib_database.cpp - ${libmlk-server_SOURCE_DIR}/malikania/server/db/spell.cpp ${libmlk-server_SOURCE_DIR}/malikania/server/net/auth_handler.cpp ${libmlk-server_SOURCE_DIR}/malikania/server/client.cpp ${libmlk-server_SOURCE_DIR}/malikania/server/server.cpp
--- a/libserver/malikania/server/db/account.cpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/db/account.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -17,8 +17,6 @@ */ #include "account.hpp" -#include "account_dao.hpp" -#include "character.hpp" #include "database.hpp" namespace mlk { @@ -29,62 +27,39 @@ * account::add_character * ------------------------------------------------------------------ */ - -void account::add_character(std::shared_ptr<character> ch) +const std::unique_ptr<character>& account::add_character(std::unique_ptr<character> ch) { assert(is_published()); assert(ch && ch->is_draft()); - ch->do_link(id_); - characters_.insert(std::move(ch)); + ch->do_save(id_); + + return *characters_.insert(std::move(ch)).first; } /* * account::remove_character * ------------------------------------------------------------------ */ - -void account::remove_character(std::shared_ptr<character> ch) +void account::remove_character(const std::unique_ptr<character>& ch) { - assert(ch); - assert(ch->is_published()); + assert(is_published()); + assert(ch && ch->is_published()); auto it = characters_.find(ch); if (it == characters_.end()) return; - ch->do_unlink(); + ch->do_remove(); characters_.erase(it); } /* - * account::dao::find_by_login - * ------------------------------------------------------------------ - */ - -std::shared_ptr<account> account::dao::find_by_login(const std::string& login) -{ - auto ac = accounts_.find_if([&] (const auto& a) { - return a->login() == login; - }); - - if (!ac) { - ac = do_find_by_login(login); - - if (ac) - accounts_.push_back(ac); - } - - return ac; -} - -/* * account::dao::authenticate * ------------------------------------------------------------------ */ - -std::shared_ptr<account> account::dao::authenticate(const std::string& login, +std::unique_ptr<account> account::dao::authenticate(const std::string& login, const std::string& password) { auto ac = find_by_login(login);
--- a/libserver/malikania/server/db/account.hpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/db/account.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -19,42 +19,103 @@ #ifndef MALIKANIA_SERVER_DB_ACCOUNT_HPP #define MALIKANIA_SERVER_DB_ACCOUNT_HPP -#include <string> -#include <unordered_set> - -#include <malikania/weak_array.hpp> +/** + * \file account.hpp + * \brief Database account object. + */ #include "model.hpp" +#include "character.hpp" namespace mlk { namespace server { -class account : public model<account> { +/** + * \brief Database account object. + */ +class account : public model { public: class dao; - using character_set_t = std::unordered_set<std::shared_ptr<class character>>; +protected: + std::string login_; //!< unique login (not null) + std::string password_; //!< password stored as-is + std::string email_; //!< raw email + std::string firstname_; //!< optional first name + std::string lastname_; //!< optional last name + character_set characters_; //!< set of characters -protected: - std::string login_; - std::string password_; - std::string email_; - std::string firstname_; - std::string lastname_; - character_set_t characters_; + /** + * Save the account. + * + * The implementation will be called only if the account is draft and must + * do the following: + * + * 1. Register the account to its database, + * 2. Create a unique and global id and set it as id_ member variable. + * + * \note called from account::save + * \throw std::exception if the operation could not succeed + */ + virtual void do_save() = 0; + + /** + * Remove the account. + * + * The implementation will be called only if the account is public and must + * do remove from the database all the associated characters along with + * their spells. + * + * \note called from account::remove + * \throw std::exception if the operation could not succeed + */ + virtual void do_remove() = 0; - virtual void do_save() = 0; + /** + * Update the account password in database. + * + * Only called when the password needs to be changed, the implementation + * does not need to update password_ field. + * + * \note called from set_password helper + * \throw std::exception if the operation could not succeed + */ virtual void do_set_password(const std::string& password) = 0; + + /** + * Update the account email in database. + * + * Only called when the email needs to be changed, the implementation does + * not need to update email_ field. + * + * \note called from set_email helper + * \throw std::exception if the operation could not succeed + */ virtual void do_set_email(const std::string& email) = 0; + + /** + * Update the account firstname in database. + * + * Only called when the first name needs to be changed, the implementation + * does not need to update firstname_ field. + * + * \note called from set_firstname helper + * \throw std::exception if the operation could not succeed + */ virtual void do_set_firstname(const std::string& name) = 0; + + /** + * Update the account lastname in database. + * + * Only called when the last name needs to be changed, the implementation + * does not need to update lastname_ field. + * + * \note called from set_lastname helper + * \throw std::exception if the operation could not succeed + */ virtual void do_set_lastname(const std::string& name) = 0; -#if 0 - virtual void do_add_character(const std::shared_ptr<class character>& ch); - virtual void do_remove_character(const std::shared_ptr<class character>& ch); -#endif - /** * Create a draft account. * @@ -130,7 +191,7 @@ { assert(!email.empty()); - if (is_published()) + if (is_published() && email_ != email) do_set_email(email); email_ = std::move(email); @@ -153,7 +214,7 @@ */ inline void set_firstname(std::string name) { - if (is_published()) + if (is_published() && firstname_ != name) do_set_firstname(name); firstname_ = std::move(name); @@ -176,7 +237,7 @@ */ inline void set_lastname(std::string name) { - if (is_published()) + if (is_published() && lastname_ != name) do_set_lastname(name); lastname_ = std::move(name); @@ -187,7 +248,7 @@ * * \return the associated characters. */ - inline const character_set_t& characters() const noexcept + inline const character_set& characters() const noexcept { return characters_; } @@ -195,14 +256,22 @@ /** * Add the character to the account. * - * If the character is draft, it is first saved. + * Account takes ownership of character. * - * \pre the character must be draft or parented to this account + * \pre ch->is_draft() * \param ch the character + * \return the attached character + * \post ch->is_published() */ - void add_character(std::shared_ptr<class character> ch); + const std::unique_ptr<character>& add_character(std::unique_ptr<character> ch); - void remove_character(std::shared_ptr<class character>); + /** + * Remove the character from the account. + * + * \param ch the character + * \post ch->is_draft() and empty + */ + void remove_character(const std::unique_ptr<character>& ch); /** * Save the account, does nothing if is_published(). @@ -217,37 +286,40 @@ assert(is_published()); } + + /** + * Destroy the account. + * + * The account will contains no characters anymore. + * + * \throw std::exception on errors + * \post is_draft() + */ + inline void remove() + { + if (is_published()) + do_remove(); + + id_ = -1; + characters_.clear(); + + assert(is_draft()); + } }; +/** + * \brief DAO for accounts. + */ class account::dao { -private: - weak_array<account> accounts_; - -protected: +public: /** - * Find an account by login - * - * This function will be called only if no instance of that account is - * already loaded. + * Find an account by login. * * \param login the login name * \return the account or null if not found * \throw exception on other errors */ - virtual std::shared_ptr<account> do_find_by_login(const std::string& login) = 0; - -public: - /** - * Find an acocunt by login. - * - * If a previous login was found, return that instance, othwerise, search - * the database to load it. - * - * \param login the login name - * \return the account or nullptr if not found - * \throw std::exception on any other errors - */ - std::shared_ptr<account> find_by_login(const std::string& login); + virtual std::unique_ptr<account> find_by_login(const std::string& login) = 0; /** * Authenticate the user. @@ -256,7 +328,7 @@ * \param password the password * \return the account or null if does not exist */ - std::shared_ptr<account> authenticate(const std::string& login, + std::unique_ptr<account> authenticate(const std::string& login, const std::string& password); };
--- a/libserver/malikania/server/db/account_dao.hpp Mon Sep 11 16:26:35 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -/* - * account_dao.hpp -- database account interface - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MALIKANIA_SERVER_DB_ACCOUNT_DAO_HPP -#define MALIKANIA_SERVER_DB_ACCOUNT_DAO_HPP - -/** - * \file account_dao.hpp - * \brief Database account interface. - */ - -#include <cstdint> -#include <string> - -#include "dao.hpp" - -namespace mlk { - -namespace server { - -class account; - -/** - * \file account_dao.hpp - * \brief Database account interface. - */ -class account_dao : public dao { -public: - /** - * Inherited constructors. - */ - using dao::dao; - - /** - * Virtual destructor defaulted. - */ - virtual ~account_dao() noexcept = default; - - /** - * Save the account and return its new id. - * - * \param ac the account - * \return the newly created id - * \post returned it >= 0 - */ - virtual std::int64_t save(std::shared_ptr<account> ac) = 0; - - /** - * Find an account, the implementation must return the same instance of the - * account. - * - * \param login the account id - * \return the account or null if does not exist - */ - virtual std::shared_ptr<account> find(const std::string& login) = 0; - - /** - * Authenticate the user. - * - * \param login the login - * \param password the password - * \return the account or null if does not exist - */ - virtual std::shared_ptr<account> authenticate(const std::string& login, - const std::string& password) = 0; - - /** - * Update the account email. - * - * \param ac the account owner - * \param email the new email - */ - virtual void set_email(std::shared_ptr<account> ac, const std::string& email) = 0; - - /** - * Update the account first name. - * - * \param ac the account owner - * \param name the first name - */ - virtual void set_firstname(std::shared_ptr<account> ac, const std::string& name) = 0; - - /** - * Update the account last name. - * - * \param ac the account owner - * \param name the new name - */ - virtual void set_lastname(std::shared_ptr<account> ac, const std::string& name) = 0; -}; - -} // !server - -} // !mlk - -#endif // !MALIKANIA_SERVER_DB_ACCOUNT_DAO_HPP
--- a/libserver/malikania/server/db/character.cpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/db/character.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -16,103 +16,44 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <algorithm> -#include <cassert> -#include <stdexcept> -#include <string> - -#include "account.hpp" #include "character.hpp" -#include "character_dao.hpp" -#include "database.hpp" -#include "spell.hpp" -#include "spell_dao.hpp" namespace mlk { namespace server { -#if 0 - -void character::set_name(std::string name) +/* + * character::add_spell + * ------------------------------------------------------------------ + */ +const std::unique_ptr<spell>& character::add_spell(std::unique_ptr<spell> sp) { - if (is_published()) - db_->character_dao()->set_name(shared_from_this(), name); + assert(is_published()); + assert(sp && sp->is_draft()); - name_ = std::move(name); + sp->do_save(id_); + + return *spells_.insert(std::move(sp)).first; } -void character::add_spell(std::shared_ptr<spell>) +/* + * character::remove_spell + * ------------------------------------------------------------------ + */ +void character::remove_spell(const std::unique_ptr<spell>& sp) { -#if 0 - assert(spell); - - if (std::find(spells_.begin(), spells_.end(), spell) != spells_.end()) - return; - if (spell->is_published()) - throw referenced_error(shared_from_this(), spell); + assert(is_published()); + assert(sp && sp->is_published()); - db_->spell_dao()->save(*spell, *this); - spells_.push_back(std::move(spell)); -#endif -} + auto it = spells_.find(sp); -void character::remove_spell(std::shared_ptr<spell>) -{ -#if 0 - assert(spell); - - if (std::find(spells_.begin(), spells_.end(), spell) == spells_.end()) + if (it == spells_.end()) return; - db_->spell_dao()->remove(*spell); -#if 0 - spell->set_id(-1); - spell->set_character(nullptr); - spells_.erase(std::remove(spells_.begin(), spells_.end(), spell), spells_.end()); -#endif -#endif -} - -void character::save(std::shared_ptr<class account> parent) -{ - if (is_published()) - return; - - assert(parent); - assert(parent->is_published()); - - auto self = shared_from_this(); - - id_ = db_->character_dao()->save(self, parent); - account_ = parent; - - parent->add_character(self); + sp->do_remove(); + spells_.erase(it); } -void character::remove() -{ - if (is_draft()) - return; - - auto self = shared_from_this(); - - if (is_published()) - db_->character_dao()->remove(self); - - id_ = -1; - - auto parent = account_.lock(); - - if (parent) - parent->remove_character(self); - - account_.reset(); - spells_.clear(); -} - -#endif - } // !server } // !mlk
--- a/libserver/malikania/server/db/character.hpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/db/character.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -19,37 +19,66 @@ #ifndef MALIKANIA_SERVER_DB_CHARACTER_HPP #define MALIKANIA_SERVER_DB_CHARACTER_HPP -#include <cstdint> -#include <memory> -#include <string> -#include <vector> +/* + * \file character.hpp + * \brief Database character object. + */ #include "model.hpp" +#include "spell.hpp" namespace mlk { namespace server { -class spell; - -/** - * \brief Describe an account character. +/* + * \brief Database character object. */ -class character : public model<character> { +class character : public model { private: friend class account; -public: - class dao; +protected: + std::int64_t account_id_{-1}; //!< parent acocunt + std::string nickname_; //!< nickname (non null) + std::string classname_; //!< class type to instanciate + std::uint16_t level_{1}; //!< character level + spell_set spells_; //!< set of spells + + /** + * Save this character. + * + * The implementation must save the character and update the id, account_id + * member variables. + * + * Then it will be added into account.characters_ variable. + * + * \param account_id the parent account + * \note called by account::add_character. + * \throw std::exception if the operation could not succeed + */ + virtual void do_save(std::int64_t account_id) = 0; -protected: - std::string nickname_; - std::string classname_; - std::uint16_t level_{1}; + /** + * Remove this character. + * + * The implementation must remove the character from the database and update + * id, account_id member variables. + * + * \note called by account::remove_character. + */ + virtual void do_remove() = 0; + /** + * Update the character level in database. + * + * Only called when the level needs to be changed, the implementation does + * not need to update level_ field. + * + * \note called from set_level helper + * \throw std::exception if the operation could not succeed + */ virtual void do_set_level(std::uint16_t level) = 0; - virtual void do_link(std::int64_t account_id) = 0; - virtual void do_unlink() = 0; /** * Construct a character, no database is modified yet. @@ -99,56 +128,52 @@ } /** + * Get the set of spells. + * + * \return the spells + */ + inline const spell_set& spells() const noexcept + { + return spells_; + } + + /** + * Add a spell. + * + * Character takes ownership of spell. + * + * \pre sp->is_draft() + * \param sp the spell + * \return the attached spell + */ + const std::unique_ptr<spell>& add_spell(std::unique_ptr<spell> sp); + + /** + * Remove the spell from the character. + * + * \param sp the spell + * \post sp->is_draft() and empty + */ + void remove_spell(const std::unique_ptr<spell>& sp); + + /** * Set the account level. * * \param level the level */ inline void set_level(std::uint16_t level) { - if (is_published()) + if (is_published() && level_ != level) do_set_level(level); - } - -#if 0 - - /** - * Add a spell into the database and the object. - * - * \param spell the spell - * \throw referenced_error if spell is public - * \throw phase_error if character is not public - */ - void add_spell(std::shared_ptr<spell> spell); - /** - * Remove the specified spell. - * - * \param spell the spell to remove - */ - void remove_spell(std::shared_ptr<spell> spell); - - /** - * Save the character and update its parent. - * - * No-op if already published. - * - * \pre parent is not null and parent->is_published() - * \param parent the parent account - */ - void save(std::shared_ptr<class account> parent); - - /** - * Remove the character and update its parent. - */ - void remove(); -#endif + level_ = level; + } }; -class character::dao { -public: - dao() noexcept = default; - virtual ~dao() noexcept = default; -}; +/** + * Type for storing characters. + */ +using character_set = std::unordered_set<std::unique_ptr<character>>; } // !server
--- a/libserver/malikania/server/db/character_dao.hpp Mon Sep 11 16:26:35 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/* - * character_dao.hpp -- database character interface - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MALIKANIA_SERVER_DB_CHARACTER_DAO_HPP -#define MALIKANIA_SERVER_DB_CHARACTER_DAO_HPP - -#include <cstdint> -#include <string> - -#include "dao.hpp" - -namespace mlk { - -namespace server { - -class account; -class character; - -class character_dao : public dao { -public: - /** - * Inherited constructors. - */ - using dao::dao; - - virtual std::int64_t save(std::shared_ptr<character>, std::shared_ptr<account>) = 0; - - virtual void set_name(std::shared_ptr<character>, const std::string&) = 0; - - virtual void remove(std::shared_ptr<character>) = 0; -}; - -} // !server - -} // !mlk - -#endif // !MALIKANIA_SERVER_DB_CHARACTER_DAO_HPP
--- a/libserver/malikania/server/db/dao.hpp Mon Sep 11 16:26:35 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -#ifndef MALIKANIA_SERVER_DB_DAO_HPP -#define MALIKANIA_SERVER_DB_DAO_HPP - -#include <cassert> -#include <memory> -#include <utility> - -namespace mlk { - -namespace server { - -class database; - -class dao { -protected: - std::shared_ptr<database> db_; - -public: - /** - * Construct the dao object. - * - * \pre db != nullptr - * \param db the database object - */ - inline dao(std::shared_ptr<database> db) noexcept - : db_(std::move(db)) - { - assert(db_); - } - - /** - * Virtual destructor defaulted. - */ - virtual ~dao() noexcept = default; -}; - -} // !server - -} // !mlk - -#endif // !MALIKANIA_SERVER_DB_DAO_HPP
--- a/libserver/malikania/server/db/database.hpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/db/database.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -19,7 +19,10 @@ #ifndef MALIKANIA_SERVER_DB_DATABASE_HPP #define MALIKANIA_SERVER_DB_DATABASE_HPP -#include <memory> +/** + * \file database.hpp + * \brief Abstract database interface. + */ #include "account.hpp" #include "character.hpp" @@ -29,6 +32,9 @@ namespace server { +/** + * \brief Abstract database interface. + */ class database { public: /** @@ -57,22 +63,24 @@ * \post value->is_draft() * \see account::account */ - virtual std::shared_ptr<account> account_draft(std::string login, std::string password) = 0; + virtual std::unique_ptr<account> account_draft(std::string login, std::string password) = 0; /** * Create the account dao. * * \return the account dao */ - virtual std::shared_ptr<account::dao> account_dao() = 0; + virtual account::dao& account_dao() = 0; /** * Create a draft character. * + * \param nickname the character nickname + * \param classname the character class type * \return the draft character * \post value->is_draft() */ - virtual std::shared_ptr<character> character_draft() = 0; + virtual std::unique_ptr<character> character_draft(std::string nickname, std::string classname) = 0; }; } // !server
--- a/libserver/malikania/server/db/dynlib_database.cpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/db/dynlib_database.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -16,9 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "account_dao.hpp" -#include "character_dao.hpp" -#include "spell_dao.hpp" #include "dynlib_database.hpp" namespace mlk {
--- a/libserver/malikania/server/db/model.hpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/db/model.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -19,63 +19,42 @@ #ifndef MALIKANIA_SERVER_DB_MODEL_HPP #define MALIKANIA_SERVER_DB_MODEL_HPP +/** + * \file model.hpp + * \brief Abstract database object. + */ + #include <cassert> #include <cstdint> -#include <exception> -#include <memory> -#include <ostream> -#include <sstream> -#include <string> namespace mlk { namespace server { /** - * \brief Describe availability of an object. - */ -enum class model_phase { - draft, //!< object is not saved - published //!< object is saved -}; - -/** - * Show the model_phase. - * - * \param out the output stream - * \param phase the object phase - * \return out + * \brief Abstract database object. */ -inline std::ostream& operator<<(std::ostream& out, model_phase phase) -{ - out << (phase == model_phase::draft ? "draft" : "published"); - - return out; -} +class model { +private: + model(const model&) = delete; + model& operator=(const model&) = delete; -/** - * \brief Base class for database object. - */ -class basic_model { + model(model&&) = delete; + model& operator=(model&&) = delete; + protected: - std::int64_t id_{-1}; //!< object id -#if 0 - std::shared_ptr<database> db_; //!< database object -#endif + std::int64_t id_{-1}; //!< object id public: -#if 0 - inline basic_model(std::shared_ptr<database> db) noexcept - : db_(std::move(db)) - { - } -#endif - basic_model() noexcept = default; + /** + * Default constructor. + */ + model() noexcept = default; /** * Virtual destructor defaulted. */ - virtual ~basic_model() noexcept = default; + virtual ~model() noexcept = default; /** * Get the id. @@ -87,32 +66,6 @@ return id_; } -#if 0 - - /** - * Set the model id. - * - * \pre is_draft() - */ - inline void set_id(std::int64_t id) noexcept - { - assert(is_draft()); - - id_ = id; - } - - inline const std::shared_ptr<database>& db() const noexcept - { - return db_; - } - - inline std::shared_ptr<database>& db() noexcept - { - return db_; - } - -#endif - /** * Tells if the object is not persistent. * @@ -132,174 +85,8 @@ { return id_ >= 0; } - - /** - * Get the current phase. - * - * \return the phase - */ - inline model_phase phase() const noexcept - { - return is_draft() ? model_phase::draft : model_phase::published; - } }; -/** - * \brief Class helper for std::enabled_shared_from_this. - */ -template <typename T> -class model - : public basic_model - , public std::enable_shared_from_this<T> -{ -public: - /** - * Inherited constructors. - */ - using basic_model::basic_model; -}; - -#if 0 - -/** - * \brief Already referenced error. - * - * This class is used when an operation attempted to add a child object that - * already have a parent. - */ -class referenced_error : public std::exception { -private: - std::string message_; - std::shared_ptr<basic_model> parent_; - std::shared_ptr<basic_model> object_; - -public: - /** - * Construct a referenced_error. - * - * \pre parent != nullptr && object != nullptr - * \param parent the parent that already have object - * \param object the child object of parent - */ - inline referenced_error(std::shared_ptr<basic_model> parent, - std::shared_ptr<basic_model> object) - : parent_(std::move(parent)) - , object_(std::move(object)) - { - assert(parent_ && object_); - - std::ostringstream oss; - - oss << "object " << object_->table() << "(" << object_->id() << ") is "; - oss << "already referenced in " << parent_->table() << "(" << parent_->id() << ")"; - - message_ = oss.str(); - } - - /** - * Get the parent object - * - * \return the parent - */ - inline const std::shared_ptr<basic_model>& parent() const noexcept - { - return parent_; - } - - /** - * Get the child object that already have a parent. - * - * \return the object - */ - inline const std::shared_ptr<basic_model>& object() const noexcept - { - return object_; - } - - /** - * Get the error message. - * - * \return the message - */ - const char* what() const noexcept - { - return message_.c_str(); - } -}; - -/** - * \brief Error when an operation required a different phase. - */ -class phase_error : public std::exception { -private: - std::string message_; - std::shared_ptr<basic_model> object_; - model_phase expected_phase_; - -public: - /** - * Create a phase_error - * - * \pre object != nullptr - * \param object the object model - * \param expected_phase the required phase - */ - inline phase_error(std::shared_ptr<basic_model> object, model_phase expected_phase) noexcept - : object_(std::move(object)) - , expected_phase_(expected_phase) - { - assert(object_); - - std::ostringstream oss; - - oss << "object '" << object_->table() << "' has invalid phase "; - oss << "(phase " << expected_phase_ << " expected, "; - oss << "got " << object_->phase() << ")"; - } - - /** - * Get the object. - * - * \return the object - */ - inline const std::shared_ptr<basic_model>& object() const noexcept - { - return object_; - } - - /** - * Get the expected phase. - * - * \return the expected phase - */ - inline model_phase expected_phase() const noexcept - { - return expected_phase_; - } - - /** - * Get the current object phase. - * - * \return the object phase - */ - inline model_phase acquired_phase() const noexcept - { - return object_->phase(); - } - - /** - * Get the error message. - * - * \return the message - */ - const char* what() const noexcept - { - return message_.c_str(); - } -}; - -#endif - } // !server } // !mlk
--- a/libserver/malikania/server/db/spell.cpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/db/spell.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -19,38 +19,11 @@ #include "database.hpp" #include "character.hpp" #include "spell.hpp" -#include "spell_dao.hpp" namespace mlk { namespace server { -#if 0 - -void spell::set_level(std::uint8_t level) -{ -#if 0 - if (is_published()) - db_->spell_dao()->set_level(shared_from_this(), level); - - level_ = level; -#endif -} - -#if 0 - -void spell::set_character(std::shared_ptr<class character> ch) -{ - if (is_published()) - throw std::runtime_error("attempt to reparent spell"); - - character_ = std::move(ch); -} - -#endif - -#endif - } // !server } // !mlk
--- a/libserver/malikania/server/db/spell.hpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/db/spell.hpp Mon Sep 11 13:18:43 2017 +0200 @@ -19,26 +19,66 @@ #ifndef MALIKANIA_SERVER_DB_SPELL_HPP #define MALIKANIA_SERVER_DB_SPELL_HPP -#include <cstdint> +/** + * \file spell.hpp + * \brief Database spell object. + */ + #include <memory> +#include <unordered_set> #include "model.hpp" namespace mlk { +/** + * \brief Database spell object. + */ namespace server { -class character; - /** * \brief Describe a spell. */ -class spell : public model<spell> { +class spell : public model { +private: + friend class character; + protected: - std::string classname_; - std::uint8_t level_{1}; - std::weak_ptr<class character> character_; + std::int64_t character_id_{-1}; //!< parent character + std::string classname_; //!< class type to instanciate + std::uint8_t level_{1}; //!< spell level + + /** + * Save this spell. + * + * The implementation must save the spell and update id, character_id + * member variables. + * + * Then it will be added into character.spells_ variable. + * + * \note called by character::add_spell. + */ + virtual void do_save(std::int64_t character_id) = 0; + /** + * Remove this spell. + * + * The implementation must remove the spell from the database and update + * id, character_id member variables. + * + * \note called by character::remove_spell + */ + virtual void do_remove() = 0; + + /** + * Update the spell level in database. + * + * Only called when the level needs to be changed, the implementation does + * not need to update level_ field. + * + * \note called from set_level helper + * \throw std::exception if the operation could not succeed + */ virtual void do_set_level(std::uint8_t level) = 0; /** @@ -76,38 +116,12 @@ level_ = level; } - - /** - * Get the owner. - * - * \return the owner or null if is_draft() or if character does not exist - */ - inline std::shared_ptr<class character> character() const noexcept - { - return character_.lock(); - } - -#if 0 - /** - * Set the character owner. - * - * \param ch the character (may be null) - * \throw referenced_error if is_public() - */ - void set_character(std::shared_ptr<class character> ch); -#endif }; -#if 0 - -class spell::dao { -public: - virtual void save(std::shared_ptr<spell>&, std::shared_ptr<class character>&) = 0; - virtual void set_level(std::shared_ptr<spell>&, std::uint8_t) = 0; - virtual void remove(std::shared_ptr<spell>&) = 0; -}; - -#endif +/** + * Type for storing spells. + */ +using spell_set = std::unordered_set<std::unique_ptr<spell>>; } // !server
--- a/libserver/malikania/server/db/spell_dao.hpp Mon Sep 11 16:26:35 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/* - * spell_dao.hpp -- database spell interface - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MALIKANIA_SERVER_DB_SPELL_DAO_HPP -#define MALIKANIA_SERVER_DB_SPELL_DAO_HPP - -#include <cstdint> - -#include "dao.hpp" - -namespace mlk { - -namespace server { - -class character; -class spell; - -class spell_dao : public dao { -public: - /** - * Inherited constructors. - */ - using dao::dao; - - virtual ~spell_dao() noexcept = default; - virtual std::int64_t save(std::shared_ptr<spell>, std::shared_ptr<character>) = 0; - virtual void set_level(std::shared_ptr<spell>, std::uint8_t) = 0; - virtual void remove(std::shared_ptr<spell>) = 0; -}; - -} // !server - -} // !mlk - -#endif // !MALIKANIA_SERVER_DB_SPELL_DAO_HPP
--- a/libserver/malikania/server/net/auth_handler.cpp Mon Sep 11 16:26:35 2017 +0200 +++ b/libserver/malikania/server/net/auth_handler.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -19,7 +19,7 @@ #include <util.hpp> #include "db/database.hpp" -#include "db/account_dao.hpp" +#include "db/account.hpp" #include "auth_handler.hpp" #include "client.hpp" @@ -32,8 +32,8 @@ void auth_handler::exec(server& server, std::shared_ptr<client> clt, nlohmann::json object) noexcept { try { - auto dao = server.database().account_dao(); - auto ac = dao->authenticate( + // TODO: VERIFY ALREADY LOGGED IN! + auto ac = server.database().account_dao().authenticate( util::json::require_string(object, "/login"_json_pointer), util::json::require_string(object, "/password"_json_pointer) ); @@ -41,7 +41,6 @@ if (ac) clt->error("auth", "invalid credentials"); else { - clt->set_account(ac); clt->set_state(client::state::ready); clt->ok("auth"); }
--- a/tests/libserver/db/account/main.cpp Mon Sep 11 16:26:35 2017 +0200 +++ b/tests/libserver/db/account/main.cpp Mon Sep 11 13:18:43 2017 +0200 @@ -21,6 +21,7 @@ #include <malikania/server/db/test_database.hpp> #include <malikania/server/db/broken_account.hpp> +#include <malikania/server/db/broken_character.hpp> namespace mlk { @@ -29,10 +30,46 @@ class account_fixture { protected: std::shared_ptr<test_database> db_{new test_database}; + + 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 + { + 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(); + } + + const nlohmann::json& get(std::int64_t id) const noexcept + { + return db_->test_account_dao().accounts().at(id); + } }; +/* + * Basic suite using test database. + * ------------------------------------------------------------------ + */ + BOOST_FIXTURE_TEST_SUITE(basic_suite, account_fixture) +/* + * account::save. + * ------------------------------------------------------------------ + * + * Verify that database is untouched during all operation on a draft account. + */ BOOST_AUTO_TEST_CASE(save) { auto acc = db_->account_draft("nanahara", "dummypassword"); @@ -53,7 +90,7 @@ BOOST_TEST(acc->email() == "nanahara@malikania.fr"); BOOST_TEST(acc->firstname() == "Alexis"); BOOST_TEST(acc->lastname() == "Dörr"); - BOOST_TEST(db_->test_account_dao()->accounts().size() == 0U); + BOOST_TEST(accounts().size() == 0U); // Do save, database must be updated and account published. acc->save(); @@ -63,119 +100,143 @@ BOOST_TEST(acc->email() == "nanahara@malikania.fr"); BOOST_TEST(acc->firstname() == "Alexis"); BOOST_TEST(acc->lastname() == "Dörr"); - BOOST_TEST(db_->test_account_dao()->accounts().size() == 1U); + BOOST_TEST(accounts().size() == 1U); } +/* + * account::set_password. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_CASE(set_password) { auto ac = db_->account_draft("markand", "nopassword"); ac->set_password("temporarypassword"); - - BOOST_TEST(db_->test_account_dao()->accounts().size() == 0U); - ac->save(); ac->set_password("newpassword"); - BOOST_TEST(db_->test_account_dao()->accounts().at(ac->id()).password() == "newpassword"); + BOOST_TEST(get(ac->id())["password"].get<std::string>() == "newpassword"); } +/* + * account::set_email. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_CASE(set_email) { auto ac = db_->account_draft("markand", "nopassword"); ac->set_email("fake@malikania.fr"); - - BOOST_TEST(db_->test_account_dao()->accounts().size() == 0U); - ac->save(); ac->set_email("markand@malikania.fr"); - BOOST_TEST(db_->test_account_dao()->accounts().at(ac->id()).email() == "markand@malikania.fr"); + BOOST_TEST(get(ac->id())["email"].get<std::string>() == "markand@malikania.fr"); } +/* + * account::set_firstname. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_CASE(set_firstname) { auto ac = db_->account_draft("markand", "nopassword"); ac->set_firstname("Jean"); - - BOOST_TEST(db_->test_account_dao()->accounts().size() == 0U); - ac->save(); ac->set_firstname("David"); - BOOST_TEST(db_->test_account_dao()->accounts().at(ac->id()).firstname() == "David"); + BOOST_TEST(get(ac->id())["firstname"].get<std::string>() == "David"); } +/* + * account::set_lastname. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_CASE(set_lastname) { auto ac = db_->account_draft("markand", "nopassword"); ac->set_lastname("Bertrand"); - - BOOST_TEST(db_->test_account_dao()->accounts().size() == 0U); - ac->save(); ac->set_lastname("Demelier"); - BOOST_TEST(db_->test_account_dao()->accounts().at(ac->id()).lastname() == "Demelier"); + BOOST_TEST(get(ac->id())["lastname"].get<std::string>() == "Demelier"); } -BOOST_AUTO_TEST_SUITE(find_by_login) - -BOOST_AUTO_TEST_CASE(simple) +/* + * account::add_character. + * ------------------------------------------------------------------ + */ +BOOST_AUTO_TEST_CASE(add_character) { - db_->account_draft("markand", "nopassword")->save(); - - auto ac = db_->account_dao()->find_by_login("markand"); + auto ac = db_->account_draft("markand", "nopassword"); - BOOST_TEST(ac); - BOOST_TEST(ac->is_published()); - BOOST_TEST(ac->login() == "markand"); -} + ac->save(); -BOOST_AUTO_TEST_CASE(not_found) -{ - auto ac = db_->account_dao()->find_by_login("doesnotexist"); + auto& ch = ac->add_character(db_->character_draft("erekin", "fire")); - BOOST_TEST(!ac); + BOOST_TEST(ch->is_published()); + BOOST_TEST(characters().size() == 1U); } -BOOST_AUTO_TEST_CASE(same_instance) +/* + * account::dao::find_by_login suite. + * ------------------------------------------------------------------ + */ +BOOST_AUTO_TEST_SUITE(find_by_login) + +/* + * Find existing account. + * ------------------------------------------------------------------ + */ +BOOST_AUTO_TEST_CASE(simple) { - db_->account_draft("markand", "nopassword")->save(); + { + auto ac = db_->account_draft("markand", "nopassword"); - auto a1 = db_->account_dao()->find_by_login("markand"); - auto a2 = db_->account_dao()->find_by_login("markand"); + ac->set_email("markand@malikania.fr"); + ac->set_firstname("David"); + ac->set_lastname("Demelier"); + ac->save(); + ac->add_character(db_->character_draft("erekin", "mage")); + ac->add_character(db_->character_draft("irina", "fairy")); + } - BOOST_TEST(db_->test_account_dao()->accounts().size() == 1U); - BOOST_TEST(a1 == a2); + auto ac = db_->account_dao().find_by_login("markand"); + + BOOST_TEST(ac->is_published()); + BOOST_TEST(ac->login() == "markand"); + BOOST_TEST(ac->email() == "markand@malikania.fr"); + BOOST_TEST(ac->firstname() == "David"); + BOOST_TEST(ac->lastname() == "Demelier"); + BOOST_TEST(ac->characters().size() == 2U); } -BOOST_AUTO_TEST_CASE(new_instance) +/* + * Not existing account. + * ------------------------------------------------------------------ + */ +BOOST_AUTO_TEST_CASE(not_found) { - db_->account_draft("markand", "nopassword")->save(); + auto ac = db_->account_dao().find_by_login("doesnotexist"); - /* - * Get rid of a1 reference, account::dao is supposed to call - * do_find_by_login again, creating a new pointer to the account. - */ - auto a1 = db_->account_dao()->find_by_login("markand"); - a1 = nullptr; - - auto a2 = db_->account_dao()->find_by_login("markand"); - - BOOST_TEST(db_->test_account_dao()->accounts().size() == 1U); - BOOST_TEST(a1 != a2); + BOOST_TEST(!ac); } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() +/* + * Test behaviour with broken implementation. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_SUITE(broken) +/* + * account::save. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_CASE(save) { broken_account ac("markand", "nopassword"); @@ -187,6 +248,27 @@ BOOST_TEST(ac.is_draft()); } +/* + * account::remove. + * ------------------------------------------------------------------ + */ +BOOST_AUTO_TEST_CASE(remove) +{ + broken_account ac("markand", "nopassword", broken_account::allow_flags::save); + + ac.save(); + + try { + ac.remove(); + } catch (...) {} + + BOOST_TEST(ac.is_published()); +} + +/* + * account::set_password. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_CASE(set_password) { broken_account ac("markand", "nopassword", broken_account::allow_flags::save); @@ -200,6 +282,10 @@ BOOST_TEST(ac.password() == "nopassword"); } +/* + * account::set_email. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_CASE(set_email) { broken_account ac("markand", "nopassword", broken_account::allow_flags::save); @@ -213,6 +299,10 @@ BOOST_TEST(ac.email() == ""); } +/* + * account::set_firstname. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_CASE(set_firstname) { broken_account ac("markand", "nopassword", broken_account::allow_flags::save); @@ -226,6 +316,10 @@ BOOST_TEST(ac.firstname() == ""); } +/* + * account::set_lastname. + * ------------------------------------------------------------------ + */ BOOST_AUTO_TEST_CASE(set_lastname) { broken_account ac("markand", "nopassword", broken_account::allow_flags::save); @@ -239,6 +333,23 @@ BOOST_TEST(ac.lastname() == ""); } +/* + * account::add_character. + * ------------------------------------------------------------------ + */ +BOOST_AUTO_TEST_CASE(add_character) +{ + broken_account ac("markand", "nopassword", broken_account::allow_flags::save); + + ac.save(); + + try { + ac.add_character(std::make_unique<broken_character>("erekin", "fire")); + } catch (...) {} + + BOOST_TEST(ac.characters().size() == 0U); +} + BOOST_AUTO_TEST_SUITE_END() } // !server