Public Access
feat(library): exclude unreleased games from picker and pile
This commit is contained in:
1 parent
33b230ee91
commit
7b538b7f30
48 files changed
+1238
-69
No files matched your search
@@ -9,6 +9,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.client.MockRestServiceServer;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -189,6 +190,46 @@ class IgdbApiServiceTest extends AbstractUnitTest {
|
||||
assertThat(service.getReleaseYears(List.of(620L))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getReleaseDates_SteamAppLinkedToTwoIgdbGames_GetsTheEarliestUtcDate() {
|
||||
expectToken();
|
||||
igdb.expect(requestTo(containsString("/external_games")))
|
||||
.andRespond(withSuccess("[{\"game\": 72, \"uid\": \"620\"}, {\"game\": 233, \"uid\": \"620\"}, {\"game\": 9, \"uid\": \"220\"}]",
|
||||
MediaType.APPLICATION_JSON));
|
||||
igdb.expect(requestTo(containsString("/games")))
|
||||
// 2006-06-01 and 2017-05-05 UTC; game 9 has no date
|
||||
.andRespond(withSuccess("[{\"id\": 233, \"first_release_date\": 1493942400}, {\"id\": 72, \"first_release_date\": 1149120000}, {\"id\": 9}]",
|
||||
MediaType.APPLICATION_JSON));
|
||||
|
||||
assertThat(service.getReleaseDates(List.of(620L, 220L))).contains(Map.of(620L, LocalDate.of(2006, 6, 1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getReleaseDatesByGameIds_MapsTheIgdbIdToItsDate() {
|
||||
expectToken();
|
||||
igdb.expect(requestTo(containsString("/games")))
|
||||
.andExpect(content().string(containsString("id = (1025,1026)")))
|
||||
.andRespond(withSuccess("[{\"id\": 1025, \"first_release_date\": 1493942400}, {\"id\": 1026}]", MediaType.APPLICATION_JSON));
|
||||
|
||||
assertThat(service.getReleaseDatesByGameIds(List.of(1025L, 1026L))).contains(Map.of(1025L, LocalDate.of(2017, 5, 5)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getReleaseDates_IgdbError_IsEmpty() {
|
||||
expectToken();
|
||||
igdb.expect(requestTo(containsString("/external_games"))).andRespond(withServerError());
|
||||
|
||||
assertThat(service.getReleaseDates(List.of(620L))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getReleaseDatesByGameIds_IgdbError_IsEmpty() {
|
||||
expectToken();
|
||||
igdb.expect(requestTo(containsString("/games"))).andRespond(withServerError());
|
||||
|
||||
assertThat(service.getReleaseDatesByGameIds(List.of(1025L))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGameTags_NoSteamAppKnown_AsksNoGames() {
|
||||
expectToken();
|
||||
|
||||
@@ -6,6 +6,7 @@ import core_service.modules.library.domain.GameSource;
|
||||
import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.domain.UserGameId;
|
||||
import core_service.modules.library.repo.GameReleaseRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
import core_service.modules.library.service.GameImportWorker;
|
||||
import core_service.shared.test.AbstractMockedAdaptersTest;
|
||||
@@ -22,6 +23,7 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -71,6 +73,9 @@ class GameImportControllerTest extends AbstractMockedAdaptersTest {
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
jdbc.update("delete from game_alias");
|
||||
@@ -269,6 +274,18 @@ class GameImportControllerTest extends AbstractMockedAdaptersTest {
|
||||
assertEquals(id, importIdOf(-119388L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_StoresTheReleaseDateOfTheAddedGames() throws Exception {
|
||||
long id = createReady("{\"source\": \"TEXT\", \"rows\": [{\"title\": \"Zelda Tears\"}]}");
|
||||
gameReleaseRepository.deleteAll();
|
||||
LocalDate releaseDate = LocalDate.now().plusDays(60);
|
||||
when(igdbApiService.getReleaseDatesByGameIds(any())).thenReturn(Optional.of(Map.of(119388L, releaseDate)));
|
||||
|
||||
saveAndWait(id, "{\"lineNo\": 1, \"igdbId\": 119388, \"platform\": \"Switch\", \"status\": \"BACKLOG\"}");
|
||||
|
||||
assertEquals(releaseDate, gameReleaseRepository.findById(-119388L).orElseThrow().getReleaseDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void undo_RemovesTheGamesStillAddedByHand_AndCountsTheChangedOnes() throws Exception {
|
||||
long id = createReady("{\"source\": \"TEXT\", \"rows\": [{\"title\": \"Elden Ring\"}, {\"title\": \"Zelda Tears\"}]}");
|
||||
|
||||
@@ -171,6 +171,8 @@ class GameMergeFlowTest extends AbstractIntegrationTest {
|
||||
jdbc.update("delete from game_link_pair");
|
||||
jdbc.update("delete from game_link_request");
|
||||
jdbc.update("delete from game_edition");
|
||||
jdbc.update("delete from game_release");
|
||||
jdbc.update("insert into game_release (appid, release_date, fetched_at) values (?, current_date, now())", MANUAL);
|
||||
jdbc.update("insert into game_link (appid, set_id) values (?, 1), (?, 1)", MANUAL, MANUAL_2);
|
||||
jdbc.update("insert into game_link_pair (appid_a, appid_b, state) values (?, ?, 'OPEN')", MANUAL_2, STEAM_2);
|
||||
jdbc.update("insert into game_link_pair (appid_a, appid_b, state) values (?, ?, 'OPEN')", MANUAL_2, MANUAL);
|
||||
@@ -185,6 +187,7 @@ class GameMergeFlowTest extends AbstractIntegrationTest {
|
||||
assertThat(jdbc.queryForObject("select count(*) from game_link_pair", Integer.class)).isEqualTo(1);
|
||||
assertThat(jdbc.queryForObject("select count(*) from game_link_request", Integer.class)).isEqualTo(1);
|
||||
assertThat(jdbc.queryForObject("select count(*) from game_edition", Integer.class)).isZero();
|
||||
assertThat(jdbc.queryForObject("select count(*) from game_release", Integer.class)).isZero();
|
||||
assertThat(audit()).hasSize(1).first().asString().contains("GAME_MERGE");
|
||||
}
|
||||
|
||||
|
||||
@@ -186,9 +186,12 @@ class GameMergeUndoFlowTest extends AbstractIntegrationTest {
|
||||
manualRow(AUDITOR_ID, MANUAL, GameStatus.PLAYING);
|
||||
gameMoodsRepository.upsert(MANUAL, new String[]{"ACTION"}, new String[]{"Roguelike"}, false, Instant.now());
|
||||
jdbc.update("insert into game_time_to_beat (appid, normally_minutes, fetched_at) values (?, 600, now())", MANUAL);
|
||||
jdbc.update("delete from game_release");
|
||||
jdbc.update("insert into game_release (appid, release_date, fetched_at) values (?, current_date, now())", MANUAL);
|
||||
jdbc.update("insert into game_correction (appid, field, value, corrected_by, corrected_at) "
|
||||
+ "values (?, 'NAME', 'Hades Switch', 1, now())", MANUAL);
|
||||
merge(MANUAL, STEAM).andExpect(status().isOk());
|
||||
assertThat(count("select count(*) from game_release")).isZero();
|
||||
jdbc.update("update user_game set status = 'FINISHED', rating = 4 where user_id = ? and appid = ?", AUDITOR_ID, STEAM);
|
||||
int history = logRows(AUDITOR_ID, STEAM);
|
||||
|
||||
@@ -204,6 +207,7 @@ class GameMergeUndoFlowTest extends AbstractIntegrationTest {
|
||||
assertThat(steamGameRepository.findById(MANUAL).orElseThrow().getName()).isEqualTo("Hades (Switch)");
|
||||
assertThat(gameMoodsRepository.findById(MANUAL)).isPresent();
|
||||
assertThat(gameTimeToBeatRepository.findById(MANUAL)).isPresent();
|
||||
assertThat(count("select count(*) from game_release where appid = ?", MANUAL)).isEqualTo(1);
|
||||
assertThat(count("select count(*) from game_correction where appid = ?", MANUAL)).isEqualTo(1);
|
||||
assertThat(aliasOf(MANUAL)).isNull();
|
||||
assertThat(count("select count(*) from game_merge_undo")).isZero();
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package core_service.modules.library.api;
|
||||
|
||||
import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.repo.GameReleaseRepository;
|
||||
import core_service.modules.library.repo.SteamGameRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
import core_service.shared.test.AbstractMockedAdaptersTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* The dev profile authenticates every request as user 1. Games added by hand, so the detail page asks Steam nothing.
|
||||
*/
|
||||
@AutoConfigureMockMvc
|
||||
class LibraryControllerComingTest extends AbstractMockedAdaptersTest {
|
||||
|
||||
private static final long USER_ID = 1L;
|
||||
private static final long COMING = -4001L;
|
||||
private static final long COMING_SOON = -4002L;
|
||||
private static final long RELEASED = -4003L;
|
||||
private static final LocalDate TOMORROW = LocalDate.now(ZoneId.of("Europe/Berlin")).plusDays(1);
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private SteamGameRepository steamGameRepository;
|
||||
|
||||
@Autowired
|
||||
private UserGameRepository userGameRepository;
|
||||
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
userGameRepository.deleteAll();
|
||||
gameReleaseRepository.deleteAll();
|
||||
for (long appid : new long[]{COMING, COMING_SOON, RELEASED}) {
|
||||
steamGameRepository.save(new SteamGame(appid, "Game " + appid, null, Instant.now()));
|
||||
userGameRepository.save(UserGame.manual(USER_ID, appid, "PC", GameStatus.BACKLOG, 0, Instant.now()));
|
||||
}
|
||||
gameReleaseRepository.upsert(COMING, TOMORROW, false, Instant.now());
|
||||
gameReleaseRepository.upsert(COMING_SOON, null, true, Instant.now());
|
||||
gameReleaseRepository.upsert(RELEASED, TOMORROW.minusDays(10), false, Instant.now());
|
||||
}
|
||||
|
||||
@Test
|
||||
void list_MarksComingGamesWithTheirDate() throws Exception {
|
||||
mockMvc.perform(get("/api/library/games"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content[?(@.appid == " + COMING + ")].coming").value(true))
|
||||
.andExpect(jsonPath("$.content[?(@.appid == " + COMING + ")].comingOn").value(TOMORROW.toString()))
|
||||
.andExpect(jsonPath("$.content[?(@.appid == " + COMING_SOON + ")].coming").value(true))
|
||||
.andExpect(jsonPath("$.content[?(@.appid == " + COMING_SOON + ")].comingOn").value(contains(nullValue())))
|
||||
.andExpect(jsonPath("$.content[?(@.appid == " + RELEASED + ")].coming").value(false))
|
||||
.andExpect(jsonPath("$.content[?(@.appid == " + RELEASED + ")].comingOn").value(contains(nullValue())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detail_ComingGame_HasTheDate() throws Exception {
|
||||
mockMvc.perform(get("/api/library/games/" + COMING))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.coming").value(true))
|
||||
.andExpect(jsonPath("$.comingOn").value(TOMORROW.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detail_ReleasedGame_IsNotComing() throws Exception {
|
||||
mockMvc.perform(get("/api/library/games/" + RELEASED))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.coming").value(false))
|
||||
.andExpect(jsonPath("$.comingOn").value(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detail_ComingGameSomeonePlayed_IsNotComing() throws Exception {
|
||||
jdbc.update("update user_game set playtime_minutes = 30 where appid = ?", COMING);
|
||||
|
||||
mockMvc.perform(get("/api/library/games/" + COMING))
|
||||
.andExpect(jsonPath("$.coming").value(false));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.domain.UserGameId;
|
||||
import core_service.modules.library.repo.GameMoodsRepository;
|
||||
import core_service.modules.library.repo.GameReleaseRepository;
|
||||
import core_service.modules.library.repo.GameSpeedrunRepository;
|
||||
import core_service.modules.library.repo.GameTimeToBeatRepository;
|
||||
import core_service.modules.library.repo.SteamGameRepository;
|
||||
@@ -19,6 +20,8 @@ import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
@@ -57,8 +60,12 @@ class LibraryControllerPickerTest extends AbstractMockedLibraryServicesTest {
|
||||
@Autowired
|
||||
private GameSpeedrunRepository gameSpeedrunRepository;
|
||||
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@BeforeEach
|
||||
void clean() {
|
||||
gameReleaseRepository.deleteAll();
|
||||
userGameRepository.deleteAll();
|
||||
gameTimeToBeatRepository.deleteAll();
|
||||
gameMoodsRepository.deleteAll();
|
||||
@@ -283,4 +290,20 @@ class LibraryControllerPickerTest extends AbstractMockedLibraryServicesTest {
|
||||
|
||||
verify(syncService).syncIfStale(USER_ID, "76561198000000001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void picker_ComingGame_IsNeverPickedUntilItsReleaseDay() throws Exception {
|
||||
LocalDate berlinToday = LocalDate.now(ZoneId.of("Europe/Berlin"));
|
||||
game(10L, GameStatus.BACKLOG, null, null);
|
||||
game(20L, GameStatus.BACKLOG, null, null);
|
||||
gameReleaseRepository.upsert(20L, berlinToday.plusDays(1), false, Instant.now());
|
||||
|
||||
mockMvc.perform(get("/api/library/picker"))
|
||||
.andExpect(jsonPath("$.games[*].appid", containsInAnyOrder(10)));
|
||||
|
||||
gameReleaseRepository.upsert(20L, berlinToday, false, Instant.now());
|
||||
|
||||
mockMvc.perform(get("/api/library/picker"))
|
||||
.andExpect(jsonPath("$.games[*].appid", containsInAnyOrder(10, 20)));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.GameTimeToBeat;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.repo.GameReleaseRepository;
|
||||
import core_service.modules.library.repo.GameTimeToBeatRepository;
|
||||
import core_service.modules.library.repo.SteamGameRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
@@ -15,6 +16,8 @@ import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
@@ -42,9 +45,13 @@ class LibraryControllerSummaryTest extends AbstractIntegrationTest {
|
||||
@Autowired
|
||||
private GameTimeToBeatRepository gameTimeToBeatRepository;
|
||||
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@BeforeEach
|
||||
void clean() {
|
||||
userGameRepository.deleteAll();
|
||||
gameReleaseRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -108,4 +115,24 @@ class LibraryControllerSummaryTest extends AbstractIntegrationTest {
|
||||
mockMvc.perform(get("/api/library/summary").param("exclude", "220"))
|
||||
.andExpect(jsonPath("$.suggestion").value(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void summary_ComingGame_StaysInTheStatusCountButLeavesThePile() throws Exception {
|
||||
steamGameRepository.saveAll(List.of(
|
||||
new SteamGame(220L, "Half-Life 2", null, Instant.now()),
|
||||
new SteamGame(620L, "Dawn of War 4", null, Instant.now())));
|
||||
userGameRepository.saveAll(List.of(new UserGame(USER_ID, 220L, 0, Instant.now(), Instant.now()),
|
||||
new UserGame(USER_ID, 620L, 0, Instant.now(), Instant.now())));
|
||||
gameTimeToBeatRepository.saveAll(List.of(
|
||||
new GameTimeToBeat(220L, 500, 700, 900, Instant.now()),
|
||||
new GameTimeToBeat(620L, 1, 9999, 1, Instant.now())));
|
||||
gameReleaseRepository.upsert(620L, LocalDate.now(ZoneId.of("Europe/Berlin")).plusDays(1), false, Instant.now());
|
||||
|
||||
mockMvc.perform(get("/api/library/summary"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.byStatus.BACKLOG").value(2))
|
||||
.andExpect(jsonPath("$.pileGames").value(1))
|
||||
.andExpect(jsonPath("$.backlogMinutes").value(700))
|
||||
.andExpect(jsonPath("$.suggestion.appid").value(220));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.domain.UserGameId;
|
||||
import core_service.modules.library.repo.GameReleaseRepository;
|
||||
import core_service.modules.library.repo.GameTimeToBeatRepository;
|
||||
import core_service.modules.library.repo.SteamGameRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
@@ -21,6 +22,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -66,6 +68,9 @@ class ManualGameControllerTest extends AbstractMockedAdaptersTest {
|
||||
@Autowired
|
||||
private GameTimeToBeatRepository gameTimeToBeatRepository;
|
||||
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@@ -113,6 +118,17 @@ class ManualGameControllerTest extends AbstractMockedAdaptersTest {
|
||||
assertEquals(3600, gameTimeToBeatRepository.findById(-119388L).orElseThrow().getNormallyMinutes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void add_StoresTheReleaseDate() throws Exception {
|
||||
gameReleaseRepository.deleteAll();
|
||||
LocalDate releaseDate = LocalDate.now().plusDays(60);
|
||||
when(igdbApiService.getReleaseDatesByGameIds(any())).thenReturn(Optional.of(Map.of(119388L, releaseDate)));
|
||||
|
||||
add(119388, "Switch").andExpect(status().isCreated());
|
||||
|
||||
assertEquals(releaseDate, gameReleaseRepository.findById(-119388L).orElseThrow().getReleaseDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void add_GameAddedBeforeIgdbKnewItOnSteam_Is409() throws Exception {
|
||||
add(119388, "Switch").andExpect(status().isCreated());
|
||||
|
||||
@@ -7,6 +7,7 @@ import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.domain.UserGameAchievementMeta;
|
||||
import core_service.modules.library.repo.GameAchievementSchemaMetaRepository;
|
||||
import core_service.modules.library.repo.GameReleaseRepository;
|
||||
import core_service.modules.library.repo.SteamGameRepository;
|
||||
import core_service.modules.library.repo.UserGameAchievementMetaRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
@@ -21,8 +22,11 @@ import org.springframework.test.web.servlet.ResultActions;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
@@ -70,8 +74,12 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
|
||||
return game;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
gameReleaseRepository.deleteAll();
|
||||
userGameRepository.deleteAll();
|
||||
schemaMetaRepository.deleteAll();
|
||||
userAchievementMetaRepository.deleteAll();
|
||||
@@ -109,6 +117,22 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
|
||||
.andExpect(jsonPath("$..priority").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void publicLibrary_ComingGame_ShowsItsDateAndAReleasedOneDoesNot() throws Exception {
|
||||
LocalDate tomorrow = LocalDate.now(ZoneId.of("Europe/Berlin")).plusDays(1);
|
||||
steamGameRepository.save(new SteamGame(900L, "Dawn of War 4", null, Instant.now()));
|
||||
userGameRepository.save(game(900L, 0, GameStatus.PLAYING, null));
|
||||
gameReleaseRepository.upsert(900L, tomorrow, false, Instant.now());
|
||||
gameReleaseRepository.upsert(220L, tomorrow.minusDays(100), false, Instant.now());
|
||||
|
||||
mockMvc.perform(get("/api/public/profiles/library").header("X-Profile-Ticket", ticket(USER_ID)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.playing[?(@.appid == 900)].coming").value(true))
|
||||
.andExpect(jsonPath("$.playing[?(@.appid == 900)].comingOn").value(tomorrow.toString()))
|
||||
.andExpect(jsonPath("$.playing[?(@.appid == 220)].coming").value(false))
|
||||
.andExpect(jsonPath("$.playing[?(@.appid == 220)].comingOn").value(contains(nullValue())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void publicLibrary_ShowsPerfectGamesAndAverageCompletion() throws Exception {
|
||||
schemaMetaRepository.saveAll(List.of(
|
||||
|
||||
@@ -12,8 +12,10 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -124,4 +126,36 @@ class BacklogSnapshotRepositoryTest extends AbstractPersistenceTest {
|
||||
|
||||
assertThat(repository.findSince(2L, DAY)).isEmpty();
|
||||
}
|
||||
|
||||
private void comingTomorrow(long appid) {
|
||||
jdbc.update("insert into game_release (appid, release_date, fetched_at) values (?, ?, now())", appid,
|
||||
Date.valueOf(LocalDate.now(ZoneId.of("Europe/Berlin")).plusDays(1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void comingGame_LeavesTheBacklogNumbersOfNewSnapshotsAndLive() {
|
||||
userGameRepository.saveAllAndFlush(List.of(game(1L, 220L, 0, GameStatus.BACKLOG),
|
||||
game(1L, 1000L, 0, GameStatus.BACKLOG)));
|
||||
comingTomorrow(1000L);
|
||||
|
||||
repository.writeAll(DAY);
|
||||
|
||||
UserStats.PileDay expected = new UserStats.PileDay(DAY, 1, 2, 600);
|
||||
assertThat(repository.findSince(1L, DAY)).containsExactly(expected);
|
||||
assertThat(repository.live(1L, DAY)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void comingGame_AnOldSnapshotKeepsItsValue() {
|
||||
userGameRepository.saveAllAndFlush(List.of(game(1L, 220L, 0, GameStatus.BACKLOG),
|
||||
game(1L, 1000L, 0, GameStatus.BACKLOG)));
|
||||
repository.writeAll(DAY.minusDays(1));
|
||||
comingTomorrow(1000L);
|
||||
|
||||
repository.writeAll(DAY);
|
||||
|
||||
assertThat(repository.findSince(1L, DAY.minusDays(1))).containsExactly(
|
||||
new UserStats.PileDay(DAY.minusDays(1), 2, 2, 10599),
|
||||
new UserStats.PileDay(DAY, 1, 2, 600));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package core_service.modules.library.repo;
|
||||
|
||||
import core_service.modules.library.domain.ComingGame;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.shared.test.AbstractPersistenceTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ComingGameRepositoryTest extends AbstractPersistenceTest {
|
||||
|
||||
private static final long GAME = 4000L;
|
||||
private static final LocalDate TODAY = LocalDate.now(ZoneId.of("Europe/Berlin"));
|
||||
|
||||
@Autowired
|
||||
private ComingGameRepository comingGameRepository;
|
||||
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@Autowired
|
||||
private UserGameRepository userGameRepository;
|
||||
|
||||
@Autowired
|
||||
private SteamGameRepository steamGameRepository;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
private void release(LocalDate date, boolean comingSoon) {
|
||||
gameReleaseRepository.upsert(GAME, date, comingSoon, Instant.now());
|
||||
}
|
||||
|
||||
private void owned(long userId, int playtime) {
|
||||
steamGameRepository.saveAndFlush(new SteamGame(GAME, "Dawn of War 4", null, Instant.now()));
|
||||
userGameRepository.saveAndFlush(new UserGame(userId, GAME, playtime, Instant.now(), Instant.now()));
|
||||
}
|
||||
|
||||
private boolean coming() {
|
||||
return comingGameRepository.findById(GAME).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void view_FutureDateAndNoPlay_IsComingWithItsDate() {
|
||||
release(TODAY.plusDays(1), false);
|
||||
owned(1L, 0);
|
||||
|
||||
assertThat(comingGameRepository.findById(GAME)).get().extracting(ComingGame::getReleaseDate)
|
||||
.isEqualTo(TODAY.plusDays(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void view_OnTheReleaseDay_IsNotComing() {
|
||||
release(TODAY, false);
|
||||
|
||||
assertThat(coming()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void view_PastDate_IsNotComing() {
|
||||
release(TODAY.minusDays(1), false);
|
||||
|
||||
assertThat(coming()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void view_UnknownDate_IsNotComing() {
|
||||
release(null, false);
|
||||
|
||||
assertThat(coming()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void view_ComingSoonWithoutDate_IsComingWithoutDate() {
|
||||
release(null, true);
|
||||
|
||||
assertThat(comingGameRepository.findById(GAME)).get().extracting(ComingGame::getReleaseDate).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void view_ComingSoonWithoutDateButPlaytime_IsNotComing() {
|
||||
release(null, true);
|
||||
owned(1L, 5);
|
||||
|
||||
assertThat(coming()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void view_FutureDateButAnotherUsersPlaytime_IsNotComing() {
|
||||
release(TODAY.plusDays(30), false);
|
||||
owned(1L, 0);
|
||||
owned(2L, 10);
|
||||
|
||||
assertThat(coming()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void view_FutureDateButUnlockedAchievement_IsNotComing() {
|
||||
release(TODAY.plusDays(30), false);
|
||||
owned(1L, 0);
|
||||
jdbc.update("insert into user_game_achievement_meta (user_id, appid, achievements_unlocked) values (1, ?, 1)", GAME);
|
||||
|
||||
assertThat(coming()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void view_FutureDateAndAchievementMetaWithoutUnlocks_IsComing() {
|
||||
release(TODAY.plusDays(30), false);
|
||||
jdbc.update("insert into user_game_achievement_meta (user_id, appid, achievements_unlocked) values (1, ?, 0)", GAME);
|
||||
|
||||
assertThat(coming()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findFreshAppids_ComingRowsFetchedTwoDaysAgoAreStale_ReleasedRowIsFresh() {
|
||||
Instant twoDaysAgo = Instant.now().minusSeconds(2 * 86_400L);
|
||||
gameReleaseRepository.upsert(1L, TODAY.plusDays(5), false, twoDaysAgo);
|
||||
gameReleaseRepository.upsert(2L, null, true, twoDaysAgo);
|
||||
gameReleaseRepository.upsert(3L, TODAY.minusDays(5), false, twoDaysAgo);
|
||||
gameReleaseRepository.upsert(4L, null, false, twoDaysAgo);
|
||||
gameReleaseRepository.upsert(5L, TODAY.plusDays(5), false, Instant.now());
|
||||
|
||||
Set<Long> fresh = gameReleaseRepository.findFreshAppids(List.of(1L, 2L, 3L, 4L, 5L, 6L),
|
||||
Instant.now().minusSeconds(30 * 86_400L), TODAY, Instant.now().minusSeconds(86_400L));
|
||||
|
||||
assertThat(fresh).containsExactlyInAnyOrder(3L, 4L, 5L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findFreshAppids_OldRowBeyondTheTtl_IsStale() {
|
||||
gameReleaseRepository.upsert(3L, TODAY.minusDays(50), false, Instant.now().minusSeconds(40 * 86_400L));
|
||||
|
||||
assertThat(gameReleaseRepository.findFreshAppids(List.of(3L), Instant.now().minusSeconds(30 * 86_400L), TODAY,
|
||||
Instant.now().minusSeconds(86_400L))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void upsert_SecondCall_ReplacesTheRow() {
|
||||
release(TODAY.plusDays(5), false);
|
||||
release(null, true);
|
||||
|
||||
assertThat(gameReleaseRepository.findById(GAME)).get()
|
||||
.satisfies(r -> {
|
||||
assertThat(r.getReleaseDate()).isNull();
|
||||
assertThat(r.isComingSoon()).isTrue();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,8 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -34,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
|
||||
private static final long USER_ID = 1L;
|
||||
private static final ZoneId BERLIN = ZoneId.of("Europe/Berlin");
|
||||
private static final PageRequest PAGE = PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "playtimeMinutes"));
|
||||
|
||||
@Autowired
|
||||
@@ -51,6 +54,9 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
@Autowired
|
||||
private GameMoodsRepository gameMoodsRepository;
|
||||
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
@@ -547,4 +553,57 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
assertEquals(List.of(1003L), ids(userGameRepository.findSimilarGames(USER_ID, 1001L, null, Mood.COOP, List.of(1002L), 10)));
|
||||
assertEquals(List.of(1003L), ids(userGameRepository.findLongestWaitingGames(USER_ID, null, Mood.COOP, List.of(1002L), 10)));
|
||||
}
|
||||
|
||||
private void backlogGameReleasing(long appid, LocalDate releaseDate, String... moods) {
|
||||
addGame(appid, GameStatus.BACKLOG, null, Instant.now(), 0, moods);
|
||||
gameReleaseRepository.upsert(appid, releaseDate, false, Instant.now());
|
||||
}
|
||||
|
||||
@Test
|
||||
void comingGame_IsNeverPickedFromAnySource() {
|
||||
hide(380L, 700L);
|
||||
LocalDate tomorrow = LocalDate.now(BERLIN).plusDays(1);
|
||||
addGame(1001L, GameStatus.BACKLOG, null, Instant.now(), 0, "STORY");
|
||||
backlogGameReleasing(1002L, tomorrow, "STORY");
|
||||
|
||||
assertEquals(List.of(1001L), ids(userGameRepository.findPickerGames(USER_ID, null, null, List.of(), 10)));
|
||||
assertEquals(List.of(1001L), ids(userGameRepository.findLongestWaitingGames(USER_ID, null, null, List.of(), 10)));
|
||||
assertTrue(userGameRepository.findSimilarGames(USER_ID, 1001L, null, null, List.of(), 10).isEmpty());
|
||||
for (int i = 0; i < 20; i++) {
|
||||
assertEquals(1001L, userGameRepository.findRandomBacklogGame(USER_ID, UserGameRepository.NO_GAME).orElseThrow().getAppid());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void releasedGame_IsPickedAndCounted() {
|
||||
hide(380L, 700L);
|
||||
backlogGameReleasing(1002L, LocalDate.now(BERLIN), "STORY");
|
||||
backlogGameReleasing(1003L, LocalDate.now(BERLIN).minusDays(1), "STORY");
|
||||
|
||||
assertEquals(List.of(1002L, 1003L), ids(userGameRepository.findPickerGames(USER_ID, null, null, List.of(), 10))
|
||||
.stream().sorted().toList());
|
||||
assertEquals(2, userGameRepository.countPile(USER_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
void comingGame_LeavesThePileCountAndTheBacklogMinutesButKeepsItsStatus() {
|
||||
LocalDate tomorrow = LocalDate.now(BERLIN).plusDays(1);
|
||||
backlogGameReleasing(1002L, tomorrow);
|
||||
gameTimeToBeatRepository.save(new GameTimeToBeat(1002L, 1, 5000, 1, Instant.now()));
|
||||
gameTimeToBeatRepository.save(new GameTimeToBeat(380L, 100, 200, 300, Instant.now()));
|
||||
|
||||
assertEquals(2, userGameRepository.countPile(USER_ID));
|
||||
assertEquals(3, userGameRepository.countEveryStatus(USER_ID).get(GameStatus.BACKLOG));
|
||||
assertEquals(200, userGameRepository.sumBacklogMinutes(USER_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
void comingGame_WithAnyUsersPlaytime_IsPickedAndCounted() {
|
||||
hide(380L, 700L);
|
||||
backlogGameReleasing(1002L, LocalDate.now(BERLIN).plusDays(30));
|
||||
userGameRepository.saveAndFlush(new UserGame(2L, 1002L, 10, Instant.now(), Instant.now()));
|
||||
|
||||
assertEquals(List.of(1002L), ids(userGameRepository.findPickerGames(USER_ID, null, null, List.of(), 10)));
|
||||
assertEquals(1, userGameRepository.countPile(USER_ID));
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,14 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.tuple;
|
||||
|
||||
@Import(UserStatsRepository.class)
|
||||
class UserStatsRepositoryTest extends AbstractPersistenceTest {
|
||||
@@ -370,4 +372,40 @@ class UserStatsRepositoryTest extends AbstractPersistenceTest {
|
||||
assertThat(repository.countGames(USER_ID, StatKey.TOTAL, "", BERLIN)).isEqualTo(1);
|
||||
assertThat(repository.games(USER_ID, StatKey.TOTAL, "", BERLIN, 0, 10)).hasSize(1);
|
||||
}
|
||||
|
||||
private void comingTomorrow(long appid) {
|
||||
jdbc.update("insert into game_release (appid, release_date, fetched_at) values (?, ?, now())", appid,
|
||||
Date.valueOf(LocalDate.now(BERLIN).plusDays(1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void comingGame_LeavesThePileNumbersAndDrillSets() {
|
||||
userGameRepository.saveAllAndFlush(List.of(
|
||||
game(220L, 0, GameStatus.BACKLOG, Instant.parse("2026-01-01T00:00:00Z")),
|
||||
game(620L, 0, GameStatus.BACKLOG, Instant.EPOCH)));
|
||||
gameTimeToBeatRepository.saveAllAndFlush(List.of(new GameTimeToBeat(220L, 300, 600, 1200, Instant.now()),
|
||||
new GameTimeToBeat(620L, 300, 5000, 8000, Instant.now())));
|
||||
comingTomorrow(620L);
|
||||
|
||||
assertThat(repository.pileTimes(USER_ID)).isEqualTo(new UserStats.PileTimes(600, 1200, 1));
|
||||
assertThat(repository.oldestUnplayed(USER_ID).orElseThrow().appid()).isEqualTo(220L);
|
||||
assertThat(repository.countGames(USER_ID, StatKey.PILE, "", BERLIN)).isEqualTo(1);
|
||||
assertThat(repository.countGames(USER_ID, StatKey.PILE_TIME_TO_BEAT, "", BERLIN)).isEqualTo(1);
|
||||
assertThat(repository.countGames(USER_ID, StatKey.TOTAL, "", BERLIN)).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void games_MarkComingGamesWithTheirDate() {
|
||||
userGameRepository.saveAllAndFlush(List.of(
|
||||
game(220L, 0, GameStatus.BACKLOG, Instant.now()),
|
||||
game(620L, 0, GameStatus.BACKLOG, Instant.now())));
|
||||
comingTomorrow(620L);
|
||||
|
||||
List<UserStats.StatGame> games = repository.games(USER_ID, StatKey.TOTAL, "", BERLIN, 0, 10);
|
||||
|
||||
assertThat(games).extracting(UserStats.StatGame::appid, UserStats.StatGame::coming, UserStats.StatGame::comingOn)
|
||||
.containsExactlyInAnyOrder(
|
||||
tuple(220L, false, null),
|
||||
tuple(620L, true, LocalDate.now(BERLIN).plusDays(1)));
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class GameImportWorkerTest {
|
||||
.thenReturn(Optional.of(7L), Optional.empty());
|
||||
when(repository.findStalled(any())).thenReturn(Map.of());
|
||||
GameImportWorker worker = new GameImportWorker(repository, mock(ManualGameService.class),
|
||||
mock(TimeToBeatService.class), mock(MoodService.class), 5);
|
||||
mock(TimeToBeatService.class), mock(MoodService.class), mock(ReleaseDateService.class), 5);
|
||||
try {
|
||||
worker.wakeMatching();
|
||||
verify(repository, timeout(2000)).findNext(GameImportWorker.MATCHING_QUEUE);
|
||||
|
||||
@@ -126,6 +126,9 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
@Mock
|
||||
private MoodService moodService;
|
||||
|
||||
@Mock
|
||||
private ReleaseDateService releaseDateService;
|
||||
|
||||
@Mock
|
||||
private GameEditionService gameEditionService;
|
||||
|
||||
@@ -544,6 +547,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
|
||||
verify(timeToBeatService).refresh(List.of(220L, -1025L));
|
||||
verify(moodService).refresh(List.of(220L, -1025L));
|
||||
verify(releaseDateService).refresh(List.of(220L, -1025L));
|
||||
verify(gameEditionService).refresh(List.of(220L, -1025L));
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,9 @@ class ManualGameServiceTest extends AbstractUnitTest {
|
||||
@Mock
|
||||
private GameEditionService gameEditionService;
|
||||
|
||||
@Mock
|
||||
private ReleaseDateService releaseDateService;
|
||||
|
||||
@Mock
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
@@ -61,7 +64,7 @@ class ManualGameServiceTest extends AbstractUnitTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new ManualGameService(igdbApiService, userGameRepository, steamGameRepository, mergeRepository, timeToBeatService,
|
||||
moodService, gameEditionService, transactionManager);
|
||||
moodService, gameEditionService, releaseDateService, transactionManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package core_service.modules.library.service;
|
||||
|
||||
import core_service.modules.igdb_adapter.service.IgdbApiService;
|
||||
import core_service.modules.library.repo.GameReleaseRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
import core_service.modules.steam_adapter.service.SteamApiService;
|
||||
import core_service.shared.test.AbstractUnitTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyCollection;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class ReleaseDateServiceTest extends AbstractUnitTest {
|
||||
|
||||
private static final LocalDate IGDB_DATE = LocalDate.of(2026, 12, 4);
|
||||
private static final LocalDate STEAM_DATE = LocalDate.of(2026, 11, 20);
|
||||
|
||||
@Mock
|
||||
private IgdbApiService igdbApiService;
|
||||
|
||||
@Mock
|
||||
private SteamApiService steamApiService;
|
||||
|
||||
@Mock
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@Mock
|
||||
private UserGameRepository userGameRepository;
|
||||
|
||||
@Spy
|
||||
private TaskExecutor applicationTaskExecutor = new SyncTaskExecutor();
|
||||
|
||||
@InjectMocks
|
||||
private ReleaseDateService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ReflectionTestUtils.setField(service, "ttlDays", 30L);
|
||||
ReflectionTestUtils.setField(service, "zone", ZoneId.of("Europe/Berlin"));
|
||||
when(igdbApiService.isEnabled()).thenReturn(true);
|
||||
when(gameReleaseRepository.findFreshAppids(anyCollection(), any(), any(), any())).thenReturn(Set.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
void refresh_IgdbDate_WinsAndSteamIsNotAskedForIt() {
|
||||
when(igdbApiService.getReleaseDates(Set.of(620L))).thenReturn(Optional.of(Map.of(620L, IGDB_DATE)));
|
||||
|
||||
service.refresh(List.of(620L));
|
||||
|
||||
verify(gameReleaseRepository).upsert(eq(620L), eq(IGDB_DATE), eq(false), any(Instant.class));
|
||||
verify(steamApiService, never()).getReleases(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void refresh_SteamIsAskedOnlyForSteamKeysWithoutIgdbDate() {
|
||||
when(igdbApiService.getReleaseDates(Set.of(620L, 700L))).thenReturn(Optional.of(Map.of(620L, IGDB_DATE)));
|
||||
when(igdbApiService.getReleaseDatesByGameIds(Set.of(7L))).thenReturn(Optional.of(Map.of()));
|
||||
when(steamApiService.getReleases(List.of(700L))).thenReturn(Optional.of(Map.of(
|
||||
700L, new SteamApiService.StoreRelease(STEAM_DATE, false))));
|
||||
|
||||
service.refresh(List.of(620L, 700L, -7L));
|
||||
|
||||
verify(steamApiService).getReleases(List.of(700L));
|
||||
verify(gameReleaseRepository).upsert(eq(620L), eq(IGDB_DATE), eq(false), any(Instant.class));
|
||||
verify(gameReleaseRepository).upsert(eq(700L), eq(STEAM_DATE), eq(false), any(Instant.class));
|
||||
verify(gameReleaseRepository).upsert(eq(-7L), isNull(), eq(false), any(Instant.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refresh_SteamComingSoonWithoutDate_StoresNullDateAndComingSoon() {
|
||||
when(igdbApiService.getReleaseDates(Set.of(700L))).thenReturn(Optional.of(Map.of()));
|
||||
when(steamApiService.getReleases(List.of(700L))).thenReturn(Optional.of(Map.of(
|
||||
700L, new SteamApiService.StoreRelease(null, true))));
|
||||
|
||||
service.refresh(List.of(700L));
|
||||
|
||||
verify(gameReleaseRepository).upsert(eq(700L), isNull(), eq(true), any(Instant.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refresh_IgdbDateBeatsSteamComingSoon() {
|
||||
when(igdbApiService.getReleaseDates(Set.of(700L))).thenReturn(Optional.of(Map.of(700L, IGDB_DATE)));
|
||||
|
||||
service.refresh(List.of(700L));
|
||||
|
||||
verify(gameReleaseRepository).upsert(eq(700L), eq(IGDB_DATE), eq(false), any(Instant.class));
|
||||
verify(steamApiService, never()).getReleases(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void refresh_SteamKnowsNothing_StoresUnknown() {
|
||||
when(igdbApiService.getReleaseDates(Set.of(700L))).thenReturn(Optional.of(Map.of()));
|
||||
when(steamApiService.getReleases(List.of(700L))).thenReturn(Optional.of(Map.of()));
|
||||
|
||||
service.refresh(List.of(700L));
|
||||
|
||||
verify(gameReleaseRepository).upsert(eq(700L), isNull(), eq(false), any(Instant.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refresh_IgdbError_StoresNothing() {
|
||||
when(igdbApiService.getReleaseDates(any())).thenReturn(Optional.empty());
|
||||
|
||||
service.refresh(List.of(620L));
|
||||
|
||||
verify(gameReleaseRepository, never()).upsert(any(), any(), anyBoolean(), any());
|
||||
verify(steamApiService, never()).getReleases(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void refresh_SteamError_StoresOnlyTheIgdbDatedKeys() {
|
||||
when(igdbApiService.getReleaseDates(Set.of(620L, 700L))).thenReturn(Optional.of(Map.of(620L, IGDB_DATE)));
|
||||
when(steamApiService.getReleases(anyList())).thenReturn(Optional.empty());
|
||||
|
||||
service.refresh(List.of(620L, 700L));
|
||||
|
||||
verify(gameReleaseRepository).upsert(eq(620L), eq(IGDB_DATE), eq(false), any(Instant.class));
|
||||
verify(gameReleaseRepository, never()).upsert(eq(700L), any(), anyBoolean(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void refresh_FreshKeys_AreSkipped() {
|
||||
when(gameReleaseRepository.findFreshAppids(anyCollection(), any(), any(), any())).thenReturn(Set.of(620L));
|
||||
|
||||
service.refresh(List.of(620L));
|
||||
|
||||
verify(igdbApiService, never()).getReleaseDates(any());
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
@@ -239,4 +240,15 @@ class StatsRepositoryTest extends AbstractPersistenceTest {
|
||||
hiddenGames_LeaveTheTopLists();
|
||||
assertLandingListsIgnoreUser3();
|
||||
}
|
||||
|
||||
@Test
|
||||
void biggestBacklog_LeavesOutComingGames() {
|
||||
steamGameRepository.saveAndFlush(new SteamGame(3000L, "Not out yet", null, Instant.now()));
|
||||
userGameRepository.saveAllAndFlush(List.of(new UserGame(5L, 220L, 0, Instant.now(), Instant.now()),
|
||||
new UserGame(5L, 3000L, 0, Instant.now(), Instant.now())));
|
||||
jdbc.update("insert into game_release (appid, release_date, fetched_at) values (3000, ?, now())",
|
||||
Date.valueOf(LocalDate.now(ZoneId.of("Europe/Berlin")).plusDays(1)));
|
||||
|
||||
assertEquals(1, statsRepository.biggestBacklog());
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -236,6 +237,35 @@ class SteamApiServiceTest {
|
||||
assertThat(steamApiService.getStoreHeaderUrls(List.of(620L))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getReleases_SendsIncludeReleaseAndParsesDateAndComingSoon() {
|
||||
server.expect(requestTo(containsString("/IStoreBrowseService/GetItems/v1/")))
|
||||
.andExpect(queryParam("input_json", containsString("%22data_request%22%3A%7B%22include_release%22%3Atrue%7D")))
|
||||
.andRespond(withSuccess("""
|
||||
{"response": {"store_items": [
|
||||
{"appid": 620, "success": 1, "release": {"steam_release_date": 1796427000, "is_coming_soon": true}},
|
||||
{"appid": 700, "success": 1, "release": {"is_coming_soon": true}},
|
||||
{"appid": 800, "success": 1, "release": {"steam_release_date": 1493942400}},
|
||||
{"appid": 900, "success": 1}
|
||||
]}}
|
||||
""", MediaType.APPLICATION_JSON));
|
||||
|
||||
Map<Long, SteamApiService.StoreRelease> releases = steamApiService.getReleases(List.of(620L, 700L, 800L, 900L)).orElseThrow();
|
||||
|
||||
assertThat(releases).containsOnly(
|
||||
Map.entry(620L, new SteamApiService.StoreRelease(LocalDate.of(2026, 12, 4), true)),
|
||||
Map.entry(700L, new SteamApiService.StoreRelease(null, true)),
|
||||
Map.entry(800L, new SteamApiService.StoreRelease(LocalDate.of(2017, 5, 5), false)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getReleases_ServerError_IsEmpty() {
|
||||
server.expect(requestTo(containsString("/IStoreBrowseService/GetItems/v1/")))
|
||||
.andRespond(withServerError());
|
||||
|
||||
assertThat(steamApiService.getReleases(List.of(620L))).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPlayerAchievements_Forbidden_IsEmpty() {
|
||||
server.expect(requestTo(containsString("/ISteamUserStats/GetPlayerAchievements/v1/")))
|
||||
|
||||
Reference in new issue
Block a user