1 /*  PokerTH automated tests.
2 	Copyright (C) 2011 Lothar May
3 
4 	This program is free software: you can redistribute it and/or modify
5 	it under the terms of the GNU Affero General Public License as
6 	published by the Free Software Foundation, either version 3 of the
7 	License, or (at your option) any later version.
8 
9 	This program is distributed in the hope that it will be useful,
10 	but WITHOUT ANY WARRANTY; without even the implied warranty of
11 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 	GNU Affero General Public License for more details.
13 
14 	You should have received a copy of the GNU Affero General Public License
15 	along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 package de.pokerth.test;
19 
20 import java.net.Socket;
21 
22 import static org.junit.Assert.*;
23 
24 import java.sql.ResultSet;
25 import java.sql.Statement;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 
29 import org.junit.Test;
30 
31 import de.pokerth.protocol.ProtoBuf.NetGameInfo;
32 import de.pokerth.protocol.ProtoBuf.StartEventAckMessage;
33 import de.pokerth.protocol.ProtoBuf.NetGameInfo.EndRaiseMode;
34 import de.pokerth.protocol.ProtoBuf.NetGameInfo.NetGameType;
35 import de.pokerth.protocol.ProtoBuf.PlayerListMessage.PlayerListNotification;
36 import de.pokerth.protocol.ProtoBuf.PokerTHMessage.PokerTHMessageType;
37 import de.pokerth.protocol.ProtoBuf.StartEventMessage.StartEventType;
38 import de.pokerth.protocol.ProtoBuf.PokerTHMessage;
39 
40 
41 public class RejoinMultiGameTest extends TestBase {
42 
43 	@Test
testRejoinMultiGame()44 	public void testRejoinMultiGame() throws Exception {
45 
46 		Statement dbStatement = dbConn.createStatement();
47 		ResultSet countBeforeResult = dbStatement.executeQuery("SELECT COUNT(idgame) FROM game");
48 		countBeforeResult.first();
49 		long countBefore = countBeforeResult.getLong(1);
50 
51 		userInit();
52 
53 		// Waiting for player list update.
54 		PokerTHMessage msg;
55 		msg = receiveMessage();
56 		if (!msg.hasPlayerListMessage()) {
57 			failOnErrorMessage(msg);
58 			fail("Invalid message.");
59 		}
60 
61 		Collection<Integer> l = new ArrayList<Integer>();
62 		String gameName = AuthUser + " rejoin game";
63 		NetGameInfo gameInfo = createGameInfo(NetGameType.rankingGame, 5, 7, 5, EndRaiseMode.doubleBlinds, 0, 50, gameName, l, 10, 0, 11, 10000);
64 		sendMessage(createGameRequestMsg(
65 				gameInfo,
66 				"",
67 				false));
68 
69 		// Game list update (new game)
70 		msg = receiveMessage();
71 		if (!msg.hasGameListNewMessage()) {
72 			failOnErrorMessage(msg);
73 			fail("Invalid message.");
74 		}
75 
76 		// Join game ack.
77 		msg = receiveMessage();
78 		if (!msg.hasJoinGameAckMessage()) {
79 			failOnErrorMessage(msg);
80 			fail("Could not create game!");
81 		}
82 		int gameId = msg.getJoinGameAckMessage().getGameId();
83 
84 		// Game list update (player joined).
85 		msg = receiveMessage();
86 		if (!msg.hasGameListPlayerJoinedMessage()) {
87 			failOnErrorMessage(msg);
88 			fail("Invalid message.");
89 		}
90 
91 		// Let 9 additional clients join.
92 		Socket s[] = new Socket[9];
93 		int playerId[] = new int[9];
94 		Guid playerSession[] = new Guid[9];
95 		for (int i = 0; i < 9; i++) {
96 			s[i] = new Socket("localhost", 7234);
97 			playerSession[i] = new Guid();
98 			String username = "test" + (i+1);
99 			String password = username;
100 			playerId[i] = userInit(s[i], username, password, null, playerSession[i]);
101 			// Waiting for player list update.
102 			do {
103 				msg = receiveMessage(s[i]);
104 			} while (msg.hasGameListNewMessage() || msg.hasGameListPlayerJoinedMessage() || msg.hasGamePlayerJoinedMessage());
105 			if (!msg.hasPlayerListMessage()) {
106 				failOnErrorMessage(msg);
107 				fail("Invalid message.");
108 			}
109 			sendMessage(joinGameRequestMsg(gameId, "", false), s[i]);
110 			do {
111 				msg = receiveMessage(s[i]);
112 				failOnErrorMessage(msg);
113 			} while (!msg.hasJoinGameAckMessage() && !msg.hasJoinGameFailedMessage());
114 			if (!msg.hasJoinGameAckMessage()) {
115 				fail("User " + username + " could not join ranking game.");
116 			}
117 
118 			// The player should have joined the game.
119 			msg = receiveMessage();
120 			if (!msg.hasPlayerListMessage()) {
121 				failOnErrorMessage(msg);
122 				fail("Invalid message.");
123 			}
124 			msg = receiveMessage();
125 			if (!msg.hasGamePlayerJoinedMessage()) {
126 				failOnErrorMessage(msg);
127 				fail("Invalid message.");
128 			}
129 			msg = receiveMessage();
130 			if (!msg.hasGameListPlayerJoinedMessage()) {
131 				failOnErrorMessage(msg);
132 				fail("Invalid message.");
133 			}
134 		}
135 
136 		// Server should automatically send start event.
137 		msg = receiveMessage();
138 		if (!msg.hasStartEventMessage()) {
139 			failOnErrorMessage(msg);
140 			fail("Invalid message.");
141 		}
142 		for (int i = 0; i < 9; i++) {
143 			do {
144 				msg = receiveMessage(s[i]);
145 				failOnErrorMessage(msg);
146 			} while (!msg.hasStartEventMessage());
147 		}
148 		// Acknowledge start event.
149 		StartEventAckMessage startAck = StartEventAckMessage.newBuilder()
150 			.setGameId(gameId)
151 			.build();
152 		msg = PokerTHMessage.newBuilder()
153 			.setMessageType(PokerTHMessageType.Type_StartEventAckMessage)
154 			.setStartEventAckMessage(startAck)
155 			.build();
156 		sendMessage(msg);
157 		for (int i = 0; i < 9; i++) {
158 			sendMessage(msg, s[i]);
159 		}
160 
161 		// Game list update (game now running).
162 		msg = receiveMessage();
163 		if (!msg.hasGameListUpdateMessage()) {
164 			failOnErrorMessage(msg);
165 			fail("Invalid message.");
166 		}
167 
168 		msg = receiveMessage();
169 		if (!msg.hasGameStartInitialMessage()) {
170 			failOnErrorMessage(msg);
171 			fail("Invalid message.");
172 		}
173 
174 		// Wait for start of hand.
175 		do {
176 			msg = receiveMessage();
177 			failOnErrorMessage(msg);
178 			for (int i = 0; i < 9; i++) {
179 				while (s[i].getInputStream().available() > 0) {
180 					PokerTHMessage inMsg = receiveMessage(s[i]);
181 					failOnErrorMessage(inMsg);
182 				}
183 			}
184 		} while (!msg.hasHandStartMessage());
185 
186 		// 9 players leave the game by closing the socket.
187 		for (int i = 0; i < 9; i++) {
188 			s[i].close();
189 			Thread.sleep(500);
190 		}
191 		// No rejoin game id set yet.
192 		assertEquals(0, lastRejoinGameId);
193 
194 		// The remaining player should have received "player left" 9 times.
195 		for (int i = 0; i < 9; i++) {
196 			do {
197 				msg = receiveMessage();
198 				failOnErrorMessage(msg);
199 			} while (!msg.hasPlayerListMessage());
200 			assertEquals(playerId[i], msg.getPlayerListMessage().getPlayerId());
201 			assertEquals(PlayerListNotification.playerListLeft, msg.getPlayerListMessage().getPlayerListNotification());
202 		}
203 
204 		// Let all players reconnect.
205 		long playerIdAfterRejoin[] = new long[9];
206 		for (int i = 0; i < 9; i++) {
207 			s[i] = new Socket("localhost", 7234);
208 			String username = "test" + (i+1);
209 			String password = username;
210 			playerIdAfterRejoin[i] = userInit(s[i], username, password, null, playerSession[i]);
211 			assertEquals(gameId, lastRejoinGameId);
212 			// Waiting for player list update.
213 			do {
214 				msg = receiveMessage(s[i]);
215 			} while (msg.hasGameListNewMessage() || msg.hasGameListPlayerJoinedMessage() || msg.hasGamePlayerJoinedMessage());
216 			if (!msg.hasPlayerListMessage()) {
217 				failOnErrorMessage(msg);
218 				fail("Invalid message.");
219 			}
220 			sendMessage(rejoinGameRequestMsg(gameId, false), s[i]);
221 			do {
222 				msg = receiveMessage(s[i]);
223 				failOnErrorMessage(msg);
224 			} while (!msg.hasJoinGameAckMessage() && !msg.hasJoinGameFailedMessage());
225 			if (!msg.hasJoinGameAckMessage()) {
226 				fail("User " + username + " could not rejoin ranking game.");
227 			}
228 		}
229 		// The remaining player should have received "player joined" 9 times.
230 		for (int i = 0; i < 9; i++) {
231 			do {
232 				msg = receiveMessage();
233 				failOnErrorMessage(msg);
234 			} while (!msg.hasPlayerListMessage());
235 			assertEquals(playerIdAfterRejoin[i], msg.getPlayerListMessage().getPlayerId());
236 			assertEquals(PlayerListNotification.playerListNew, msg.getPlayerListMessage().getPlayerListNotification());
237 		}
238 
239 		for (int i = 0; i < 9; i++) {
240 			// Wait for start event.
241 			do {
242 				msg = receiveMessage(s[i]);
243 				failOnErrorMessage(msg);
244 			} while (!msg.hasStartEventMessage());
245 
246 			assertEquals(gameId, msg.getStartEventMessage().getGameId());
247 			assertEquals(StartEventType.rejoinEvent, msg.getStartEventMessage().getStartEventType());
248 
249 			// Acknowledge start event.
250 			startAck = StartEventAckMessage.newBuilder()
251 				.setGameId(gameId)
252 				.build();
253 			msg = PokerTHMessage.newBuilder()
254 				.setMessageType(PokerTHMessageType.Type_StartEventAckMessage)
255 				.setStartEventAckMessage(startAck)
256 				.build();
257 			sendMessage(msg, s[i]);
258 		}
259 
260 		for (int i = 0; i < 9; i++) {
261 			// Wait for game start. This may take a while, because rejoin is performed at the beginning of the next hand.
262 			do {
263 				msg = receiveMessage(s[i]);
264 				failOnErrorMessage(msg);
265 			} while (!msg.hasGameStartRejoinMessage());
266 
267 			// Check whether we got all necessary data to rejoin.
268 			assertEquals(gameId, msg.getGameStartRejoinMessage().getGameId());
269 			// We left at the first hand.
270 			assertTrue(msg.getGameStartRejoinMessage().getHandNum() >= 1);
271 			// 10 Players should now be active again.
272 			assertEquals(10, msg.getGameStartRejoinMessage().getRejoinPlayerDataCount());
273 		}
274 
275 		// The remaining player should have received 9 "player id changed".
276 		for (int i = 0; i < 9; i++) {
277 			do {
278 				msg = receiveMessage();
279 				failOnErrorMessage(msg);
280 			} while (!msg.hasPlayerIdChangedMessage());
281 			assertEquals(playerId[i], msg.getPlayerIdChangedMessage().getOldPlayerId());
282 			assertEquals(playerIdAfterRejoin[i], msg.getPlayerIdChangedMessage().getNewPlayerId());
283 		}
284 
285 		// Everyone should receive a "hand start message" now.
286 		do {
287 			msg = receiveMessage();
288 			failOnErrorMessage(msg);
289 		} while (!msg.hasHandStartMessage());
290 		for (int i = 0; i < 9; i++) {
291 			do {
292 				msg = receiveMessage(s[i]);
293 				failOnErrorMessage(msg);
294 			} while (!msg.hasHandStartMessage());
295 		}
296 
297 		// The game should continue to the end.
298 		do {
299 			msg = receiveMessage();
300 			failOnErrorMessage(msg);
301 		} while (!msg.hasEndOfGameMessage());
302 
303 		for (int i = 0; i < 9; i++) {
304 			s[i].close();
305 		}
306 		Thread.sleep(2000);
307 
308 		// Check database entry for the game.
309 		ResultSet countAfterResult = dbStatement.executeQuery("SELECT COUNT(idgame) FROM game");
310 		countAfterResult.first();
311 		long countAfter = countAfterResult.getLong(1);
312 		assertEquals(countBefore + 1, countAfter);
313 
314 		// Select the latest game.
315 		ResultSet gameResult = dbStatement.executeQuery("SELECT idgame, name, start_time, end_time FROM game WHERE start_time = (SELECT MAX(start_time) from game)");
316 		gameResult.first();
317 		long idgame = gameResult.getLong(1);
318 
319 		// Check database entries for the players in the game.
320 		// There should be exactly 10 entries, just as usual.
321 		ResultSet gamePlayerResult = dbStatement.executeQuery("SELECT COUNT(*) FROM game_has_player WHERE game_idgame = " + idgame);
322 		gamePlayerResult.first();
323 		assertEquals(10, gamePlayerResult.getLong(1));
324 		// Each player should have a place in the range 1..10
325 		ResultSet winnerResult = dbStatement.executeQuery(
326 				"SELECT place FROM game_has_player LEFT JOIN player_login on (game_has_player.player_idplayer = player_login.id) WHERE game_idgame = " + idgame);
327 		winnerResult.first();
328 		for (int i = 0; i < 9; i++) {
329 			assertTrue(winnerResult.getLong(1) >= 1);
330 			assertTrue(winnerResult.getLong(1) <= 10);
331 			winnerResult.next();
332 		}
333 	}
334 }
335