tests test polymorphic Relation instances

Added the test case which tests returning
the std::unique_ptr<Relation<>> instead of the derived relation
eg. std::unique_ptr<HasMany> from the relationship factory methods.
This behavior is considered an additional feature and is possible
thanks to the polymorphism.
This commit is contained in:
silverqx
2023-05-19 11:19:00 +02:00
parent 110e152b76
commit d9815e9d5a
9 changed files with 589 additions and 0 deletions

View File

@@ -422,9 +422,11 @@ function(tiny_model_sources out_headers out_sources)
roleuser.hpp
setting.hpp
tag.hpp
tag_returnrelation.hpp
tagged.hpp
tagproperty.hpp
torrent.hpp
torrent_returnrelation.hpp
torrenteager.hpp
torrenteager_failed.hpp
torrenteager_withdefault.hpp

View File

@@ -5,6 +5,7 @@ add_subdirectory(model)
add_subdirectory(model_conn_indep)
add_subdirectory(model_qdatetime)
add_subdirectory(model_relations)
add_subdirectory(model_return_relation)
add_subdirectory(relations_conn_indep)
add_subdirectory(relations_insrt_updt)
add_subdirectory(softdeletes)

View File

@@ -0,0 +1,12 @@
project(model_return_relation
LANGUAGES CXX
)
add_executable(model_return_relation
tst_model_return_relation.cpp
)
add_test(NAME model_return_relation COMMAND model_return_relation)
include(TinyTestCommon)
tiny_configure_test(model_return_relation INCLUDE_MODELS)

View File

@@ -0,0 +1,5 @@
include($$TINYORM_SOURCE_TREE/tests/qmake/common.pri)
include($$TINYORM_SOURCE_TREE/tests/qmake/TinyUtils.pri)
include($$TINYORM_SOURCE_TREE/tests/models/models.pri)
SOURCES += tst_model_return_relation.cpp

View File

