Compare commits
13 commits
eeae4fe609
...
f42855dc50
Author | SHA1 | Date | |
---|---|---|---|
f42855dc50 | |||
c1c7e32291 | |||
94edacc6e6 | |||
37ae599c80 | |||
8b08fad47a | |||
4d8396fac1 | |||
59e7634712 | |||
1778f48bc6 | |||
eeba4b11e0 | |||
b422664f81 | |||
779f085cca | |||
0f3e7ba799 | |||
8d58da4618 |
12 changed files with 221 additions and 65 deletions
|
@ -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)
|
||||||
|
|
|
@ -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; }
|
|
@ -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
|
|
@ -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"); }
|
|
@ -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
8
src/core/sale.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef SALE_H
|
||||||
|
#define SALE_H
|
||||||
|
|
||||||
|
class Sale : public Entity {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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:
|
||||||
|
|
|
@ -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
21
test/test_article.cpp
Normal 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);
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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");
|
||||||
}
|
}
|
Loading…
Reference in a new issue