mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-23 13:35:58 +03:00
cd6ee68fec
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
783 lines
35 KiB
C++
783 lines
35 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>
|
|
|
|
namespace {
|
|
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 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++) {
|
|
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);
|
|
|
|
_startingId++;
|
|
}
|
|
|
|
// Insert notification data
|
|
for (quint32 i = 0; i < _numItemsToInsert; i++) {
|
|
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);
|
|
|
|
_startingId++;
|
|
}
|
|
|
|
// Insert notification data
|
|
for (quint32 i = 0; i < _numItemsToInsert; i++) {
|
|
QJsonObject activity;
|
|
activity.insert(QStringLiteral("activity_id"), _startingId);
|
|
activity.insert(QStringLiteral("object_type"), "chat");
|
|
activity.insert(QStringLiteral("type"), QStringLiteral("chat"));
|
|
activity.insert(QStringLiteral("subject"), QStringLiteral("You have received %1's message").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/talk.svg"));
|
|
|
|
QJsonArray actionsArray;
|
|
|
|
QJsonObject replyAction;
|
|
replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
|
|
replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
|
|
replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
|
|
replyAction.insert(QStringLiteral("primary"), true);
|
|
actionsArray.push_back(replyAction);
|
|
|
|
QJsonObject primaryAction;
|
|
primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
|
|
primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
|
|
primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
|
|
primaryAction.insert(QStringLiteral("primary"), false);
|
|
actionsArray.push_back(primaryAction);
|
|
|
|
QJsonObject additionalAction;
|
|
additionalAction.insert(QStringLiteral("label"), QStringLiteral("Additional 1"));
|
|
additionalAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
|
|
additionalAction.insert(QStringLiteral("type"), QStringLiteral("POST"));
|
|
additionalAction.insert(QStringLiteral("primary"), false);
|
|
actionsArray.push_back(additionalAction);
|
|
additionalAction.insert(QStringLiteral("label"), QStringLiteral("Additional 2"));
|
|
actionsArray.push_back(additionalAction);
|
|
|
|
activity.insert(QStringLiteral("actions"), actionsArray);
|
|
|
|
_activityData.push_back(activity);
|
|
|
|
_startingId++;
|
|
}
|
|
|
|
// Insert notification data
|
|
for (quint32 i = 0; i < _numItemsToInsert; i++) {
|
|
QJsonObject activity;
|
|
activity.insert(QStringLiteral("activity_id"), _startingId);
|
|
activity.insert(QStringLiteral("object_type"), "room");
|
|
activity.insert(QStringLiteral("type"), QStringLiteral("room"));
|
|
activity.insert(QStringLiteral("subject"), QStringLiteral("You have been invited into room%1").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/talk.svg"));
|
|
|
|
QJsonArray actionsArray;
|
|
|
|
QJsonObject replyAction;
|
|
replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
|
|
replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
|
|
replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
|
|
replyAction.insert(QStringLiteral("primary"), true);
|
|
actionsArray.push_back(replyAction);
|
|
|
|
QJsonObject primaryAction;
|
|
primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
|
|
primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
|
|
primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
|
|
primaryAction.insert(QStringLiteral("primary"), false);
|
|
actionsArray.push_back(primaryAction);
|
|
|
|
activity.insert(QStringLiteral("actions"), actionsArray);
|
|
|
|
_activityData.push_back(activity);
|
|
|
|
_startingId++;
|
|
}
|
|
|
|
// Insert notification data
|
|
for (quint32 i = 0; i < _numItemsToInsert; i++) {
|
|
QJsonObject activity;
|
|
activity.insert(QStringLiteral("activity_id"), _startingId);
|
|
activity.insert(QStringLiteral("object_type"), "call");
|
|
activity.insert(QStringLiteral("type"), QStringLiteral("call"));
|
|
activity.insert(QStringLiteral("subject"), QStringLiteral("You have missed a %1's call").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/talk.svg"));
|
|
|
|
QJsonArray actionsArray;
|
|
|
|
QJsonObject primaryAction;
|
|
primaryAction.insert(QStringLiteral("label"), QStringLiteral("Call back"));
|
|
primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
|
|
primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
|
|
primaryAction.insert(QStringLiteral("primary"), true);
|
|
actionsArray.push_back(primaryAction);
|
|
|
|
QJsonObject replyAction;
|
|
replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
|
|
replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
|
|
replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
|
|
replyAction.insert(QStringLiteral("primary"), false);
|
|
actionsArray.push_back(replyAction);
|
|
|
|
activity.insert(QStringLiteral("actions"), actionsArray);
|
|
|
|
_activityData.push_back(activity);
|
|
|
|
_startingId++;
|
|
}
|
|
|
|
// Insert notification data
|
|
for (quint32 i = 0; i < _numItemsToInsert; i++) {
|
|
QJsonObject activity;
|
|
activity.insert(QStringLiteral("activity_id"), _startingId);
|
|
activity.insert(QStringLiteral("object_type"), "2fa_id");
|
|
activity.insert(QStringLiteral("subject"), QStringLiteral("Login attempt from 127.0.0.1"));
|
|
activity.insert(QStringLiteral("message"), QStringLiteral("Please approve or deny the login attempt."));
|
|
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/password.svg"));
|
|
|
|
QJsonArray actionsArray;
|
|
|
|
QJsonObject primaryAction;
|
|
primaryAction.insert(QStringLiteral("label"), QStringLiteral("Approve"));
|
|
primaryAction.insert(QStringLiteral("link"), QStringLiteral("/ocs/v2.php/apps/twofactor_nextcloud_notification/api/v1/attempt/39"));
|
|
primaryAction.insert(QStringLiteral("type"), QStringLiteral("POST"));
|
|
primaryAction.insert(QStringLiteral("primary"), true);
|
|
actionsArray.push_back(primaryAction);
|
|
|
|
QJsonObject secondaryAction;
|
|
secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Cancel"));
|
|
secondaryAction.insert(QStringLiteral("link"),
|
|
QString(QStringLiteral("/ocs/v2.php/apps/twofactor_nextcloud_notification/api/v1/attempt/39")));
|
|
secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
|
|
secondaryAction.insert(QStringLiteral("primary"), false);
|
|
actionsArray.push_back(secondaryAction);
|
|
|
|
activity.insert(QStringLiteral("actions"), actionsArray);
|
|
|
|
_activityData.push_back(activity);
|
|
|
|
_startingId++;
|
|
}
|
|
|
|
// Insert notification data
|
|
for (quint32 i = 0; i < _numItemsToInsert; i++) {
|
|
QJsonObject activity;
|
|
activity.insert(QStringLiteral("activity_id"), _startingId);
|
|
activity.insert(QStringLiteral("object_type"), "create");
|
|
activity.insert(QStringLiteral("subject"), QStringLiteral("Generate backup codes"));
|
|
activity.insert(QStringLiteral("message"), QStringLiteral("You enabled two-factor authentication but did not generate backup codes yet. They are needed to restore access to your account in case you lose your second factor."));
|
|
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/password.svg"));
|
|
|
|
QJsonArray actionsArray;
|
|
|
|
QJsonObject secondaryAction;
|
|
secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Dismiss"));
|
|
secondaryAction.insert(QStringLiteral("link"),
|
|
QString(QStringLiteral("ocs/v2.php/apps/notifications/api/v2/notifications/19867")));
|
|
secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
|
|
secondaryAction.insert(QStringLiteral("primary"), false);
|
|
actionsArray.push_back(secondaryAction);
|
|
|
|
activity.insert(QStringLiteral("actions"), actionsArray);
|
|
|
|
_activityData.push_back(activity);
|
|
|
|
_startingId++;
|
|
}
|
|
|
|
_startingId--;
|
|
}
|
|
|
|
const QByteArray activityJsonData(int sinceId, int limit)
|
|
{
|
|
QJsonArray data;
|
|
|
|
const auto itFound = std::find_if(
|
|
std::cbegin(_activityData), std::cend(_activityData), [&sinceId](const QJsonValue ¤tActivityValue) {
|
|
const auto currentActivityId =
|
|
currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
|
|
return currentActivityId == sinceId;
|
|
});
|
|
|
|
const int startIndex = itFound != std::cend(_activityData)
|
|
? static_cast<int>(std::distance(std::cbegin(_activityData), itFound))
|
|
: -1;
|
|
|
|
if (startIndex > 0) {
|
|
for (int dataIndex = startIndex, iteration = 0; dataIndex >= 0 && iteration < limit;
|
|
--dataIndex, ++iteration) {
|
|
if (_activityData[dataIndex].toObject().value(QStringLiteral("activity_id")).toInt()
|
|
> sinceId - limit) {
|
|
data.append(_activityData[dataIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
QJsonObject root;
|
|
QJsonObject ocs;
|
|
ocs.insert(QStringLiteral("data"), data);
|
|
root.insert(QStringLiteral("ocs"), ocs);
|
|
|
|
return QJsonDocument(root).toJson();
|
|
}
|
|
|
|
QJsonValue activityById(int id)
|
|
{
|
|
const auto itFound = std::find_if(
|
|
std::cbegin(_activityData), std::cend(_activityData), [&id](const QJsonValue ¤tActivityValue) {
|
|
const auto currentActivityId =
|
|
currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
|
|
return currentActivityId == id;
|
|
});
|
|
|
|
if (itFound != std::cend(_activityData)) {
|
|
return (*itFound);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]] int startingIdLast() const { return _startingId; }
|
|
|
|
private:
|
|
static FakeRemoteActivityStorage *_instance;
|
|
QJsonArray _activityData;
|
|
QVariantMap _metaSuccess;
|
|
quint32 _numItemsToInsert = 30;
|
|
int _startingId = startingId;
|
|
};
|
|
|
|
FakeRemoteActivityStorage *FakeRemoteActivityStorage::_instance = nullptr;
|
|
|
|
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(this, &TestingALM::activityJobStatusCode, this, &TestingALM::slotProcessReceivedActivities);
|
|
QObject::connect(job, &OCC::JsonApiJob::jsonReceived, this, &TestingALM::activitiesReceived);
|
|
|
|
QUrlQuery params;
|
|
params.addQueryItem(QLatin1String("since"), QString::number(currentItem()));
|
|
params.addQueryItem(QLatin1String("limit"), QString::number(50));
|
|
job->addQueryParams(params);
|
|
|
|
setAndRefreshCurrentlyFetching(true);
|
|
job->start();
|
|
}
|
|
|
|
public slots:
|
|
void slotProcessReceivedActivities()
|
|
{
|
|
if (rowCount() > _numRowsPrev) {
|
|
auto finalListCopy = finalList();
|
|
for (int i = _numRowsPrev; i < rowCount(); ++i) {
|
|
const auto modelIndex = index(i, 0);
|
|
auto activity = finalListCopy.at(modelIndex.row());
|
|
if (activity._links.isEmpty()) {
|
|
const auto activityJsonObject = FakeRemoteActivityStorage::instance()->activityById(activity._id);
|
|
|
|
if (!activityJsonObject.isNull()) {
|
|
// because "_links" are normally populated within the notificationhandler.cpp, which we don't run as part of this unit test, we have to fill them here
|
|
// TODO: move the logic to populate "_links" to "activitylistmodel.cpp"
|
|
auto actions = activityJsonObject.toObject().value("actions").toArray();
|
|
foreach (auto action, actions) {
|
|
activity._links.append(OCC::ActivityLink::createFomJsonObject(action.toObject()));
|
|
}
|
|
|
|
finalListCopy[modelIndex.row()] = activity;
|
|
}
|
|
}
|
|
}
|
|
|
|
setFinalList(finalListCopy);
|
|
}
|
|
_numRowsPrev = rowCount();
|
|
setAndRefreshCurrentlyFetching(false);
|
|
emit activitiesProcessed();
|
|
}
|
|
signals:
|
|
void activitiesProcessed();
|
|
|
|
private:
|
|
int _numRowsPrev = 0;
|
|
};
|
|
|
|
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;
|
|
OCC::Activity testSyncResultErrorActivity;
|
|
OCC::Activity testSyncFileItemActivity;
|
|
OCC::Activity testFileIgnoredActivity;
|
|
|
|
static constexpr int searchResultsReplyDelay = 100;
|
|
|
|
QSharedPointer<TestingALM> testingALM() {
|
|
QSharedPointer<TestingALM> model(new TestingALM);
|
|
model->setAccountState(accountState.data());
|
|
QAbstractItemModelTester modelTester(model.data());
|
|
|
|
return model;
|
|
}
|
|
|
|
void testActivityAdd(void(OCC::ActivityListModel::*addingMethod)(const OCC::Activity&, OCC::ActivityListModel::ErrorType), OCC::Activity &activity) {
|
|
const auto model = testingALM();
|
|
QCOMPARE(model->rowCount(), 0);
|
|
|
|
(model.data()->*addingMethod)(activity, OCC::ActivityListModel::ErrorType::SyncError);
|
|
QCOMPARE(model->rowCount(), 1);
|
|
|
|
const auto index = model->index(0, 0);
|
|
QVERIFY(index.isValid());
|
|
}
|
|
|
|
void testActivityAdd(void(OCC::ActivityListModel::*addingMethod)(const OCC::Activity&), OCC::Activity &activity) {
|
|
const auto model = testingALM();
|
|
QCOMPARE(model->rowCount(), 0);
|
|
|
|
(model.data()->*addingMethod)(activity);
|
|
QCOMPARE(model->rowCount(), 1);
|
|
|
|
const auto index = model->index(0, 0);
|
|
QVERIFY(index.isValid());
|
|
}
|
|
|
|
void testActivityAdd(void(OCC::ActivityListModel::*addingMethod)(const OCC::Activity&, OCC::ActivityListModel::ErrorType), OCC::Activity &activity, OCC::ActivityListModel::ErrorType type) {
|
|
const auto model = testingALM();
|
|
QCOMPARE(model->rowCount(), 0);
|
|
|
|
(model.data()->*addingMethod)(activity, type);
|
|
QCOMPARE(model->rowCount(), 1);
|
|
|
|
const auto index = model->index(0, 0);
|
|
QVERIFY(index.isValid());
|
|
}
|
|
|
|
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();
|
|
testNotificationActivity._subject = QStringLiteral("Sample notification text");
|
|
|
|
testSyncResultErrorActivity._id = 2;
|
|
testSyncResultErrorActivity._type = OCC::Activity::SyncResultType;
|
|
testSyncResultErrorActivity._syncResultStatus = OCC::SyncResult::Error;
|
|
testSyncResultErrorActivity._dateTime = QDateTime::currentDateTime();
|
|
testSyncResultErrorActivity._subject = QStringLiteral("Sample failed sync text");
|
|
testSyncResultErrorActivity._message = QStringLiteral("/path/to/thingy");
|
|
testSyncResultErrorActivity._link = QStringLiteral("/path/to/thingy");
|
|
testSyncResultErrorActivity._accName = accountState->account()->displayName();
|
|
|
|
testSyncFileItemActivity._id = 3;
|
|
testSyncFileItemActivity._type = OCC::Activity::SyncFileItemType; //client activity
|
|
testSyncFileItemActivity._syncFileItemStatus = OCC::SyncFileItem::Success;
|
|
testSyncFileItemActivity._dateTime = QDateTime::currentDateTime();
|
|
testSyncFileItemActivity._message = QStringLiteral("Sample file successfully synced text");
|
|
testSyncFileItemActivity._link = accountState->account()->url();
|
|
testSyncFileItemActivity._accName = accountState->account()->displayName();
|
|
testSyncFileItemActivity._file = QStringLiteral("xyz.pdf");
|
|
|
|
testFileIgnoredActivity._id = 4;
|
|
testFileIgnoredActivity._type = OCC::Activity::SyncFileItemType;
|
|
testFileIgnoredActivity._syncFileItemStatus = OCC::SyncFileItem::FileIgnored;
|
|
testFileIgnoredActivity._dateTime = QDateTime::currentDateTime();
|
|
testFileIgnoredActivity._subject = QStringLiteral("Sample ignored file sync text");
|
|
testFileIgnoredActivity._link = accountState->account()->url();
|
|
testFileIgnoredActivity._accName = accountState->account()->displayName();
|
|
testFileIgnoredActivity._folder = QStringLiteral("thingy");
|
|
testFileIgnoredActivity._file = QStringLiteral("test.txt");
|
|
};
|
|
|
|
// Test receiving activity from server
|
|
void testFetchingRemoteActivity() {
|
|
const auto model = testingALM();
|
|
QCOMPARE(model->rowCount(), 0);
|
|
|
|
model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
|
|
model->startFetchJob();
|
|
QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
|
|
QVERIFY(activitiesJob.wait(3000));
|
|
QCOMPARE(model->rowCount(), 50);
|
|
};
|
|
|
|
// Test receiving activity from local user action
|
|
void testLocalSyncFileAction() {
|
|
testActivityAdd(&TestingALM::addSyncFileItemToActivityList, testSyncFileItemActivity);
|
|
};
|
|
|
|
void testAddNotification() {
|
|
testActivityAdd(&TestingALM::addNotificationToActivityList, testNotificationActivity);
|
|
};
|
|
|
|
void testAddError() {
|
|
testActivityAdd(&TestingALM::addErrorToActivityList, testSyncResultErrorActivity, OCC::ActivityListModel::ErrorType::SyncError);
|
|
};
|
|
|
|
void testAddIgnoredFile() {
|
|
testActivityAdd(&TestingALM::addIgnoredFileToList, testFileIgnoredActivity);
|
|
};
|
|
|
|
// Test removing activity from list
|
|
void testRemoveActivityWithRow() {
|
|
const auto model = testingALM();
|
|
QCOMPARE(model->rowCount(), 0);
|
|
|
|
model->addNotificationToActivityList(testNotificationActivity);
|
|
QCOMPARE(model->rowCount(), 1);
|
|
|
|
model->removeActivityFromActivityList(0);
|
|
QCOMPARE(model->rowCount(), 0);
|
|
}
|
|
|
|
void testRemoveActivityWithActivity() {
|
|
const auto model = testingALM();
|
|
QCOMPARE(model->rowCount(), 0);
|
|
|
|
model->addNotificationToActivityList(testNotificationActivity);
|
|
QCOMPARE(model->rowCount(), 1);
|
|
|
|
model->removeActivityFromActivityList(testNotificationActivity);
|
|
QCOMPARE(model->rowCount(), 0);
|
|
}
|
|
|
|
void testDummyFetchingActivitiesActivity() {
|
|
const auto model = testingALM();
|
|
QCOMPARE(model->rowCount(), 0);
|
|
|
|
model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
|
|
model->startFetchJob();
|
|
|
|
// Check for the dummy before activities have arrived
|
|
QCOMPARE(model->rowCount(), 1);
|
|
|
|
QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
|
|
QVERIFY(activitiesJob.wait(3000));
|
|
// Test the dummy was removed
|
|
QCOMPARE(model->rowCount(), 50);
|
|
}
|
|
|
|
// Test getting the data from the model
|
|
void testData() {
|
|
const auto model = testingALM();
|
|
QCOMPARE(model->rowCount(), 0);
|
|
|
|
model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
|
|
model->startFetchJob();
|
|
QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
|
|
QVERIFY(activitiesJob.wait(3000));
|
|
QCOMPARE(model->rowCount(), 50);
|
|
|
|
model->addSyncFileItemToActivityList(testSyncFileItemActivity);
|
|
QCOMPARE(model->rowCount(), 51);
|
|
|
|
model->addErrorToActivityList(testSyncResultErrorActivity, OCC::ActivityListModel::ErrorType::SyncError);
|
|
QCOMPARE(model->rowCount(), 52);
|
|
|
|
model->addIgnoredFileToList(testFileIgnoredActivity);
|
|
QCOMPARE(model->rowCount(), 53);
|
|
|
|
model->addNotificationToActivityList(testNotificationActivity);
|
|
QCOMPARE(model->rowCount(), 54);
|
|
|
|
// Test all rows for things in common
|
|
for (int i = 0; i < model->rowCount(); i++) {
|
|
const auto index = model->index(i, 0);
|
|
|
|
auto text = index.data(OCC::ActivityListModel::ActionTextRole).toString();
|
|
|
|
QVERIFY(index.data(OCC::ActivityListModel::ActionRole).canConvert<int>());
|
|
const auto type = index.data(OCC::ActivityListModel::ActionRole).toInt();
|
|
QVERIFY(type >= OCC::Activity::DummyFetchingActivityType);
|
|
|
|
QVERIFY(!index.data(OCC::ActivityListModel::AccountRole).toString().isEmpty());
|
|
QVERIFY(!index.data(OCC::ActivityListModel::ActionTextColorRole).toString().isEmpty());
|
|
QVERIFY(!index.data(OCC::ActivityListModel::DarkIconRole).toString().isEmpty());
|
|
QVERIFY(!index.data(OCC::ActivityListModel::LightIconRole).toString().isEmpty());
|
|
QVERIFY(!index.data(OCC::ActivityListModel::PointInTimeRole).toString().isEmpty());
|
|
|
|
QVERIFY(index.data(OCC::ActivityListModel::ObjectTypeRole).canConvert<int>());
|
|
QVERIFY(index.data(OCC::ActivityListModel::ObjectNameRole).canConvert<QString>());
|
|
QVERIFY(index.data(OCC::ActivityListModel::ObjectIdRole).canConvert<int>());
|
|
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::ActionsLinksForActionButtonsRole).canConvert<QList<QVariant>>());
|
|
|
|
QVERIFY(index.data(OCC::ActivityListModel::AccountConnectedRole).canConvert<bool>());
|
|
QVERIFY(index.data(OCC::ActivityListModel::DisplayActions).canConvert<bool>());
|
|
|
|
QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationConversationTokenRole).canConvert<QString>());
|
|
QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageIdRole).canConvert<QString>());
|
|
QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageSentRole).canConvert<QString>());
|
|
|
|
QVERIFY(index.data(OCC::ActivityListModel::ActivityRole).canConvert<OCC::Activity>());
|
|
|
|
// 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
|
|
}
|
|
};
|
|
|
|
void testActivityActionsData()
|
|
{
|
|
const auto model = testingALM();
|
|
QCOMPARE(model->rowCount(), 0);
|
|
model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
|
|
|
|
int prevModelRowCount = model->rowCount();
|
|
|
|
do {
|
|
prevModelRowCount = model->rowCount();
|
|
model->startFetchJob();
|
|
QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
|
|
QVERIFY(activitiesJob.wait(3000));
|
|
|
|
|
|
for (int i = prevModelRowCount; i < model->rowCount(); i++) {
|
|
const auto index = model->index(i, 0);
|
|
|
|
const auto actionsLinks = index.data(OCC::ActivityListModel::ActionsLinksRole).toList();
|
|
if (!actionsLinks.isEmpty()) {
|
|
const auto actionsLinksContextMenu =
|
|
index.data(OCC::ActivityListModel::ActionsLinksContextMenuRole).toList();
|
|
|
|
// context menu must be shorter than total action links
|
|
QVERIFY(actionsLinksContextMenu.isEmpty() || actionsLinksContextMenu.size() < actionsLinks.size());
|
|
|
|
// context menu must not contain the primary action
|
|
QVERIFY(std::find_if(std::begin(actionsLinksContextMenu), std::end(actionsLinksContextMenu),
|
|
[](const QVariant &entry) { return entry.value<OCC::ActivityLink>()._primary; })
|
|
== std::end(actionsLinksContextMenu));
|
|
|
|
const auto objectType = index.data(OCC::ActivityListModel::ObjectTypeRole).toString();
|
|
|
|
const auto actionButtonsLinks =
|
|
index.data(OCC::ActivityListModel::ActionsLinksForActionButtonsRole).toList();
|
|
|
|
// Login attempt notification
|
|
if (objectType == QStringLiteral("2fa_id")) {
|
|
QVERIFY(actionsLinks.size() == 2);
|
|
QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._primary);
|
|
QVERIFY(!actionsLinks[1].value<OCC::ActivityLink>()._primary);
|
|
QVERIFY(actionsLinksContextMenu.isEmpty());
|
|
}
|
|
|
|
// Generate 2FA backup codes notification
|
|
if (objectType == QStringLiteral("create")) {
|
|
QVERIFY(actionsLinks.size() == 1);
|
|
QVERIFY(!actionsLinks[0].value<OCC::ActivityLink>()._primary);
|
|
QVERIFY(actionsLinksContextMenu.isEmpty());
|
|
}
|
|
|
|
if ((objectType == QStringLiteral("chat") || objectType == QStringLiteral("call")
|
|
|| objectType == QStringLiteral("room"))) {
|
|
|
|
auto replyActionPos = 0;
|
|
if (objectType == QStringLiteral("call")) {
|
|
replyActionPos = 1;
|
|
}
|
|
|
|
// both action links and buttons must contain a "REPLY" verb element as secondary action
|
|
QVERIFY(actionsLinks[replyActionPos].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
|
|
//QVERIFY(actionButtonsLinks[replyActionPos].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
|
|
|
|
// the first action button for chat must have image set
|
|
//QVERIFY(!actionButtonsLinks[replyActionPos].value<OCC::ActivityLink>()._imageSource.isEmpty());
|
|
//QVERIFY(!actionButtonsLinks[replyActionPos].value<OCC::ActivityLink>()._imageSourceHovered.isEmpty());
|
|
|
|
// logic for "chat" and other types of activities with multiple actions
|
|
if ((objectType == QStringLiteral("chat")
|
|
|| (objectType != QStringLiteral("room") && objectType != QStringLiteral("call")))) {
|
|
|
|
// button's label for "chat" must be renamed to "Reply"
|
|
QVERIFY(actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QObject::tr("Reply"));
|
|
|
|
if (static_cast<quint32>(actionsLinks.size()) > OCC::ActivityListModel::maxActionButtons()) {
|
|
// in case total actions is longer than ActivityListModel::maxActionButtons, only one button must be present in a list of action buttons
|
|
QVERIFY(actionButtonsLinks.size() == 1);
|
|
const auto actionButtonsAndContextMenuEntries = actionButtonsLinks + actionsLinksContextMenu;
|
|
// in case total actions is longer than ActivityListModel::maxActionButtons, then a sum of action buttons and action menu entries must be equal to a total of action links
|
|
QVERIFY(actionButtonsLinks.size() + actionsLinksContextMenu.size() == actionsLinks.size());
|
|
}
|
|
} else if ((objectType == QStringLiteral("call"))) {
|
|
QVERIFY(
|
|
actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QStringLiteral("Call back"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (prevModelRowCount < model->rowCount());
|
|
};
|
|
};
|
|
|
|
QTEST_MAIN(TestActivityListModel)
|
|
#include "testactivitylistmodel.moc"
|