@@ -0,0 +1,431 @@
#include <QCoreApplication>
#include <QtTest>
#include "databases.hpp"
#include "models/torrent_returnrelation.hpp"
using Orm::Constants::CREATED_AT;
using Orm::Constants::UPDATED_AT;
using Orm::One;
using Orm::Tiny::ConnectionOverride;
using Orm::Tiny::Relations::Pivot;
using Orm::Tiny::Types::ModelsCollection;
using TypeUtils = Orm::Utils::Type;
using TestUtils::Databases;
using Models::Tag;
using Models::Tag_ReturnRelation;
using Models::Tagged;
using Models::Torrent_ReturnRelation;
using Models::TorrentPeer;
using Models::TorrentPreviewableFile;
using Models::User;
/* This test case is connection independent and it only runs against the MySQL database.
The following test methods are practically identical as in the tst_model_relations
test case with only one difference, that the relationship methods return
the std::unique_ptr<Relation<>> base Relation instance instead of
the derived relation, eg. std::unique_ptr<HasMany<>>.
This behavior is considered an additional feature and is possible thanks to
the polymorphism.
In the early stages of the TinyORM project this was the only way how the relationship
methods instantiated the actual Relation. Nevertheless, I refactored it to the current
state so that it can also return the Derived Relation instance eg. HasMany.
And even in later stages of development, I wanted to remove this polymorphic behavior,
precisely to make the Relation's addEagerConstraints(), initRelation(), match() only
templated methods, it was during the TinyBuilder::eagerLoadRelations() refactor
to also accept the ModelsCollection<Model *>, when it was ideal to made these
methods templated only, but I rejected it exactly because of this feature.
In the end, I have decided to keep these Relation's methods polymorphic so as
not to lose this feature. 🙃
And because of all of this, this test case exists, to test this behavior 😎. */
class tst_Model_Return_Relation : public QObject // clazy:exclude=ctor-missing-parent-argument
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase() const;
void getRelation_EagerLoad_HasOne() const;
void getRelation_EagerLoad_HasMany() const;
void getRelation_EagerLoad_BelongsTo() const;
void getRelation_EagerLoad_BelongsToMany_BasicPivot_WithPivotAttributes() const;
void getRelation_EagerLoad_BelongsToMany_CustomPivot_WithPivotAttributes() const;
void getRelationValue_LazyLoad_HasOne() const;
void getRelationValue_LazyLoad_HasMany() const;
void getRelationValue_LazyLoad_BelongsTo() const;
void getRelationValue_LazyLoad_BelongsToMany_BasicPivot_WithPivotAttributes() const;
void getRelationValue_LazyLoad_BelongsToMany_CustomPivot_WithPivotAttributes() const;
// NOLINTNEXTLINE(readability-redundant-access-specifiers)
private:
/*! Connection name used in this test case. */
QString m_connection {};
};
/* private slots */
// NOLINTBEGIN(readability-convert-member-functions-to-static)
void tst_Model_Return_Relation::initTestCase()
{
ConnectionOverride::connection = m_connection =
Databases::createConnection(Databases::MYSQL);
if (m_connection.isEmpty())
QSKIP(TestUtils::AutoTestSkipped
.arg(TypeUtils::classPureBasename(*this), Databases::MYSQL)
.toUtf8().constData(), );
}
void tst_Model_Return_Relation::cleanupTestCase() const
{
// Reset connection override
ConnectionOverride::connection.clear();
}
void tst_Model_Return_Relation::getRelation_EagerLoad_HasOne() const
{
auto torrent = Torrent_ReturnRelation::with("torrentPeer")->find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
QCOMPARE(torrent->getKey(), QVariant(2));
// TorrentPeer has-one relation
QVERIFY(torrent->relationLoaded("torrentPeer"));
auto *torrentPeer = torrent->getRelation<TorrentPeer, One>("torrentPeer");
QVERIFY(torrentPeer);
QVERIFY(torrentPeer->exists);
QCOMPARE(torrentPeer->getKey(), QVariant(2));
QCOMPARE(torrentPeer->getAttribute("torrent_id"), QVariant(2));
QCOMPARE(typeid (TorrentPeer *), typeid (torrentPeer));
}
void tst_Model_Return_Relation::getRelation_EagerLoad_HasMany() const
{
auto torrent = Torrent_ReturnRelation::with("torrentFiles")->find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
auto torrentId = torrent->getKey();
QCOMPARE(torrentId, QVariant(2));
// TorrentPreviewableFile has-many relation
QVERIFY(torrent->relationLoaded("torrentFiles"));
auto files = torrent->getRelationValue<TorrentPreviewableFile>("torrentFiles");
QCOMPARE(files.size(), 2);
QCOMPARE(typeid (ModelsCollection<TorrentPreviewableFile *>), typeid (files));
// Expected file IDs
QVector<QVariant> fileIds {2, 3};
for (auto *const file : files) {
QVERIFY(file);
QVERIFY(file->exists);
QCOMPARE(file->getAttribute("torrent_id"), torrentId);
QVERIFY(fileIds.contains(file->getKey()));
QCOMPARE(typeid (TorrentPreviewableFile *), typeid (file));
}
}
void tst_Model_Return_Relation::getRelation_EagerLoad_BelongsTo() const
{
auto torrent = Torrent_ReturnRelation::with("user")->find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
QCOMPARE(torrent->getKey(), QVariant(2));
QCOMPARE(torrent->getAttribute("user_id"), QVariant(1));
// User belongs-to relation
QVERIFY(torrent->relationLoaded("user"));
auto *user = torrent->getRelationValue<User, One>("user");
QVERIFY(user);
QVERIFY(user->exists);
QCOMPARE(user->getKey(), QVariant(1));
QCOMPARE(typeid (User *), typeid (user));
}
void tst_Model_Return_Relation::
getRelation_EagerLoad_BelongsToMany_BasicPivot_WithPivotAttributes() const
{
auto torrent = Torrent_ReturnRelation::with("tags")->find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
const auto torrentId = torrent->getKey();
QCOMPARE(torrentId, QVariant(2));
// Tag has-many relation (basic pivot)
QVERIFY(torrent->relationLoaded("tags"));
auto tags = torrent->getRelationValue<Tag_ReturnRelation>("tags");
QCOMPARE(tags.size(), 4);
QCOMPARE(typeid (ModelsCollection<Tag_ReturnRelation *>), typeid (tags));
// Expected tag IDs and pivot attribute 'active', tagId to active
std::unordered_map<quint64, int> activeMap {{1, 1}, {2, 1}, {3, 0}, {4, 1}};
for (auto *const tag : tags) {
QVERIFY(tag);
QVERIFY(tag->exists);
const auto tagId = tag->getKey().template value<quint64>();
QVERIFY(activeMap.contains(tagId));
QCOMPARE(typeid (Tag_ReturnRelation *), typeid (tag));
/* Custom Pivot relation as the Tagged class, under the 'tagged' key
in the m_relations hash. */
QVERIFY(tag->relationLoaded("pivot"));
auto *pivot = tag->getRelation<Pivot, One>("pivot");
QVERIFY(pivot);
QVERIFY(pivot->exists);
QCOMPARE(typeid (Pivot *), typeid (pivot));
QVERIFY(pivot->usesTimestamps());
QVERIFY(!pivot->getIncrementing());
QCOMPARE(pivot->getForeignKey(), QString("torrent_id"));
QCOMPARE(pivot->getRelatedKey(), QString("tag_id"));
const auto &attributesHash = pivot->getAttributesHash();
QCOMPARE(attributesHash.size(), static_cast<std::size_t>(5));
QCOMPARE(pivot->getAttribute("torrent_id"), torrentId);
// With pivot attributes, active
QCOMPARE(pivot->getAttribute("active").template value<int>(),
activeMap.at(tagId));
QVERIFY(attributesHash.contains(CREATED_AT));
QVERIFY(attributesHash.contains(UPDATED_AT));
}
}
void tst_Model_Return_Relation::
getRelation_EagerLoad_BelongsToMany_CustomPivot_WithPivotAttributes() const
{
auto torrent = Torrent_ReturnRelation::with("tagsCustom")->find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
const auto torrentId = torrent->getKey();
QCOMPARE(torrentId, QVariant(2));
// Tag has-many relation (custom Tagged pivot)
QVERIFY(torrent->relationLoaded("tagsCustom"));
auto tags = torrent->getRelationValue<Tag>("tagsCustom");
QCOMPARE(tags.size(), 4);
QCOMPARE(typeid (ModelsCollection<Tag *>), typeid (tags));
// Expected tag IDs and pivot attribute 'active', tagId to active
std::unordered_map<quint64, int> activeMap {{1, 1}, {2, 1}, {3, 0}, {4, 1}};
for (auto *const tag : tags) {
QVERIFY(tag);
QVERIFY(tag->exists);
const auto tagId = tag->getKey().template value<quint64>();
QVERIFY(activeMap.contains(tagId));
QCOMPARE(typeid (Tag *), typeid (tag));
/* Custom Pivot relation as the Tagged class, under the 'tagged' key
in the m_relations hash. */
QVERIFY(tag->relationLoaded("tagged"));
auto *tagged = tag->getRelation<Tagged, One>("tagged");
QVERIFY(tagged);
QVERIFY(tagged->exists);
QCOMPARE(typeid (Tagged *), typeid (tagged));
QVERIFY(tagged->usesTimestamps());
QVERIFY(!tagged->getIncrementing());
QCOMPARE(tagged->getForeignKey(), QString("torrent_id"));
QCOMPARE(tagged->getRelatedKey(), QString("tag_id"));
const auto &attributesHash = tagged->getAttributesHash();
QCOMPARE(attributesHash.size(), static_cast<std::size_t>(5));
QCOMPARE(tagged->getAttribute("torrent_id"), torrentId);
// With pivot attributes, active
QCOMPARE(tagged->getAttribute("active").template value<int>(),
activeMap.at(tagId));
QVERIFY(attributesHash.contains(CREATED_AT));
QVERIFY(attributesHash.contains(UPDATED_AT));
}
}
void tst_Model_Return_Relation::getRelationValue_LazyLoad_HasOne() const
{
auto torrent = Torrent_ReturnRelation::find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
QCOMPARE(torrent->getKey(), QVariant(2));
// TorrentPeer has-one relation
QVERIFY(!torrent->relationLoaded("torrentPeer"));
auto *torrentPeer = torrent->getRelationValue<TorrentPeer, One>("torrentPeer");
QVERIFY(torrent->relationLoaded("torrentPeer"));
QVERIFY(torrentPeer);
QVERIFY(torrentPeer->exists);
QCOMPARE(torrentPeer->getKey(), QVariant(2));
QCOMPARE(torrentPeer->getAttribute("torrent_id"), QVariant(2));
QCOMPARE(typeid (TorrentPeer *), typeid (torrentPeer));
}
void tst_Model_Return_Relation::getRelationValue_LazyLoad_HasMany() const
{
auto torrent = Torrent_ReturnRelation::find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
auto torrentId = torrent->getKey();
QCOMPARE(torrentId, QVariant(2));
// TorrentPreviewableFile has-many relation
QVERIFY(!torrent->relationLoaded("torrentFiles"));
auto files = torrent->getRelationValue<TorrentPreviewableFile>("torrentFiles");
QVERIFY(torrent->relationLoaded("torrentFiles"));
QCOMPARE(files.size(), 2);
QCOMPARE(typeid (ModelsCollection<TorrentPreviewableFile *>), typeid (files));
// Expected file IDs
QVector<QVariant> fileIds {2, 3};
for (auto *const file : files) {
QVERIFY(file);
QVERIFY(file->exists);
QCOMPARE(file->getAttribute("torrent_id"), torrentId);
QVERIFY(fileIds.contains(file->getKey()));
QCOMPARE(typeid (TorrentPreviewableFile *), typeid (file));
}
}
void tst_Model_Return_Relation::getRelationValue_LazyLoad_BelongsTo() const
{
auto torrent = Torrent_ReturnRelation::find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
QCOMPARE(torrent->getKey(), QVariant(2));
QCOMPARE(torrent->getAttribute("user_id"), QVariant(1));
// User belongs-to relation
QVERIFY(!torrent->relationLoaded("user"));
auto *user = torrent->getRelationValue<User, One>("user");
QVERIFY(torrent->relationLoaded("user"));
QVERIFY(user);
QVERIFY(user->exists);
QCOMPARE(user->getKey(), QVariant(1));
QCOMPARE(typeid (User *), typeid (user));
}
void tst_Model_Return_Relation::
getRelationValue_LazyLoad_BelongsToMany_BasicPivot_WithPivotAttributes() const
{
auto torrent = Torrent_ReturnRelation::find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
const auto torrentId = torrent->getKey();
QCOMPARE(torrentId, QVariant(2));
// Tag has-many relation (basic pivot)
QVERIFY(!torrent->relationLoaded("tags"));
auto tags = torrent->getRelationValue<Tag_ReturnRelation>("tags");
QVERIFY(torrent->relationLoaded("tags"));
QCOMPARE(tags.size(), 4);
QCOMPARE(typeid (ModelsCollection<Tag_ReturnRelation *>), typeid (tags));
// Expected tag IDs and pivot attribute 'active', tagId to active
std::unordered_map<quint64, int> activeMap {{1, 1}, {2, 1}, {3, 0}, {4, 1}};
for (auto *const tag : tags) {
QVERIFY(tag);
QVERIFY(tag->exists);
const auto tagId = tag->getKey().template value<quint64>();
QVERIFY(activeMap.contains(tagId));
QCOMPARE(typeid (Tag_ReturnRelation *), typeid (tag));
/* Custom Pivot relation as the Tagged class, under the 'tagged' key
in the m_relations hash. */
QVERIFY(tag->relationLoaded("pivot"));
auto *pivot = tag->getRelation<Pivot, One>("pivot");
QVERIFY(pivot);
QVERIFY(pivot->exists);
QCOMPARE(typeid (Pivot *), typeid (pivot));
QVERIFY(pivot->usesTimestamps());
QVERIFY(!pivot->getIncrementing());
QCOMPARE(pivot->getForeignKey(), QString("torrent_id"));
QCOMPARE(pivot->getRelatedKey(), QString("tag_id"));
const auto &attributesHash = pivot->getAttributesHash();
QCOMPARE(attributesHash.size(), static_cast<std::size_t>(5));
QCOMPARE(pivot->getAttribute("torrent_id"), torrentId);
// With pivot attributes, active
QCOMPARE(pivot->getAttribute("active").template value<int>(),
activeMap.at(tagId));
QVERIFY(attributesHash.contains(CREATED_AT));
QVERIFY(attributesHash.contains(UPDATED_AT));
}
}
void tst_Model_Return_Relation::
getRelationValue_LazyLoad_BelongsToMany_CustomPivot_WithPivotAttributes() const
{
auto torrent = Torrent_ReturnRelation::find(2);
QVERIFY(torrent);
QVERIFY(torrent->exists);
const auto torrentId = torrent->getKey();
QCOMPARE(torrentId, QVariant(2));
// Tag has-many relation (custom Tagged pivot)
QVERIFY(!torrent->relationLoaded("tagsCustom"));
auto tags = torrent->getRelationValue<Tag>("tagsCustom");
QVERIFY(torrent->relationLoaded("tagsCustom"));
QCOMPARE(tags.size(), 4);
QCOMPARE(typeid (ModelsCollection<Tag *>), typeid (tags));
// Expected tag IDs and pivot attribute 'active', tagId to active
std::unordered_map<quint64, int> activeMap {{1, 1}, {2, 1}, {3, 0}, {4, 1}};
for (auto *const tag : tags) {
QVERIFY(tag);
QVERIFY(tag->exists);
const auto tagId = tag->getKey().template value<quint64>();
QVERIFY(activeMap.contains(tagId));
QCOMPARE(typeid (Tag *), typeid (tag));
/* Custom Pivot relation as the Tagged class, under the 'tagged' key
in the m_relations hash. */
QVERIFY(tag->relationLoaded("tagged"));
auto *tagged = tag->getRelation<Tagged, One>("tagged");
QVERIFY(tagged);
QVERIFY(tagged->exists);
QCOMPARE(typeid (Tagged *), typeid (tagged));
QVERIFY(tagged->usesTimestamps());
QVERIFY(!tagged->getIncrementing());
QCOMPARE(tagged->getForeignKey(), QString("torrent_id"));
QCOMPARE(tagged->getRelatedKey(), QString("tag_id"));
const auto &attributesHash = tagged->getAttributesHash();
QCOMPARE(attributesHash.size(), static_cast<std::size_t>(5));
QCOMPARE(tagged->getAttribute("torrent_id"), torrentId);
// With pivot attributes, active
QCOMPARE(tagged->getAttribute("active").template value<int>(),
activeMap.at(tagId));
QVERIFY(attributesHash.contains(CREATED_AT));
QVERIFY(attributesHash.contains(UPDATED_AT));
}
}
// NOLINTEND(readability-convert-member-functions-to-static)
QTEST_MAIN(tst_Model_Return_Relation)
#include "tst_model_return_relation.moc"

