package com.faforever.client.replay; import com.faforever.client.game.GameService; import com.faforever.client.game.GameType; import com.faforever.client.i18n.I18n; import com.faforever.client.notification.ImmediateNotification; import com.faforever.client.notification.NotificationService; import com.faforever.client.notification.PersistentNotification; import com.faforever.client.preferences.PreferencesService; import com.faforever.client.task.TaskService; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; import java.util.concurrent.CompletableFuture; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; public class ReplayServiceImplTest { /** * First 64 bytes of a SCFAReplay file with version 3599. ASCII representation: * <pre> * Supreme Commande * r v1.50.3599.... * Replay v1.9../ma * ps/forbidden pas * s.v0001/forbidde * n pass.scmap.... * </pre> */ private static final byte[] REPLAY_FIRST_BYTES = new byte[]{ 0x53, 0x75, 0x70, 0x72, 0x65, 0x6D, 0x65, 0x20, 0x43, 0x6F, 0x6D, 0x6D, 0x61, 0x6E, 0x64, 0x65, 0x72, 0x20, 0x76, 0x31, 0x2E, 0x35, 0x30, 0x2E, 0x33, 0x35, 0x39, 0x39, 0x00, 0x0D, 0x0A, 0x00, 0x52, 0x65, 0x70, 0x6C, 0x61, 0x79, 0x20, 0x76, 0x31, 0x2E, 0x39, 0x0D, 0x0A, 0x2F, 0x6D, 0x61, 0x70, 0x73, 0x2F, 0x66, 0x6F, 0x72, 0x62, 0x69, 0x64, 0x64, 0x65, 0x6E, 0x20, 0x70, 0x61, 0x73, 0x73, 0x2E, 0x76, 0x30, 0x30, 0x30, 0x31, 0x2F, 0x66, 0x6F, 0x72, 0x62, 0x69, 0x64, 0x64, 0x65, 0x6E, 0x20, 0x70, 0x61, 0x73, 0x73, 0x2E, 0x73, 0x63, 0x6D, 0x61, 0x70, 0x00, 0x0D, 0x0A, 0x1A }; private static final String TEST_MAP_NAME = "forbidden pass.v0001"; @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule public TemporaryFolder replayDirectory = new TemporaryFolder(); @Rule public TemporaryFolder cacheDirectory = new TemporaryFolder(); private ReplayServiceImpl instance; @Mock private I18n i18n; @Mock private Environment environment; @Mock private PreferencesService preferencesService; @Mock private ReplayFileReader replayFileReader; @Mock private NotificationService notificationService; @Mock private ReplayServerAccessor replayServerAccessor; @Mock private ApplicationContext applicationContext; @Mock private TaskService taskService; @Mock private GameService gameService; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); instance = new ReplayServiceImpl(); instance.i18n = i18n; instance.environment = environment; instance.preferencesService = preferencesService; instance.replayFileReader = replayFileReader; instance.notificationService = notificationService; instance.replayServerAccessor = replayServerAccessor; instance.applicationContext = applicationContext; instance.taskService = taskService; instance.gameService = gameService; when(preferencesService.getReplaysDirectory()).thenReturn(replayDirectory.getRoot().toPath()); when(preferencesService.getCorruptedReplaysDirectory()).thenReturn(replayDirectory.getRoot().toPath().resolve("corrupt")); when(preferencesService.getCacheDirectory()).thenReturn(cacheDirectory.getRoot().toPath()); when(environment.getProperty("replayFileGlob")).thenReturn("*.fafreplay"); doAnswer(invocation -> invocation.getArgumentAt(0, Object.class)).when(taskService).submitTask(any()); } @Test public void testParseSupComVersion() throws Exception { Integer version = ReplayServiceImpl.parseSupComVersion(REPLAY_FIRST_BYTES); assertEquals((Integer) 3599, version); } @Test public void testParseMapName() throws Exception { String mapName = ReplayServiceImpl.parseMapName(REPLAY_FIRST_BYTES); assertEquals(TEST_MAP_NAME, mapName); } @Test public void testGuessModByFileNameModIsMissing() throws Exception { String mod = ReplayServiceImpl.guessModByFileName("110621-2128 Saltrock Colony.SCFAReplay"); assertEquals(GameType.DEFAULT.getString(), mod); } @Test public void testGuessModByFileNameModIsBlackops() throws Exception { String mod = ReplayServiceImpl.guessModByFileName("110621-2128 Saltrock Colony.blackops.SCFAReplay"); assertEquals("blackops", mod); } @Test public void testGetLocalReplaysMovesCorruptFiles() throws Exception { Path file1 = replayDirectory.newFile("replay.fafreplay").toPath(); Path file2 = replayDirectory.newFile("replay2.fafreplay").toPath(); doThrow(new IOException("Junit test exception")).when(replayFileReader).readReplayInfo(file1); doThrow(new IOException("Junit test exception")).when(replayFileReader).readReplayInfo(file2); Collection<ReplayInfoBean> localReplays = instance.getLocalReplays(); assertThat(localReplays, empty()); verify(replayFileReader).readReplayInfo(file1); verify(replayFileReader).readReplayInfo(file2); verify(notificationService, times(2)).addNotification(any(PersistentNotification.class)); assertThat(Files.exists(file1), is(false)); assertThat(Files.exists(file2), is(false)); } @Test public void testGetLocalReplays() throws Exception { Path file1 = replayDirectory.newFile("replay.fafreplay").toPath(); LocalReplayInfo localReplayInfo = new LocalReplayInfo(); localReplayInfo.setUid(123); localReplayInfo.setTitle("title"); when(replayFileReader.readReplayInfo(file1)).thenReturn(localReplayInfo); Collection<ReplayInfoBean> localReplays = instance.getLocalReplays(); assertThat(localReplays, hasSize(1)); assertThat(localReplays.iterator().next().getId(), is(123)); assertThat(localReplays.iterator().next().getTitle(), is("title")); } @Test public void testGetOnlineReplays() throws Exception { instance.getOnlineReplays(); verify(replayServerAccessor).requestOnlineReplays(); } @Test public void testRunFafReplayFile() throws Exception { Path replayFile = replayDirectory.newFile("replay.fafreplay").toPath(); ReplayInfoBean replayInfoBean = new ReplayInfoBean(); replayInfoBean.setReplayFile(replayFile); LocalReplayInfo replayInfo = new LocalReplayInfo(); replayInfo.setUid(123); replayInfo.setSimMods(Collections.emptyMap()); replayInfo.setFeaturedModVersions(emptyMap()); replayInfo.setFeaturedMod("faf"); replayInfo.setMapname(TEST_MAP_NAME); when(replayFileReader.readReplayInfo(replayFile)).thenReturn(replayInfo); when(replayFileReader.readReplayData(replayFile)).thenReturn(REPLAY_FIRST_BYTES); instance.runReplay(replayInfoBean); verify(gameService).runWithReplay(any(), eq(123), eq("faf"), eq(3599), eq(emptyMap()), eq(emptySet()), eq(TEST_MAP_NAME)); verifyZeroInteractions(notificationService); } @Test public void testRunScFaReplayFile() throws Exception { Path replayFile = replayDirectory.newFile("replay.scfareplay").toPath(); ReplayInfoBean replayInfoBean = new ReplayInfoBean(); replayInfoBean.setReplayFile(replayFile); when(replayFileReader.readReplayData(replayFile)).thenReturn(REPLAY_FIRST_BYTES); instance.runReplay(replayInfoBean); verify(gameService).runWithReplay(any(), eq(null), eq("faf"), eq(3599), eq(emptyMap()), eq(emptySet()), eq(TEST_MAP_NAME)); verifyZeroInteractions(notificationService); } @Test public void testRunReplayFileExceptionTriggersNotification() throws Exception { Path replayFile = replayDirectory.newFile("replay.scfareplay").toPath(); doThrow(new RuntimeException("Junit test exception")).when(replayFileReader).readReplayData(replayFile); ReplayInfoBean replayInfoBean = new ReplayInfoBean(); replayInfoBean.setReplayFile(replayFile); expectedException.expect(RuntimeException.class); expectedException.expectMessage("Junit test exception"); instance.runReplay(replayInfoBean); } @Test public void testRunFafReplayFileExceptionTriggersNotification() throws Exception { Path replayFile = replayDirectory.newFile("replay.fafreplay").toPath(); doThrow(new IOException("Junit test exception")).when(replayFileReader).readReplayInfo(replayFile); when(replayFileReader.readReplayData(replayFile)).thenReturn(REPLAY_FIRST_BYTES); ReplayInfoBean replayInfoBean = new ReplayInfoBean(); replayInfoBean.setReplayFile(replayFile); instance.runReplay(replayInfoBean); verify(notificationService).addNotification(any(ImmediateNotification.class)); verifyNoMoreInteractions(gameService); } @Test public void testRunFafOnlineReplay() throws Exception { Path replayFile = replayDirectory.newFile("replay.fafreplay").toPath(); ReplayDownloadTask replayDownloadTask = mock(ReplayDownloadTask.class); when(replayDownloadTask.getFuture()).thenReturn(CompletableFuture.completedFuture(replayFile)); when(applicationContext.getBean(ReplayDownloadTask.class)).thenReturn(replayDownloadTask); ReplayInfoBean replayInfoBean = new ReplayInfoBean(); LocalReplayInfo replayInfo = new LocalReplayInfo(); replayInfo.setUid(123); replayInfo.setSimMods(Collections.emptyMap()); replayInfo.setFeaturedModVersions(emptyMap()); replayInfo.setFeaturedMod("faf"); replayInfo.setMapname(TEST_MAP_NAME); when(replayFileReader.readReplayInfo(replayFile)).thenReturn(replayInfo); when(replayFileReader.readReplayData(replayFile)).thenReturn(REPLAY_FIRST_BYTES); instance.runReplay(replayInfoBean); verify(taskService).submitTask(replayDownloadTask); verify(gameService).runWithReplay(any(), eq(123), eq("faf"), eq(3599), eq(emptyMap()), eq(emptySet()), eq(TEST_MAP_NAME)); verifyZeroInteractions(notificationService); } @Test public void testRunScFaOnlineReplay() throws Exception { Path replayFile = replayDirectory.newFile("replay.scfareplay").toPath(); ReplayDownloadTask replayDownloadTask = mock(ReplayDownloadTask.class); when(replayDownloadTask.getFuture()).thenReturn(CompletableFuture.completedFuture(replayFile)); when(applicationContext.getBean(ReplayDownloadTask.class)).thenReturn(replayDownloadTask); ReplayInfoBean replayInfoBean = new ReplayInfoBean(); when(replayFileReader.readReplayData(replayFile)).thenReturn(REPLAY_FIRST_BYTES); instance.runReplay(replayInfoBean); verify(taskService).submitTask(replayDownloadTask); verify(gameService).runWithReplay(replayFile, null, "faf", 3599, emptyMap(), emptySet(), TEST_MAP_NAME); verifyZeroInteractions(notificationService); } @Test public void testRunScFaOnlineReplayExceptionTriggersNotification() throws Exception { Path replayFile = replayDirectory.newFile("replay.scfareplay").toPath(); doThrow(new IOException("Junit test exception")).when(replayFileReader).readReplayInfo(replayFile); ReplayDownloadTask replayDownloadTask = mock(ReplayDownloadTask.class); when(replayDownloadTask.getFuture()).thenReturn(CompletableFuture.completedFuture(replayFile)); when(applicationContext.getBean(ReplayDownloadTask.class)).thenReturn(replayDownloadTask); ReplayInfoBean replayInfoBean = new ReplayInfoBean(); instance.runReplay(replayInfoBean); verify(notificationService).addNotification(any(ImmediateNotification.class)); verifyNoMoreInteractions(gameService); } @Test public void testRunLiveReplay() throws Exception { when(gameService.runWithLiveReplay(any(URI.class), anyInt(), anyString(), anyString())) .thenReturn(CompletableFuture.completedFuture(null)); instance.runLiveReplay(new URI("faflive://example.com/123/456.scfareplay?mod=faf&map=map%20name")); verify(gameService).runWithLiveReplay(new URI("gpgnet://example.com/123/456.scfareplay"), 123, "faf", "map name"); } }