From a5e2de7e1d688596215d88687b4c6e402cc311c6 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 6 Feb 2023 16:29:48 +0100 Subject: [PATCH 01/22] Move activity list syncfileitemstatus errors to the top of the list Signed-off-by: Claudio Cambra --- src/gui/tray/sortedactivitylistmodel.cpp | 30 +++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/gui/tray/sortedactivitylistmodel.cpp b/src/gui/tray/sortedactivitylistmodel.cpp index fcd58d1385..95728b7e95 100644 --- a/src/gui/tray/sortedactivitylistmodel.cpp +++ b/src/gui/tray/sortedactivitylistmodel.cpp @@ -44,20 +44,23 @@ bool SortedActivityListModel::lessThan(const QModelIndex &sourceLeft, const QMod return false; } - if (const auto rightType = rightActivity._type; leftType != rightType) { - return leftType < rightType; - } - + // Let's now check for errors as we want those near the top too const auto leftSyncFileItemStatus = leftActivity._syncFileItemStatus; const auto rightSyncFileItemStatus = rightActivity._syncFileItemStatus; + const bool leftIsErrorFileItemStatus = leftSyncFileItemStatus == SyncFileItem::FatalError || + leftSyncFileItemStatus == SyncFileItem::SoftError || + leftSyncFileItemStatus == SyncFileItem::NormalError; - // Then compare by status - if (leftSyncFileItemStatus != rightSyncFileItemStatus) { - // We want to shove errors towards the top. - return (leftSyncFileItemStatus != SyncFileItem::NoStatus && - leftSyncFileItemStatus != SyncFileItem::Success) || - leftSyncFileItemStatus == SyncFileItem::FatalError || - leftSyncFileItemStatus < rightSyncFileItemStatus; + const bool rightIsErrorFileItemStatus = rightSyncFileItemStatus == SyncFileItem::FatalError || + rightSyncFileItemStatus == SyncFileItem::SoftError || + rightSyncFileItemStatus == SyncFileItem::NormalError; + + if (leftIsErrorFileItemStatus != rightIsErrorFileItemStatus) { + return leftIsErrorFileItemStatus; + } + + if (const auto rightType = rightActivity._type; leftType != rightType) { + return leftType < rightType; } const auto leftSyncResultStatus = leftActivity._syncResultStatus; @@ -70,6 +73,11 @@ bool SortedActivityListModel::lessThan(const QModelIndex &sourceLeft, const QMod leftSyncResultStatus == SyncResult::Error; } + if (leftSyncFileItemStatus != rightSyncFileItemStatus) { + // We want to shove erors towards the top. + return leftSyncFileItemStatus < rightSyncFileItemStatus; + } + // Finally sort by time, latest first const auto leftDateTime = leftActivity._dateTime; const auto rightDateTime = rightActivity._dateTime; From 68d92e1ca7330d9a99c27427fd556e54044ae62f Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 6 Feb 2023 16:37:02 +0100 Subject: [PATCH 02/22] Move activity list SyncResult errors to the top of the list Signed-off-by: Claudio Cambra --- src/gui/tray/sortedactivitylistmodel.cpp | 26 +++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/gui/tray/sortedactivitylistmodel.cpp b/src/gui/tray/sortedactivitylistmodel.cpp index 95728b7e95..8a5bf54406 100644 --- a/src/gui/tray/sortedactivitylistmodel.cpp +++ b/src/gui/tray/sortedactivitylistmodel.cpp @@ -45,6 +45,23 @@ bool SortedActivityListModel::lessThan(const QModelIndex &sourceLeft, const QMod } // Let's now check for errors as we want those near the top too + // Sync result errors go first + const auto leftSyncResultStatus = leftActivity._syncResultStatus; + const auto rightSyncResultStatus = rightActivity._syncResultStatus; + + const auto leftIsSyncResultError = leftSyncResultStatus == SyncResult::Error || + leftSyncResultStatus == SyncResult::SetupError || + leftSyncResultStatus == SyncResult::Problem; + + const auto rightIsSyncResultError = rightSyncResultStatus == SyncResult::Error || + rightSyncResultStatus == SyncResult::SetupError || + rightSyncResultStatus == SyncResult::Problem; + + if (leftIsSyncResultError != rightIsSyncResultError) { + return leftIsSyncResultError; + } // If they are both errors then we will order the errors according to enum order later + + // Then sync file item status errors const auto leftSyncFileItemStatus = leftActivity._syncFileItemStatus; const auto rightSyncFileItemStatus = rightActivity._syncFileItemStatus; const bool leftIsErrorFileItemStatus = leftSyncFileItemStatus == SyncFileItem::FatalError || @@ -63,14 +80,9 @@ bool SortedActivityListModel::lessThan(const QModelIndex &sourceLeft, const QMod return leftType < rightType; } - const auto leftSyncResultStatus = leftActivity._syncResultStatus; - const auto rightSyncResultStatus = rightActivity._syncResultStatus; - if (leftSyncResultStatus != rightSyncResultStatus) { - // We only ever use SyncResult::Error in activities - return (leftSyncResultStatus != SyncResult::Undefined && - leftSyncResultStatus != SyncResult::Success) || - leftSyncResultStatus == SyncResult::Error; + return leftSyncResultStatus != SyncResult::Undefined && + leftSyncResultStatus != SyncResult::Success; } if (leftSyncFileItemStatus != rightSyncFileItemStatus) { From 55fc6a1d0f914d3d61eb5884443eed7554b8599a Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 6 Feb 2023 16:45:48 +0100 Subject: [PATCH 03/22] Place all sorts of sync file item status errors near the top Signed-off-by: Claudio Cambra --- src/gui/tray/sortedactivitylistmodel.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/gui/tray/sortedactivitylistmodel.cpp b/src/gui/tray/sortedactivitylistmodel.cpp index 8a5bf54406..d62ad4985b 100644 --- a/src/gui/tray/sortedactivitylistmodel.cpp +++ b/src/gui/tray/sortedactivitylistmodel.cpp @@ -64,29 +64,26 @@ bool SortedActivityListModel::lessThan(const QModelIndex &sourceLeft, const QMod // Then sync file item status errors const auto leftSyncFileItemStatus = leftActivity._syncFileItemStatus; const auto rightSyncFileItemStatus = rightActivity._syncFileItemStatus; - const bool leftIsErrorFileItemStatus = leftSyncFileItemStatus == SyncFileItem::FatalError || - leftSyncFileItemStatus == SyncFileItem::SoftError || - leftSyncFileItemStatus == SyncFileItem::NormalError; + const bool leftIsErrorFileItemStatus = leftSyncFileItemStatus != SyncFileItem::NoStatus && + leftSyncFileItemStatus != SyncFileItem::Success; - const bool rightIsErrorFileItemStatus = rightSyncFileItemStatus == SyncFileItem::FatalError || - rightSyncFileItemStatus == SyncFileItem::SoftError || - rightSyncFileItemStatus == SyncFileItem::NormalError; + const bool rightIsErrorFileItemStatus = rightSyncFileItemStatus != SyncFileItem::NoStatus && + rightSyncFileItemStatus != SyncFileItem::Success; if (leftIsErrorFileItemStatus != rightIsErrorFileItemStatus) { return leftIsErrorFileItemStatus; } + // Let's go back to more broadly comparing by type if (const auto rightType = rightActivity._type; leftType != rightType) { return leftType < rightType; } if (leftSyncResultStatus != rightSyncResultStatus) { - return leftSyncResultStatus != SyncResult::Undefined && - leftSyncResultStatus != SyncResult::Success; + return leftSyncResultStatus < rightSyncResultStatus; } if (leftSyncFileItemStatus != rightSyncFileItemStatus) { - // We want to shove erors towards the top. return leftSyncFileItemStatus < rightSyncFileItemStatus; } From 00ac5a4568495f3d6f709563bb0b4a6a1182e435 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 28 Feb 2023 23:50:49 +0100 Subject: [PATCH 04/22] Move FakeRemoteActivityStorate to new activitylistmodeltestutils Signed-off-by: Claudio Cambra --- test/CMakeLists.txt | 1 + test/activitylistmodeltestutils.cpp | 325 ++++++++++++++++++++++++++++ test/activitylistmodeltestutils.h | 48 ++++ test/testactivitylistmodel.cpp | 319 +-------------------------- 4 files changed, 375 insertions(+), 318 deletions(-) create mode 100644 test/activitylistmodeltestutils.cpp create mode 100644 test/activitylistmodeltestutils.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2dbb5a9b7f..a8ccda7ede 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,7 @@ set(CMAKE_AUTOMOC TRUE) add_library(testutils STATIC + activitylistmodeltestutils.cpp syncenginetestutils.cpp pushnotificationstestutils.cpp themeutils.cpp diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp new file mode 100644 index 0000000000..72d40b15d0 --- /dev/null +++ b/test/activitylistmodeltestutils.cpp @@ -0,0 +1,325 @@ +/* + * Copyright (C) by Claudio Cambra + * + * 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 "activitylistmodeltestutils.h" + +#include +#include +#include +#include + +FakeRemoteActivityStorage *FakeRemoteActivityStorage::_instance = nullptr; + +FakeRemoteActivityStorage* FakeRemoteActivityStorage::instance() +{ + if (!_instance) { + _instance = new FakeRemoteActivityStorage(); + _instance->init(); + } + + return _instance; +} + +void FakeRemoteActivityStorage::destroy() +{ + if (_instance) { + delete _instance; + } + + _instance = nullptr; +} + +void FakeRemoteActivityStorage::init() +{ + if (!_activityData.isEmpty()) { + return; + } + + _metaSuccess = {{QStringLiteral("status"), QStringLiteral("ok")}, {QStringLiteral("statuscode"), 200}, + {QStringLiteral("message"), QStringLiteral("OK")}}; + + initActivityData(); +} + +void FakeRemoteActivityStorage::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--; +} + +QByteArray FakeRemoteActivityStorage::activityJsonData(const int sinceId, const 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(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 FakeRemoteActivityStorage::activityById(const int id) const +{ + 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 {}; +} + +int FakeRemoteActivityStorage::startingIdLast() const +{ + return _startingId; +} diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h new file mode 100644 index 0000000000..d6dc042b38 --- /dev/null +++ b/test/activitylistmodeltestutils.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) by Claudio Cambra + * + * 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 +#include + +#pragma once + +class QByteArray; +class QJsonValue; + +class FakeRemoteActivityStorage +{ + FakeRemoteActivityStorage() = default; + +public: + static FakeRemoteActivityStorage *instance(); + + static void destroy(); + + void init(); + void initActivityData(); + + [[nodiscard]] QByteArray activityJsonData(const int sinceId, const int limit); + + [[nodiscard]] QJsonValue activityById(const int id) const; + + [[nodiscard]] int startingIdLast() const; + +private: + QJsonArray _activityData; + QVariantMap _metaSuccess; + quint32 _numItemsToInsert = 30; + int _startingId = 90000; + + static FakeRemoteActivityStorage *_instance; +}; diff --git a/test/testactivitylistmodel.cpp b/test/testactivitylistmodel.cpp index a98db6b612..b280119076 100644 --- a/test/testactivitylistmodel.cpp +++ b/test/testactivitylistmodel.cpp @@ -17,6 +17,7 @@ #include "account.h" #include "accountstate.h" #include "accountmanager.h" +#include "activitylistmodeltestutils.h" #include "syncenginetestutils.h" #include "syncresult.h" @@ -25,10 +26,6 @@ #include #include -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":[]}} )"; @@ -41,320 +38,6 @@ 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(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 From 1c16728cb043b176057acd9950b1f828d2ca8127 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 28 Feb 2023 23:59:27 +0100 Subject: [PATCH 05/22] Move TestingALM to activitylistmodeltestutils Signed-off-by: Claudio Cambra --- test/CMakeLists.txt | 2 +- test/activitylistmodeltestutils.cpp | 47 +++++++++++++++++++++ test/activitylistmodeltestutils.h | 25 +++++++++++ test/testactivitylistmodel.cpp | 65 ----------------------------- 4 files changed, 73 insertions(+), 66 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a8ccda7ede..8a9d245b4c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,13 +6,13 @@ set(CMAKE_AUTOMOC TRUE) add_library(testutils STATIC - activitylistmodeltestutils.cpp syncenginetestutils.cpp pushnotificationstestutils.cpp themeutils.cpp testhelper.cpp sharetestutils.cpp endtoendtestutils.cpp + activitylistmodeltestutils.cpp ) target_link_libraries(testutils PUBLIC Nextcloud::sync Qt5::Test) diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp index 72d40b15d0..348cd7cb67 100644 --- a/test/activitylistmodeltestutils.cpp +++ b/test/activitylistmodeltestutils.cpp @@ -323,3 +323,50 @@ int FakeRemoteActivityStorage::startingIdLast() const { return _startingId; } + + +void TestingALM::startFetchJob() +{ + 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(); +} + +void TestingALM::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(); +} diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index d6dc042b38..91dc030fed 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -15,6 +15,12 @@ #include #include +#include "gui/tray/activitylistmodel.h" + +#include "libsync/account.h" +#include "gui/accountstate.h" +#include "gui/accountmanager.h" + #pragma once class QByteArray; @@ -46,3 +52,22 @@ private: static FakeRemoteActivityStorage *_instance; }; + +class TestingALM : public OCC::ActivityListModel +{ + Q_OBJECT + +public: + TestingALM() = default; + + void startFetchJob() override; + +public slots: + void slotProcessReceivedActivities(); + +signals: + void activitiesProcessed(); + +private: + int _numRowsPrev = 0; +}; diff --git a/test/testactivitylistmodel.cpp b/test/testactivitylistmodel.cpp index b280119076..cacc1364c0 100644 --- a/test/testactivitylistmodel.cpp +++ b/test/testactivitylistmodel.cpp @@ -12,11 +12,6 @@ * for more details. */ -#include "gui/tray/activitylistmodel.h" - -#include "account.h" -#include "accountstate.h" -#include "accountmanager.h" #include "activitylistmodeltestutils.h" #include "syncenginetestutils.h" #include "syncresult.h" @@ -38,66 +33,6 @@ 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(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 From 6364c6226c1c698996900a753498355d603da504 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 01:23:37 +0100 Subject: [PATCH 06/22] Replace unnecessary folder include in ActivityData with SyncResult Signed-off-by: Claudio Cambra --- src/gui/tray/activitydata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/tray/activitydata.h b/src/gui/tray/activitydata.h index c5517d1161..29df3c878c 100644 --- a/src/gui/tray/activitydata.h +++ b/src/gui/tray/activitydata.h @@ -16,7 +16,7 @@ #define ACTIVITYDATA_H #include "syncfileitem.h" -#include "folder.h" +#include "syncresult.h" #include "account.h" #include From e42f5cf33e5fafa5e422cb3c98a7425462adbed4 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 01:52:22 +0100 Subject: [PATCH 07/22] Move activitylistmodel testing QNAM override to dedicated convenience method in ActivityTestUtils Signed-off-by: Claudio Cambra --- test/activitylistmodeltestutils.cpp | 55 +++++++++++++++++++++++++++++ test/activitylistmodeltestutils.h | 14 ++++++++ test/testactivitylistmodel.cpp | 44 +++++------------------ 3 files changed, 77 insertions(+), 36 deletions(-) diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp index 348cd7cb67..e86bf6fca0 100644 --- a/test/activitylistmodeltestutils.cpp +++ b/test/activitylistmodeltestutils.cpp @@ -13,12 +13,65 @@ */ #include "activitylistmodeltestutils.h" +#include "syncenginetestutils.h" #include #include #include #include +namespace { +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":[]}} +)"; +} + +namespace ActivityListModelTestUtils +{ + +QNetworkReply *almTestQnamOverride(FakeQNAM * const fakeQnam, + const QNetworkAccessManager::Operation op, + const QNetworkRequest &req, + const QString &accountUrl, + QObject * const parent, + const int searchResultsReplyDelay, + QIODevice * const 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(accountUrl)) { + reply = new FakeErrorReply(op, req, parent, 404, fake404Response); + } + if (format != QStringLiteral("json")) { + reply = new FakeErrorReply(op, req, parent, 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); + } + + if (!reply) { + return qobject_cast(new FakeErrorReply(op, req, parent, 404, QByteArrayLiteral("{error: \"Not found!\"}"))); + } + + return reply; +} + FakeRemoteActivityStorage *FakeRemoteActivityStorage::_instance = nullptr; FakeRemoteActivityStorage* FakeRemoteActivityStorage::instance() @@ -370,3 +423,5 @@ void TestingALM::slotProcessReceivedActivities() setAndRefreshCurrentlyFetching(false); emit activitiesProcessed(); } + +} diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index 91dc030fed..19dc37ded0 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -23,9 +23,21 @@ #pragma once +class FakeQNAM; class QByteArray; class QJsonValue; +namespace ActivityListModelTestUtils +{ + +[[nodiscard]] QNetworkReply *almTestQnamOverride(FakeQNAM * const fakeQnam, + const QNetworkAccessManager::Operation op, + const QNetworkRequest &req, + const QString &accountUrl, + QObject * const parent = nullptr, + const int searchResultsReplyDelay = 0, + QIODevice * const device = nullptr); + class FakeRemoteActivityStorage { FakeRemoteActivityStorage() = default; @@ -71,3 +83,5 @@ signals: private: int _numRowsPrev = 0; }; + +} diff --git a/test/testactivitylistmodel.cpp b/test/testactivitylistmodel.cpp index cacc1364c0..0fe77b83dc 100644 --- a/test/testactivitylistmodel.cpp +++ b/test/testactivitylistmodel.cpp @@ -21,17 +21,7 @@ #include #include -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":[]}} -)"; +using namespace ActivityListModelTestUtils; class TestActivityListModel : public QObject { @@ -107,31 +97,13 @@ private slots: 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(new FakeErrorReply(op, req, this, 404, QByteArrayLiteral("{error: \"Not found!\"}"))); - } - - return reply; + Q_UNUSED(device) + return almTestQnamOverride(fakeQnam.data(), + op, + req, + accountState->account()->url().toString(), + this, + searchResultsReplyDelay); }); OCC::AccountManager::instance()->addAccount(account); From 06a42908f71cadff8d3c9cad371a648db25d506a Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 02:06:24 +0100 Subject: [PATCH 08/22] Move creation of example activities from testactivitylistmodel to activitylistmodeltestutils Signed-off-by: Claudio Cambra --- test/activitylistmodeltestutils.cpp | 65 +++++++++++++++++++++++++++++ test/activitylistmodeltestutils.h | 5 +++ test/testactivitylistmodel.cpp | 40 +++--------------- 3 files changed, 76 insertions(+), 34 deletions(-) diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp index e86bf6fca0..a226caccf2 100644 --- a/test/activitylistmodeltestutils.cpp +++ b/test/activitylistmodeltestutils.cpp @@ -72,6 +72,71 @@ QNetworkReply *almTestQnamOverride(FakeQNAM * const fakeQnam, return reply; } +// Activity comparison is done by checking type, id, and accName +// We need an activity with these details, at least + +OCC::Activity exampleNotificationActivity(const QString &accountName, const int id) +{ + OCC::Activity testNotificationActivity; + + testNotificationActivity._accName = accountName; + testNotificationActivity._id = id; + testNotificationActivity._type = OCC::Activity::NotificationType; + testNotificationActivity._dateTime = QDateTime::currentDateTime(); + testNotificationActivity._subject = QStringLiteral("Sample notification text"); + + return testNotificationActivity; +} + +OCC::Activity exampleSyncResultErrorActivity(const QString &accountName, const int id) +{ + OCC::Activity testSyncResultErrorActivity; + + testSyncResultErrorActivity._id = id; + 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 = accountName; + + return testSyncResultErrorActivity; +} + +OCC::Activity exampleSyncFileItemActivity(const QString &accountName, const QUrl &link, const int id) +{ + OCC::Activity testSyncFileItemActivity; + + testSyncFileItemActivity._id = id; + 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 = link; + testSyncFileItemActivity._accName = accountName; + testSyncFileItemActivity._file = QStringLiteral("xyz.pdf"); + + return testSyncFileItemActivity; +} + +OCC::Activity exampleFileIgnoredActivity(const QString &accountName, const QUrl &link, const int id) +{ + OCC::Activity testFileIgnoredActivity; + + testFileIgnoredActivity._id = id; + testFileIgnoredActivity._type = OCC::Activity::SyncFileItemType; + testFileIgnoredActivity._syncFileItemStatus = OCC::SyncFileItem::FileIgnored; + testFileIgnoredActivity._dateTime = QDateTime::currentDateTime(); + testFileIgnoredActivity._subject = QStringLiteral("Sample ignored file sync text"); + testFileIgnoredActivity._link = link; + testFileIgnoredActivity._accName = accountName; + testFileIgnoredActivity._folder = QStringLiteral("thingy"); + testFileIgnoredActivity._file = QStringLiteral("test.txt"); + + return testFileIgnoredActivity; +} + FakeRemoteActivityStorage *FakeRemoteActivityStorage::_instance = nullptr; FakeRemoteActivityStorage* FakeRemoteActivityStorage::instance() diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index 19dc37ded0..16b8614dce 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -38,6 +38,11 @@ namespace ActivityListModelTestUtils const int searchResultsReplyDelay = 0, QIODevice * const device = nullptr); +[[nodiscard]] OCC::Activity exampleNotificationActivity(const QString &accountName, const int id = 1); +[[nodiscard]] OCC::Activity exampleSyncResultErrorActivity(const QString &accountName, const int id = 2); +[[nodiscard]] OCC::Activity exampleSyncFileItemActivity(const QString &accountName, const QUrl &link, const int id = 3); +[[nodiscard]] OCC::Activity exampleFileIgnoredActivity(const QString &accountName, const QUrl &link = {}, const int id = 4); + class FakeRemoteActivityStorage { FakeRemoteActivityStorage() = default; diff --git a/test/testactivitylistmodel.cpp b/test/testactivitylistmodel.cpp index 0fe77b83dc..7e2a734e75 100644 --- a/test/testactivitylistmodel.cpp +++ b/test/testactivitylistmodel.cpp @@ -108,41 +108,13 @@ private slots: 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"); + const auto accName = accountState->account()->displayName(); + const auto accUrl = accountState->account()->url(); - 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"); + testNotificationActivity = exampleNotificationActivity(accName); + testSyncResultErrorActivity = exampleSyncResultErrorActivity(accName); + testSyncFileItemActivity = exampleSyncFileItemActivity(accName, accUrl); + testFileIgnoredActivity = exampleFileIgnoredActivity(accName, accUrl); }; // Test receiving activity from server From 8003fcda85c41af86c9a9294b449cf74e98d520f Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 12:26:29 +0100 Subject: [PATCH 09/22] Add SortedActivityListModel test Signed-off-by: Claudio Cambra --- test/CMakeLists.txt | 1 + test/testsortedactivitylistmodel.cpp | 89 ++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 test/testsortedactivitylistmodel.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8a9d245b4c..69ebc08d51 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -64,6 +64,7 @@ nextcloud_add_test(IconUtils) nextcloud_add_test(SetUserStatusDialog) nextcloud_add_test(UnifiedSearchListmodel) nextcloud_add_test(ActivityListModel) +nextcloud_add_test(SortedActivityListModel) nextcloud_add_test(ActivityData) nextcloud_add_test(TalkReply) nextcloud_add_test(LockFile) diff --git a/test/testsortedactivitylistmodel.cpp b/test/testsortedactivitylistmodel.cpp new file mode 100644 index 0000000000..7a3f6b1258 --- /dev/null +++ b/test/testsortedactivitylistmodel.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) by Claudio Cambra + * + * 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 "activitylistmodeltestutils.h" +#include "syncenginetestutils.h" +#include "syncresult.h" +#include "tray/sortedactivitylistmodel.h" + +#include +#include +#include +#include + +using namespace ActivityListModelTestUtils; + +class TestSortedActivityListModel : public QObject +{ + Q_OBJECT + +public: + TestSortedActivityListModel() = default; + ~TestSortedActivityListModel() override + { + OCC::AccountManager::instance()->deleteAccount(accountState.data()); + } + + QScopedPointer fakeQnam; + OCC::AccountPtr account; + QScopedPointer accountState; + + OCC::Activity testNotificationActivity; + OCC::Activity testSyncResultErrorActivity; + OCC::Activity testSyncFileItemActivity; + OCC::Activity testFileIgnoredActivity; + + QSharedPointer testingSortedALM() { + const auto model = new TestingALM; + model->setAccountState(accountState.data()); + + QSharedPointer sortedModel(new OCC::SortedActivityListModel); + QAbstractItemModelTester sortedModelTester(sortedModel.data()); + + return sortedModel; + } + +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) + return almTestQnamOverride(fakeQnam.data(), + op, + req, + accountState->account()->url().toString(), + this); + }); + + OCC::AccountManager::instance()->addAccount(account); + + const auto accName = accountState->account()->displayName(); + const auto accUrl = accountState->account()->url(); + + testNotificationActivity = exampleNotificationActivity(accName); + testSyncResultErrorActivity = exampleSyncResultErrorActivity(accName); + testSyncFileItemActivity = exampleSyncFileItemActivity(accName, accUrl); + testFileIgnoredActivity = exampleFileIgnoredActivity(accName, accUrl); + }; +}; + +QTEST_MAIN(TestSortedActivityListModel) +#include "testsortedactivitylistmodel.moc" From bbb0cddd08b40e3720293addb432bd1d60e965b8 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 12:52:34 +0100 Subject: [PATCH 10/22] Verify sorted model is showing the rows in source activity model Signed-off-by: Claudio Cambra --- test/testsortedactivitylistmodel.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/testsortedactivitylistmodel.cpp b/test/testsortedactivitylistmodel.cpp index 7a3f6b1258..5fa195bf0c 100644 --- a/test/testsortedactivitylistmodel.cpp +++ b/test/testsortedactivitylistmodel.cpp @@ -49,6 +49,7 @@ public: model->setAccountState(accountState.data()); QSharedPointer sortedModel(new OCC::SortedActivityListModel); + sortedModel->setActivityListModel(model); QAbstractItemModelTester sortedModelTester(sortedModel.data()); return sortedModel; @@ -83,6 +84,26 @@ private slots: testSyncFileItemActivity = exampleSyncFileItemActivity(accName, accUrl); testFileIgnoredActivity = exampleFileIgnoredActivity(accName, accUrl); }; + + void testMatchingRowCounts() + { + const auto model = testingSortedALM(); + const auto sourceModel = dynamic_cast(model->sourceModel()); + QCOMPARE(sourceModel->rowCount(), 0); + QCOMPARE(model->rowCount(), sourceModel->rowCount()); + + sourceModel->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast()); + sourceModel->startFetchJob(); + QSignalSpy activitiesJob(sourceModel, &TestingALM::activitiesProcessed); + QVERIFY(activitiesJob.wait(3000)); + QCOMPARE(sourceModel->rowCount(), 50); + QCOMPARE(model->rowCount(), sourceModel->rowCount()); + } + + void testSortOrder() + { + + } }; QTEST_MAIN(TestSortedActivityListModel) From a1111938ad040dd28ee1fd3638d57f7330d5854f Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 13:16:37 +0100 Subject: [PATCH 11/22] Add test to check that the sortedactivitylistmodel correctly gets updated when the sourcemodel does Signed-off-by: Claudio Cambra --- test/testsortedactivitylistmodel.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/test/testsortedactivitylistmodel.cpp b/test/testsortedactivitylistmodel.cpp index 5fa195bf0c..e6214b9337 100644 --- a/test/testsortedactivitylistmodel.cpp +++ b/test/testsortedactivitylistmodel.cpp @@ -44,7 +44,8 @@ public: OCC::Activity testSyncFileItemActivity; OCC::Activity testFileIgnoredActivity; - QSharedPointer testingSortedALM() { + QSharedPointer testingSortedALM() + { const auto model = new TestingALM; model->setAccountState(accountState.data()); @@ -55,6 +56,17 @@ public: return sortedModel; } + void addActivity(QSharedPointer model, + void(OCC::ActivityListModel::*addingMethod)(const OCC::Activity&), + OCC::Activity &activity) + { + const auto originalRowCount = model->rowCount(); + const auto sourceModel = dynamic_cast(model->sourceModel()); + + (sourceModel->*addingMethod)(activity); + QCOMPARE(model->rowCount(), originalRowCount + 1); + } + private slots: void initTestCase() { @@ -100,9 +112,21 @@ private slots: QCOMPARE(model->rowCount(), sourceModel->rowCount()); } - void testSortOrder() + void testUpdate() { + const auto model = testingSortedALM(); + const auto sourceModel = dynamic_cast(model->sourceModel()); + sourceModel->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast()); + sourceModel->startFetchJob(); + QSignalSpy activitiesJob(sourceModel, &TestingALM::activitiesProcessed); + QVERIFY(activitiesJob.wait(3000)); + QCOMPARE(sourceModel->rowCount(), 50); + + addActivity(model, &TestingALM::addSyncFileItemToActivityList, testSyncFileItemActivity); + addActivity(model, &TestingALM::addNotificationToActivityList, testNotificationActivity); + addActivity(model, &TestingALM::addErrorToActivityList, testSyncResultErrorActivity); + addActivity(model, &TestingALM::addIgnoredFileToList, testFileIgnoredActivity); } }; From cd56841156a42b383aa680b7ada91674409bb857 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 13:35:36 +0100 Subject: [PATCH 12/22] Make TestingALM a friend class of ActivityListModel Signed-off-by: Claudio Cambra --- src/gui/tray/activitylistmodel.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/tray/activitylistmodel.h b/src/gui/tray/activitylistmodel.h index 72716aa4d4..4c0e75e36c 100644 --- a/src/gui/tray/activitylistmodel.h +++ b/src/gui/tray/activitylistmodel.h @@ -21,6 +21,10 @@ class QJsonDocument; +namespace ActivityListModelTestUtils { +class TestingALM; +} + namespace OCC { Q_DECLARE_LOGGING_CATEGORY(lcActivity) @@ -217,6 +221,8 @@ private: QElapsedTimer _durationSinceDisconnection; static constexpr quint32 MaxActionButtons = 3; + + friend class ActivityListModelTestUtils::TestingALM; }; } From 1585c2e0ae9f556d5c0f3dff1a2fe1a44eeaa447 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 13:36:18 +0100 Subject: [PATCH 13/22] Expose insert dummy activity method in TestingALM Signed-off-by: Claudio Cambra --- test/activitylistmodeltestutils.cpp | 5 +++++ test/activitylistmodeltestutils.h | 1 + 2 files changed, 6 insertions(+) diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp index a226caccf2..06f6109ba6 100644 --- a/test/activitylistmodeltestutils.cpp +++ b/test/activitylistmodeltestutils.cpp @@ -459,6 +459,11 @@ void TestingALM::startFetchJob() job->start(); } +void TestingALM::insertOrRemoveDummyFetchingActivity() +{ + OCC::ActivityListModel::insertOrRemoveDummyFetchingActivity(); +} + void TestingALM::slotProcessReceivedActivities() { if (rowCount() > _numRowsPrev) { diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index 16b8614dce..9ce12aeab2 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -78,6 +78,7 @@ public: TestingALM() = default; void startFetchJob() override; + void insertOrRemoveDummyFetchingActivity(); public slots: void slotProcessReceivedActivities(); From aa00b477c73bd6a55549f9c80de277489457c9c1 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 14:06:36 +0100 Subject: [PATCH 14/22] Fix fetch handling in TestingALM Signed-off-by: Claudio Cambra --- test/activitylistmodeltestutils.cpp | 40 ++++++++++++----------------- test/activitylistmodeltestutils.h | 1 - 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp index 06f6109ba6..3e8ef27ae8 100644 --- a/test/activitylistmodeltestutils.cpp +++ b/test/activitylistmodeltestutils.cpp @@ -459,37 +459,31 @@ void TestingALM::startFetchJob() job->start(); } -void TestingALM::insertOrRemoveDummyFetchingActivity() -{ - OCC::ActivityListModel::insertOrRemoveDummyFetchingActivity(); -} - void TestingALM::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); + 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; + 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" + const auto actions = activityJsonObject.toObject().value("actions").toArray(); + for (const auto &action : actions) { + activity._links.append(OCC::ActivityLink::createFomJsonObject(action.toObject())); } } } - setFinalList(finalListCopy); + finalListCopy[modelIndex.row()] = activity; + qDebug() << activity._subject << activity._subjectDisplay; } - _numRowsPrev = rowCount(); + + setFinalList(finalListCopy); + setAndRefreshCurrentlyFetching(false); emit activitiesProcessed(); } diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index 9ce12aeab2..16b8614dce 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -78,7 +78,6 @@ public: TestingALM() = default; void startFetchJob() override; - void insertOrRemoveDummyFetchingActivity(); public slots: void slotProcessReceivedActivities(); From b7283fa18cff3762dad221b6eade553ff26dde5e Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 14:31:21 +0100 Subject: [PATCH 15/22] Clean up declarations in activitylistmodel header, remove unused methods Signed-off-by: Claudio Cambra --- src/gui/tray/activitylistmodel.cpp | 12 ------------ src/gui/tray/activitylistmodel.h | 23 +++++++++-------------- test/activitylistmodeltestutils.cpp | 6 +++--- 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp index 9da43924d6..f6c28a85d3 100644 --- a/src/gui/tray/activitylistmodel.cpp +++ b/src/gui/tray/activitylistmodel.cpp @@ -438,18 +438,6 @@ void ActivityListModel::startFetchJob() job->start(); } -void ActivityListModel::setFinalList(const ActivityList &finalList) -{ - _finalList = finalList; - - emit allConflictsChanged(); -} - -const ActivityList &ActivityListModel::finalList() const -{ - return _finalList; -} - int ActivityListModel::currentItem() const { return _currentItem; diff --git a/src/gui/tray/activitylistmodel.h b/src/gui/tray/activitylistmodel.h index 4c0e75e36c..6335e8413f 100644 --- a/src/gui/tray/activitylistmodel.h +++ b/src/gui/tray/activitylistmodel.h @@ -93,9 +93,7 @@ public: Q_ENUM(ErrorType) explicit ActivityListModel(QObject *parent = nullptr); - - explicit ActivityListModel(AccountState *accountState, - QObject *parent = nullptr); + explicit ActivityListModel(AccountState *accountState, QObject *parent = nullptr); [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -103,7 +101,6 @@ public: [[nodiscard]] QHash roleNames() const override; [[nodiscard]] bool canFetchMore(const QModelIndex &) const override; - void fetchMore(const QModelIndex &) override; ActivityList activityList() { return _finalList; } ActivityList errorsList() { return _notificationErrorsLists; } @@ -124,6 +121,8 @@ public: [[nodiscard]] OCC::ActivityList allConflicts() const; public slots: + void fetchMore(const QModelIndex &) override; + void slotRefreshActivity(); void slotRefreshActivityInitial(); void slotRemoveAccount(); @@ -155,21 +154,22 @@ signals: protected: [[nodiscard]] bool currentlyFetching() const; - [[nodiscard]] const ActivityList &finalList() const; // added for unit tests - protected slots: void activitiesReceived(const QJsonDocument &json, int statusCode); void setAndRefreshCurrentlyFetching(bool value); void setDoneFetching(bool value); void setHideOldActivities(bool value); void setDisplayActions(bool value); - void setFinalList(const OCC::ActivityList &finalList); // added for unit tests virtual void startFetchJob(); private slots: void addEntriesToActivityList(const OCC::ActivityList &activityList); void accountStateHasChanged(); + void ingestActivities(const QJsonArray &activities); + void appendMoreActivitiesAvailableEntry(); + void insertOrRemoveDummyFetchingActivity(); + void triggerCaseClashAction(OCC::Activity activity); private: static QVariantList convertLinksToMenuEntries(const Activity &activity); @@ -178,11 +178,6 @@ private: [[nodiscard]] bool canFetchActivities() const; - void ingestActivities(const QJsonArray &activities); - void appendMoreActivitiesAvailableEntry(); - void insertOrRemoveDummyFetchingActivity(); - void triggerCaseClashAction(Activity activity); - void displaySingleConflictDialog(const Activity &activity); void setHasSyncConflicts(bool conflictsFound); @@ -201,8 +196,8 @@ private: bool _displayActions = true; int _currentItem = 0; - int _maxActivities = 100; - int _maxActivitiesDays = 30; + static constexpr int _maxActivities = 100; + static constexpr int _maxActivitiesDays = 30; bool _showMoreActivitiesAvailableEntry = false; QPointer _currentConflictDialog; diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp index 3e8ef27ae8..7527d990e4 100644 --- a/test/activitylistmodeltestutils.cpp +++ b/test/activitylistmodeltestutils.cpp @@ -461,7 +461,7 @@ void TestingALM::startFetchJob() void TestingALM::slotProcessReceivedActivities() { - auto finalListCopy = finalList(); + auto finalListCopy = _finalList; for (int i = _numRowsPrev; i < rowCount(); ++i) { const auto modelIndex = index(i, 0); auto activity = finalListCopy.at(modelIndex.row()); @@ -479,10 +479,10 @@ void TestingALM::slotProcessReceivedActivities() } finalListCopy[modelIndex.row()] = activity; - qDebug() << activity._subject << activity._subjectDisplay; } - setFinalList(finalListCopy); + _finalList = finalListCopy; + emit allConflictsChanged(); setAndRefreshCurrentlyFetching(false); emit activitiesProcessed(); From 26777175b724ed1f436ba63c9d76375d01716bc6 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 14:43:51 +0100 Subject: [PATCH 16/22] Add convenience method in TestingALM to fetch more than maximum activities to be displayed in activitylistmodel Signed-off-by: Claudio Cambra --- test/activitylistmodeltestutils.cpp | 16 ++++++++++++++-- test/activitylistmodeltestutils.h | 7 +++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp index 7527d990e4..bbeb1b658a 100644 --- a/test/activitylistmodeltestutils.cpp +++ b/test/activitylistmodeltestutils.cpp @@ -172,6 +172,8 @@ void FakeRemoteActivityStorage::init() void FakeRemoteActivityStorage::initActivityData() { + _activityData = {}; + // Insert activity data for (quint32 i = 0; i <= _numItemsToInsert; i++) { QJsonObject activity; @@ -443,7 +445,7 @@ int FakeRemoteActivityStorage::startingIdLast() const } -void TestingALM::startFetchJob() +void TestingALM::startFetchJobWithNumActivities(const int numActivities) { auto *job = new OCC::JsonApiJob( accountState()->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this); @@ -452,13 +454,23 @@ void TestingALM::startFetchJob() QUrlQuery params; params.addQueryItem(QLatin1String("since"), QString::number(currentItem())); - params.addQueryItem(QLatin1String("limit"), QString::number(50)); + params.addQueryItem(QLatin1String("limit"), QString::number(numActivities)); job->addQueryParams(params); setAndRefreshCurrentlyFetching(true); job->start(); } +void TestingALM::startFetchJob() +{ + startFetchJobWithNumActivities(); +} + +void TestingALM::startMaxActivitiesFetchJob() +{ + startFetchJobWithNumActivities(_maxActivities + 1); +} + void TestingALM::slotProcessReceivedActivities() { auto finalListCopy = _finalList; diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index 16b8614dce..ef92342819 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -77,14 +77,17 @@ class TestingALM : public OCC::ActivityListModel public: TestingALM() = default; - void startFetchJob() override; - public slots: + void startFetchJob() override; + void startMaxActivitiesFetchJob(); void slotProcessReceivedActivities(); signals: void activitiesProcessed(); +private slots: + void startFetchJobWithNumActivities(const int numActivities = 50); + private: int _numRowsPrev = 0; }; From b3f209e6febafebde667b41b1b64b9191bf0dfb9 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 14:45:25 +0100 Subject: [PATCH 17/22] Expose max activities in TestingALM Signed-off-by: Claudio Cambra --- test/activitylistmodeltestutils.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index ef92342819..6b9fe83d6a 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -77,6 +77,10 @@ class TestingALM : public OCC::ActivityListModel public: TestingALM() = default; + int maxActivities() const { return _maxActivities; }; + // Need to include the dummy "show more in activities app" activity + int maxPossibleActivities() const { return maxActivities() + 1; } + public slots: void startFetchJob() override; void startMaxActivitiesFetchJob(); From 6d3bbe828f64fb8e30da9afcdb16dbfe7c159d2d Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 16:33:24 +0100 Subject: [PATCH 18/22] Expose max activity data from activity list model in TestingALM Signed-off-by: Claudio Cambra --- test/activitylistmodeltestutils.cpp | 8 ++++++++ test/activitylistmodeltestutils.h | 12 ++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp index bbeb1b658a..4700a6b6d3 100644 --- a/test/activitylistmodeltestutils.cpp +++ b/test/activitylistmodeltestutils.cpp @@ -444,6 +444,14 @@ int FakeRemoteActivityStorage::startingIdLast() const return _startingId; } +int FakeRemoteActivityStorage::numItemsToInsert() const { + return _numItemsToInsert; +} + +int FakeRemoteActivityStorage::totalNumActivites() const { + return _activityData.count(); +} + void TestingALM::startFetchJobWithNumActivities(const int numActivities) { diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index 6b9fe83d6a..8156116049 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -50,16 +50,16 @@ class FakeRemoteActivityStorage public: static FakeRemoteActivityStorage *instance(); - static void destroy(); - - void init(); - void initActivityData(); - [[nodiscard]] QByteArray activityJsonData(const int sinceId, const int limit); - [[nodiscard]] QJsonValue activityById(const int id) const; [[nodiscard]] int startingIdLast() const; + [[nodiscard]] int numItemsToInsert() const; + [[nodiscard]] int totalNumActivites() const; + + static void destroy(); + void init(); + void initActivityData(); private: QJsonArray _activityData; From af3146da52c098b5a595567d2107151e85beb4fb Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Wed, 1 Mar 2023 16:34:04 +0100 Subject: [PATCH 19/22] Test sorted activity list model sort order Signed-off-by: Claudio Cambra --- test/testsortedactivitylistmodel.cpp | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/testsortedactivitylistmodel.cpp b/test/testsortedactivitylistmodel.cpp index e6214b9337..52c5068dc6 100644 --- a/test/testsortedactivitylistmodel.cpp +++ b/test/testsortedactivitylistmodel.cpp @@ -128,6 +128,62 @@ private slots: addActivity(model, &TestingALM::addErrorToActivityList, testSyncResultErrorActivity); addActivity(model, &TestingALM::addIgnoredFileToList, testFileIgnoredActivity); } + + void testSort() + { + const auto model = testingSortedALM(); + const auto sourceModel = dynamic_cast(model->sourceModel()); + + sourceModel->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast()); + sourceModel->startMaxActivitiesFetchJob(); + QSignalSpy activitiesJob(sourceModel, &TestingALM::activitiesProcessed); + QVERIFY(activitiesJob.wait(3000)); + QCOMPARE(sourceModel->rowCount(), sourceModel->maxPossibleActivities()); + + auto errorSyncFileItemActivity = exampleSyncFileItemActivity(accountState->account()->displayName(), {}); + errorSyncFileItemActivity._message = QStringLiteral("Something went wrong and eveything exploded!"); + errorSyncFileItemActivity._syncFileItemStatus = OCC::SyncFileItem::FatalError; + + addActivity(model, &TestingALM::addSyncFileItemToActivityList, errorSyncFileItemActivity); + addActivity(model, &TestingALM::addSyncFileItemToActivityList, testSyncFileItemActivity); + addActivity(model, &TestingALM::addNotificationToActivityList, testNotificationActivity); + addActivity(model, &TestingALM::addErrorToActivityList, testSyncResultErrorActivity); + addActivity(model, &TestingALM::addIgnoredFileToList, testFileIgnoredActivity); + + const QVector activityDefaultTypeOrder { + OCC::Activity::DummyFetchingActivityType, + OCC::Activity::NotificationType, + OCC::Activity::SyncResultType, + OCC::Activity::SyncFileItemType, + OCC::Activity::ActivityType, + OCC::Activity::DummyMoreActivitiesAvailableType}; + auto currentTypeSection = 1; + auto previousType = activityDefaultTypeOrder[currentTypeSection]; + + for (auto i = 0; i < model->rowCount(); ++i) { + const auto index = model->index(i, 0); + const auto activity = index.data(OCC::ActivityListModel::ActivityRole).value(); + + qDebug() << i << activity._type << activity._subject << activity._message; + if (i == 0) { // Error syncresult activity should be at top + QCOMPARE(activity._type, OCC::Activity::SyncResultType); + QCOMPARE(activity._syncResultStatus, OCC::SyncResult::Error); + } else if (i == 1) { // Error syncfileitem activity should be next up + QCOMPARE(activity._type, OCC::Activity::SyncFileItemType); + QCOMPARE(activity._syncFileItemStatus, OCC::SyncFileItem::FatalError); + } else if (i == 2) { // Ignored file syncfileitem activity should be next up + QCOMPARE(activity._type, OCC::Activity::SyncFileItemType); + QCOMPARE(activity._syncFileItemStatus, OCC::SyncFileItem::FileIgnored); + } else { // Now normal type order + while (i != 3 && activity._type != previousType) { + ++currentTypeSection; + previousType = activityDefaultTypeOrder[currentTypeSection]; + } + + QCOMPARE(activity._type, activityDefaultTypeOrder[currentTypeSection]); + } + } + } }; QTEST_MAIN(TestSortedActivityListModel) From 68d89216bac0f695b200b0fc607f0424b5fff9c2 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Thu, 20 Jul 2023 19:33:29 +0800 Subject: [PATCH 20/22] Fix setting of sourcemodel in sorted activity list test Signed-off-by: Claudio Cambra --- test/testsortedactivitylistmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testsortedactivitylistmodel.cpp b/test/testsortedactivitylistmodel.cpp index 52c5068dc6..9614a77305 100644 --- a/test/testsortedactivitylistmodel.cpp +++ b/test/testsortedactivitylistmodel.cpp @@ -50,7 +50,7 @@ public: model->setAccountState(accountState.data()); QSharedPointer sortedModel(new OCC::SortedActivityListModel); - sortedModel->setActivityListModel(model); + sortedModel->setSourceModel(model); QAbstractItemModelTester sortedModelTester(sortedModel.data()); return sortedModel; From de4ae7cb9c4abab03186134affeb91526c6fe3fd Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Thu, 20 Jul 2023 19:33:48 +0800 Subject: [PATCH 21/22] Fix testing of sync result error activities in sorted activity list test Signed-off-by: Claudio Cambra --- test/testsortedactivitylistmodel.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/testsortedactivitylistmodel.cpp b/test/testsortedactivitylistmodel.cpp index 9614a77305..90b8d74c8b 100644 --- a/test/testsortedactivitylistmodel.cpp +++ b/test/testsortedactivitylistmodel.cpp @@ -67,6 +67,21 @@ public: QCOMPARE(model->rowCount(), originalRowCount + 1); } + void addActivity(QSharedPointer model, + void (OCC::ActivityListModel::*addingMethod)(const OCC::Activity &, OCC::ActivityListModel::ErrorType), + OCC::Activity &activity, + OCC::ActivityListModel::ErrorType type) + { + const auto originalRowCount = model->rowCount(); + const auto sourceModel = dynamic_cast(model->sourceModel()); + + (sourceModel->*addingMethod)(activity, type); + QCOMPARE(model->rowCount(), originalRowCount + 1); + + const auto index = model->index(0, 0); + QVERIFY(index.isValid()); + } + private slots: void initTestCase() { @@ -125,7 +140,7 @@ private slots: addActivity(model, &TestingALM::addSyncFileItemToActivityList, testSyncFileItemActivity); addActivity(model, &TestingALM::addNotificationToActivityList, testNotificationActivity); - addActivity(model, &TestingALM::addErrorToActivityList, testSyncResultErrorActivity); + addActivity(model, &TestingALM::addErrorToActivityList, testSyncResultErrorActivity, OCC::ActivityListModel::ErrorType::SyncError); addActivity(model, &TestingALM::addIgnoredFileToList, testFileIgnoredActivity); } @@ -147,7 +162,7 @@ private slots: addActivity(model, &TestingALM::addSyncFileItemToActivityList, errorSyncFileItemActivity); addActivity(model, &TestingALM::addSyncFileItemToActivityList, testSyncFileItemActivity); addActivity(model, &TestingALM::addNotificationToActivityList, testNotificationActivity); - addActivity(model, &TestingALM::addErrorToActivityList, testSyncResultErrorActivity); + addActivity(model, &TestingALM::addErrorToActivityList, testSyncResultErrorActivity, OCC::ActivityListModel::ErrorType::SyncError); addActivity(model, &TestingALM::addIgnoredFileToList, testFileIgnoredActivity); const QVector activityDefaultTypeOrder { From 74bdbb4aa13bf92bff05e82b3a4605d6a224f9f7 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 25 Jul 2023 08:42:09 +0800 Subject: [PATCH 22/22] Use nodiscard in TestingALM Signed-off-by: Claudio Cambra --- test/activitylistmodeltestutils.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index 8156116049..55310f197b 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -77,9 +77,15 @@ class TestingALM : public OCC::ActivityListModel public: TestingALM() = default; - int maxActivities() const { return _maxActivities; }; + [[nodiscard]] int maxActivities() const + { + return _maxActivities; + }; // Need to include the dummy "show more in activities app" activity - int maxPossibleActivities() const { return maxActivities() + 1; } + [[nodiscard]] int maxPossibleActivities() const + { + return maxActivities() + 1; + } public slots: void startFetchJob() override;