diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d9f0a1..c53101c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,13 +10,14 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) if(MSVC) add_compile_options(/W4 /WX) else() - add_compile_options(-W -Wall -Werror) + add_compile_options(-Wall -Wpedantic) endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +include(CTest) enable_testing() add_subdirectory(src) diff --git a/src/core/article.cpp b/src/core/article.cpp index 4ba0b33..3270da7 100644 --- a/src/core/article.cpp +++ b/src/core/article.cpp @@ -1,24 +1,17 @@ #include "article.h" -Article::Article() : Entity() -{} +Article::Article() : Entity() {} -Article::Article(const std::shared_ptr sellerPtr) : Entity() -{ - this->sellerPtr = sellerPtr; -} +Article::Article(std::shared_ptr sellerPtr) : Entity() { sellerPtr_ = sellerPtr; } -void Article::setArticleNo(int articleNo) -{ - this->articleNo = articleNo; -} +void Article::setArticleNo(int articleNo) { articleNo_ = articleNo; } -void Article::setPrice(int price) -{ - this->price = price; -} +void Article::setPrice(int price) { price_ = price; } -void Article::setDescription(const std::string& description) -{ - this->description = description; -} \ No newline at end of file +void Article::setDescription(const std::string& description) { description_ = description; } + +std::string Article::getDescription() { return description_; } + +void Article::setSale(std::shared_ptr salePtr) { salePtr_ = salePtr; } + +bool Article::isSold() { return salePtr_ ? true : false; } \ No newline at end of file diff --git a/src/core/article.h b/src/core/article.h index cc25ffa..1d0487b 100644 --- a/src/core/article.h +++ b/src/core/article.h @@ -2,26 +2,33 @@ #define ARTICLE_H #include "entity.h" +#include "sale.h" #include "seller.h" -#include #include +#include class Seller; class Article : public Entity { -public: + public: Article(); Article(std::shared_ptr sellerPtr); void setArticleNo(int articleNo); void setPrice(int price); void setDescription(const std::string& description); -private: - std::shared_ptr sellerPtr{}; - int articleNo{}; - int price{}; - std::string description{}; + std::string getDescription(); + bool isSold(); + void setSale(std::shared_ptr salePtr); + void setSeller(std::shared_ptr sellerPtr); + + private: + std::shared_ptr sellerPtr_{}; + std::shared_ptr salePtr_{}; + int articleNo_{}; + int price_{}; + std::string description_{}; }; #endif \ No newline at end of file diff --git a/src/core/database.cpp b/src/core/database.cpp index 42603ad..fc1a1c8 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -2,28 +2,40 @@ #include #include +#include Database::Database(const std::string& dbname) : db_(nullptr) { - const int errCode = sqlite3_open(dbname.c_str(), &db_); + dbname_ = dbname; + const int errCode = sqlite3_open(dbname_.c_str(), &db_); if (errCode) { throw std::runtime_error("Could not open database file."); } - exec("PRAGMA foreign_key = 1"); + sqlite3_db_config(db_, SQLITE_DBCONFIG_ENABLE_FKEY); } Database::~Database() { sqlite3_close(db_); } void Database::exec(const std::string& sql) { - const int errCode = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, nullptr); + char* errMsg; + const int errCode = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &errMsg); if (errCode) { - throw std::runtime_error("Error in SQL execution."); + std::string errMsgString(errMsg); // Make a C++ string of the errMsg, so that we can call + // sqlite3_free() before throwing the exception + sqlite3_free(errMsg); + throw std::runtime_error("Error in SQL execution: " + errMsgString); } } -void Database::init() +void Database::createNew() { + std::vector sqlStrings{}; + + std::string sqlCreateKima2{"CREATE TABLE IF NOT EXISTS kima2 (" + "version INTEGER NOT NULL);" + "INSERT INTO kima2 (version) VALUES (1);"}; + sqlStrings.push_back(sqlCreateKima2); std::string sqlCreateSellers{"CREATE TABLE IF NOT EXISTS sellers (" "id TEXT PRIMARY KEY NOT NULL, " "seller_no INTEGER, " @@ -32,7 +44,7 @@ void Database::init() "offered_articles INTEGER, " "UNIQUE (seller_no)" ");"}; - + sqlStrings.push_back(sqlCreateSellers); std::string sqlCreateArticles{ "CREATE TABLE IF NOT EXISTS articles (" "id TEXT PRIMARY KEY NOT NULL, " @@ -45,13 +57,90 @@ void Database::init() "FOREIGN KEY (seller_id) REFERENCES sellers(id) ON DELETE CASCADE, " "CHECK (article_no BETWEEN 0 AND 99999)" ");"}; + sqlStrings.push_back(sqlCreateArticles); + std::string sqlCreateSales{"CREATE TABLE IF NOT EXISTS sales (" + " id TEXT PRIMARY KEY NOT NULL," + " source_no INTEGER NOT NULL," + " sold_at TEXT" + ");"}; + sqlStrings.push_back(sqlCreateSales); + std::string sqlCreateSalesItems{ + "CREATE TABLE IF NOT EXISTS sales_items(" + " sale_id TEXT NOT NULL," + " article_id TEXT NOT NULL," + " FOREIGN KEY (sale_id) REFERENCES sales(id) ON DELETE CASCADE," + " FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE" + ");"}; + sqlStrings.push_back(sqlCreateSalesItems); beginTransaction(); - exec(sqlCreateSellers); - exec(sqlCreateArticles); + for (const auto& sql : sqlStrings) { + exec(sql); + } endTransaction(); } +void Database::init() +{ + int version = getVersion(); + + switch (version) { + case 0: + createNew(); + break; + // perhaps handle upgrades for db schema here... + default: + // Do nothing because we are up-to-date. + break; + } +} + +int Database::getVersion() +{ + int retCode{}; + sqlite3_stmt* stmt; + + // Check if there's already a kima2 table available. + // If not, return version == 0. + retCode = sqlite3_prepare_v2( + db_, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='kima2';", -1, &stmt, + nullptr); + if (retCode != SQLITE_OK) + throw std::string(sqlite3_errmsg(db_)); + retCode = sqlite3_step(stmt); + if (retCode != SQLITE_ROW && retCode != SQLITE_DONE) { + std::string errMsg(sqlite3_errmsg(db_)); + sqlite3_finalize(stmt); + throw errMsg; + } else if (retCode != SQLITE_DONE) { + int count = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + if (count == 0) + return 0; // no kima2 table, so version is 0 + } + + // Now that we know that the kima2 table is present, read and return the schema version. + retCode = sqlite3_prepare_v2(db_, "SELECT version FROM kima2", -1, &stmt, nullptr); + if (retCode != SQLITE_OK) + throw std::string(sqlite3_errmsg(db_)); + + retCode = sqlite3_step(stmt); + + if (retCode != SQLITE_ROW && retCode != SQLITE_DONE) { + std::string errMsg(sqlite3_errmsg(db_)); + sqlite3_finalize(stmt); + throw errMsg; + } else if (retCode == SQLITE_DONE) { + sqlite3_finalize(stmt); + return 0; // no version entry, so version is 0 + } + + int version = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + + return version; +} + void Database::beginTransaction() { exec("BEGIN TRANSACTION"); } void Database::endTransaction() { exec("END TRANSACTION"); } \ No newline at end of file diff --git a/src/core/database.h b/src/core/database.h index c0b4de4..ff6f3d8 100644 --- a/src/core/database.h +++ b/src/core/database.h @@ -16,8 +16,11 @@ public: void init(); private: sqlite3 *db_; + std::string dbname_; void beginTransaction(); void endTransaction(); + void createNew(); + int getVersion(); }; #endif // DATABASE_H \ No newline at end of file diff --git a/src/core/sale.h b/src/core/sale.h new file mode 100644 index 0000000..06f350d --- /dev/null +++ b/src/core/sale.h @@ -0,0 +1,8 @@ +#ifndef SALE_H +#define SALE_H + +class Sale : public Entity { + +}; + +#endif \ No newline at end of file diff --git a/src/core/seller.cpp b/src/core/seller.cpp index e4de084..634e585 100644 --- a/src/core/seller.cpp +++ b/src/core/seller.cpp @@ -1,26 +1,40 @@ #include "seller.h" -void Seller::setSellerNo(int seller_no) -{ - sellerNo_ = seller_no; -} +Seller::Seller() : Entity() {} -void Seller::setFirstName(const std::string& firstName) +Seller::Seller(const std::string& firstName, const std::string& lastName, int sellerNo, + int numberOfArticles) + : Entity() { firstName_ = firstName; -} - -void Seller::setLastName(const std::string& lastName) -{ lastName_ = lastName; + sellerNo_ = sellerNo; + numberOfOfferedArticles_ = numberOfArticles; } -void Seller::setNumberOfOfferedArticles(int number) -{ - numberOfOfferedArticles_ = number; +inline void Seller::setSellerNo(int seller_no) { sellerNo_ = seller_no; } + +inline void Seller::setFirstName(const std::string& firstName) { firstName_ = firstName; } + +inline void Seller::setLastName(const std::string& lastName) { lastName_ = lastName; } + +inline void Seller::setNumberOfOfferedArticles(int number) { numberOfOfferedArticles_ = number; } + +inline size_t Seller::getNumberOfOfferedArticles() { return articles_.size(); } + +void Seller::addArticle(Article article) { + articles_.push_back(article); } -size_t Seller::getNumberOfOfferedArticles() +std::vector Seller::getArticles(bool onlySold) { - return articles_.size(); + std::vector articles; + for (auto& article : articles_) { + if (onlySold && article.isSold()) { + articles.push_back(&article); + } else if (!onlySold) { + articles.push_back(&article); + } + } + return articles; } \ No newline at end of file diff --git a/src/core/seller.h b/src/core/seller.h index 2848579..4ac6a98 100644 --- a/src/core/seller.h +++ b/src/core/seller.h @@ -1,8 +1,8 @@ #ifndef SELLER_H #define SELLER_H -#include "entity.h" #include "article.h" +#include "entity.h" #include #include @@ -12,11 +12,17 @@ class Article; class Seller : public Entity { public: + Seller(); + Seller(const std::string& firstName, const std::string& lastName, int sellerNo = 0, + int numberOfArticles = 0); + void setSellerNo(int sellerNo); void setFirstName(const std::string& firstName); void setLastName(const std::string& lastName); void setNumberOfOfferedArticles(int number); + void addArticle(Article article); + std::vector getArticles(bool onlySold = false); size_t getNumberOfOfferedArticles(); private: diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2c68a6d..382580f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,10 @@ add_executable(test_seller test_seller.cpp) target_link_libraries(test_seller core Boost::unit_test_framework) add_test(NAME Seller COMMAND ${CMAKE_BINARY_DIR}/bin/test_seller) +add_executable(test_article test_article.cpp) +target_link_libraries(test_article core Boost::unit_test_framework) +add_test(NAME Article COMMAND ${CMAKE_BINARY_DIR}/bin/test_article) + add_executable(test_database test_database.cpp) target_link_libraries(test_database core Boost::unit_test_framework stdc++fs) add_test(NAME Database COMMAND ${CMAKE_BINARY_DIR}/bin/test_database) diff --git a/test/test_article.cpp b/test/test_article.cpp new file mode 100644 index 0000000..e0792ad --- /dev/null +++ b/test/test_article.cpp @@ -0,0 +1,21 @@ +#define BOOST_TEST_MODULE article + +#include "../src/core/article.h" + +#include + +BOOST_AUTO_TEST_CASE(create_article) +{ + Article article{}; + BOOST_TEST(article.getUuid().is_nil() == true); +} + +BOOST_AUTO_TEST_CASE(check_is_sold) +{ + Article article{}; + BOOST_TEST(article.isSold() == false); + + auto salePtr = std::make_shared(); + article.setSale(salePtr); + BOOST_TEST(article.isSold() == true); +} \ No newline at end of file diff --git a/test/test_database.cpp b/test/test_database.cpp index 28d47dc..de5c004 100644 --- a/test/test_database.cpp +++ b/test/test_database.cpp @@ -8,18 +8,8 @@ BOOST_AUTO_TEST_CASE(create_database) { - std::filesystem::path testPath = std::filesystem::temp_directory_path() / "kima2test.db"; - if (std::filesystem::exists(testPath)) { - std::filesystem::remove(testPath); - } - std::string filename = testPath.generic_string(); + Database db(":memory:"); + BOOST_CHECK_NO_THROW(db.init()); - Database db(filename); - db.init(); - - BOOST_TEST(std::filesystem::exists(testPath) == true); - if (std::filesystem::exists(testPath)) { - std::filesystem::remove(testPath); - } } \ No newline at end of file diff --git a/test/test_seller.cpp b/test/test_seller.cpp index d193340..5de2ed2 100644 --- a/test/test_seller.cpp +++ b/test/test_seller.cpp @@ -2,17 +2,37 @@ #include "../src/core/seller.h" +#include + #include -//using namespace boost::unit_test; - -BOOST_AUTO_TEST_CASE( create_uuid_nil ) { +BOOST_AUTO_TEST_CASE(create_uuid_nil) +{ Seller seller{}; BOOST_TEST(seller.getUuid().is_nil() == true); } -BOOST_AUTO_TEST_CASE(create_uuid) { +BOOST_AUTO_TEST_CASE(create_uuid) +{ Seller seller{}; seller.createUuid(); BOOST_TEST(seller.getUuid().is_nil() == false); +} + +BOOST_AUTO_TEST_CASE(create_many) +{ + constexpr unsigned int QUANTITY{10000}; + std::array sellers; + for (unsigned i = 0; i < sellers.size(); i++) { + sellers[i] = Seller(); + sellers[i].createUuid(); + } +} + +BOOST_AUTO_TEST_CASE(with_article) { + Seller seller("Max", "Mustermann"); + Article article{}; + article.setDescription("Test article"); + seller.addArticle(article); + BOOST_TEST(seller.getArticles().at(0)->getDescription() == "Test article"); } \ No newline at end of file