Skip to content

Commit

Permalink
tests added Model::replicate tests
Browse files Browse the repository at this point in the history
 - also extracted common code to
   AttributeUtils::exceptAttributesForReplicate
  • Loading branch information
silverqx committed Aug 21, 2022
1 parent 4fd32ed commit 39e1c7f
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 30 deletions.
30 changes: 3 additions & 27 deletions include/orm/tiny/model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -944,33 +944,9 @@ namespace Orm::Tiny
Derived Model<Derived, AllRelations...>::replicate(
const std::unordered_set<QString> &except)
{
std::unordered_set<QString> defaults {
getKeyName(),
this->getCreatedAtColumn(),
this->getUpdatedAtColumn(),
};
// Remove empty attribute names
std::erase_if(defaults, [](const auto &attribute)
{
return attribute.isEmpty();
});

// Merge defaults into except
std::unordered_set<QString> exceptMerged(defaults.size() + except.size());
if (!except.empty()) {
exceptMerged = except;
exceptMerged.merge(defaults);
}
else
exceptMerged = std::move(defaults);

// Get all attributes excluding those in the exceptMerged set
auto attributes = this->getAttributes()
| ranges::views::filter([&exceptMerged](const AttributeItem &attribute)
{
return !exceptMerged.contains(attribute.key);
})
| ranges::to<QVector<AttributeItem>>();
/* Get all attributes excluding the primary key, created_at, and updated_at
attributes and those in the except set. */
auto attributes = AttributeUtils::exceptAttributesForReplicate(*this, except);

/* Create a new instance (with correctly set a table and connection names),
set obtained attributes and relations. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ TINY_SYSTEM_HEADER
#include <QtSql/QSqlRecord>

#include <range/v3/algorithm/sort.hpp>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/set_algorithm.hpp>

#include "orm/exceptions/domainerror.hpp"
Expand Down
48 changes: 48 additions & 0 deletions include/orm/tiny/utils/attribute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
#include "orm/macros/systemheader.hpp"
TINY_SYSTEM_HEADER

#include <unordered_set>

#include <range/v3/range/conversion.hpp>
#include <range/v3/view/filter.hpp>

#include "orm/tiny/tinytypes.hpp"

TINYORM_BEGIN_COMMON_NAMESPACE
Expand Down Expand Up @@ -49,8 +54,51 @@ namespace Orm::Tiny::Utils
joinAttributesForFirstOr(const QVector<WhereItem> &attributes,
const QVector<AttributeItem> &values,
const QString &keyName);

/*! Remove a given attributes from the model attributes vector and return
a copy. */
template<typename Model>
static QVector<AttributeItem>
exceptAttributesForReplicate(const Model &model,
const std::unordered_set<QString> &except = {});
};

/* public */

template<typename Model>
QVector<AttributeItem>
Attribute::exceptAttributesForReplicate(const Model &model,
const std::unordered_set<QString> &except)
{
std::unordered_set<QString> defaults {
model.getKeyName(),
model.getCreatedAtColumn(),
model.getUpdatedAtColumn(),
};
// Remove empty attribute names
std::erase_if(defaults, [](const auto &attribute)
{
return attribute.isEmpty();
});

// Merge defaults into except
std::unordered_set<QString> exceptMerged(defaults.size() + except.size());
if (!except.empty()) {
exceptMerged = except;
exceptMerged.merge(defaults);
}
else
exceptMerged = std::move(defaults);

// Get all attributes excluding those in the exceptMerged set
return model.getAttributes()
| ranges::views::filter([&exceptMerged](const AttributeItem &attribute)
{
return !exceptMerged.contains(attribute.key);
})
| ranges::to<QVector<AttributeItem>>();
}

} // namespace Orm::Tiny::Utils

TINYORM_END_COMMON_NAMESPACE
Expand Down
2 changes: 0 additions & 2 deletions src/orm/tiny/utils/attribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
#include <unordered_set>

#include <range/v3/algorithm/contains.hpp>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/remove_if.hpp>
#include <range/v3/view/transform.hpp>

TINYORM_BEGIN_COMMON_NAMESPACE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ using Orm::Constants::SIZE;
using Orm::DB;
using Orm::Exceptions::MultipleRecordsFoundError;
using Orm::Exceptions::RecordsNotFoundError;
using Orm::One;

using Orm::Tiny::ConnectionOverride;
using Orm::Tiny::Exceptions::MassAssignmentError;

using AttributeUtils = Orm::Tiny::Utils::Attribute;
using TypeUtils = Orm::Utils::Type;

using TestUtils::Databases;
Expand All @@ -36,6 +38,8 @@ using Models::Torrent_AllowedMassAssignment;
using Models::Torrent_GuardedAttribute;
using Models::Torrent_TotallyGuarded;
using Models::TorrentEager;
using Models::TorrentPreviewableFileProperty;
using Models::User;

