nextcloud-desktop/test/testactivitylistmodel.cpp
Claudio Cambra ce5494b4ec Add testing for ActivityListModel
Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
2022-02-01 13:57:08 +00:00

414 lines
15 KiB
C++

/*
* Copyright (C) by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "gui/tray/activitylistmodel.h"
#include "account.h"
#include "accountstate.h"
#include "accountmanager.h"
#include "syncenginetestutils.h"
#include "syncresult.h"
#include <QAbstractItemModelTester>
#include <QDesktopServices>
#include <QSignalSpy>
#include <QTest>
constexpr auto startingId = 90000;
static QByteArray fake404Response = R"(
{"ocs":{"meta":{"status":"failure","statuscode":404,"message":"Invalid query, please check the syntax. API specifications are here: http:\/\/www.freedesktop.org\/wiki\/Specifications\/open-collaboration-services.\n"},"data":[]}}
)";
static QByteArray fake400Response = R"(
{"ocs":{"meta":{"status":"failure","statuscode":400,"message":"Parameter is incorrect.\n"},"data":[]}}
)";
static QByteArray fake500Response = R"(
{"ocs":{"meta":{"status":"failure","statuscode":500,"message":"Internal Server Error.\n"},"data":[]}}
)";
class TestingALM : public OCC::ActivityListModel
{
Q_OBJECT
public:
TestingALM() = default;
void startFetchJob() override
{
auto *job = new OCC::JsonApiJob(accountState()->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this);
QObject::connect(job, &OCC::JsonApiJob::jsonReceived,
this, &TestingALM::activitiesReceived);
QUrlQuery params;
params.addQueryItem(QLatin1String("since"), QString::number(startingId));
params.addQueryItem(QLatin1String("limit"), QString::number(50));
job->addQueryParams(params);
job->start();
};
};
class FakeRemoteActivityStorage
{
FakeRemoteActivityStorage() = default;
public:
static FakeRemoteActivityStorage *instance()
{
if (!_instance) {
_instance = new FakeRemoteActivityStorage();
_instance->init();
}
return _instance;
}
static void destroy()
{
if (_instance) {
delete _instance;
}
_instance = nullptr;
}
void init()
{
if (!_activityData.isEmpty()) {
return;
}
_metaSuccess = {{QStringLiteral("status"), QStringLiteral("ok")}, {QStringLiteral("statuscode"), 200},
{QStringLiteral("message"), QStringLiteral("OK")}};
initActivityData();
}
void initActivityData()
{
// Insert activity data
for (quint32 i = 0; i <= _numItemsToInsert; i++) {
_startingId++;
QJsonObject activity;
activity.insert(QStringLiteral("object_type"), "files");
activity.insert(QStringLiteral("activity_id"), _startingId);
activity.insert(QStringLiteral("type"), QStringLiteral("file"));
activity.insert(QStringLiteral("subject"), QStringLiteral("You created %1.txt").arg(i));
activity.insert(QStringLiteral("message"), QStringLiteral(""));
activity.insert(QStringLiteral("object_name"), QStringLiteral("%1.txt").arg(i));
activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/apps/files/img/add-color.svg"));
_activityData.push_back(activity);
}
// Insert notification data
for (quint32 i = 0; i < _numItemsToInsert; i++) {
_startingId++;
QJsonObject activity;
activity.insert(QStringLiteral("activity_id"), _startingId);
activity.insert(QStringLiteral("object_type"), "calendar");
activity.insert(QStringLiteral("type"), QStringLiteral("calendar-event"));
activity.insert(QStringLiteral("subject"), QStringLiteral("You created event %1 in calendar Events").arg(i));
activity.insert(QStringLiteral("message"), QStringLiteral(""));
activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/calendar.svg"));
_activityData.push_back(activity);
}
}
const QByteArray activityJsonData(int sinceId, int limit)
{
QJsonArray data;
for(int dataIndex = _activityData.size() - 1, iteration = 0;
dataIndex > 0 && iteration < limit;
--dataIndex, ++iteration) {
if(_activityData[dataIndex].toObject().value(QStringLiteral("activity_id")).toInt() > sinceId) {
data.append(_activityData[dataIndex]);
}
}
QJsonObject root;
QJsonObject ocs;
ocs.insert(QStringLiteral("data"), data);
root.insert(QStringLiteral("ocs"), ocs);
return QJsonDocument(root).toJson();
}
private:
static FakeRemoteActivityStorage *_instance;
QJsonArray _activityData;
QVariantMap _metaSuccess;
quint32 _numItemsToInsert = 30;
int _startingId = startingId;
};
FakeRemoteActivityStorage *FakeRemoteActivityStorage::_instance = nullptr;
class TestActivityListModel : public QObject
{
Q_OBJECT
public:
TestActivityListModel() = default;
~TestActivityListModel() override {
OCC::AccountManager::instance()->deleteAccount(accountState.data());
}
QScopedPointer<FakeQNAM> fakeQnam;
OCC::AccountPtr account;
QScopedPointer<OCC::AccountState> accountState;
OCC::Activity testNotificationActivity;
static constexpr int searchResultsReplyDelay = 100;
private slots:
void initTestCase()
{
fakeQnam.reset(new FakeQNAM({}));
account = OCC::Account::create();
account->setCredentials(new FakeCredentials{fakeQnam.data()});
account->setUrl(QUrl(("http://example.de")));
accountState.reset(new OCC::AccountState(account));
fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
Q_UNUSED(device);
QNetworkReply *reply = nullptr;
const auto urlQuery = QUrlQuery(req.url());
const auto format = urlQuery.queryItemValue(QStringLiteral("format"));
const auto since = urlQuery.queryItemValue(QStringLiteral("since")).toInt();
const auto limit = urlQuery.queryItemValue(QStringLiteral("limit")).toInt();
const auto path = req.url().path();
if (!req.url().toString().startsWith(accountState->account()->url().toString())) {
reply = new FakeErrorReply(op, req, this, 404, fake404Response);
}
if (format != QStringLiteral("json")) {
reply = new FakeErrorReply(op, req, this, 400, fake400Response);
}
if (path.startsWith(QStringLiteral("/ocs/v2.php/apps/activity/api/v2/activity"))) {
reply = new FakePayloadReply(op, req, FakeRemoteActivityStorage::instance()->activityJsonData(since, limit), searchResultsReplyDelay, fakeQnam.data());
}
if (!reply) {
return qobject_cast<QNetworkReply*>(new FakeErrorReply(op, req, this, 404, QByteArrayLiteral("{error: \"Not found!\"}")));
}
return reply;
});
OCC::AccountManager::instance()->addAccount(account);
// Activity comparison is done by checking type, id, and accName
// We need an activity with these details, at least
testNotificationActivity._accName = accountState->account()->displayName();
testNotificationActivity._id = 1;
testNotificationActivity._type = OCC::Activity::NotificationType;
testNotificationActivity._dateTime = QDateTime::currentDateTime();
};
// Test receiving activity from server
void testFetchingRemoteActivity() {
TestingALM model;
model.setAccountState(accountState.data());
QAbstractItemModelTester modelTester(&model);
QCOMPARE(model.rowCount(), 0);
model.startFetchJob();
QSignalSpy activitiesJob(&model, &TestingALM::activityJobStatusCode);
QVERIFY(activitiesJob.wait(3000));
QCOMPARE(model.rowCount(), 50);
};
// Test receiving activity from local user action
void testLocalSyncFileAction() {
TestingALM model;
model.setAccountState(accountState.data());
QAbstractItemModelTester modelTester(&model);
QCOMPARE(model.rowCount(), 0);
OCC::Activity activity;
model.addSyncFileItemToActivityList(activity);
QCOMPARE(model.rowCount(), 1);
const auto index = model.index(0, 0);
QVERIFY(index.isValid());
};
void testAddNotification() {
TestingALM model;
model.setAccountState(accountState.data());
QAbstractItemModelTester modelTester(&model);
QCOMPARE(model.rowCount(), 0);
model.addNotificationToActivityList(testNotificationActivity);
QCOMPARE(model.rowCount(), 1);
const auto index = model.index(0, 0);
QVERIFY(index.isValid());
};
void testAddError() {
TestingALM model;
model.setAccountState(accountState.data());
QAbstractItemModelTester modelTester(&model);
QCOMPARE(model.rowCount(), 0);
OCC::Activity activity;
model.addErrorToActivityList(activity);
QCOMPARE(model.rowCount(), 1);
const auto index = model.index(0, 0);
QVERIFY(index.isValid());
};
void testAddIgnoredFile() {
TestingALM model;
model.setAccountState(accountState.data());
QAbstractItemModelTester modelTester(&model);
QCOMPARE(model.rowCount(), 0);
OCC::Activity activity;
activity._folder = QStringLiteral("thingy");
activity._file = QStringLiteral("test.txt");
model.addIgnoredFileToList(activity);
// We need to add another activity to the model for the combineActivityLists method to be called
model.addNotificationToActivityList(testNotificationActivity);
QCOMPARE(model.rowCount(), 2);
const auto index = model.index(0, 0);
QVERIFY(index.isValid());
};
// Test removing activity from list
void testRemoveActivityWithRow() {
TestingALM model;
model.setAccountState(accountState.data());
QAbstractItemModelTester modelTester(&model);
QCOMPARE(model.rowCount(), 0);
model.addNotificationToActivityList(testNotificationActivity);
QCOMPARE(model.rowCount(), 1);
model.removeActivityFromActivityList(0);
QCOMPARE(model.rowCount(), 0);
}
void testRemoveActivityWithActivity() {
TestingALM model;
model.setAccountState(accountState.data());
QAbstractItemModelTester modelTester(&model);
QCOMPARE(model.rowCount(), 0);
model.addNotificationToActivityList(testNotificationActivity);
QCOMPARE(model.rowCount(), 1);
model.removeActivityFromActivityList(testNotificationActivity);
QCOMPARE(model.rowCount(), 0);
}
// Test getting the data from the model
void testData() {
TestingALM model;
model.setAccountState(accountState.data());
QAbstractItemModelTester modelTester(&model);
QCOMPARE(model.rowCount(), 0);
model.startFetchJob();
QSignalSpy activitiesJob(&model, &TestingALM::activityJobStatusCode);
QVERIFY(activitiesJob.wait(3000));
QCOMPARE(model.rowCount(), 50);
model.addNotificationToActivityList(testNotificationActivity);
QCOMPARE(model.rowCount(), 51);
OCC::Activity syncResultActivity;
syncResultActivity._id = 2;
syncResultActivity._type = OCC::Activity::SyncResultType;
syncResultActivity._status = OCC::SyncResult::Error;
syncResultActivity._dateTime = QDateTime::currentDateTime();
syncResultActivity._subject = QStringLiteral("Sample failed sync text");
syncResultActivity._message = QStringLiteral("/path/to/thingy");
syncResultActivity._link = QStringLiteral("/path/to/thingy");
syncResultActivity._accName = accountState->account()->displayName();
model.addSyncFileItemToActivityList(syncResultActivity);
QCOMPARE(model.rowCount(), 52);
OCC::Activity syncFileItemActivity;
syncFileItemActivity._id = 3;
syncFileItemActivity._type = OCC::Activity::SyncFileItemType; //client activity
syncFileItemActivity._status = OCC::SyncFileItem::Success;
syncFileItemActivity._dateTime = QDateTime::currentDateTime();
syncFileItemActivity._message = QStringLiteral("You created xyz.pdf");
syncFileItemActivity._link = accountState->account()->url();
syncFileItemActivity._accName = accountState->account()->displayName();
syncFileItemActivity._file = QStringLiteral("xyz.pdf");
syncFileItemActivity._fileAction = "";
model.addSyncFileItemToActivityList(syncFileItemActivity);
QCOMPARE(model.rowCount(), 53);
// Test all rows for things in common
for (int i = 0; i < model.rowCount(); i++) {
const auto index = model.index(i, 0);
QVERIFY(index.data(OCC::ActivityListModel::ObjectTypeRole).canConvert<int>());
const auto type = index.data(OCC::ActivityListModel::ObjectTypeRole).toInt();
QVERIFY(type >= OCC::Activity::ActivityType);
QVERIFY(!index.data(OCC::ActivityListModel::ObjectTypeRole).toInt());
QVERIFY(!index.data(OCC::ActivityListModel::AccountRole).toString().isEmpty());
QVERIFY(!index.data(OCC::ActivityListModel::ActionTextColorRole).toString().isEmpty());
QVERIFY(!index.data(OCC::ActivityListModel::ActionIconRole).toString().isEmpty());
QVERIFY(!index.data(OCC::ActivityListModel::PointInTimeRole).toString().isEmpty());
QVERIFY(index.data(OCC::ActivityListModel::ActionsLinksRole).canConvert<QList<QVariant>>());
QVERIFY(index.data(OCC::ActivityListModel::ActionTextRole).canConvert<QString>());
QVERIFY(index.data(OCC::ActivityListModel::MessageRole).canConvert<QString>());
QVERIFY(index.data(OCC::ActivityListModel::LinkRole).canConvert<QUrl>());
QVERIFY(index.data(OCC::ActivityListModel::AccountConnectedRole).canConvert<bool>());
QVERIFY(index.data(OCC::ActivityListModel::DisplayActions).canConvert<bool>());
// Unfortunately, trying to check anything relating to filepaths causes a crash
// when the folder manager is invoked by the model to look for the relevant file
}
};
};
QTEST_MAIN(TestActivityListModel)
#include "testactivitylistmodel.moc"