View File

@@ -8,6 +8,7 @@ SUBDIRS = \
model_conn_indep \
model_qdatetime \
model_relations \
model_return_relation \
relations_conn_indep \
relations_insrt_updt \
softdeletes \

View File

@@ -11,9 +11,11 @@ HEADERS += \
$$PWD/models/roleuser.hpp \
$$PWD/models/setting.hpp \
$$PWD/models/tag.hpp \
$$PWD/models/tag_returnrelation.hpp \
$$PWD/models/tagged.hpp \
$$PWD/models/tagproperty.hpp \
$$PWD/models/torrent.hpp \
$$PWD/models/torrent_returnrelation.hpp \
$$PWD/models/torrenteager.hpp \
$$PWD/models/torrenteager_failed.hpp \
$$PWD/models/torrenteager_withdefault.hpp \

View File

@@ -0,0 +1,30 @@
#pragma once
#ifndef MODELS_TAG_RETURNRELATION_HPP
#define MODELS_TAG_RETURNRELATION_HPP
#include "orm/tiny/relations/pivot.hpp"
#include "models/torrent_returnrelation.hpp"
namespace Models
{
using Orm::Tiny::Model;
using Orm::Tiny::Relations::Pivot;
class Torrent_ReturnRelation;
// NOLINTNEXTLINE(misc-no-recursion, bugprone-exception-escape)
class Tag_ReturnRelation final : public Model<Tag_ReturnRelation, Torrent_ReturnRelation,
Pivot>
{
friend Model;
using Model::Model;
/*! The table associated with the model. */
QString u_table {"torrent_tags"};
};
} // namespace Models
#endif // MODELS_TAG_RETURNRELATION_HPP

