Compare commits

...

13 commits

12 changed files with 221 additions and 65 deletions

View file

@ -10,13 +10,14 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(MSVC) if(MSVC)
add_compile_options(/W4 /WX) add_compile_options(/W4 /WX)
else() else()
add_compile_options(-W -Wall -Werror) add_compile_options(-Wall -Wpedantic)
endif() endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
include(CTest)
enable_testing() enable_testing()
add_subdirectory(src) add_subdirectory(src)

View file

@ -1,24 +1,17 @@
#include "article.h" #include "article.h"
Article::Article() : Entity() Article::Article() : Entity() {}
{}
Article::Article(const std::shared_ptr<Seller> sellerPtr) : Entity() Article::Article(std::shared_ptr<Seller> sellerPtr) : Entity() { sellerPtr_ = sellerPtr; }
{
this->sellerPtr = sellerPtr;
}
void Article::setArticleNo(int articleNo) void Article::setArticleNo(int articleNo) { articleNo_ = articleNo; }
{
this->articleNo = articleNo;
}
void Article::setPrice(int price) void Article::setPrice(int price) { price_ = price; }
{
this->price = price;
}
void Article::setDescription(const std::string& description) void Article::setDescription(const std::string& description) { description_ = description; }
{
this->description = description; std::string Article::getDescription() { return description_; }
}
void Article::setSale(std::shared_ptr<Sale> salePtr) { salePtr_ = salePtr; }
bool Article::isSold() { return salePtr_ ? true : false; }

View file

@ -2,26 +2,33 @@
#define ARTICLE_H #define ARTICLE_H
#include "entity.h" #include "entity.h"
#include "sale.h"
#include "seller.h" #include "seller.h"
#include <string>
#include <memory> #include <memory>
#include <string>
class Seller; class Seller;
class Article : public Entity class Article : public Entity
{ {
public: public:
Article(); Article();
Article(std::shared_ptr<Seller> sellerPtr); Article(std::shared_ptr<Seller> sellerPtr);
void setArticleNo(int articleNo); void setArticleNo(int articleNo);
void setPrice(int price); void setPrice(int price);
void setDescription(const std::string& description); void setDescription(const std::string& description);
private: std::string getDescription();
std::shared_ptr<Seller> sellerPtr{}; bool isSold();
int articleNo{}; void setSale(std::shared_ptr<Sale> salePtr);
int price{}; void setSeller(std::shared_ptr<Seller> sellerPtr);
std::string description{};
private:
std::shared_ptr<Seller> sellerPtr_{};
std::shared_ptr<Sale> salePtr_{};
int articleNo_{};
int price_{};
std::string description_{};
}; };
#endif #endif

View file