class tst_Model_Connection_Independent : public QObject // clazy:exclude=ctor-missing-parent-argument
{
Expand All @@ -56,6 +60,10 @@ private slots:
void equalComparison() const;
void notEqualComparison() const;

void replicate() const;
void replicate_WithCreate() const;
void replicate_WithRelations() const;

void defaultAttributeValues() const;

void massAssignment_Fillable() const;
Expand Down Expand Up @@ -359,6 +367,119 @@ void tst_Model_Connection_Independent::notEqualComparison() const
}
}

void tst_Model_Connection_Independent::replicate() const
{
auto user1 = User::find(1);
QVERIFY(user1);
QVERIFY(user1->exists);

auto user1Replicated = user1->replicate();

auto user1Attributes = AttributeUtils::exceptAttributesForReplicate(*user1);
auto user1ReplicatedAttributes = AttributeUtils::exceptAttributesForReplicate(
user1Replicated);

QVERIFY(!user1Replicated.exists);
QVERIFY(user1Attributes == user1ReplicatedAttributes);
QVERIFY(user1->getRelations().empty());
QVERIFY(user1Replicated.getRelations().empty());
}

void tst_Model_Connection_Independent::replicate_WithCreate() const
{
// Following is the most used case for the replicate method so I will test it
auto user = User::create({{"name", "xyz"},
{"is_banned", true},
{"note", "test"}});
QVERIFY(user.exists);

std::unordered_set except {NAME};

auto userReplicated = user.replicate(except);

auto userAttributes = AttributeUtils::exceptAttributesForReplicate(user, except);
auto userReplicatedAttributes = AttributeUtils::exceptAttributesForReplicate(
userReplicated);

QVERIFY(!userReplicated.exists);
QVERIFY(userAttributes == userReplicatedAttributes);

QCOMPARE(user.getAttribute(NAME), QVariant("xyz"));
QVERIFY(!userReplicated.getAttribute(NAME).isValid());

QVERIFY(user.getRelations().empty());
QVERIFY(userReplicated.getRelations().empty());

// Restore db
QVERIFY(user.remove());
}

void tst_Model_Connection_Independent::replicate_WithRelations() const
{
auto torrent2 = Torrent::with("torrentFiles.fileProperty")->find(2);
QVERIFY(torrent2);
QVERIFY(torrent2->exists);

auto torrent2Replicated = torrent2->replicate();

auto torrent2Attributes = AttributeUtils::exceptAttributesForReplicate(*torrent2);
auto torrent2ReplicatedAttributes = AttributeUtils::exceptAttributesForReplicate(
torrent2Replicated);

const auto &torrent2Relations = torrent2->getRelations();
const auto &torrent2ReplicatedRelations = torrent2Replicated.getRelations();

/* Crazy, but I'm going to check all relations 😮😎, yeah it's definitely crazy and
overmotivated, but it checks almost everything I wanted. 🙃🤙 */
// Torrent
QVERIFY(!torrent2Replicated.exists);
QVERIFY(torrent2Attributes == torrent2ReplicatedAttributes);
QVERIFY(torrent2Relations.size() == torrent2ReplicatedRelations.size());
QVERIFY(torrent2->relationLoaded("torrentFiles"));
QVERIFY(torrent2Replicated.relationLoaded("torrentFiles"));
// The Model::operator== was needed to make this real
QVERIFY(torrent2Relations == torrent2ReplicatedRelations);

// TorrentPreviewableFile
const auto torrentFiles2 =
torrent2->getRelationValue<TorrentPreviewableFile>("torrentFiles");
const auto torrentFiles2Replicated =
torrent2Replicated.getRelationValue<TorrentPreviewableFile>("torrentFiles");

QVERIFY(torrentFiles2.size() == torrentFiles2Replicated.size());

for (std::remove_cvref_t<decltype (torrentFiles2)>::size_type i = 0;
torrentFiles2.size() < i; ++i
) {
auto *torrentFile = torrentFiles2.value(i);
QVERIFY(torrentFile->exists);
QVERIFY(torrentFile->relationLoaded("fileProperty"));

auto *torrentFileReplicated = torrentFiles2Replicated.value(i);
QVERIFY(torrentFileReplicated->exists);
QVERIFY(torrentFileReplicated->relationLoaded("fileProperty"));

QVERIFY(torrentFile->getAttributes() == torrentFileReplicated->getAttributes());
// The Model::operator== was needed to make this real
QVERIFY(torrentFile->getRelations() == torrentFileReplicated->getRelations());

// TorrentPreviewableFileProperty
auto *fileProperty =
torrentFile->getRelationValue<TorrentPreviewableFileProperty, One>(
"fileProperty");
auto *filePropertyReplicated =
torrentFileReplicated->getRelationValue<TorrentPreviewableFileProperty,
One>("fileProperty");

QVERIFY(fileProperty->exists);
QVERIFY(fileProperty->getRelations().empty());
QVERIFY(filePropertyReplicated->exists);
QVERIFY(filePropertyReplicated->getRelations().empty());
QVERIFY(fileProperty->getAttributes() ==
filePropertyReplicated->getAttributes());
}
}

void tst_Model_Connection_Independent::defaultAttributeValues() const
{
{
Expand Down

0 comments on commit 39e1c7f

Please sign in to comment.