View File

@@ -0,0 +1,105 @@
#pragma once
#ifndef MODELS_TORRENT_RETURNRELATION_HPP
#define MODELS_TORRENT_RETURNRELATION_HPP
#include "orm/tiny/relations/pivot.hpp"
#include "models/tag.hpp"
#include "models/tag_returnrelation.hpp"
#include "models/tagged.hpp"
#include "models/torrentpeer.hpp"
#include "models/torrentpreviewablefile.hpp"
#include "models/user.hpp"
namespace Models
{
using Orm::Tiny::Model;
using Orm::Tiny::Relations::Pivot;
using Orm::Tiny::Relations::Relation;
class Tag;
class Tag_ReturnRelation;
class TorrentPeer;
class TorrentPreviewableFile;
class User;
// NOLINTNEXTLINE(misc-no-recursion, bugprone-exception-escape)
class Torrent_ReturnRelation final :
public Model<Torrent_ReturnRelation, TorrentPreviewableFile, TorrentPeer, Tag,
Tag_ReturnRelation, User, Pivot>
{
friend Model;
using Model::Model;
public:
/*! Get previewable files associated with the torrent. */
std::unique_ptr<Relation<Torrent_ReturnRelation, TorrentPreviewableFile>>
torrentFiles()
{
return hasMany<TorrentPreviewableFile>("torrent_id");
}
/*! Get a torrent peer associated with the torrent. */
std::unique_ptr<Relation<Torrent_ReturnRelation, TorrentPeer>>
torrentPeer()
{
return hasOne<TorrentPeer>("torrent_id");
}
/*! Get tags that belong to the torrent. */
std::unique_ptr<Relation<Torrent_ReturnRelation, Tag_ReturnRelation>>
tags()
{
// Basic pivot model
// Ownership of a unique_ptr()
auto relation = belongsToMany<Tag_ReturnRelation>("tag_torrent", "torrent_id",
"tag_id", {}, {}, "tags");
relation->withPivot("active")
.withTimestamps();
return relation;
}
/*! Get tags that belong to the torrent. */
std::unique_ptr<Relation<Torrent_ReturnRelation, Tag>>
tagsCustom()
{
// Custom 'Tagged' pivot model ✨
// Ownership of a unique_ptr()
auto relation = belongsToMany<Tag, Tagged>("tag_torrent", "torrent_id", {},
{}, {}, "tags");
relation->as("tagged")
.withPivot("active")
.withTimestamps();
return relation;
}
/*! Get a user that owns the torrent. */
std::unique_ptr<Relation<Torrent_ReturnRelation, User>>
user()
{
return belongsTo<User>({}, {}, QString::fromUtf8(__func__));
}
private:
/*! The table associated with the model. */
QString u_table {"torrents"};
/*! Map of relation names to methods. */
QHash<QString, RelationVisitor> u_relations {
{"torrentFiles", [](auto &v) { v(&Torrent_ReturnRelation::torrentFiles); }},
{"torrentPeer", [](auto &v) { v(&Torrent_ReturnRelation::torrentPeer); }},
{"tags", [](auto &v) { v(&Torrent_ReturnRelation::tags); }},
{"tagsCustom", [](auto &v) { v(&Torrent_ReturnRelation::tagsCustom); }},
{"user", [](auto &v) { v(&Torrent_ReturnRelation::user); }},
};
/*! The attributes that should be mutated to dates. */
inline static const QStringList u_dates {"added_on"};
};
} // namespace Models
#endif // MODELS_TORRENT_RETURNRELATION_HPP