@ -2,28 +2,40 @@
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>
#include <vector>
Database::Database(const std::string& dbname) : db_(nullptr) 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) { if (errCode) {
throw std::runtime_error("Could not open database file."); 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_); } Database::~Database() { sqlite3_close(db_); }
void Database::exec(const std::string& sql) 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) { 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<std::string> 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 (" std::string sqlCreateSellers{"CREATE TABLE IF NOT EXISTS sellers ("
"id TEXT PRIMARY KEY NOT NULL, " "id TEXT PRIMARY KEY NOT NULL, "
"seller_no INTEGER, " "seller_no INTEGER, "
@ -32,7 +44,7 @@ void Database::init()
"offered_articles INTEGER, " "offered_articles INTEGER, "
"UNIQUE (seller_no)" "UNIQUE (seller_no)"
");"}; ");"};
sqlStrings.push_back(sqlCreateSellers);
std::string sqlCreateArticles{ std::string sqlCreateArticles{
"CREATE TABLE IF NOT EXISTS articles (" "CREATE TABLE IF NOT EXISTS articles ("
"id TEXT PRIMARY KEY NOT NULL, " "id TEXT PRIMARY KEY NOT NULL, "
@ -45,13 +57,90 @@ void Database::init()
"FOREIGN KEY (seller_id) REFERENCES sellers(id) ON DELETE CASCADE, " "FOREIGN KEY (seller_id) REFERENCES sellers(id) ON DELETE CASCADE, "
"CHECK (article_no BETWEEN 0 AND 99999)" "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(); beginTransaction();
exec(sqlCreateSellers); for (const auto& sql : sqlStrings) {
exec(sqlCreateArticles); exec(sql);
}
endTransaction(); 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::beginTransaction() { exec("BEGIN TRANSACTION"); }
void Database::endTransaction() { exec("END TRANSACTION"); } void Database::endTransaction() { exec("END TRANSACTION"); }

View file

@ -16,8 +16,11 @@ public:
void init(); void init();
private: private:
sqlite3 *db_; sqlite3 *db_;
std::string dbname_;
void beginTransaction(); void beginTransaction();
void endTransaction(); void endTransaction();
void createNew();
int getVersion();
}; };
#endif // DATABASE_H #endif // DATABASE_H

8
src/core/sale.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef SALE_H
#define SALE_H
class Sale : public Entity {
};
#endif

View file

@ -1,26 +1,40 @@
#include "seller.h" #include "seller.h"
void Seller::setSellerNo(int seller_no) Seller::Seller() : Entity() {}
{
sellerNo_ = seller_no;
}
void Seller::setFirstName(const std::string& firstName) Seller::Seller(const std::string& firstName, const std::string& lastName, int sellerNo,
int numberOfArticles)
: Entity()
{ {
firstName_ = firstName; firstName_ = firstName;
}
void Seller::setLastName(const std::string& lastName)
{
lastName_ = lastName; lastName_ = lastName;
sellerNo_ = sellerNo;
numberOfOfferedArticles_ = numberOfArticles;
} }
void Seller::setNumberOfOfferedArticles(int number) inline void Seller::setSellerNo(int seller_no) { sellerNo_ = seller_no; }
{
numberOfOfferedArticles_ = number; 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<Article*> Seller::getArticles(bool onlySold)
{ {
return articles_.size(); std::vector<Article*> articles;
for (auto& article : articles_) {
if (onlySold && article.isSold()) {
articles.push_back(&article);
} else if (!onlySold) {
articles.push_back(&article);
}
}
return articles;
} }

View file

@ -1,8 +1,8 @@
#ifndef SELLER_H #ifndef SELLER_H
#define SELLER_H #define SELLER_H
#include "entity.h"
#include "article.h" #include "article.h"
#include "entity.h"
#include <string> #include <string>
#include <vector> #include <vector>
@ -12,11 +12,17 @@ class Article;
class Seller : public Entity class Seller : public Entity
{ {
public: public:
Seller();
Seller(const std::string& firstName, const std::string& lastName, int sellerNo = 0,
int numberOfArticles = 0);
void setSellerNo(int sellerNo); void setSellerNo(int sellerNo);
void setFirstName(const std::string& firstName); void setFirstName(const std::string& firstName);
void setLastName(const std::string& lastName); void setLastName(const std::string& lastName);
void setNumberOfOfferedArticles(int number); void setNumberOfOfferedArticles(int number);
void addArticle(Article article);
std::vector<Article*> getArticles(bool onlySold = false);
size_t getNumberOfOfferedArticles(); size_t getNumberOfOfferedArticles();
private: private:

View file

@ -4,6 +4,10 @@ add_executable(test_seller test_seller.cpp)
target_link_libraries(test_seller core Boost::unit_test_framework) target_link_libraries(test_seller core Boost::unit_test_framework)
add_test(NAME Seller COMMAND ${CMAKE_BINARY_DIR}/bin/test_seller) 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) add_executable(test_database test_database.cpp)
target_link_libraries(test_database core Boost::unit_test_framework stdc++fs) target_link_libraries(test_database core Boost::unit_test_framework stdc++fs)
add_test(NAME Database COMMAND ${CMAKE_BINARY_DIR}/bin/test_database) add_test(NAME Database COMMAND ${CMAKE_BINARY_DIR}/bin/test_database)

21
test/test_article.cpp Normal file
View file

@ -0,0 +1,21 @@
#define BOOST_TEST_MODULE article
#include "../src/core/article.h"
#include <boost/test/included/unit_test.hpp>
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<Sale>();
article.setSale(salePtr);
BOOST_TEST(article.isSold() == true);
}

View file

@ -8,18 +8,8 @@
BOOST_AUTO_TEST_CASE(create_database) 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);
}
} }

View file

@ -2,17 +2,37 @@
#include "../src/core/seller.h" #include "../src/core/seller.h"
#include <array>
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
//using namespace boost::unit_test; BOOST_AUTO_TEST_CASE(create_uuid_nil)
{
BOOST_AUTO_TEST_CASE( create_uuid_nil ) {
Seller seller{}; Seller seller{};
BOOST_TEST(seller.getUuid().is_nil() == true); BOOST_TEST(seller.getUuid().is_nil() == true);
} }
BOOST_AUTO_TEST_CASE(create_uuid) { BOOST_AUTO_TEST_CASE(create_uuid)
{
Seller seller{}; Seller seller{};
seller.createUuid(); seller.createUuid();
BOOST_TEST(seller.getUuid().is_nil() == false); BOOST_TEST(seller.getUuid().is_nil() == false);
}
BOOST_AUTO_TEST_CASE(create_many)
{
constexpr unsigned int QUANTITY{10000};
std::array<Seller, QUANTITY> 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");
} }