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) |
13 | int DummyMysqlInit = (MysqlInit(), 1); |
14 | #endif |
15 | |
16 | char *CSaveTeam::GetString() |
17 | { |
18 | // Dummy implementation for testing |
19 | return nullptr; |
20 | } |
21 | |
22 | int CSaveTeam::FromString(const char *) |
23 | { |
24 | // Dummy implementation for testing |
25 | return 1; |
26 | } |
27 | |
28 | bool 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 | |
34 | TEST(SQLite, Version) |
35 | { |
36 | ASSERT_GE(sqlite3_libversion_number(), 3025000) << "SQLite >= 3.25.0 required for Window functions" ; |
37 | } |
38 | |
39 | struct 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 | |
133 | struct 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 | |
146 | TEST_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 | |
156 | TEST_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 | |
166 | TEST_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 | |
173 | TEST_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 | |
180 | TEST_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 | |
192 | TEST_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 | |
203 | TEST_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 | |
211 | TEST_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 | |
219 | TEST_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 | |
256 | TEST_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 | |
274 | TEST_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 | |
281 | struct 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 | |
318 | TEST_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 | |
327 | TEST_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 | |
337 | TEST_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 | |
344 | TEST_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 | |
355 | struct 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 | |
363 | TEST_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 | |
376 | TEST_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 | |
390 | TEST_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 | |
404 | TEST_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 | |
411 | struct 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 | |
419 | TEST_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 | |
429 | TEST_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 | |
439 | TEST_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 | |
446 | struct 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 | |
456 | TEST_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 | |
462 | TEST_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 | |
469 | TEST_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 | |
476 | TEST_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 | |
486 | TEST_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 | |
494 | TEST_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 | |
506 | TEST_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 | |
515 | TEST_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 | |
528 | struct 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 | |
541 | TEST_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 | |
550 | TEST_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 | |
559 | TEST_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 | |
568 | TEST_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 | |
577 | TEST_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 | |
586 | auto g_pSqliteConn = CreateSqliteConnection(pFilename: ":memory:" , Setup: true); |
587 | #if defined(CONF_TEST_MYSQL) |
588 | CMysqlConfig 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 | }; |
598 | auto g_pMysqlConn = CreateMysqlConnection(Config: gMysqlConfig); |
599 | #endif |
600 | |
601 | auto 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 | |
621 | INSTANTIATE(SingleScore); |
622 | INSTANTIATE(TeamScore); |
623 | INSTANTIATE(MapInfo); |
624 | INSTANTIATE(MapVote); |
625 | INSTANTIATE(Points); |
626 | INSTANTIATE(RandomMap); |
627 | |