mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
248 lines
7.6 KiB
C++
248 lines
7.6 KiB
C++
#include "interface/widgets/server/user/user_games_model.h"
|
|
|
|
#include <QAbstractItemModelTester>
|
|
#include <QCoreApplication>
|
|
#include <QPersistentModelIndex>
|
|
#include <QSignalSpy>
|
|
#include <gtest/gtest.h>
|
|
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
|
#include <libcockatrice/protocol/pb/commands.pb.h>
|
|
#include <libcockatrice/protocol/pb/response.pb.h>
|
|
#include <libcockatrice/protocol/pb/response_get_games_of_user.pb.h>
|
|
#include <libcockatrice/protocol/pb/server_message.pb.h>
|
|
#include <libcockatrice/protocol/pending_command.h>
|
|
#include <libcockatrice/rng/rng_abstract.h>
|
|
#include <memory>
|
|
|
|
RNG_Abstract *rng = nullptr; // required by the transitive server library; this suite sends no game commands
|
|
|
|
namespace
|
|
{
|
|
class GamesClient : public AbstractClient
|
|
{
|
|
public:
|
|
QList<CommandContainer> sent;
|
|
|
|
GamesClient()
|
|
{
|
|
setStatus(StatusLoggedIn);
|
|
}
|
|
|
|
~GamesClient() override
|
|
{
|
|
qDeleteAll(pendingCommands);
|
|
pendingCommands.clear();
|
|
}
|
|
|
|
void changeStatus(ClientStatus status)
|
|
{
|
|
setStatus(status);
|
|
}
|
|
|
|
void answer(int request, Response response)
|
|
{
|
|
response.set_cmd_id(sent.at(request).cmd_id());
|
|
ServerMessage message;
|
|
message.set_message_type(ServerMessage::RESPONSE);
|
|
*message.mutable_response() = response;
|
|
processProtocolItem(message);
|
|
}
|
|
|
|
protected:
|
|
void sendCommandContainer(const CommandContainer &command) override
|
|
{
|
|
sent.append(command);
|
|
}
|
|
};
|
|
|
|
Response gamesResponse(std::initializer_list<int> ids)
|
|
{
|
|
Response response;
|
|
response.set_response_code(Response::RespOk);
|
|
auto *games = response.MutableExtension(Response_GetGamesOfUser::ext);
|
|
for (int id : ids) {
|
|
auto *game = games->add_game_list();
|
|
game->set_game_id(id);
|
|
game->set_room_id(7);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
using State = UserGamesModel::State;
|
|
|
|
TEST(UserGamesModel, OfflineUserDoesNotSendARequest)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client);
|
|
model.setUser("Alice", false);
|
|
model.refresh();
|
|
EXPECT_TRUE(client.sent.isEmpty());
|
|
EXPECT_EQ(State::Offline, model.getState());
|
|
EXPECT_FALSE(model.canRefresh());
|
|
EXPECT_EQ(0, model.rowCount());
|
|
}
|
|
|
|
TEST(UserGamesModel, PresenceLossInvalidatesTheOutstandingResponse)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client);
|
|
model.setUser("Alice", true);
|
|
EXPECT_EQ(State::Loading, model.getState());
|
|
EXPECT_FALSE(model.canRefresh());
|
|
model.setUser("Alice", false);
|
|
client.answer(0, gamesResponse({1}));
|
|
EXPECT_EQ(State::Offline, model.getState());
|
|
EXPECT_EQ(0, model.rowCount());
|
|
|
|
model.setUser("Alice", true);
|
|
client.answer(1, gamesResponse({2}));
|
|
EXPECT_EQ(State::Ready, model.getState());
|
|
EXPECT_TRUE(model.canRefresh());
|
|
EXPECT_EQ(2, model.data(model.index(0), UserGamesModel::GameRole).value<ServerInfo_Game>().game_id());
|
|
}
|
|
|
|
TEST(UserGamesModel, SwitchingBackToTheSameUserDoesNotAcceptTheirOldRequest)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client);
|
|
model.setUser("Alice", true);
|
|
model.setUser("Bob", true);
|
|
model.setUser("Alice", true);
|
|
client.answer(2, gamesResponse({3}));
|
|
client.answer(0, gamesResponse({1}));
|
|
client.answer(1, gamesResponse({2}));
|
|
EXPECT_EQ(1, model.rowCount());
|
|
EXPECT_EQ(3, model.data(model.index(0), UserGamesModel::GameRole).value<ServerInfo_Game>().game_id());
|
|
}
|
|
|
|
TEST(UserGamesModel, RefreshSupersedesThePreviousRequest)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client);
|
|
model.setUser("Alice", true);
|
|
model.refresh();
|
|
client.answer(0, gamesResponse({1}));
|
|
EXPECT_EQ(State::Loading, model.getState());
|
|
EXPECT_EQ(0, model.rowCount());
|
|
client.answer(1, gamesResponse({2}));
|
|
EXPECT_EQ(State::Ready, model.getState());
|
|
EXPECT_EQ(2, model.data(model.index(0), UserGamesModel::GameRole).value<ServerInfo_Game>().game_id());
|
|
}
|
|
|
|
TEST(UserGamesModel, EmptySuccessAndMissingPayloadAreDifferentStates)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client);
|
|
model.setUser("Alice", true);
|
|
client.answer(0, gamesResponse({}));
|
|
EXPECT_EQ(State::Empty, model.getState());
|
|
EXPECT_TRUE(model.canRefresh());
|
|
|
|
model.refresh();
|
|
Response missingPayload;
|
|
missingPayload.set_response_code(Response::RespOk);
|
|
client.answer(1, missingPayload);
|
|
EXPECT_EQ(State::Error, model.getState());
|
|
EXPECT_TRUE(model.canRefresh());
|
|
}
|
|
|
|
TEST(UserGamesModel, FailedRequestsCanBeRetried)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client);
|
|
model.setUser("Alice", true);
|
|
Response error;
|
|
error.set_response_code(Response::RespNameNotFound);
|
|
client.answer(0, error);
|
|
EXPECT_EQ(State::Error, model.getState());
|
|
model.refresh();
|
|
client.answer(1, gamesResponse({4}));
|
|
EXPECT_EQ(State::Ready, model.getState());
|
|
}
|
|
|
|
TEST(UserGamesModel, DisconnectRejectsLateRepliesAndReconnectFetchesAgain)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client);
|
|
model.setUser("Alice", true);
|
|
client.changeStatus(StatusDisconnecting);
|
|
client.answer(0, gamesResponse({1}));
|
|
EXPECT_EQ(State::Disconnected, model.getState());
|
|
EXPECT_FALSE(model.canRefresh());
|
|
model.refresh();
|
|
EXPECT_EQ(1, client.sent.size());
|
|
client.changeStatus(StatusLoggedIn);
|
|
client.answer(1, gamesResponse({2}));
|
|
EXPECT_EQ(State::Ready, model.getState());
|
|
}
|
|
|
|
TEST(UserGamesModel, ClientDestructionClearsGamesAndDisablesRefresh)
|
|
{
|
|
auto client = std::make_unique<GamesClient>();
|
|
UserGamesModel model(client.get());
|
|
model.setUser("Alice", true);
|
|
client->answer(0, gamesResponse({1}));
|
|
client.reset();
|
|
EXPECT_EQ(State::Disconnected, model.getState());
|
|
EXPECT_EQ(0, model.rowCount());
|
|
EXPECT_FALSE(model.canRefresh());
|
|
model.refresh();
|
|
EXPECT_EQ(State::Disconnected, model.getState());
|
|
}
|
|
|
|
TEST(UserGamesModel, ResetInvalidatesAnOpenGameActionAndPreservesTheModelContract)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client);
|
|
QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::Fatal);
|
|
model.setUser("Alice", true);
|
|
client.answer(0, gamesResponse({1, 2, 3}));
|
|
QPersistentModelIndex selected(model.index(1));
|
|
EXPECT_TRUE(selected.isValid());
|
|
model.refresh();
|
|
EXPECT_FALSE(selected.isValid());
|
|
EXPECT_EQ(0, model.rowCount());
|
|
client.answer(1, gamesResponse({4}));
|
|
EXPECT_EQ(1, model.rowCount());
|
|
}
|
|
|
|
TEST(UserGamesModel, StateListenerCanSelectAnotherUserBeforeTheRequestIsSent)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client);
|
|
bool switched = false;
|
|
QObject::connect(&model, &UserGamesModel::stateChanged, &model, [&] {
|
|
if (!switched && model.getState() == State::Loading) {
|
|
switched = true;
|
|
model.setUser("Bob", false);
|
|
}
|
|
});
|
|
model.setUser("Alice", true);
|
|
EXPECT_TRUE(client.sent.isEmpty());
|
|
EXPECT_EQ(State::Offline, model.getState());
|
|
}
|
|
|
|
TEST(UserGamesModel, TimedOutResponseCannotReplaceARetry)
|
|
{
|
|
GamesClient client;
|
|
UserGamesModel model(&client, 50);
|
|
model.setUser("Alice", true);
|
|
QSignalSpy changes(&model, &UserGamesModel::stateChanged);
|
|
ASSERT_TRUE(changes.wait(1000));
|
|
ASSERT_EQ(State::Error, model.getState());
|
|
EXPECT_TRUE(model.canRefresh());
|
|
model.refresh();
|
|
client.answer(0, gamesResponse({1}));
|
|
EXPECT_EQ(State::Loading, model.getState());
|
|
client.answer(1, gamesResponse({2}));
|
|
EXPECT_EQ(State::Ready, model.getState());
|
|
EXPECT_EQ(2, model.data(model.index(0), UserGamesModel::GameRole).value<ServerInfo_Game>().game_id());
|
|
}
|
|
} // namespace
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
QCoreApplication app(argc, argv);
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|