1#include <base/detect.h>
2
3#include <engine/server/databases/connection.h>
4#include <engine/server/databases/connection_pool.h>
5#include <engine/shared/config.h>
6
7#include <game/server/scoreworker.h>
8
9#include <gmock/gmock.h>
10#include <gtest/gtest.h>
11#include <sqlite3.h>
12
13#if defined(CONF_TEST_MYSQL)
14int DummyMysqlInit = (MysqlInit(), 1);
15#endif
16
17TEST(SQLite, Version)
18{
19 ASSERT_GE(sqlite3_libversion_number(), 3025000) << "SQLite >= 3.25.0 required for Window functions";
20}
21
22struct Score : public testing::TestWithParam<IDbConnection *>
23{
24 Score()
25 {
26 Connect();
27 LoadBestTime();
28 InsertMap(pName: "Kobra 3", pMapper: "Zerodin", pServer: "Novice", Points: 5, Stars: 5);
29 }
30
31 ~Score()
32 {
33 m_pConn->Disconnect();
34 }
35
36 void Connect()
37 {
38 ASSERT_TRUE(m_pConn->Connect(m_aError, sizeof(m_aError))) << m_aError;
39
40 // Delete all existing entries for persistent databases like MySQL
41 int NumInserted = 0;
42 ASSERT_TRUE(m_pConn->PrepareStatement("DELETE FROM record_race", m_aError, sizeof(m_aError))) << m_aError;
43 ASSERT_TRUE(m_pConn->ExecuteUpdate(&NumInserted, m_aError, sizeof(m_aError))) << m_aError;
44 ASSERT_TRUE(m_pConn->PrepareStatement("DELETE FROM record_teamrace", m_aError, sizeof(m_aError))) << m_aError;
45 ASSERT_TRUE(m_pConn->ExecuteUpdate(&NumInserted, m_aError, sizeof(m_aError))) << m_aError;
46 ASSERT_TRUE(m_pConn->PrepareStatement("DELETE FROM record_maps", m_aError, sizeof(m_aError))) << m_aError;
47 ASSERT_TRUE(m_pConn->ExecuteUpdate(&NumInserted, m_aError, sizeof(m_aError))) << m_aError;
48 ASSERT_TRUE(m_pConn->PrepareStatement("DELETE FROM record_points", m_aError, sizeof(m_aError))) << m_aError;
49 ASSERT_TRUE(m_pConn->ExecuteUpdate(&NumInserted, m_aError, sizeof(m_aError))) << m_aError;
50 ASSERT_TRUE(m_pConn->PrepareStatement("DELETE FROM record_saves", m_aError, sizeof(m_aError))) << m_aError;
51 ASSERT_TRUE(m_pConn->ExecuteUpdate(&NumInserted, m_aError, sizeof(m_aError))) << m_aError;
52 }
53
54 void LoadBestTime()
55 {
56 CSqlLoadBestTimeRequest loadBestTimeReq(std::make_shared<CScoreLoadBestTimeResult>());
57 str_copy(dst: loadBestTimeReq.m_aMap, src: "Kobra 3", dst_size: sizeof(loadBestTimeReq.m_aMap));
58 ASSERT_TRUE(CScoreWorker::LoadBestTime(m_pConn, &loadBestTimeReq, m_aError, sizeof(m_aError))) << m_aError;
59 }
60
61 void InsertMap(const char *pName, const char *pMapper, const char *pServer, int Points, int Stars)
62 {
63 char aTimestamp[32];
64 str_timestamp_format(buffer: aTimestamp, buffer_size: sizeof(aTimestamp), FORMAT_SPACE);
65 char aBuf[512];
66 str_format(buffer: aBuf, buffer_size: sizeof(aBuf),
67 format: "%s into %s_maps(Map, Server, Mapper, Points, Stars, Timestamp) "
68 "VALUES (\"%s\", \"%s\", \"%s\", %d, %d, %s)",
69 m_pConn->InsertIgnore(), m_pConn->GetPrefix(), pName, pServer, pMapper, Points, Stars, m_pConn->InsertTimestampAsUtc());
70 ASSERT_TRUE(m_pConn->PrepareStatement(aBuf, m_aError, sizeof(m_aError))) << m_aError;
71 m_pConn->BindString(Idx: 1, pString: aTimestamp);
72 int NumInserted = 0;
73 ASSERT_TRUE(m_pConn->ExecuteUpdate(&NumInserted, m_aError, sizeof(m_aError))) << m_aError;
74 ASSERT_EQ(NumInserted, 1);
75 }
76
77 void InsertRank(float Time = 100.0, bool WithTimeCheckPoints = false)
78 {
79 str_copy(dst: g_Config.m_SvSqlServerName, src: "USA", dst_size: sizeof(g_Config.m_SvSqlServerName));
80 CSqlScoreData ScoreData(std::make_shared<CScorePlayerResult>());
81 str_copy(dst: ScoreData.m_aMap, src: "Kobra 3", dst_size: sizeof(ScoreData.m_aMap));
82 str_copy(dst: ScoreData.m_aGameUuid, src: "8d300ecf-5873-4297-bee5-95668fdff320", dst_size: sizeof(ScoreData.m_aGameUuid));
83 str_copy(dst: ScoreData.m_aName, src: "nameless tee", dst_size: sizeof(ScoreData.m_aName));
84 ScoreData.m_ClientId = 0;
85 ScoreData.m_Time = Time;
86 str_copy(dst: ScoreData.m_aTimestamp, src: "2021-11-24 19:24:08", dst_size: sizeof(ScoreData.m_aTimestamp));
87 for(int i = 0; i < NUM_CHECKPOINTS; i++)
88 ScoreData.m_aCurrentTimeCp[i] = WithTimeCheckPoints ? i : 0;
89 str_copy(dst: ScoreData.m_aRequestingPlayer, src: "deen", dst_size: sizeof(ScoreData.m_aRequestingPlayer));
90 ASSERT_TRUE(CScoreWorker::SaveScore(m_pConn, &ScoreData, Write::NORMAL, m_aError, sizeof(m_aError))) << m_aError;
91 }
92
93 void ExpectLines(const std::shared_ptr<CScorePlayerResult> &pPlayerResult, std::initializer_list<const char *> Lines, bool All = false)
94 {
95 EXPECT_EQ(pPlayerResult->m_MessageKind, All ? CScorePlayerResult::ALL : CScorePlayerResult::DIRECT);
96
97 int i = 0;
98 for(const char *pLine : Lines)
99 {
100 EXPECT_STREQ(pPlayerResult->m_Data.m_aaMessages[i], pLine);
101 i++;
102 }
103
104 for(; i < CScorePlayerResult::MAX_MESSAGES; i++)
105 {
106 EXPECT_STREQ(pPlayerResult->m_Data.m_aaMessages[i], "");
107 }
108 }
109
110 IDbConnection *m_pConn{GetParam()};
111 char m_aError[256] = {};
112 std::shared_ptr<CScorePlayerResult> m_pPlayerResult{std::make_shared<CScorePlayerResult>()};
113 CSqlPlayerRequest m_PlayerRequest{m_pPlayerResult};
114};
115
116struct SingleScore : public Score
117{
118 SingleScore()
119 {
120 InsertRank();
121 str_copy(dst: m_PlayerRequest.m_aMap, src: "Kobra 3", dst_size: sizeof(m_PlayerRequest.m_aMap));
122 str_copy(dst: m_PlayerRequest.m_aRequestingPlayer, src: "brainless tee", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
123 m_PlayerRequest.m_Offset = 0;
124 str_copy(dst: m_PlayerRequest.m_aServer, src: "GER", dst_size: sizeof(m_PlayerRequest.m_aServer));
125 str_copy(dst: m_PlayerRequest.m_aName, src: "nameless tee", dst_size: sizeof(m_PlayerRequest.m_aMap));
126 }
127};
128
129TEST_P(SingleScore, TopRegional)
130{
131 g_Config.m_SvRegionalRankings = true;
132 ASSERT_TRUE(CScoreWorker::ShowTop(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
133 ExpectLines(pPlayerResult: m_pPlayerResult,
134 Lines: {"------------ Global Top ------------",
135 "1. nameless tee Time: 01:40.00",
136 "------------ GER Top ------------"});
137}
138
139TEST_P(SingleScore, Top)
140{
141 g_Config.m_SvRegionalRankings = false;
142 ASSERT_TRUE(CScoreWorker::ShowTop(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
143 ExpectLines(pPlayerResult: m_pPlayerResult,
144 Lines: {"------------ Global Top ------------",
145 "1. nameless tee Time: 01:40.00",
146 "-----------------------------------------"});
147}
148
149TEST_P(SingleScore, RankRegional)
150{
151 g_Config.m_SvRegionalRankings = true;
152 ASSERT_TRUE(CScoreWorker::ShowRank(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
153 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"nameless tee - 01:40.00 - better than 100% - requested by brainless tee", "Global rank 1 - GER unranked"}, All: true);
154}
155
156TEST_P(SingleScore, Rank)
157{
158 g_Config.m_SvRegionalRankings = false;
159 ASSERT_TRUE(CScoreWorker::ShowRank(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
160 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"nameless tee - 01:40.00 - better than 100% - requested by brainless tee", "Global rank 1"}, All: true);
161}
162
163TEST_P(SingleScore, TopServerRegional)
164{
165 g_Config.m_SvRegionalRankings = true;
166 str_copy(dst: m_PlayerRequest.m_aServer, src: "USA", dst_size: sizeof(m_PlayerRequest.m_aServer));
167 ASSERT_TRUE(CScoreWorker::ShowTop(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
168 ExpectLines(pPlayerResult: m_pPlayerResult,
169 Lines: {"------------ Global Top ------------",
170 "1. nameless tee Time: 01:40.00",
171 "------------ USA Top ------------",
172 "1. nameless tee Time: 01:40.00"});
173}
174
175TEST_P(SingleScore, TopServer)
176{
177 g_Config.m_SvRegionalRankings = false;
178 str_copy(dst: m_PlayerRequest.m_aServer, src: "USA", dst_size: sizeof(m_PlayerRequest.m_aServer));
179 ASSERT_TRUE(CScoreWorker::ShowTop(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
180 ExpectLines(pPlayerResult: m_pPlayerResult,
181 Lines: {"------------ Global Top ------------",
182 "1. nameless tee Time: 01:40.00",
183 "-----------------------------------------"});
184}
185
186TEST_P(SingleScore, RankServerRegional)
187{
188 g_Config.m_SvRegionalRankings = true;
189 str_copy(dst: m_PlayerRequest.m_aServer, src: "USA", dst_size: sizeof(m_PlayerRequest.m_aServer));
190 ASSERT_TRUE(CScoreWorker::ShowRank(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
191 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"nameless tee - 01:40.00 - better than 100% - requested by brainless tee", "Global rank 1 - USA rank 1"}, All: true);
192}
193
194TEST_P(SingleScore, RankServer)
195{
196 g_Config.m_SvRegionalRankings = false;
197 str_copy(dst: m_PlayerRequest.m_aServer, src: "USA", dst_size: sizeof(m_PlayerRequest.m_aServer));
198 ASSERT_TRUE(CScoreWorker::ShowRank(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
199 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"nameless tee - 01:40.00 - better than 100% - requested by brainless tee", "Global rank 1"}, All: true);
200}
201
202TEST_P(SingleScore, LoadPlayerData)
203{
204 InsertRank(Time: 120.0, WithTimeCheckPoints: true);
205 str_copy(dst: m_PlayerRequest.m_aName, src: "", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
206 ASSERT_TRUE(CScoreWorker::LoadPlayerData(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
207
208 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::PLAYER_INFO);
209 ASSERT_FALSE(m_pPlayerResult->m_Data.m_Info.m_Time.has_value());
210 for(auto &Time : m_pPlayerResult->m_Data.m_Info.m_aTimeCp)
211 {
212 ASSERT_EQ(Time, 0);
213 }
214
215 str_copy(dst: m_PlayerRequest.m_aRequestingPlayer, src: "nameless tee", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
216 str_copy(dst: m_PlayerRequest.m_aName, src: "", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
217 ASSERT_TRUE(CScoreWorker::LoadPlayerData(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
218
219 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::PLAYER_INFO);
220 ASSERT_TRUE(m_pPlayerResult->m_Data.m_Info.m_Time.has_value());
221 ASSERT_EQ(*m_pPlayerResult->m_Data.m_Info.m_Time, 100.0);
222 for(int i = 0; i < NUM_CHECKPOINTS; i++)
223 {
224 ASSERT_EQ(m_pPlayerResult->m_Data.m_Info.m_aTimeCp[i], i);
225 }
226
227 str_copy(dst: m_PlayerRequest.m_aRequestingPlayer, src: "finishless", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
228 str_copy(dst: m_PlayerRequest.m_aName, src: "nameless tee", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
229 ASSERT_TRUE(CScoreWorker::LoadPlayerData(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
230
231 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::PLAYER_INFO);
232 ASSERT_FALSE(m_pPlayerResult->m_Data.m_Info.m_Time.has_value());
233 for(int i = 0; i < NUM_CHECKPOINTS; i++)
234 {
235 ASSERT_EQ(m_pPlayerResult->m_Data.m_Info.m_aTimeCp[i], i);
236 }
237}
238
239TEST_P(SingleScore, TimesExists)
240{
241 ASSERT_TRUE(CScoreWorker::ShowTimes(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
242 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::DIRECT);
243 EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[0], "------------- Last Times -------------");
244 char aBuf[128];
245 str_copy(dst: aBuf, src: m_pPlayerResult->m_Data.m_aaMessages[1], dst_size: 7);
246 EXPECT_STREQ(aBuf, "[USA] ");
247
248 str_copy(dst: aBuf, src: m_pPlayerResult->m_Data.m_aaMessages[1] + str_length(str: m_pPlayerResult->m_Data.m_aaMessages[1]) - 10, dst_size: 11);
249 EXPECT_STREQ(aBuf, ", 01:40.00");
250 EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[2], "-------------------------------------------");
251 for(int i = 3; i < CScorePlayerResult::MAX_MESSAGES; i++)
252 {
253 EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[i], "");
254 }
255}
256
257TEST_P(SingleScore, TimesDoesntExist)
258{
259 str_copy(dst: m_PlayerRequest.m_aName, src: "foo", dst_size: sizeof(m_PlayerRequest.m_aMap));
260 ASSERT_TRUE(CScoreWorker::ShowTimes(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
261 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"There are no times in the specified range"});
262}
263
264struct TeamScore : public Score
265{
266 void SetUp() override
267 {
268 InsertTeamRank(Time: 100.0);
269 }
270
271 void InsertTeamRank(float Time = 100.0)
272 {
273 str_copy(dst: g_Config.m_SvSqlServerName, src: "USA", dst_size: sizeof(g_Config.m_SvSqlServerName));
274 CSqlTeamScoreData teamScoreData;
275 CSqlScoreData ScoreData(std::make_shared<CScorePlayerResult>());
276 str_copy(dst: teamScoreData.m_aMap, src: "Kobra 3", dst_size: sizeof(teamScoreData.m_aMap));
277 str_copy(dst: ScoreData.m_aMap, src: "Kobra 3", dst_size: sizeof(ScoreData.m_aMap));
278 str_copy(dst: teamScoreData.m_aGameUuid, src: "8d300ecf-5873-4297-bee5-95668fdff320", dst_size: sizeof(teamScoreData.m_aGameUuid));
279 str_copy(dst: ScoreData.m_aGameUuid, src: "8d300ecf-5873-4297-bee5-95668fdff320", dst_size: sizeof(ScoreData.m_aGameUuid));
280 teamScoreData.m_Size = 2;
281 str_copy(dst: teamScoreData.m_aaNames[0], src: "nameless tee", dst_size: sizeof(teamScoreData.m_aaNames[0]));
282 str_copy(dst: teamScoreData.m_aaNames[1], src: "brainless tee", dst_size: sizeof(teamScoreData.m_aaNames[1]));
283 teamScoreData.m_Time = Time;
284 ScoreData.m_Time = Time;
285 str_copy(dst: teamScoreData.m_aTimestamp, src: "2021-11-24 19:24:08", dst_size: sizeof(teamScoreData.m_aTimestamp));
286 str_copy(dst: ScoreData.m_aTimestamp, src: "2021-11-24 19:24:08", dst_size: sizeof(ScoreData.m_aTimestamp));
287 for(int i = 0; i < NUM_CHECKPOINTS; i++)
288 ScoreData.m_aCurrentTimeCp[i] = 0;
289 ASSERT_TRUE(CScoreWorker::SaveTeamScore(m_pConn, &teamScoreData, Write::NORMAL, m_aError, sizeof(m_aError))) << m_aError;
290
291 str_copy(dst: m_PlayerRequest.m_aMap, src: "Kobra 3", dst_size: sizeof(m_PlayerRequest.m_aMap));
292 str_copy(dst: m_PlayerRequest.m_aRequestingPlayer, src: "brainless tee", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
293 str_copy(dst: ScoreData.m_aRequestingPlayer, src: "brainless tee", dst_size: sizeof(ScoreData.m_aRequestingPlayer));
294
295 str_copy(dst: ScoreData.m_aName, src: "nameless tee", dst_size: sizeof(ScoreData.m_aName));
296 ScoreData.m_ClientId = 0;
297 ASSERT_TRUE(CScoreWorker::SaveScore(m_pConn, &ScoreData, Write::NORMAL, m_aError, sizeof(m_aError))) << m_aError;
298 str_copy(dst: ScoreData.m_aName, src: "brainless tee", dst_size: sizeof(ScoreData.m_aName));
299 ScoreData.m_ClientId = 1;
300 ASSERT_TRUE(CScoreWorker::SaveScore(m_pConn, &ScoreData, Write::NORMAL, m_aError, sizeof(m_aError))) << m_aError;
301 m_PlayerRequest.m_Offset = 0;
302 }
303};
304
305TEST_P(TeamScore, All)
306{
307 g_Config.m_SvRegionalRankings = false;
308 ASSERT_TRUE(CScoreWorker::ShowTeamTop5(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
309 ExpectLines(pPlayerResult: m_pPlayerResult,
310 Lines: {"------- Team Top 5 -------",
311 "1. brainless tee & nameless tee Team Time: 01:40.00",
312 "-------------------------------"});
313}
314
315TEST_P(TeamScore, TeamTop5Regional)
316{
317 g_Config.m_SvRegionalRankings = true;
318 str_copy(dst: m_PlayerRequest.m_aServer, src: "USA", dst_size: sizeof(m_PlayerRequest.m_aServer));
319 ASSERT_TRUE(CScoreWorker::ShowTeamTop5(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
320 ExpectLines(pPlayerResult: m_pPlayerResult,
321 Lines: {"------- Team Top 5 -------",
322 "1. brainless tee & nameless tee Team Time: 01:40.00",
323 "----- USA Team Top -----",
324 "1. brainless tee & nameless tee Team Time: 01:40.00"});
325}
326
327TEST_P(TeamScore, PlayerExists)
328{
329 str_copy(dst: m_PlayerRequest.m_aName, src: "brainless tee", dst_size: sizeof(m_PlayerRequest.m_aName));
330 ASSERT_TRUE(CScoreWorker::ShowPlayerTeamTop5(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
331 ExpectLines(pPlayerResult: m_pPlayerResult,
332 Lines: {"------- Team Top 5 -------",
333 "1. brainless tee & nameless tee Team Time: 01:40.00",
334 "---------------------------------"});
335}
336
337TEST_P(TeamScore, PlayerDoesntExist)
338{
339 str_copy(dst: m_PlayerRequest.m_aName, src: "foo", dst_size: sizeof(m_PlayerRequest.m_aName));
340 ASSERT_TRUE(CScoreWorker::ShowPlayerTeamTop5(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
341 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"foo has no team ranks"});
342}
343
344TEST_P(TeamScore, RankUpdates)
345{
346 InsertTeamRank(Time: 98.0);
347 str_copy(dst: m_PlayerRequest.m_aName, src: "brainless tee", dst_size: sizeof(m_PlayerRequest.m_aName));
348 ASSERT_TRUE(CScoreWorker::ShowPlayerTeamTop5(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
349 ExpectLines(pPlayerResult: m_pPlayerResult,
350 Lines: {"------- Team Top 5 -------",
351 "1. brainless tee & nameless tee Team Time: 01:38.00",
352 "---------------------------------"});
353}
354
355struct MapInfo : public Score
356{
357 MapInfo()
358 {
359 str_copy(dst: m_PlayerRequest.m_aRequestingPlayer, src: "brainless tee", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
360 }
361};
362
363TEST_P(MapInfo, ExactNoFinish)
364{
365 str_copy(dst: m_PlayerRequest.m_aName, src: "Kobra 3", dst_size: sizeof(m_PlayerRequest.m_aName));
366 ASSERT_TRUE(CScoreWorker::MapInfo(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
367
368 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::DIRECT);
369 EXPECT_THAT(m_pPlayerResult->m_Data.m_aaMessages[0], testing::MatchesRegex("\"Kobra 3\" by Zerodin on Novice, ★★★★★, 5 points, released .* ago, 0 finishes by 0 tees"));
370 for(int i = 1; i < CScorePlayerResult::MAX_MESSAGES; i++)
371 {
372 EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[i], "");
373 }
374}
375
376TEST_P(MapInfo, ExactFinish)
377{
378 InsertRank();
379 str_copy(dst: m_PlayerRequest.m_aName, src: "Kobra 3", dst_size: sizeof(m_PlayerRequest.m_aName));
380 ASSERT_TRUE(CScoreWorker::MapInfo(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
381
382 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::DIRECT);
383 EXPECT_THAT(m_pPlayerResult->m_Data.m_aaMessages[0], testing::MatchesRegex("\"Kobra 3\" by Zerodin on Novice, ★★★★★, 5 points, released .* ago, 1 finish by 1 tee in 01:40 median"));
384 for(int i = 1; i < CScorePlayerResult::MAX_MESSAGES; i++)
385 {
386 EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[i], "");
387 }
388}
389
390TEST_P(MapInfo, Fuzzy)
391{
392 InsertRank();
393 str_copy(dst: m_PlayerRequest.m_aName, src: "k3", dst_size: sizeof(m_PlayerRequest.m_aName));
394 ASSERT_TRUE(CScoreWorker::MapInfo(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
395
396 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::DIRECT);
397 EXPECT_THAT(m_pPlayerResult->m_Data.m_aaMessages[0], testing::MatchesRegex("\"Kobra 3\" by Zerodin on Novice, ★★★★★, 5 points, released .* ago, 1 finish by 1 tee in 01:40 median"));
398 for(int i = 1; i < CScorePlayerResult::MAX_MESSAGES; i++)
399 {
400 EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[i], "");
401 }
402}
403
404TEST_P(MapInfo, FuzzyCase)
405{
406 InsertMap(pName: "Reflect", pMapper: "DarkOort", pServer: "Dummy", Points: 20, Stars: 3);
407 InsertMap(pName: "reflects", pMapper: "Ninjed & Pipou", pServer: "Solo", Points: 16, Stars: 4);
408 str_copy(dst: m_PlayerRequest.m_aName, src: "reflect", dst_size: sizeof(m_PlayerRequest.m_aName));
409 ASSERT_TRUE(CScoreWorker::MapInfo(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
410
411 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::DIRECT);
412 EXPECT_THAT(m_pPlayerResult->m_Data.m_aaMessages[0], testing::MatchesRegex("\"Reflect\" by DarkOort on Dummy, ★★★✰✰, 20 points, released .* ago, 0 finishes by 0 tees"));
413 for(int i = 1; i < CScorePlayerResult::MAX_MESSAGES; i++)
414 {
415 EXPECT_STREQ(m_pPlayerResult->m_Data.m_aaMessages[i], "");
416 }
417}
418
419TEST_P(MapInfo, DoesntExit)
420{
421 str_copy(dst: m_PlayerRequest.m_aName, src: "f", dst_size: sizeof(m_PlayerRequest.m_aName));
422 ASSERT_TRUE(CScoreWorker::MapInfo(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
423 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"No map like \"f\" found."});
424}
425
426struct MapVote : public Score
427{
428 MapVote()
429 {
430 str_copy(dst: m_PlayerRequest.m_aRequestingPlayer, src: "brainless tee", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
431 }
432};
433
434TEST_P(MapVote, Exact)
435{
436 str_copy(dst: m_PlayerRequest.m_aName, src: "Kobra 3", dst_size: sizeof(m_PlayerRequest.m_aName));
437 ASSERT_TRUE(CScoreWorker::MapVote(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
438 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::MAP_VOTE);
439 EXPECT_STREQ(m_pPlayerResult->m_Data.m_MapVote.m_aMap, "Kobra 3");
440 EXPECT_STREQ(m_pPlayerResult->m_Data.m_MapVote.m_aReason, "/map");
441 EXPECT_STREQ(m_pPlayerResult->m_Data.m_MapVote.m_aServer, "novice");
442}
443
444TEST_P(MapVote, Fuzzy)
445{
446 str_copy(dst: m_PlayerRequest.m_aName, src: "k3", dst_size: sizeof(m_PlayerRequest.m_aName));
447 ASSERT_TRUE(CScoreWorker::MapVote(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
448 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::MAP_VOTE);
449 EXPECT_STREQ(m_pPlayerResult->m_Data.m_MapVote.m_aMap, "Kobra 3");
450 EXPECT_STREQ(m_pPlayerResult->m_Data.m_MapVote.m_aReason, "/map");
451 EXPECT_STREQ(m_pPlayerResult->m_Data.m_MapVote.m_aServer, "novice");
452}
453
454TEST_P(MapVote, FuzzyCase)
455{
456 InsertMap(pName: "Reflect", pMapper: "DarkOort", pServer: "Dummy", Points: 20, Stars: 3);
457 InsertMap(pName: "reflects", pMapper: "Ninjed & Pipou", pServer: "Solo", Points: 16, Stars: 4);
458 str_copy(dst: m_PlayerRequest.m_aName, src: "reflect", dst_size: sizeof(m_PlayerRequest.m_aName));
459 ASSERT_TRUE(CScoreWorker::MapVote(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
460 EXPECT_EQ(m_pPlayerResult->m_MessageKind, CScorePlayerResult::MAP_VOTE);
461 EXPECT_STREQ(m_pPlayerResult->m_Data.m_MapVote.m_aMap, "Reflect");
462 EXPECT_STREQ(m_pPlayerResult->m_Data.m_MapVote.m_aReason, "/map");
463 EXPECT_STREQ(m_pPlayerResult->m_Data.m_MapVote.m_aServer, "dummy");
464}
465
466TEST_P(MapVote, DoesntExist)
467{
468 str_copy(dst: m_PlayerRequest.m_aName, src: "f", dst_size: sizeof(m_PlayerRequest.m_aName));
469 ASSERT_TRUE(CScoreWorker::MapVote(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
470 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"No map like \"f\" found. Try adding a '%' at the start if you don't know the first character. Example: /map %castle for \"Out of Castle\""});
471}
472
473struct Points : public Score
474{
475 Points()
476 {
477 str_copy(dst: m_PlayerRequest.m_aName, src: "nameless tee", dst_size: sizeof(m_PlayerRequest.m_aName));
478 str_copy(dst: m_PlayerRequest.m_aRequestingPlayer, src: "brainless tee", dst_size: sizeof(m_PlayerRequest.m_aRequestingPlayer));
479 m_PlayerRequest.m_Offset = 0;
480 }
481};
482
483TEST_P(Points, NoPoints)
484{
485 ASSERT_TRUE(CScoreWorker::ShowPoints(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
486 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"nameless tee has not collected any points so far"});
487}
488
489TEST_P(Points, NoPointsTop)
490{
491 ASSERT_TRUE(CScoreWorker::ShowTopPoints(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
492 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"-------- Top Points --------",
493 "-------------------------------"});
494}
495
496TEST_P(Points, OnePoints)
497{
498 m_pConn->AddPoints(pPlayer: "nameless tee", Points: 2, pError: m_aError, ErrorSize: sizeof(m_aError));
499 ASSERT_TRUE(CScoreWorker::ShowPoints(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
500 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"1. nameless tee Points: 2, requested by brainless tee"}, All: true);
501}
502
503TEST_P(Points, OnePointsTop)
504{
505 m_pConn->AddPoints(pPlayer: "nameless tee", Points: 2, pError: m_aError, ErrorSize: sizeof(m_aError));
506 ASSERT_TRUE(CScoreWorker::ShowTopPoints(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
507 ExpectLines(pPlayerResult: m_pPlayerResult,
508 Lines: {"-------- Top Points --------",
509 "1. nameless tee Points: 2",
510 "-------------------------------"});
511}
512
513TEST_P(Points, TwoPoints)
514{
515 m_pConn->AddPoints(pPlayer: "nameless tee", Points: 2, pError: m_aError, ErrorSize: sizeof(m_aError));
516 m_pConn->AddPoints(pPlayer: "brainless tee", Points: 3, pError: m_aError, ErrorSize: sizeof(m_aError));
517 ASSERT_TRUE(CScoreWorker::ShowPoints(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
518 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"2. nameless tee Points: 2, requested by brainless tee"}, All: true);
519}
520
521TEST_P(Points, TwoPointsTop)
522{
523 m_pConn->AddPoints(pPlayer: "nameless tee", Points: 2, pError: m_aError, ErrorSize: sizeof(m_aError));
524 m_pConn->AddPoints(pPlayer: "brainless tee", Points: 3, pError: m_aError, ErrorSize: sizeof(m_aError));
525 ASSERT_TRUE(CScoreWorker::ShowTopPoints(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
526 ExpectLines(pPlayerResult: m_pPlayerResult,
527 Lines: {"-------- Top Points --------",
528 "1. brainless tee Points: 3",
529 "2. nameless tee Points: 2",
530 "-------------------------------"});
531}
532
533TEST_P(Points, EqualPoints)
534{
535 m_pConn->AddPoints(pPlayer: "nameless tee", Points: 2, pError: m_aError, ErrorSize: sizeof(m_aError));
536 m_pConn->AddPoints(pPlayer: "brainless tee", Points: 3, pError: m_aError, ErrorSize: sizeof(m_aError));
537 m_pConn->AddPoints(pPlayer: "nameless tee", Points: 1, pError: m_aError, ErrorSize: sizeof(m_aError));
538 ASSERT_TRUE(CScoreWorker::ShowPoints(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
539 ExpectLines(pPlayerResult: m_pPlayerResult, Lines: {"1. nameless tee Points: 3, requested by brainless tee"}, All: true);
540}
541
542TEST_P(Points, EqualPointsTop)
543{
544 m_pConn->AddPoints(pPlayer: "nameless tee", Points: 2, pError: m_aError, ErrorSize: sizeof(m_aError));
545 m_pConn->AddPoints(pPlayer: "brainless tee", Points: 3, pError: m_aError, ErrorSize: sizeof(m_aError));
546 m_pConn->AddPoints(pPlayer: "nameless tee", Points: 1, pError: m_aError, ErrorSize: sizeof(m_aError));
547 ASSERT_TRUE(CScoreWorker::ShowTopPoints(m_pConn, &m_PlayerRequest, m_aError, sizeof(m_aError))) << m_aError;
548 ExpectLines(pPlayerResult: m_pPlayerResult,
549 Lines: {"-------- Top Points --------",
550 "1. brainless tee Points: 3",
551 "1. nameless tee Points: 3",
552 "-------------------------------"});
553}
554
555struct RandomMap : public Score
556{
557 std::shared_ptr<CScoreRandomMapResult> m_pRandomMapResult{std::make_shared<CScoreRandomMapResult>(args: 0)};
558 CSqlRandomMapRequest m_RandomMapRequest{m_pRandomMapResult};
559
560 RandomMap()
561 {
562 str_copy(dst: m_RandomMapRequest.m_aServerType, src: "Novice", dst_size: sizeof(m_RandomMapRequest.m_aServerType));
563 str_copy(dst: m_RandomMapRequest.m_aCurrentMap, src: "Kobra 4", dst_size: sizeof(m_RandomMapRequest.m_aCurrentMap));
564 str_copy(dst: m_RandomMapRequest.m_aRequestingPlayer, src: "nameless tee", dst_size: sizeof(m_RandomMapRequest.m_aRequestingPlayer));
565 }
566};
567
568TEST_P(RandomMap, NoStars)
569{
570 m_RandomMapRequest.m_MinStars = -1;
571 m_RandomMapRequest.m_MaxStars = -1;
572 ASSERT_TRUE(CScoreWorker::RandomMap(m_pConn, &m_RandomMapRequest, m_aError, sizeof(m_aError))) << m_aError;
573 EXPECT_EQ(m_pRandomMapResult->m_ClientId, 0);
574 EXPECT_STREQ(m_pRandomMapResult->m_aMap, "Kobra 3");
575 EXPECT_STREQ(m_pRandomMapResult->m_aMessage, "");
576}
577
578TEST_P(RandomMap, StarsExists)
579{
580 m_RandomMapRequest.m_MinStars = 5;
581 m_RandomMapRequest.m_MaxStars = 5;
582 ASSERT_TRUE(CScoreWorker::RandomMap(m_pConn, &m_RandomMapRequest, m_aError, sizeof(m_aError))) << m_aError;
583 EXPECT_EQ(m_pRandomMapResult->m_ClientId, 0);
584 EXPECT_STREQ(m_pRandomMapResult->m_aMap, "Kobra 3");
585 EXPECT_STREQ(m_pRandomMapResult->m_aMessage, "");
586}
587
588TEST_P(RandomMap, StarsRangeExists)
589{
590 m_RandomMapRequest.m_MinStars = 1;
591 m_RandomMapRequest.m_MaxStars = 5;
592 ASSERT_TRUE(CScoreWorker::RandomMap(m_pConn, &m_RandomMapRequest, m_aError, sizeof(m_aError))) << m_aError;
593 EXPECT_EQ(m_pRandomMapResult->m_ClientId, 0);
594 EXPECT_STRNE(m_pRandomMapResult->m_aMap, "");
595 EXPECT_STREQ(m_pRandomMapResult->m_aMessage, "");
596}
597
598TEST_P(RandomMap, StarsDoesntExist)
599{
600 m_RandomMapRequest.m_MinStars = 3;
601 m_RandomMapRequest.m_MaxStars = 3;
602 ASSERT_TRUE(CScoreWorker::RandomMap(m_pConn, &m_RandomMapRequest, m_aError, sizeof(m_aError))) << m_aError;
603 EXPECT_EQ(m_pRandomMapResult->m_ClientId, 0);
604 EXPECT_STREQ(m_pRandomMapResult->m_aMap, "");
605 EXPECT_STREQ(m_pRandomMapResult->m_aMessage, "No maps found on this server!");
606}
607
608TEST_P(RandomMap, UnfinishedExists)
609{
610 m_RandomMapRequest.m_MinStars = -1;
611 m_RandomMapRequest.m_MaxStars = -1;
612 ASSERT_TRUE(CScoreWorker::RandomUnfinishedMap(m_pConn, &m_RandomMapRequest, m_aError, sizeof(m_aError))) << m_aError;
613 EXPECT_EQ(m_pRandomMapResult->m_ClientId, 0);
614 EXPECT_STREQ(m_pRandomMapResult->m_aMap, "Kobra 3");
615 EXPECT_STREQ(m_pRandomMapResult->m_aMessage, "");
616}
617
618TEST_P(RandomMap, UnfinishedDoesntExist)
619{
620 InsertRank();
621 ASSERT_TRUE(CScoreWorker::RandomUnfinishedMap(m_pConn, &m_RandomMapRequest, m_aError, sizeof(m_aError))) << m_aError;
622 EXPECT_EQ(m_pRandomMapResult->m_ClientId, 0);
623 EXPECT_STREQ(m_pRandomMapResult->m_aMap, "");
624 EXPECT_STREQ(m_pRandomMapResult->m_aMessage, "nameless tee has no more unfinished maps on this server!");
625}
626
627auto g_pSqliteConn = CreateSqliteConnection(pFilename: ":memory:", Setup: true);
628#if defined(CONF_TEST_MYSQL)
629CMysqlConfig gMysqlConfig{
630 .m_aDatabase: "ddnet", // database
631 .m_aPrefix: "record", // prefix
632 .m_aUser: "ddnet", // user
633 .m_aPass: "thebestpassword", // password
634 .m_aIp: "localhost", // ip
635 .m_aBindaddr: "", // bindaddr
636 .m_Port: 3306, // port
637 .m_Setup: true, // setup
638};
639auto g_pMysqlConn = CreateMysqlConnection(Config: gMysqlConfig);
640#endif
641
642auto g_TestValues{
643 testing::Values(
644#if defined(CONF_TEST_MYSQL)
645 v: g_pMysqlConn.get(),
646#endif
647 v: g_pSqliteConn.get())};
648
649#define INSTANTIATE(SUITE) \
650 INSTANTIATE_TEST_SUITE_P(Sql, SUITE, g_TestValues, \
651 [](const testing::TestParamInfo<Score::ParamType> &Info) { \
652 switch(Info.index) \
653 { \
654 case 0: return "SQLite"; \
655 case 1: return "MySQL"; \
656 default: return "Unknown"; \
657 } \
658 })
659
660INSTANTIATE(SingleScore);
661INSTANTIATE(TeamScore);
662INSTANTIATE(MapInfo);
663INSTANTIATE(MapVote);
664INSTANTIATE(Points);
665INSTANTIATE(RandomMap);
666