Files
chatterino2/tests/src/EventSubMessages.cpp

358 lines
11 KiB
C++

#include "common/Literals.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "lib/Snapshot.hpp"
#include "messages/Message.hpp"
#include "mocks/BaseApplication.hpp"
#include "mocks/Logging.hpp"
#include "mocks/TwitchIrcServer.hpp"
#include "providers/twitch/eventsub/Connection.hpp"
#include "Test.hpp"
#include "util/QCompareTransparent.hpp"
#include <QString>
using namespace chatterino;
using namespace literals;
namespace {
/// Controls whether snapshots will be updated (true) or verified (false)
///
/// In CI, all snapshots must be verified, thus the integrity tests checks for
/// this constant.
///
/// When adding a test, start with `{ "input": {...} }` and set this to `true`
/// to generate an initial snapshot. Make sure to verify the output!
constexpr bool UPDATE_SNAPSHOTS = false;
const QString CATEGORY = u"EventSub"_s;
/// JSON for the `subscription` object in `payload`
const std::map<QString, std::string_view, QCompareCaseInsensitive>
SUBSCRIPTIONS{
{
"channel-moderate",
R"({
"id": "7297f7eb-3bf5-461f-8ae6-7cd7781ebce3",
"status": "enabled",
"type": "channel.moderate",
"version": "2",
"cost": 0,
"condition": {
"broadcaster_user_id": "11148817",
"moderator_user_id": "11148817"
},
"transport": {
"method": "websocket",
"session_id": "AgoQUlB8aB2SSsavWVfcs5ljnBIGY2VsbC1j"
},
"created_at": "2024-02-23T21:12:33.771005262Z"
})",
},
{
"channel-ban",
R"({
"id": "4aa632e0-fca3-590b-e981-bbd12abdb3fe",
"status": "enabled",
"type": "channel.ban",
"version": "1",
"condition": { "broadcaster_user_id": "11148817" },
"transport": { "method": "websocket", "session_id": "38de428e_b11f07be" },
"created_at": "2023-05-20T12:30:55.518375571Z",
"cost": 0
})",
},
{
"automod-message-hold",
R"({
"id": "a3122e32-6498-4847-8675-109b9b94f29c",
"status": "enabled",
"type": "automod.message.hold",
"version": "2",
"condition": {
"broadcaster_user_id": "489584266",
"moderator_user_id": "489584266"
},
"transport": {
"method":"websocket",
"session_id":"AgoQ59RRLw0mS6S000QtK8f54BIGY2VsbC1j"
},
"created_at": "2025-02-28T15:55:37.85489173Z",
"cost": 0
})",
},
{
"automod-message-update",
R"({
"id": "a3122e32-6498-4847-8675-109b9b94f29c",
"status": "enabled",
"type": "automod.message.update",
"version": "2",
"condition": {
"broadcaster_user_id": "489584266",
"moderator_user_id": "489584266"
},
"transport": {
"method":"websocket",
"session_id":"AgoQ59RRLw0mS6S000QtK8f54BIGY2VsbC1j"
},
"created_at": "2025-02-28T15:55:37.85489173Z",
"cost": 0
})",
},
{
"channel-suspicious-user-message",
R"({
"id": "a3122e32-6498-4847-8675-109b9b94f29c",
"status": "enabled",
"type": "channel.suspicious_user.message",
"version": "1",
"condition": {
"broadcaster_user_id": "489584266",
"moderator_user_id": "489584266"
},
"transport": {
"method":"websocket",
"session_id":"AgoQ59RRLw0mS6S000QtK8f54BIGY2VsbC1j"
},
"created_at": "2025-02-28T15:55:37.85489173Z",
"cost": 0
})",
},
{
"channel-suspicious-user-update",
R"({
"id": "a3122e32-6498-4847-8675-109b9b94f29c",
"status": "enabled",
"type": "channel.suspicious_user.update",
"version": "1",
"condition": {
"broadcaster_user_id": "489584266",
"moderator_user_id": "489584266"
},
"transport": {
"method":"websocket",
"session_id":"AgoQ59RRLw0mS6S000QtK8f54BIGY2VsbC1j"
},
"created_at": "2025-02-28T15:55:37.85489173Z",
"cost": 0
})",
},
{
"channel-chat-user-message-hold",
R"({
"id": "a3122e32-6498-4847-8675-109b9b94f29c",
"status": "enabled",
"type": "channel.chat.user_message_hold",
"version": "1",
"condition": {
"broadcaster_user_id": "11148817",
"moderator_user_id": "489584266"
},
"transport": {
"method":"websocket",
"session_id":"AgoQ59RRLw0mS6S000QtK8f54BIGY2VsbC1j"
},
"created_at": "2025-02-28T15:55:37.85489173Z",
"cost": 0
})",
},
{
"channel-chat-user-message-update",
R"({
"id": "a3122e32-6498-4847-8675-109b9b94f29c",
"status": "enabled",
"type": "channel.chat.user_message_update",
"version": "1",
"condition": {
"broadcaster_user_id": "11148817",
"moderator_user_id": "489584266"
},
"transport": {
"method":"websocket",
"session_id":"AgoQ59RRLw0mS6S000QtK8f54BIGY2VsbC1j"
},
"created_at": "2025-02-28T15:55:37.85489173Z",
"cost": 0
})",
},
};
class MockApplication : public mock::BaseApplication
{
public:
MockApplication()
: highlights(this->settings, &this->accounts)
{
}
ILogging *getChatLogger() override
{
return &this->logging;
}
ITwitchIrcServer *getTwitch() override
{
return &this->twitch;
}
AccountController *getAccounts() override
{
return &this->accounts;
}
HighlightController *getHighlights() override
{
return &this->highlights;
}
mock::EmptyLogging logging;
mock::MockTwitchIrcServer twitch;
AccountController accounts;
HighlightController highlights;
};
std::shared_ptr<TwitchChannel> makeMockTwitchChannel(const QString &name)
{
auto chan = std::make_shared<TwitchChannel>(name);
return chan;
}
boost::beast::flat_buffer makePayload(std::string_view subJson,
QJsonObject event)
{
auto subscription =
QJsonDocument::fromJson(
QByteArray::fromRawData(subJson.data(),
static_cast<qsizetype>(subJson.size())))
.object();
QString timestamp = "2024-05-14T12:31:47.995298776Z";
if (event.contains("__timestamp"))
{
timestamp = event["__timestamp"].toString();
event.remove("__timestamp");
}
QJsonObject metadata{
{"message_id", "e8edc592-5550-4aa5-bba6-39e31a2be435"},
{"message_type", "notification"},
{"message_timestamp", timestamp},
{"subscription_type", subscription["type"]},
{"subscription_version", subscription["version"]},
};
QJsonObject payload{
{"metadata", metadata},
{"payload",
QJsonObject{
{"subscription", subscription},
{"event", event},
}},
};
auto bytes = QJsonDocument(payload).toJson();
boost::beast::flat_buffer buf;
auto inner = buf.prepare(bytes.size());
std::memcpy(inner.data(), bytes.data(), inner.size());
buf.commit(inner.size());
return buf;
}
} // namespace
class TestEventSubMessagesP : public ::testing::TestWithParam<QString>
{
public:
void SetUp() override
{
auto param = TestEventSubMessagesP::GetParam();
this->snapshot = testlib::Snapshot::read(CATEGORY, param);
this->mockApplication = std::make_unique<MockApplication>();
this->mainChannel = makeMockTwitchChannel("pajlada");
this->mainChannel->setRoomId("11148817");
this->mockApplication->twitch.mockChannels.emplace("pajlada",
this->mainChannel);
}
void TearDown() override
{
this->mainChannel.reset();
this->mockApplication.reset();
this->snapshot.reset();
}
std::shared_ptr<TwitchChannel> mainChannel;
std::unique_ptr<MockApplication> mockApplication;
std::unique_ptr<testlib::Snapshot> snapshot;
};
TEST_P(TestEventSubMessagesP, Run)
{
QStringView subcategory(snapshot->name());
subcategory = subcategory.sliced(0, subcategory.indexOf('/'));
auto subscription = SUBSCRIPTIONS.find(subcategory);
ASSERT_NE(subscription, SUBSCRIPTIONS.end()) << subcategory;
QJsonArray input{snapshot->input()};
if (snapshot->input().isArray())
{
input = snapshot->input().toArray();
}
std::unique_ptr<eventsub::lib::Listener> listener =
std::make_unique<eventsub::Connection>();
boost::asio::io_context ioc;
boost::asio::ssl::context ssl(
boost::asio::ssl::context::method::tls_client);
auto sess =
std::make_shared<eventsub::lib::Session>(ioc, ssl, std::move(listener));
for (const auto inputRef : input)
{
auto inputObj = inputRef.toObject();
// "__subscription" overrides the subscription type the message is built
// as. By default, the subcategory (directory) name is used.
auto eventSubscription = subscription;
if (inputObj.contains(u"__subscription"))
{
eventSubscription =
SUBSCRIPTIONS.find(inputObj[u"__subscription"].toString());
ASSERT_NE(eventSubscription, SUBSCRIPTIONS.end());
inputObj.remove("__subscription");
}
auto json = makePayload(eventSubscription->second, inputObj);
auto ec = sess->handleMessage(json);
ASSERT_FALSE(ec.failed())
<< ec.what() << ec.message() << ec.location().to_string();
}
auto messages = mainChannel->getMessageSnapshot();
QJsonArray output;
for (const auto &message : messages)
{
output.append(message->toJson());
}
ASSERT_TRUE(snapshot->run(output, UPDATE_SNAPSHOTS))
<< "Snapshot " << snapshot->name() << " failed. Expected JSON to be\n"
<< QJsonDocument(snapshot->output().toArray()).toJson() << "\nbut got\n"
<< QJsonDocument(output).toJson() << "\ninstead.";
}
INSTANTIATE_TEST_SUITE_P(
EventSubMessages, TestEventSubMessagesP,
testing::ValuesIn(testlib::Snapshot::discoverNested(CATEGORY)));
TEST(TestEventSubMessagesP, Integrity)
{
ASSERT_FALSE(UPDATE_SNAPSHOTS); // make sure fixtures are actually tested
}