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