From a3f948ac0402fdc5d36226d9aa08961cd2d8e983 Mon Sep 17 00:00:00 2001 From: Bartosz Wieczorek Date: Thu, 1 Feb 2018 08:02:23 +0100 Subject: [PATCH] add database tests --- sql/schema.sql | 31 +---- src/eedb/data/User.hpp | 109 ++++++++++++------ src/eedb/data/Users.hpp | 9 +- src/eedb/db/config.cpp | 10 ++ src/eedb/db/config.hpp | 1 + src/eedb/db/data/PgUser.cpp | 61 ++++++---- src/eedb/db/data/PgUser.hpp | 28 ++--- src/eedb/db/data/PgUsers.cpp | 60 ++++++++-- src/eedb/db/data/PgUsers.hpp | 21 +++- src/eedb/db/model/user.h | 18 +-- .../{ => db}/test_eedb_data_PgCategories.cpp | 0 tests/unit/db/test_eedb_data_PgCategory.cpp | 19 +++ tests/unit/db/test_eedb_data_PgUsers.cpp | 96 +++++++++++++++ .../DbTestBase.hpp} | 31 ++--- 14 files changed, 333 insertions(+), 161 deletions(-) rename tests/unit/{ => db}/test_eedb_data_PgCategories.cpp (100%) create mode 100644 tests/unit/db/test_eedb_data_PgCategory.cpp create mode 100644 tests/unit/db/test_eedb_data_PgUsers.cpp rename tests/{unit/test_eedb_data_PgCategory.cpp => utils/DbTestBase.hpp} (77%) diff --git a/sql/schema.sql b/sql/schema.sql index a6d5ea6..fd3d183 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -1,3 +1,7 @@ +DROP SCHEMA public CASCADE; +CREATE SCHEMA public; +GRANT ALL ON SCHEMA public TO postgres; +GRANT ALL ON SCHEMA public TO public; create EXTENSIon IF NOT EXISTS ltree; @@ -25,33 +29,6 @@ BEGIN END; $$ -- $$ language 'plpgsql'; - -drop table if exists "user" cascade; -drop table if exists "auth_info" cascade; -drop table if exists "auth_token" cascade; -drop table if exists "user_audit_action" cascade; -drop table if exists "user_audit" cascade; -drop table if exists "stat" cascade; -drop table if exists "user_groups" cascade; -drop table if exists action cascade; -drop table if exists metric_systems cascade; -drop table if exists measurands cascade; -drop table if exists privilege cascade; -drop table if exists pointed_values cascade; -drop table if exists "auth_identity" cascade; -drop table if exists "group" cascade; - -drop table if exists category_files; -drop table if exists units_conversions; -drop table if exists in_stock; -drop table if exists implemented_action; -drop table if exists inventory_history; -drop table if exists user_history; -drop table if exists item_files; -drop table if exists packages_files; -drop table if exists system_info; -drop table if exists user_inventory; - create table "system_info"( "id" serial not null, "name" text, diff --git a/src/eedb/data/User.hpp b/src/eedb/data/User.hpp index ab26379..ac57667 100644 --- a/src/eedb/data/User.hpp +++ b/src/eedb/data/User.hpp @@ -21,38 +21,14 @@ class Email { using string = std::string; public: - Email(string email) : _data(std::move(email)) {} - virtual ~Email() = default; + Email(string email) : _address(std::move(email)) {} - virtual bool isValid() const { - return true; - } - - virtual bool isEmpty() const { - return _data.empty(); - } - - virtual operator string_view() const { - return _data; + string_view address() const { + return _address; } private: - string _data; -}; - -class UserPassword { - protected: - using string_view = std::string_view; - using string = std::string; - - virtual void hash() = 0; - - public: - virtual ~UserPassword() = default; - - virtual string_view value() const = 0; - virtual string_view function() const = 0; - virtual string_view salt() const = 0; + string _address; }; /// user data @@ -70,20 +46,59 @@ class UserName { virtual operator string_view() const = 0; }; +class Password { + public: + Password(std::string function, std::string salt, std::string value) + : _function{std::move(function)}, _salt{std::move(salt)}, _value{std::move(value)} {} + + /*! \brief Returns the function identifier. + */ + std::string function() const { + return _function; + } + + /*! \brief Returns the salt. + */ + std::string salt() const { + return _salt; + } + + /*! \brief Returns the hash value. + */ + std::string value() const { + return _value; + } + + private: + std::string _function; + std::string _salt; + std::string _value; +}; + class AuthInfo { protected: using string_view = std::string_view; using string = std::string; public: - virtual ~AuthInfo() = default; + AuthInfo(Password pass, Email email) : _password{std::move(pass)}, _email{std::move(email)} {} - virtual const Email & email() const = 0; - virtual void email(string email) = 0; + const Password & password() const { + return _password; + } - virtual bool emailVerified() const = 0; - virtual void verifyEmail() = 0; - virtual bool isUnique() const = 0; + const Email & email() const { + return _email; + }; + + bool emailVerified() const {} + bool isUnique() const {} + void verifyEmail() {} + + private: + Password _password; + Email _email; + string _emailTomek; }; class AuthIdentity { @@ -96,7 +111,27 @@ class AuthIdentity { virtual string_view identity() const = 0; virtual string_view provider() const = 0; - virtual AuthInfo * authInfo() const = 0; + + private: +}; + +class ConstAuthIdentity final : public AuthIdentity { + protected: + using string_view = std::string_view; + using string = std::string; + + public: + ConstAuthIdentity(string identity, string provider) : _identity{std::move(identity)}, _provider{std::move(provider)} {} + virtual string_view identity() const override { + return _identity; + } + virtual string_view provider() const override { + return _provider; + } + + private: + string _identity; + string _provider; }; class User { @@ -113,8 +148,8 @@ class User { virtual UserConfig & config() = 0; // user auth - virtual void changePassword(UserPassword & password) = 0; - virtual void login(UserPassword & password) = 0; +// virtual void changePassword(UserPassword & password) = 0; +// virtual void login(Pa & password) = 0; virtual void logout() = 0; }; } // namespace eedb diff --git a/src/eedb/data/Users.hpp b/src/eedb/data/Users.hpp index d03fbfe..bc3163b 100644 --- a/src/eedb/data/Users.hpp +++ b/src/eedb/data/Users.hpp @@ -19,12 +19,11 @@ class Users { public: virtual ~Users() = default; - virtual shared_ptr< User > findWith(int) const = 0; - virtual shared_ptr< User > findWith(const AuthIdentity & name) const = 0; - virtual shared_ptr< User > findWith(const Email & email) const = 0; - virtual shared_ptr< User > findWith(const AuthToken & token) const = 0; + virtual unique_ptr< User > findWith(const AuthIdentity & name) const = 0; + virtual unique_ptr< User > findWith(const Email & email) const = 0; + virtual unique_ptr< User > findWith(const AuthToken & token) const = 0; // adds new user into users - virtual shared_ptr< User > addUser(unique_ptr< AuthInfo >, unique_ptr< AuthIdentity >) = 0; + virtual unique_ptr< User > addUser(unique_ptr< AuthInfo >, unique_ptr< AuthIdentity >) = 0; }; } // namespace eedb diff --git a/src/eedb/db/config.cpp b/src/eedb/db/config.cpp index 1bb9643..508d8b6 100644 --- a/src/eedb/db/config.cpp +++ b/src/eedb/db/config.cpp @@ -4,6 +4,16 @@ #include namespace eedb::db { + +PgConfig::PgConfig() { + host = "localhost"; + user = "postgres"; + password = "postgres"; + dbname = "postgres"; + port = 5432; + debug = true; +} + PgConfig::PgConfig(const Wt::WEnvironment & env) { auto readString = [&env](auto && name, auto & val, auto def) { if(!env.server()->readConfigurationProperty(name, val)) { diff --git a/src/eedb/db/config.hpp b/src/eedb/db/config.hpp index e682070..ffeb9aa 100644 --- a/src/eedb/db/config.hpp +++ b/src/eedb/db/config.hpp @@ -10,6 +10,7 @@ namespace eedb::db { class PgConfig : public sqlpp::postgresql::connection_config { public: + PgConfig(); explicit PgConfig(const Wt::WEnvironment & env); }; } // namespace eedb::db diff --git a/src/eedb/db/data/PgUser.cpp b/src/eedb/db/data/PgUser.cpp index 7a2500a..2f289f7 100644 --- a/src/eedb/db/data/PgUser.cpp +++ b/src/eedb/db/data/PgUser.cpp @@ -32,29 +32,47 @@ auto auth_token_info = user_auth_info.join(authToken).on(authToken.auth_info_ namespace eedb { -PgUserIdentity::PgUserIdentity(db::PgConnection & con, int uid, string provider) : _db(con), _provider(std::move(provider)), _email("") { - select(all_of(t_identity)) // - .from(auth_info_identity) // - .where(t_info.user_uid == uid); +struct PgUser::PgUserPriv{ + int id; +}; + +PgUser::PgUser(int uid) : _priv{spimpl::make_unique_impl< PgUserPriv >()} { + _priv->id = uid; } -AuthIdentity::string_view PgUserIdentity::identity() const { - const auto id_eq = t_info.user_uid == 0; - const auto provider_eq = t_identity.provider == _provider; - - auto res = _db(select(t_identity.identity) // - .from(auth_info_identity) // - .where(id_eq and provider_eq)); - - if(res.empty()) - return {}; - - // _name = res.front().identity; - - return _name; +int PgUser::uid() const +{ + return _priv->id; } -AuthIdentity::string_view PgUserIdentity::provider() const {} +const AuthInfo & PgUser::authInfo() const +{ +} + + +//PgUserIdentity::PgUserIdentity(db::PgConnection & con, int uid, string provider) : _db(con), _provider(std::move(provider)), _email("") { +// select(all_of(t_identity)) // +// .from(auth_info_identity) // +// .where(t_info.user_uid == uid); +//} + +//AuthIdentity::string_view PgUserIdentity::identity() const { +// const auto id_eq = t_info.user_uid == 0; +// const auto provider_eq = t_identity.provider == _provider; + +// auto res = _db(select(t_identity.identity) // +// .from(auth_info_identity) // +// .where(id_eq and provider_eq)); + +// if(res.empty()) +// return {}; + +// // _name = res.front().identity; + +// return _name; +//} + +//AuthIdentity::string_view PgUserIdentity::provider() const {} const AuthIdentity & PgUser::identity() const {} @@ -62,9 +80,10 @@ const UserConfig & PgUser::config() const {} UserConfig & PgUser::config() {} -void PgUser::changePassword(UserPassword & password) {} +//void PgUser::changePassword(UserPassword & password) {} -void PgUser::login(UserPassword & password) {} +//void PgUser::login(UserPassword & password) {} void PgUser::logout() {} } // namespace eedb + diff --git a/src/eedb/db/data/PgUser.hpp b/src/eedb/db/data/PgUser.hpp index d1a49b2..c327b70 100644 --- a/src/eedb/db/data/PgUser.hpp +++ b/src/eedb/db/data/PgUser.hpp @@ -2,37 +2,29 @@ #include +#include + namespace eedb::db { class PgConnection; } namespace eedb { -class PgUserIdentity : public AuthIdentity { - // UserIdentity interface - public: - PgUserIdentity(db::PgConnection & con, int uid, string provider); - - string_view identity() const override; - string_view provider() const override; - - private: - eedb::db::PgConnection & _db; - string _name; - string _provider; - Email _email; -}; - class PgUser : public User { - // User interface public: + PgUser(int uid); + int uid() const override; + + const AuthInfo &authInfo() const override; const AuthIdentity & identity() const override; const UserConfig & config() const override; UserConfig & config() override; - void changePassword(UserPassword & password) override; - void login(UserPassword & password) override; void logout() override; + + private: + struct PgUserPriv; + spimpl::unique_impl_ptr< PgUserPriv > _priv; }; } // namespace eedb diff --git a/src/eedb/db/data/PgUsers.cpp b/src/eedb/db/data/PgUsers.cpp index 199d94e..dd8d661 100644 --- a/src/eedb/db/data/PgUsers.cpp +++ b/src/eedb/db/data/PgUsers.cpp @@ -1,23 +1,69 @@ -#include #include +#include + +#include #include #include #include +#include + +constexpr eedb::user t_user; +constexpr eedb::auth_identity t_auth_identity; +constexpr eedb::auth_info t_auth_info; +constexpr eedb::auth_token t_auth_token; namespace eedb { template < typename T > -using shared_ptr = PgUsers::shared_ptr< T >; +using unique_ptr = PgUsers::unique_ptr< T >; -shared_ptr< User > PgUsers::findWith(int) const {} +struct PgUsers::PgUsersPriv { + PgUsersPriv(eedb::db::PgConnection & db) : _db{db} {} -shared_ptr< User > PgUsers::findWith(const AuthIdentity & identity) const {} + eedb::db::PgConnection & _db; +}; -shared_ptr< User > PgUsers::findWith(const Email & email) const {} +PgUsers::PgUsers(eedb::db::PgConnection & db) : _priv{spimpl::make_unique_impl< PgUsersPriv >(db)} {} -shared_ptr< User > PgUsers::findWith(const Users::AuthToken & token) const {} +unique_ptr< User > PgUsers::findWith(const AuthIdentity & identity) const { + return {}; +} -shared_ptr< User > PgUsers::addUser(unique_ptr< AuthIdentity >) {} +unique_ptr< User > PgUsers::findWith(const Email & email) const { + return {}; +} + +unique_ptr< User > PgUsers::findWith(const Users::AuthToken & token) const { + return {}; +} + +unique_ptr< User > PgUsers::addUser(unique_ptr< AuthInfo > authInfo, unique_ptr< AuthIdentity > authIdentity) { + assert(authInfo); + assert(authIdentity); + + auto & db = *(_priv->_db.native()); + + auto user_id = db(sqlpp::postgresql::insert_into(t_user) // + .set( // + t_user.full_name = "none", // + t_user.status = 0) + .returning(t_user.uid)) + .front() + .uid; + + const auto & pass = authInfo->password(); + const auto & email = authInfo->email(); + + db(insert_into(t_auth_info) + .set( // + t_auth_info.password_hash = pass.value(), // + t_auth_info.password_method = pass.function(), // + t_auth_info.password_salt = pass.salt(), // + t_auth_info.user_uid = user_id, // + t_auth_info.email = std::string{email.address()})); + + return std::make_unique< eedb::PgUser >(user_id); +} } // namespace eedb diff --git a/src/eedb/db/data/PgUsers.hpp b/src/eedb/db/data/PgUsers.hpp index 124f734..5c323c4 100644 --- a/src/eedb/db/data/PgUsers.hpp +++ b/src/eedb/db/data/PgUsers.hpp @@ -2,15 +2,26 @@ #include +#include + +namespace eedb::db { +class PgConnection; +} + namespace eedb { class PgUsers : public Users { // Users interface public: - shared_ptr< User > findWith(int) const override; - shared_ptr< User > findWith(const AuthIdentity & name) const override; - shared_ptr< User > findWith(const Email & email) const override; - shared_ptr< User > findWith(const AuthToken & token) const override; + PgUsers(eedb::db::PgConnection & db); - shared_ptr< User > addUser(unique_ptr< AuthIdentity >) override; + unique_ptr< User > findWith(const AuthIdentity & name) const override; + unique_ptr< User > findWith(const Email & email) const override; + unique_ptr< User > findWith(const AuthToken & token) const override; + + unique_ptr< User > addUser(unique_ptr< AuthInfo >, unique_ptr< AuthIdentity >) override; + + private: + struct PgUsersPriv; + spimpl::unique_impl_ptr< PgUsersPriv > _priv; }; } // namespace eedb diff --git a/src/eedb/db/model/user.h b/src/eedb/db/model/user.h index 619f092..74d63e3 100644 --- a/src/eedb/db/model/user.h +++ b/src/eedb/db/model/user.h @@ -113,21 +113,6 @@ namespace eedb { using _traits = ::sqlpp::make_traits<::sqlpp::text>; }; - - struct Auth_info_id { - struct _alias_t { - static constexpr const char _literal[] = R"("auth_info_id")"; - using _name_t = sqlpp::make_char_sequence; - template - struct _member_t { - T auth_info_id; - T &operator()() { return auth_info_id; } - const T &operator()() const { return auth_info_id; } - }; - }; - - using _traits = ::sqlpp::make_traits<::sqlpp::bigint, sqlpp::tag::can_be_null>; - }; } struct user : sqlpp::table_t { + user_::Config> { using _value_type = sqlpp::no_value_t; struct _alias_t { static constexpr const char _literal[] = R"("user")"; diff --git a/tests/unit/test_eedb_data_PgCategories.cpp b/tests/unit/db/test_eedb_data_PgCategories.cpp similarity index 100% rename from tests/unit/test_eedb_data_PgCategories.cpp rename to tests/unit/db/test_eedb_data_PgCategories.cpp diff --git a/tests/unit/db/test_eedb_data_PgCategory.cpp b/tests/unit/db/test_eedb_data_PgCategory.cpp new file mode 100644 index 0000000..2001ee1 --- /dev/null +++ b/tests/unit/db/test_eedb_data_PgCategory.cpp @@ -0,0 +1,19 @@ +#include + +#include + +class PgCategoryTest : public DbTestBase< PgCategoryTest > { + public: + PgCategoryTest() { + db().native()->start_transaction(); +// db().native()->execute(R"sql( INSERT INTO "category" ("owner", "name", "parent_id", "parent_path") VALUES ('root', NULL, 'root'); )sql"); + } + + ~PgCategoryTest() { + db().native()->rollback_transaction(false); + } +}; + +template<> std::unique_ptr< PgTestDatabasePrepare > DbTestBase::_test_db = {}; + +TEST_F(PgCategoryTest, rootCategoryHasNoParent) {} diff --git a/tests/unit/db/test_eedb_data_PgUsers.cpp b/tests/unit/db/test_eedb_data_PgUsers.cpp new file mode 100644 index 0000000..fccbc82 --- /dev/null +++ b/tests/unit/db/test_eedb_data_PgUsers.cpp @@ -0,0 +1,96 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +constexpr eedb::user t_user; +constexpr eedb::user_audit t_user_audit; +constexpr eedb::user_action t_user_action; +constexpr eedb::auth_identity t_auth_identity; +constexpr eedb::auth_info t_auth_info; +constexpr eedb::auth_token t_auth_token; + +class PgUsersTest : public DbTestBase< PgUsersTest > { + public: + PgUsersTest() { + sut = std::make_unique< eedb::PgUsers >(db()); + db().native()->start_transaction(); + } + + ~PgUsersTest() { + db().native()->rollback_transaction(false); + } + + auto createUser(std::string name, std::string email) { + auto info_ = std::make_unique< eedb::AuthInfo >( + eedb::Password{"bcrypt", "OM/Z1c4WBFXvwkxh", "$2y$07$RyytUhDhLDbAPjf0b0r2Y.dsg.FlQ7L.xzWHMmoelI81u0MfBrW7q"}, eedb::Email{email}); + auto identity_ = std::make_unique< eedb::ConstAuthIdentity >(name, "loginname"); + return sut->addUser(std::move(info_), std::move(identity_)); + } + + auto createTestUsers(){ + createUser("test_user_1", "test_user_1@eedb.pl"); + createUser("test_user_2", "test_user_2@eedb.pl"); + createUser("test_user_3", "test_user_3@eedb.pl"); + createUser("test_user_4", "test_user_4@eedb.pl"); + createUser("test_user_5", "test_user_5@eedb.pl"); + createUser("test_user_6", "test_user_6@eedb.pl"); + } + + protected: + std::unique_ptr< eedb::PgUsers > sut; +}; + +template <> +std::unique_ptr< PgTestDatabasePrepare > DbTestBase< PgUsersTest >::_test_db = {}; + +using namespace sqlpp; + +TEST_F(PgUsersTest, createValidUser) { + auto info_ = std::make_unique< eedb::AuthInfo >( + eedb::Password{"bcrypt", "OM/Z1c4WBFXvwkxh", "$2y$07$RyytUhDhLDbAPjf0b0r2Y.dsg.FlQ7L.xzWHMmoelI81u0MfBrW7q"}, + eedb::Email{"none@eedb.pl"}); + auto identity_ = std::make_unique< eedb::ConstAuthIdentity >("username", "loginname"); + + auto new_user = sut->addUser(std::move(info_), std::move(identity_)); + ASSERT_TRUE(new_user); + + auto info = db()(select(sqlpp::all_of(t_auth_info)).from(t_auth_info).where(t_auth_info.user_uid == new_user->uid())); + EXPECT_FALSE(info.empty()); + + EXPECT_EQ(info.front().password_hash, "$2y$07$RyytUhDhLDbAPjf0b0r2Y.dsg.FlQ7L.xzWHMmoelI81u0MfBrW7q"); + EXPECT_EQ(info.front().password_method, "bcrypt"); + EXPECT_EQ(info.front().password_salt, "OM/Z1c4WBFXvwkxh"); + EXPECT_EQ(info.front().email, "none@eedb.pl"); + EXPECT_EQ(info.front().email_verified, false); + EXPECT_EQ(info.front().user_uid.value(), new_user->uid()); + + auto user = db()(select(sqlpp::all_of(t_user)).from(t_user).where(t_user.uid == new_user->uid())); + /// TODO check username structure + + // auto identity = db()(select(sqlpp::all_of(t_auth_identity)).from(t_auth_identity).where(t_auth_identity.auth_info_id == + // info.front().id)); EXPECT_EQ(identity.front().provider, "loginname"); EXPECT_EQ(identity.front().identity, "username"); +} + +TEST_F(PgUsersTest, createSameUserMustFail) { + createUser("_test_user_", "test_email@eedb.pl"); + EXPECT_THROW(createUser("_test_user_", "test_email@eedb.pl"), sqlpp::postgresql::integrity_constraint_violation); +} + +TEST_F(PgUsersTest, createWithSameEmailMustFail) { + createUser("_test_user_1", "test_email@eedb.pl"); + EXPECT_THROW(createUser("_test_user_2", "test_email@eedb.pl"), sqlpp::postgresql::integrity_constraint_violation); +} + +TEST_F(PgUsersTest, findByName) { + createTestUsers(); + + EXPECT_TRUE(sut->findWith(eedb::ConstAuthIdentity{"test_user_1", "loginname"})); +} diff --git a/tests/unit/test_eedb_data_PgCategory.cpp b/tests/utils/DbTestBase.hpp similarity index 77% rename from tests/unit/test_eedb_data_PgCategory.cpp rename to tests/utils/DbTestBase.hpp index ade7361..ffa9dea 100644 --- a/tests/unit/test_eedb_data_PgCategory.cpp +++ b/tests/utils/DbTestBase.hpp @@ -1,15 +1,10 @@ #include -#include - #include #include #include #include -#include -#include - #include #include @@ -44,11 +39,9 @@ class DockerRunner { class PgTestDatabasePrepare { public: PgTestDatabasePrepare() { - Wt::Test::WTestEnvironment env; - _docker = std::make_unique< DockerRunner >("eedb_test_database"); - auto dbConfig = std::make_shared< eedb::db::PgConfig >(env); + auto dbConfig = std::make_shared< eedb::db::PgConfig >(); dbConfig->host = "localhost"; dbConfig->port = 5432; dbConfig->password = "postgres"; @@ -82,33 +75,23 @@ class PgTestDatabasePrepare { std::unique_ptr< eedb::db::PgConnection > _db; }; -class PgCategoryTest : public testing::Test { +template +class DbTestBase : public testing::Test { public: static void SetUpTestCase() { _test_db = std::make_unique< PgTestDatabasePrepare >(); eedb::db::exec_file(_test_db->db(), "/home/bwieczor/src/eedb/sql/schema.sql"); - eedb::db::exec_file(_test_db->db(), ""); - } - - PgCategoryTest() { - _test_db->db().native()->start_transaction(); - _test_db->db().native()->execute(R"sql( INSERT INTO "category" ("owner", "name", "parent_id", "parent_path") VALUES ('root', NULL, 'root'); )sql"); - } - - ~PgCategoryTest() { - _test_db->db().native()->rollback_transaction(false); } static void TearDownTestCase() { _test_db.reset(); } + eedb::db::PgConnection & db(){ + return _test_db->db(); + } + protected: static std::unique_ptr< PgTestDatabasePrepare > _test_db; - std::unique_ptr< eedb::PgCategory > sut; }; - -std::unique_ptr< PgTestDatabasePrepare > PgCategoryTest::_test_db = {}; - -TEST_F(PgCategoryTest, rootCategoryHasNoParent) {}