mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Report] Add report reopen action for closed reports (#7442)
- New Command_ReportReopen moderator command (extension 1206) with server handler that reopens resolved/dismissed reports, resets resolution state and notified, and records the reopening note in the audit log - Reopen... button in the report queue action bar, gated to resolved and dismissed reports, with optional note prompt and tooltip - Success confirmations for Assign, Resolve/Dismiss and Reopen now survive the list refresh via a pending status message instead of being clobbered - Dispatch/auth coverage in server_moderator_role_test Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
315cbae345
commit
4a010bd17f
9 changed files with 280 additions and 2 deletions
|
|
@ -38,6 +38,7 @@
|
||||||
#include <libcockatrice/protocol/pb/command_report_assign.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_assign.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_details.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_details.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_list.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_list.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_report_reopen.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_resolve.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_resolve.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_stats.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_stats.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_user_info.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_user_info.pb.h>
|
||||||
|
|
@ -233,12 +234,14 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T
|
||||||
resolveButton = new QPushButton;
|
resolveButton = new QPushButton;
|
||||||
resolveWithNoteButton = new QPushButton;
|
resolveWithNoteButton = new QPushButton;
|
||||||
dismissButton = new QPushButton;
|
dismissButton = new QPushButton;
|
||||||
|
reopenButton = new QPushButton;
|
||||||
viewReplayButton = new QPushButton;
|
viewReplayButton = new QPushButton;
|
||||||
joinGameButton = new QPushButton;
|
joinGameButton = new QPushButton;
|
||||||
connect(assignButton, &QPushButton::clicked, this, &TabReport::assignReport);
|
connect(assignButton, &QPushButton::clicked, this, &TabReport::assignReport);
|
||||||
connect(resolveButton, &QPushButton::clicked, this, [this]() { resolveReport(false, false); });
|
connect(resolveButton, &QPushButton::clicked, this, [this]() { resolveReport(false, false); });
|
||||||
connect(resolveWithNoteButton, &QPushButton::clicked, this, [this]() { resolveReport(false, true); });
|
connect(resolveWithNoteButton, &QPushButton::clicked, this, [this]() { resolveReport(false, true); });
|
||||||
connect(dismissButton, &QPushButton::clicked, this, [this]() { resolveReport(true, true); });
|
connect(dismissButton, &QPushButton::clicked, this, [this]() { resolveReport(true, true); });
|
||||||
|
connect(reopenButton, &QPushButton::clicked, this, &TabReport::reopenReport);
|
||||||
connect(viewReplayButton, &QPushButton::clicked, this, &TabReport::viewReplay);
|
connect(viewReplayButton, &QPushButton::clicked, this, &TabReport::viewReplay);
|
||||||
connect(joinGameButton, &QPushButton::clicked, this, &TabReport::joinGame);
|
connect(joinGameButton, &QPushButton::clicked, this, &TabReport::joinGame);
|
||||||
|
|
||||||
|
|
@ -249,6 +252,7 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T
|
||||||
actionBar->addWidget(resolveButton);
|
actionBar->addWidget(resolveButton);
|
||||||
actionBar->addWidget(resolveWithNoteButton);
|
actionBar->addWidget(resolveWithNoteButton);
|
||||||
actionBar->addWidget(dismissButton);
|
actionBar->addWidget(dismissButton);
|
||||||
|
actionBar->addWidget(reopenButton);
|
||||||
actionBar->addSpacing(20);
|
actionBar->addSpacing(20);
|
||||||
actionBar->addWidget(viewReplayButton);
|
actionBar->addWidget(viewReplayButton);
|
||||||
actionBar->addWidget(joinGameButton);
|
actionBar->addWidget(joinGameButton);
|
||||||
|
|
@ -315,6 +319,8 @@ void TabReport::retranslateUi()
|
||||||
resolveButton->setText(tr("Resolve"));
|
resolveButton->setText(tr("Resolve"));
|
||||||
resolveWithNoteButton->setText(tr("Resolve with note..."));
|
resolveWithNoteButton->setText(tr("Resolve with note..."));
|
||||||
dismissButton->setText(tr("Dismiss..."));
|
dismissButton->setText(tr("Dismiss..."));
|
||||||
|
reopenButton->setText(tr("Reopen..."));
|
||||||
|
reopenButton->setToolTip(tr("Available for resolved or dismissed reports."));
|
||||||
viewReplayButton->setText(tr("View Replay"));
|
viewReplayButton->setText(tr("View Replay"));
|
||||||
joinGameButton->setText(tr("Join Game"));
|
joinGameButton->setText(tr("Join Game"));
|
||||||
}
|
}
|
||||||
|
|
@ -346,6 +352,7 @@ void TabReport::reportListResponse(const Response &response)
|
||||||
refreshButton->setEnabled(true);
|
refreshButton->setEnabled(true);
|
||||||
|
|
||||||
if (response.response_code() != Response::RespOk) {
|
if (response.response_code() != Response::RespOk) {
|
||||||
|
pendingStatusMessage.clear();
|
||||||
statusLabel->setText(tr("Failed to load reports."));
|
statusLabel->setText(tr("Failed to load reports."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -359,6 +366,11 @@ void TabReport::reportListResponse(const Response &response)
|
||||||
applyFilters();
|
applyFilters();
|
||||||
updateStats();
|
updateStats();
|
||||||
|
|
||||||
|
if (!pendingStatusMessage.isEmpty()) {
|
||||||
|
statusLabel->setText(pendingStatusMessage);
|
||||||
|
pendingStatusMessage.clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedReportIdBeforeRefresh >= 0) {
|
if (selectedReportIdBeforeRefresh >= 0) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (int row = 0; row < table->rowCount(); ++row) {
|
for (int row = 0; row < table->rowCount(); ++row) {
|
||||||
|
|
@ -579,6 +591,7 @@ void TabReport::updateActionStates()
|
||||||
resolveButton->setEnabled(status == "open" || status == "assigned");
|
resolveButton->setEnabled(status == "open" || status == "assigned");
|
||||||
resolveWithNoteButton->setEnabled(status == "open" || status == "assigned");
|
resolveWithNoteButton->setEnabled(status == "open" || status == "assigned");
|
||||||
dismissButton->setEnabled(status == "open" || status == "assigned");
|
dismissButton->setEnabled(status == "open" || status == "assigned");
|
||||||
|
reopenButton->setEnabled(status == "resolved" || status == "dismissed");
|
||||||
|
|
||||||
const bool hasGameId = report.game_id() > 0;
|
const bool hasGameId = report.game_id() > 0;
|
||||||
const bool hasReplay = hasGameId && report.has_replay_id() && report.replay_id() > 0;
|
const bool hasReplay = hasGameId && report.has_replay_id() && report.replay_id() > 0;
|
||||||
|
|
@ -592,6 +605,7 @@ void TabReport::setActionsEnabled(bool enabled)
|
||||||
resolveButton->setEnabled(enabled);
|
resolveButton->setEnabled(enabled);
|
||||||
resolveWithNoteButton->setEnabled(enabled);
|
resolveWithNoteButton->setEnabled(enabled);
|
||||||
dismissButton->setEnabled(enabled);
|
dismissButton->setEnabled(enabled);
|
||||||
|
reopenButton->setEnabled(enabled);
|
||||||
viewReplayButton->setEnabled(false);
|
viewReplayButton->setEnabled(false);
|
||||||
joinGameButton->setEnabled(false);
|
joinGameButton->setEnabled(false);
|
||||||
commentButton->setEnabled(enabled);
|
commentButton->setEnabled(enabled);
|
||||||
|
|
@ -645,7 +659,7 @@ void TabReport::assignReport()
|
||||||
void TabReport::assignResponse(const Response &response)
|
void TabReport::assignResponse(const Response &response)
|
||||||
{
|
{
|
||||||
if (response.response_code() == Response::RespOk) {
|
if (response.response_code() == Response::RespOk) {
|
||||||
statusLabel->setText(tr("Assigned."));
|
pendingStatusMessage = tr("Assigned.");
|
||||||
refreshList();
|
refreshList();
|
||||||
} else {
|
} else {
|
||||||
statusLabel->setText(tr("Assignment failed."));
|
statusLabel->setText(tr("Assignment failed."));
|
||||||
|
|
@ -689,7 +703,46 @@ void TabReport::resolveReport(bool dismissed, bool promptNote)
|
||||||
void TabReport::resolveResponse(const Response &response)
|
void TabReport::resolveResponse(const Response &response)
|
||||||
{
|
{
|
||||||
if (response.response_code() == Response::RespOk) {
|
if (response.response_code() == Response::RespOk) {
|
||||||
statusLabel->setText(tr("Done."));
|
pendingStatusMessage = tr("Done.");
|
||||||
|
refreshList();
|
||||||
|
} else {
|
||||||
|
statusLabel->setText(tr("Action failed."));
|
||||||
|
updateActionStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabReport::reopenReport()
|
||||||
|
{
|
||||||
|
const int reportId = selectedReportId();
|
||||||
|
if (reportId < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
QString note = QInputDialog::getText(this, tr("Reopen Report"), tr("Reopening note (optional):"), QLineEdit::Normal,
|
||||||
|
QString(), &ok);
|
||||||
|
if (!ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActionsEnabled(false);
|
||||||
|
statusLabel->setText(tr("Reopening..."));
|
||||||
|
|
||||||
|
Command_ReportReopen cmd;
|
||||||
|
cmd.set_report_id(reportId);
|
||||||
|
if (!note.isEmpty()) {
|
||||||
|
cmd.set_reopen_note(note.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||||
|
connect(pend, &PendingCommand::finished, this, &TabReport::reopenResponse);
|
||||||
|
client->sendCommand(pend);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabReport::reopenResponse(const Response &response)
|
||||||
|
{
|
||||||
|
if (response.response_code() == Response::RespOk) {
|
||||||
|
pendingStatusMessage = tr("Reopened.");
|
||||||
refreshList();
|
refreshList();
|
||||||
} else {
|
} else {
|
||||||
statusLabel->setText(tr("Action failed."));
|
statusLabel->setText(tr("Action failed."));
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ private slots:
|
||||||
void reportDetailsResponse(const Response &response);
|
void reportDetailsResponse(const Response &response);
|
||||||
void requestStats();
|
void requestStats();
|
||||||
void statsResponse(const Response &response);
|
void statsResponse(const Response &response);
|
||||||
|
void reopenReport();
|
||||||
|
void reopenResponse(const Response &response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int selectedReportId() const;
|
int selectedReportId() const;
|
||||||
|
|
@ -87,6 +89,7 @@ private:
|
||||||
QPushButton *resolveButton;
|
QPushButton *resolveButton;
|
||||||
QPushButton *resolveWithNoteButton;
|
QPushButton *resolveWithNoteButton;
|
||||||
QPushButton *dismissButton;
|
QPushButton *dismissButton;
|
||||||
|
QPushButton *reopenButton;
|
||||||
QPushButton *viewReplayButton;
|
QPushButton *viewReplayButton;
|
||||||
QPushButton *joinGameButton;
|
QPushButton *joinGameButton;
|
||||||
QLabel *statusLabel;
|
QLabel *statusLabel;
|
||||||
|
|
@ -108,6 +111,7 @@ private:
|
||||||
QLabel *userContextNotesLabel;
|
QLabel *userContextNotesLabel;
|
||||||
QLabel *userContextRecentReportsLabel;
|
QLabel *userContextRecentReportsLabel;
|
||||||
QString lastRequestedUser;
|
QString lastRequestedUser;
|
||||||
|
QString pendingStatusMessage;
|
||||||
QGroupBox *statsGroup;
|
QGroupBox *statsGroup;
|
||||||
QLabel *statsTotalLabel;
|
QLabel *statsTotalLabel;
|
||||||
QLabel *statsTrendLabel;
|
QLabel *statsTrendLabel;
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ set(PROTO_FILES
|
||||||
command_report_details.proto
|
command_report_details.proto
|
||||||
command_report_list.proto
|
command_report_list.proto
|
||||||
command_report_my_list.proto
|
command_report_my_list.proto
|
||||||
|
command_report_reopen.proto
|
||||||
command_report_resolve.proto
|
command_report_resolve.proto
|
||||||
command_report_stats.proto
|
command_report_stats.proto
|
||||||
command_report_user_info.proto
|
command_report_user_info.proto
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "moderator_commands.proto";
|
||||||
|
|
||||||
|
message Command_ReportReopen {
|
||||||
|
extend ModeratorCommand {
|
||||||
|
optional Command_ReportReopen ext = 1206;
|
||||||
|
}
|
||||||
|
required int32 report_id = 1;
|
||||||
|
optional string reopen_note = 2;
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ message ModeratorCommand {
|
||||||
REPLAY_DOWNLOAD_BY_GAME_ID = 1203;
|
REPLAY_DOWNLOAD_BY_GAME_ID = 1203;
|
||||||
REPORT_USER_INFO = 1204;
|
REPORT_USER_INFO = 1204;
|
||||||
REPORT_STATS = 1205;
|
REPORT_STATS = 1205;
|
||||||
|
REPORT_REOPEN = 1206;
|
||||||
}
|
}
|
||||||
extensions 100 to max;
|
extensions 100 to max;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@
|
||||||
#include <libcockatrice/protocol/pb/command_report_details.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_details.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_list.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_list.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_my_list.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_my_list.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_report_reopen.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_resolve.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_resolve.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_stats.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_stats.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_report_user_info.pb.h>
|
#include <libcockatrice/protocol/pb/command_report_user_info.pb.h>
|
||||||
|
|
@ -434,6 +435,8 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo
|
||||||
return cmdReportAssign(cmd.GetExtension(Command_ReportAssign::ext), rc);
|
return cmdReportAssign(cmd.GetExtension(Command_ReportAssign::ext), rc);
|
||||||
case ModeratorCommand::REPORT_RESOLVE:
|
case ModeratorCommand::REPORT_RESOLVE:
|
||||||
return cmdReportResolve(cmd.GetExtension(Command_ReportResolve::ext), rc);
|
return cmdReportResolve(cmd.GetExtension(Command_ReportResolve::ext), rc);
|
||||||
|
case ModeratorCommand::REPORT_REOPEN:
|
||||||
|
return cmdReportReopen(cmd.GetExtension(Command_ReportReopen::ext), rc);
|
||||||
case ModeratorCommand::VIEWLOG_HISTORY:
|
case ModeratorCommand::VIEWLOG_HISTORY:
|
||||||
return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::ext), rc, true);
|
return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::ext), rc, true);
|
||||||
case ModeratorCommand::GRANT_REPLAY_ACCESS:
|
case ModeratorCommand::GRANT_REPLAY_ACCESS:
|
||||||
|
|
@ -2791,6 +2794,42 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportMyList(const Comm
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode AbstractServerSocketInterface::cmdReportReopen(const Command_ReportReopen &cmd,
|
||||||
|
ResponseContainer & /*rc*/)
|
||||||
|
{
|
||||||
|
if (!sqlInterface->checkSql()) {
|
||||||
|
return Response::RespInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
int reportId = cmd.report_id();
|
||||||
|
QString note = textFromStdString(cmd.reopen_note());
|
||||||
|
|
||||||
|
QSqlQuery *query = sqlInterface->prepareQuery("UPDATE {prefix}_reports "
|
||||||
|
"SET status = 'open', resolution_note = NULL, "
|
||||||
|
"resolution_time = NULL, resolved_by = NULL, assigned_to = NULL, "
|
||||||
|
"notified = 0 "
|
||||||
|
"WHERE id = :id AND status IN ('resolved', 'dismissed')");
|
||||||
|
|
||||||
|
query->bindValue(":id", reportId);
|
||||||
|
|
||||||
|
if (!sqlInterface->execSqlQuery(query)) {
|
||||||
|
return Response::RespInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query->numRowsAffected() == 0) {
|
||||||
|
return Response::RespInvalidData;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString auditDetails = QString("Report #%1 reopened").arg(reportId);
|
||||||
|
if (!note.isEmpty()) {
|
||||||
|
auditDetails += QString(". Reason: %1").arg(note.left(200));
|
||||||
|
}
|
||||||
|
sqlInterface->addAuditRecord(QString::number(reportId), this->getAddress(),
|
||||||
|
QString::fromStdString(userInfo->clientid()), "REPORT_REOPENED", auditDetails, true);
|
||||||
|
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdReportDetails(const Command_ReportDetails &cmd,
|
Response::ResponseCode AbstractServerSocketInterface::cmdReportDetails(const Command_ReportDetails &cmd,
|
||||||
ResponseContainer &rc)
|
ResponseContainer &rc)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ class Command_ReportAssign;
|
||||||
class Command_ReportDetails;
|
class Command_ReportDetails;
|
||||||
class Command_ReportList;
|
class Command_ReportList;
|
||||||
class Command_ReportMyList;
|
class Command_ReportMyList;
|
||||||
|
class Command_ReportReopen;
|
||||||
class Command_ReportResolve;
|
class Command_ReportResolve;
|
||||||
class Command_ReportStats;
|
class Command_ReportStats;
|
||||||
class Command_ReportUserInfo;
|
class Command_ReportUserInfo;
|
||||||
|
|
@ -181,6 +182,7 @@ private:
|
||||||
Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdReportAssign(const Command_ReportAssign &cmd, ResponseContainer &);
|
Response::ResponseCode cmdReportAssign(const Command_ReportAssign &cmd, ResponseContainer &);
|
||||||
Response::ResponseCode cmdReportResolve(const Command_ReportResolve &cmd, ResponseContainer &);
|
Response::ResponseCode cmdReportResolve(const Command_ReportResolve &cmd, ResponseContainer &);
|
||||||
|
Response::ResponseCode cmdReportReopen(const Command_ReportReopen &cmd, ResponseContainer &);
|
||||||
Response::ResponseCode cmdReportUserInfo(const Command_ReportUserInfo &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdReportUserInfo(const Command_ReportUserInfo &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdReportStats(const Command_ReportStats &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdReportStats(const Command_ReportStats &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc);
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,18 @@ cockatrice_add_test(
|
||||||
LABELS
|
LABELS
|
||||||
server
|
server
|
||||||
)
|
)
|
||||||
|
cockatrice_add_test(
|
||||||
|
NAME
|
||||||
|
server_moderator_role_test
|
||||||
|
NO_SANITIZE
|
||||||
|
SOURCES
|
||||||
|
server_moderator_role_test.cpp
|
||||||
|
LIBS
|
||||||
|
libcockatrice_network
|
||||||
|
libcockatrice_rng
|
||||||
|
LABELS
|
||||||
|
server
|
||||||
|
)
|
||||||
cockatrice_add_test(
|
cockatrice_add_test(
|
||||||
NAME
|
NAME
|
||||||
server_game_join_test
|
server_game_join_test
|
||||||
|
|
|
||||||
156
tests/server_moderator_role_test.cpp
Normal file
156
tests/server_moderator_role_test.cpp
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
/** @file server_moderator_role_test.cpp
|
||||||
|
* @brief Tests for the moderator staff role authorization and dispatch.
|
||||||
|
* @ingroup Tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libcockatrice/protocol/pb/response.pb.h"
|
||||||
|
#include "libcockatrice/protocol/pb/server_message.pb.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <libcockatrice/network/server/remote/server.h>
|
||||||
|
#include <libcockatrice/network/server/remote/server_protocolhandler.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_report_reopen.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/commands.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||||
|
#include <libcockatrice/rng/rng_abstract.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// The server_remote library references the global RNG, which is normally
|
||||||
|
// defined by the servatrice/client executable main(). Provide a stub so the
|
||||||
|
// unit test can link against it.
|
||||||
|
RNG_Abstract *rng = nullptr;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
class TestModeratorHandler : public Server_ProtocolHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TestModeratorHandler(Server *_server) : Server_ProtocolHandler(_server, nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getAddress() const override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
QString getConnectionType() const override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer the last response code sent to the client so tests can assert on
|
||||||
|
// the outcome of processCommandContainer().
|
||||||
|
Response::ResponseCode lastResponseCode = Response::RespNothing;
|
||||||
|
int dispatchCount = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void transmitProtocolItem(const ServerMessage &item) override
|
||||||
|
{
|
||||||
|
if (item.message_type() == ServerMessage::RESPONSE) {
|
||||||
|
lastResponseCode = item.response().response_code();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode
|
||||||
|
processExtendedModeratorCommand(int cmdType, const ModeratorCommand &, ResponseContainer &) override
|
||||||
|
{
|
||||||
|
++dispatchCount;
|
||||||
|
// Fail closed for anything not explicitly handled.
|
||||||
|
if (cmdType != ModeratorCommand::REPORT_REOPEN) {
|
||||||
|
return Response::RespFunctionNotAllowed;
|
||||||
|
}
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModeratorRoleTest : public ::testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
Server server;
|
||||||
|
TestModeratorHandler handler{&server};
|
||||||
|
|
||||||
|
void setUserLevel(uint32_t level)
|
||||||
|
{
|
||||||
|
ServerInfo_User user;
|
||||||
|
user.set_user_level(level);
|
||||||
|
handler.setUserInfo(user);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(ModeratorRoleTest, RejectsWhenNotLoggedIn)
|
||||||
|
{
|
||||||
|
CommandContainer cont;
|
||||||
|
cont.add_moderator_command();
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModeratorRoleTest, RejectsPlainUser)
|
||||||
|
{
|
||||||
|
setUserLevel(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered);
|
||||||
|
|
||||||
|
CommandContainer cont;
|
||||||
|
cont.add_moderator_command();
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModeratorRoleTest, DispatchesToModeratorCommandForModerator)
|
||||||
|
{
|
||||||
|
setUserLevel(ServerInfo_User::IsModerator);
|
||||||
|
|
||||||
|
CommandContainer cont;
|
||||||
|
ModeratorCommand *cmd = cont.add_moderator_command();
|
||||||
|
cmd->MutableExtension(Command_ReportReopen::ext);
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespOk);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModeratorRoleTest, DispatchesToModeratorCommandForAdmin)
|
||||||
|
{
|
||||||
|
setUserLevel(ServerInfo_User::IsModerator | ServerInfo_User::IsAdmin);
|
||||||
|
|
||||||
|
CommandContainer cont;
|
||||||
|
ModeratorCommand *cmd = cont.add_moderator_command();
|
||||||
|
cmd->MutableExtension(Command_ReportReopen::ext);
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespOk);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModeratorRoleTest, RejectsDeveloperThatIsNotModerator)
|
||||||
|
{
|
||||||
|
setUserLevel(ServerInfo_User::IsDeveloper);
|
||||||
|
|
||||||
|
CommandContainer cont;
|
||||||
|
cont.add_moderator_command();
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ModeratorRoleTest, FailClosedForUnknownModeratorCommand)
|
||||||
|
{
|
||||||
|
setUserLevel(ServerInfo_User::IsModerator);
|
||||||
|
|
||||||
|
CommandContainer cont;
|
||||||
|
cont.add_moderator_command(); // no extension set -> getPbExtension() returns -1
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespFunctionNotAllowed);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue