import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import controllers.Application;
import models.event.BoardState;
import models.event.IllegalMove;
import models.event.ReadyToStart;
import models.event.WaitingForOpponent;
import org.junit.Before;
import org.junit.Test;
import play.api.mvc.RequestHeader;
import play.mvc.Http;
import play.twirl.api.Content;
import java.util.HashMap;
import java.util.Set;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static play.test.Helpers.contentAsString;
import static play.test.Helpers.contentType;
/**
* Tests index template and game logic by mocking a {@code WebSocket}.
*/
public class ApplicationTest {
private final static ObjectMapper objectMapper = new ObjectMapper();
/**
* Provides the {@code Context} required by template rendering.
*/
@Before
public void setContext() {
Http.Context.current.set(new Http.Context(
0L,
mock(RequestHeader.class),
mock(Http.Request.class),
new HashMap <String, String>(),
new HashMap<String, String>(),
new HashMap<String, Object>()));
}
@Test
public void testIndexTemplate() {
Content html = views.html.index.render(0, 0);
assertThat(contentType(html)).isEqualTo("text/html");
assertThat(contentAsString(html))
.contains("There are 0 game(s) and 0 pending player(s) online.");
}
private static <T> T readPojo(MockWebSocketWrapper socket, Class<T> clazz)
throws InterruptedException, JsonProcessingException {
JsonNode data = socket.read();
assertThat(data).isNotNull();
try { return objectMapper.convertValue(data, clazz); }
catch (IllegalArgumentException iae) {
throw new IllegalArgumentException(
"Invalid JSON: " + objectMapper.writeValueAsString(data), iae);
}
}
private static void writeMove(MockWebSocketWrapper socket, Object pit) throws Throwable {
socket.write(objectMapper.valueToTree(pit));
}
@Test
public void testJoin() throws Throwable {
// Introduce the first user and read the "WaitingForOpponent" message.
MockWebSocketWrapper fstSocket = new MockWebSocketWrapper(Application.join());
WaitingForOpponent fstWfo = readPojo(fstSocket, WaitingForOpponent.class);
assertThat(Application.getPendingPlayers().size()).isEqualTo(1);
assertThat(Application.getGames().size()).isEqualTo(0);
// Introduce the second user and read the."WaitingForOpponent" message.
MockWebSocketWrapper sndSocket = new MockWebSocketWrapper(Application.join());
WaitingForOpponent sndWfo = readPojo(sndSocket, WaitingForOpponent.class);
assertThat(fstWfo.playerId.equals(sndWfo.playerId)).isFalse();
// Validate "ReadyToStart" messages.
ReadyToStart fstRts = readPojo(fstSocket, ReadyToStart.class);
ReadyToStart sndRts = readPojo(sndSocket, ReadyToStart.class);
assertThat(Application.getGames().size()).isEqualTo(1);
assertThat(Application.getPendingPlayers().size()).isEqualTo(0);
assertThat(fstRts.nextPlayerId).isEqualTo(sndRts.nextPlayerId);
assertThat(fstRts.opponentId).isEqualTo(sndWfo.playerId);
assertThat(sndRts.opponentId).isEqualTo(fstWfo.playerId);
// Let 2nd player make a move, while this is not his turn.
writeMove(sndSocket, 0);
IllegalMove im = readPojo(sndSocket, IllegalMove.class);
assertThat(im.reason).isEqualTo("It is opponent's turn.");
// Let 1st player make a move to an invalid pit index.
writeMove(fstSocket, "n/a");
im = readPojo(fstSocket, IllegalMove.class);
assertThat(im.reason).matches("^Invalid pit index: .*");
// Let 1st player make a move with a negative pit index.
writeMove(fstSocket, -1);
im = readPojo(fstSocket, IllegalMove.class);
assertThat(im.reason).matches("^Invalid pit index: .*");
// Let 1st player make a move to a pit with index larger than 5.
writeMove(fstSocket, 6);
im = readPojo(fstSocket, IllegalMove.class);
assertThat(im.reason).matches("^Invalid pit index: .*");
// Let 1st player make a move for the 1st pit.
writeMove(fstSocket, 0);
BoardState fstBs = readPojo(fstSocket, BoardState.class);
BoardState sndBs = readPojo(sndSocket, BoardState.class);
Set<String> playerIds = Sets.newHashSet(fstWfo.playerId, sndWfo.playerId);
assertThat(fstBs.board.keySet()).isEqualTo(playerIds);
assertThat(fstBs).isEqualTo(sndBs);
// Since the last stone landed on the Lubang Menggali,
// it is first player's turn again.
assertThat(fstBs.nextPlayerId).isEqualTo(fstWfo.playerId);
assertThat(fstBs.board.get(fstWfo.playerId)).isEqualTo(new int[]{0, 7, 7, 7, 7, 7, 1});
// Let 1st player make a move for the 2nd pit this time. We aim to
// sow the last stone to the pit we started, which is empty. Hence, we
// will also capture the stones in the opposite pit.
writeMove(fstSocket, 1);
fstBs = readPojo(fstSocket, BoardState.class);
sndBs = readPojo(sndSocket, BoardState.class);
assertThat(fstBs).isEqualTo(sndBs);
assertThat(fstBs.nextPlayerId).isEqualTo(sndWfo.playerId);
assertThat(fstBs.board.get(fstWfo.playerId)).isEqualTo(new int[] {1, 0, 8, 8, 8, 8, 9});
assertThat(fstBs.board.get(sndWfo.playerId)).isEqualTo(new int[] {6, 6, 6, 6, 0, 6, 0});
// Now it is 2nd players turn. First let's try to make a move on an empty pit.
writeMove(sndSocket, 4);
im = readPojo(sndSocket, IllegalMove.class);
assertThat(im.reason).matches("^No stones available at pit .*");
// Fine. Sow the stones in the first pit, where the last stone will hit
// to Lubang Menggali and give us a second chance.
writeMove(sndSocket, 0);
fstBs = readPojo(fstSocket, BoardState.class);
sndBs = readPojo(sndSocket, BoardState.class);
assertThat(fstBs).isEqualTo(sndBs);
assertThat(fstBs.nextPlayerId).isEqualTo(sndWfo.playerId);
assertThat(fstBs.board.get(sndWfo.playerId)).isEqualTo(new int[] {0, 7, 7, 7, 1, 7, 1});
// Let 1st player close the connection. 2nd player is expected to
// observe a socket close as well.
fstSocket.close();
JsonNode closeEvent = sndSocket.read();
assertThat(closeEvent).isNotNull();
assertThat(closeEvent.has("closed")).isTrue();
assertThat(closeEvent.get("closed").asBoolean()).isTrue();
}
}