Public Access
feat(library): add hand-ordered backlog with next-up picker source
This commit is contained in:
1 parent
7b538b7f30
commit
7747bc7b8b
22 files changed
+754
-20
No files matched your search
@@ -232,6 +232,18 @@ class GameMergeFlowTest extends AbstractIntegrationTest {
|
||||
"STARTED " + MANUAL, "STARTED " + STEAM);
|
||||
}
|
||||
|
||||
@Test
|
||||
void merge_BothInBacklog_KeptGameTakesTheBetterPosition() throws Exception {
|
||||
manualRow(AUDITOR_ID, MANUAL, GameStatus.BACKLOG);
|
||||
userGameRepository.save(new UserGame(AUDITOR_ID, STEAM_2, 0, Instant.now(), Instant.now()));
|
||||
userGameRepository.save(new UserGame(AUDITOR_ID, STEAM, 0, Instant.now(), Instant.now()));
|
||||
|
||||
merge(Role.AUDITOR, MANUAL, STEAM).andExpect(status().isOk());
|
||||
|
||||
assertThat(jdbc.queryForList("select appid from backlog_order where user_id = ? order by position, appid",
|
||||
Long.class, AUDITOR_ID)).containsExactly(STEAM, STEAM_2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void userWithBothRows_KeepsTheSteamRowWithTheNewerHandSetFields() throws Exception {
|
||||
UserGame steam = new UserGame(AUDITOR_ID, STEAM, 300, Instant.now(), Instant.now());
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package core_service.modules.library.api;
|
||||
|
||||
import core_service.configuration.security.ClientHeaderFilter;
|
||||
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.AbstractMockedLibraryServicesTest;
|
||||
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.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.LongStream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
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.
|
||||
*/
|
||||
@AutoConfigureMockMvc
|
||||
class LibraryControllerBacklogOrderTest extends AbstractMockedLibraryServicesTest {
|
||||
|
||||
private static final long USER_ID = 1L;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private SteamGameRepository steamGameRepository;
|
||||
|
||||
@Autowired
|
||||
private UserGameRepository userGameRepository;
|
||||
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@BeforeEach
|
||||
void clean() {
|
||||
gameReleaseRepository.deleteAll();
|
||||
userGameRepository.deleteAll();
|
||||
}
|
||||
|
||||
private void game(long userId, long appid, GameStatus status) {
|
||||
steamGameRepository.save(new SteamGame(appid, "Game " + appid, null, Instant.now()));
|
||||
UserGame game = new UserGame(userId, appid, 0, Instant.now(), Instant.now());
|
||||
game.setStatus(status);
|
||||
userGameRepository.save(game);
|
||||
}
|
||||
|
||||
private ResultActions putOrder(String appidsJson) throws Exception {
|
||||
return mockMvc.perform(put("/api/library/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test")
|
||||
.contentType(MediaType.APPLICATION_JSON).content("{\"appids\":" + appidsJson + "}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBacklog_ReturnsTheGamesInOrder_AndLimitReturnsTheFirst() throws Exception {
|
||||
game(USER_ID, 10, GameStatus.BACKLOG);
|
||||
game(USER_ID, 20, GameStatus.BACKLOG);
|
||||
game(USER_ID, 30, GameStatus.BACKLOG);
|
||||
game(USER_ID, 40, GameStatus.PLAYING);
|
||||
|
||||
mockMvc.perform(get("/api/library/backlog"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.games[*].appid", contains(10, 20, 30)));
|
||||
mockMvc.perform(get("/api/library/backlog").param("limit", "2"))
|
||||
.andExpect(jsonPath("$.games[*].appid", contains(10, 20)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBacklog_ComingGame_IsShownAndCanBeReordered_ReleasedLeavesItOut() throws Exception {
|
||||
LocalDate berlinToday = LocalDate.now(ZoneId.of("Europe/Berlin"));
|
||||
game(USER_ID, 10, GameStatus.BACKLOG);
|
||||
game(USER_ID, 20, GameStatus.BACKLOG);
|
||||
gameReleaseRepository.upsert(20L, berlinToday.plusDays(1), false, Instant.now());
|
||||
|
||||
putOrder("[20,10]").andExpect(status().isNoContent());
|
||||
|
||||
mockMvc.perform(get("/api/library/backlog"))
|
||||
.andExpect(jsonPath("$.games[*].appid", contains(20, 10)))
|
||||
.andExpect(jsonPath("$.games[0].coming").value(true));
|
||||
mockMvc.perform(get("/api/library/backlog").param("released", "true"))
|
||||
.andExpect(jsonPath("$.games[*].appid", contains(10)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void putBacklog_ThenGet_ReturnsTheNewOrder() throws Exception {
|
||||
game(USER_ID, 10, GameStatus.BACKLOG);
|
||||
game(USER_ID, 20, GameStatus.BACKLOG);
|
||||
game(USER_ID, 30, GameStatus.BACKLOG);
|
||||
|
||||
putOrder("[30,10,20]").andExpect(status().isNoContent());
|
||||
|
||||
mockMvc.perform(get("/api/library/backlog"))
|
||||
.andExpect(jsonPath("$.games[*].appid", contains(30, 10, 20)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void putBacklog_AnotherUsersAppid_DoesNotChangeThatUsersOrder() throws Exception {
|
||||
game(USER_ID, 10, GameStatus.BACKLOG);
|
||||
game(2L, 20, GameStatus.BACKLOG);
|
||||
game(2L, 30, GameStatus.BACKLOG);
|
||||
|
||||
putOrder("[30,10,20]").andExpect(status().isNoContent());
|
||||
|
||||
mockMvc.perform(get("/api/library/backlog"))
|
||||
.andExpect(jsonPath("$.games[*].appid", contains(10)));
|
||||
assertThat(userGameRepository.findBacklogInOrder(2L, false, 10))
|
||||
.extracting(UserGame::getAppid).containsExactly(20L, 30L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void putBacklog_PartialList_LeavesTheMissingGamesAfterTheSentOnes() throws Exception {
|
||||
game(USER_ID, 10, GameStatus.BACKLOG);
|
||||
game(USER_ID, 20, GameStatus.BACKLOG);
|
||||
game(USER_ID, 30, GameStatus.BACKLOG);
|
||||
|
||||
putOrder("[30]").andExpect(status().isNoContent());
|
||||
|
||||
mockMvc.perform(get("/api/library/backlog"))
|
||||
.andExpect(jsonPath("$.games[*].appid", contains(30, 10, 20)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBacklog_Empty_ReturnsNoGames() throws Exception {
|
||||
mockMvc.perform(get("/api/library/backlog"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.games", hasSize(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void putBacklog_NullElement_Is400() throws Exception {
|
||||
putOrder("[10,null]").andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void putBacklog_MoreThan20000Appids_Is400() throws Exception {
|
||||
String tooMany = LongStream.rangeClosed(1, 20001).mapToObj(Long::toString)
|
||||
.collect(Collectors.joining(",", "[", "]"));
|
||||
|
||||
putOrder(tooMany).andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBacklog_GameSetToFinished_IsGone() throws Exception {
|
||||
game(USER_ID, 10, GameStatus.BACKLOG);
|
||||
game(USER_ID, 20, GameStatus.BACKLOG);
|
||||
|
||||
mockMvc.perform(put("/api/library/games/10/status").header(ClientHeaderFilter.CLIENT_HEADER, "test")
|
||||
.contentType(MediaType.APPLICATION_JSON).content("{\"status\":\"FINISHED\"}"))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
mockMvc.perform(get("/api/library/backlog"))
|
||||
.andExpect(jsonPath("$.games[*].appid", contains(20)));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package core_service.modules.library.api;
|
||||
|
||||
import core_service.configuration.security.ClientHeaderFilter;
|
||||
import core_service.modules.library.domain.GamePriority;
|
||||
import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.GameTimeToBeat;
|
||||
@@ -11,24 +12,30 @@ 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;
|
||||
import core_service.modules.library.repo.UserGameAchievementMetaRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
import core_service.shared.test.AbstractMockedLibraryServicesTest;
|
||||
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.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -63,9 +70,14 @@ class LibraryControllerPickerTest extends AbstractMockedLibraryServicesTest {
|
||||
@Autowired
|
||||
private GameReleaseRepository gameReleaseRepository;
|
||||
|
||||
@Autowired
|
||||
private UserGameAchievementMetaRepository achievementMetaRepository;
|
||||
|
||||
@BeforeEach
|
||||
void clean() {
|
||||
gameReleaseRepository.deleteAll();
|
||||
// A game with unlocked achievements is never coming; other test classes leave such rows.
|
||||
achievementMetaRepository.deleteAll();
|
||||
userGameRepository.deleteAll();
|
||||
gameTimeToBeatRepository.deleteAll();
|
||||
gameMoodsRepository.deleteAll();
|
||||
@@ -306,4 +318,47 @@ class LibraryControllerPickerTest extends AbstractMockedLibraryServicesTest {
|
||||
mockMvc.perform(get("/api/library/picker"))
|
||||
.andExpect(jsonPath("$.games[*].appid", containsInAnyOrder(10, 20)));
|
||||
}
|
||||
|
||||
private void putOrder(Long... appids) throws Exception {
|
||||
mockMvc.perform(put("/api/library/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"appids\":" + Arrays.toString(appids) + "}"))
|
||||
.andExpect(status().isNoContent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void picker_FourSlots_NextUpComesFromTopFiveWithReason() throws Exception {
|
||||
for (long appid = 1; appid <= 7; appid++) {
|
||||
game(appid, GameStatus.BACKLOG, null, null);
|
||||
}
|
||||
putOrder(7L, 6L, 5L, 4L, 3L, 2L, 1L);
|
||||
|
||||
mockMvc.perform(get("/api/library/picker").param("count", "4"))
|
||||
.andExpect(jsonPath("$.games", hasSize(4)))
|
||||
.andExpect(jsonPath("$.games[3].reason.source").value("NEXT_UP"))
|
||||
.andExpect(jsonPath("$.games[3].appid", anyOf(is(7), is(6), is(5), is(4), is(3))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void picker_NextUp_SkipsExcludedAndFilteredGames() throws Exception {
|
||||
for (long appid = 1; appid <= 12; appid++) {
|
||||
game(appid, GameStatus.BACKLOG, null, 60);
|
||||
}
|
||||
|
||||
mockMvc.perform(get("/api/library/picker").param("count", "4").param("exclude", "1", "2", "3", "4", "5"))
|
||||
.andExpect(jsonPath("$.games[3].reason.source").value("NEXT_UP"))
|
||||
.andExpect(jsonPath("$.games[3].appid", anyOf(is(6), is(7), is(8), is(9), is(10))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void picker_NextUp_NeverPicksAComingGame() throws Exception {
|
||||
LocalDate berlinToday = LocalDate.now(ZoneId.of("Europe/Berlin"));
|
||||
game(10L, GameStatus.BACKLOG, null, null);
|
||||
game(20L, GameStatus.BACKLOG, null, null);
|
||||
gameReleaseRepository.upsert(10L, berlinToday.plusDays(1), false, Instant.now());
|
||||
|
||||
mockMvc.perform(get("/api/library/picker").param("count", "4"))
|
||||
.andExpect(jsonPath("$.games", hasSize(1)))
|
||||
.andExpect(jsonPath("$.games[0].appid").value(20));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package core_service.modules.library.domain;
|
||||
|
||||
import core_service.shared.test.AbstractUnitTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class BacklogOrderTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void reorder_KeepsTheSentOrder() {
|
||||
assertEquals(List.of(3L, 1L, 2L), BacklogOrder.reorder(List.of(1L, 2L, 3L), List.of(3L, 1L, 2L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reorder_IgnoresUnknownAppids() {
|
||||
assertEquals(List.of(2L, 1L), BacklogOrder.reorder(List.of(1L, 2L), List.of(99L, 2L, 1L, 98L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reorder_KeepsADuplicateOnce() {
|
||||
assertEquals(List.of(2L, 1L), BacklogOrder.reorder(List.of(1L, 2L), List.of(2L, 2L, 1L, 2L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reorder_AppendsTheMissingInTheirOldOrder() {
|
||||
assertEquals(List.of(4L, 1L, 2L, 3L, 5L), BacklogOrder.reorder(List.of(1L, 2L, 3L, 4L, 5L), List.of(4L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reorder_EmptyWanted_KeepsCurrent() {
|
||||
assertEquals(List.of(1L, 2L, 3L), BacklogOrder.reorder(List.of(1L, 2L, 3L), List.of()));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static core_service.modules.library.domain.PickSource.LONGEST;
|
||||
import static core_service.modules.library.domain.PickSource.NEXT_UP;
|
||||
import static core_service.modules.library.domain.PickSource.RANDOM;
|
||||
import static core_service.modules.library.domain.PickSource.SIMILAR;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -34,7 +35,7 @@ class PickMixTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void mix_ThreeSources_OneGameEachInSlotOrder() {
|
||||
List<Pick> picks = PickMix.mix(games(1), games(2), games(3), 3);
|
||||
List<Pick> picks = PickMix.mix(games(1), games(2), games(3), List.of(), 3);
|
||||
|
||||
assertEquals(List.of(SIMILAR, LONGEST, RANDOM), sources(picks));
|
||||
assertEquals(List.of(1L, 2L, 3L), appids(picks));
|
||||
@@ -42,15 +43,16 @@ class PickMixTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void mix_MoreSlotsThanSources_RotationContinues() {
|
||||
List<Pick> picks = PickMix.mix(games(1, 4, 7), games(2, 5), games(3, 6), 7);
|
||||
List<Pick> picks = PickMix.mix(games(1, 5, 9), games(2, 6), games(3, 7), games(4, 8), 9);
|
||||
|
||||
assertEquals(List.of(SIMILAR, LONGEST, RANDOM, SIMILAR, LONGEST, RANDOM, SIMILAR), sources(picks));
|
||||
assertEquals(List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L), appids(picks));
|
||||
assertEquals(List.of(SIMILAR, LONGEST, RANDOM, NEXT_UP, SIMILAR, LONGEST, RANDOM, NEXT_UP, SIMILAR),
|
||||
sources(picks));
|
||||
assertEquals(List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L), appids(picks));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mix_SameGameFirstInAllLists_AppearsOnce() {
|
||||
List<Pick> picks = PickMix.mix(games(1, 4), games(1, 5), games(1, 6), 3);
|
||||
List<Pick> picks = PickMix.mix(games(1, 4), games(1, 5), games(1, 6), List.of(), 3);
|
||||
|
||||
assertEquals(List.of(1L, 5L, 6L), appids(picks));
|
||||
assertEquals(List.of(SIMILAR, LONGEST, RANDOM), sources(picks));
|
||||
@@ -58,7 +60,7 @@ class PickMixTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void mix_EmptySimilar_SlotComesFromRandom() {
|
||||
List<Pick> picks = PickMix.mix(List.of(), games(2), games(3, 4), 3);
|
||||
List<Pick> picks = PickMix.mix(List.of(), games(2), games(3, 4), List.of(), 3);
|
||||
|
||||
assertEquals(List.of(RANDOM, LONGEST, RANDOM), sources(picks));
|
||||
assertEquals(List.of(3L, 2L, 4L), appids(picks));
|
||||
@@ -66,7 +68,7 @@ class PickMixTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void mix_ListsShorterThanCount_ReturnsAllDistinctGames() {
|
||||
List<Pick> picks = PickMix.mix(games(1), games(1, 2), games(2, 3), 10);
|
||||
List<Pick> picks = PickMix.mix(games(1), games(1, 2), games(2, 3), List.of(), 10);
|
||||
|
||||
assertEquals(List.of(1L, 2L, 3L), appids(picks).stream().sorted().toList());
|
||||
assertEquals(3, picks.size());
|
||||
@@ -74,6 +76,22 @@ class PickMixTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void mix_AllEmpty_ReturnsNothing() {
|
||||
assertTrue(PickMix.mix(List.of(), List.of(), List.of(), 3).isEmpty());
|
||||
assertTrue(PickMix.mix(List.of(), List.of(), List.of(), List.of(), 3).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void mix_FourSources_NextUpIsTheFourthSlot() {
|
||||
List<Pick> picks = PickMix.mix(games(1), games(2), games(3), games(4), 4);
|
||||
|
||||
assertEquals(List.of(SIMILAR, LONGEST, RANDOM, NEXT_UP), sources(picks));
|
||||
assertEquals(List.of(1L, 2L, 3L, 4L), appids(picks));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mix_EmptyNextUp_SlotComesFromRandom() {
|
||||
List<Pick> picks = PickMix.mix(games(1), games(2), games(3, 4), List.of(), 4);
|
||||
|
||||
assertEquals(List.of(SIMILAR, LONGEST, RANDOM, RANDOM), sources(picks));
|
||||
assertEquals(List.of(1L, 2L, 3L, 4L), appids(picks));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package core_service.modules.library.repo;
|
||||
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.shared.test.AbstractPersistenceTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Import(BacklogOrderRepository.class)
|
||||
class BacklogOrderRepositoryTest extends AbstractPersistenceTest {
|
||||
|
||||
private static final long USER = 1L;
|
||||
|
||||
@Autowired
|
||||
private BacklogOrderRepository backlogOrderRepository;
|
||||
|
||||
@Autowired
|
||||
private UserGameRepository userGameRepository;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
jdbc.update("delete from game_link");
|
||||
jdbc.update("delete from game_release");
|
||||
jdbc.update("delete from user_game");
|
||||
for (long appid : List.of(10L, 20L, 30L, 40L)) {
|
||||
jdbc.update("insert into steam_game (appid, name) values (?, ?) on conflict do nothing", appid, "Game " + appid);
|
||||
}
|
||||
for (long appid : List.of(10L, 20L, 30L)) {
|
||||
jdbc.update("insert into user_game (user_id, appid, status) values (?, ?, 'BACKLOG')", USER, appid);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Long> backlog(boolean released) {
|
||||
return userGameRepository.findBacklogInOrder(USER, released, Integer.MAX_VALUE).stream()
|
||||
.map(UserGame::getAppid).toList();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeOrder_AfterLockOrder_GivesPositionsOneToN() {
|
||||
assertThat(backlogOrderRepository.lockOrder(USER)).containsExactly(10L, 20L, 30L);
|
||||
|
||||
backlogOrderRepository.writeOrder(USER, List.of(30L, 10L, 20L));
|
||||
|
||||
assertThat(jdbc.queryForList("select position from backlog_order where user_id = ? order by position",
|
||||
Long.class, USER)).containsExactly(1L, 2L, 3L);
|
||||
assertThat(backlogOrderRepository.lockOrder(USER)).containsExactly(30L, 10L, 20L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBacklogInOrder_FollowsThePosition_AndLimit() {
|
||||
backlogOrderRepository.writeOrder(USER, List.of(30L, 10L, 20L));
|
||||
|
||||
assertThat(backlog(false)).containsExactly(30L, 10L, 20L);
|
||||
assertThat(userGameRepository.findBacklogInOrder(USER, false, 2)).extracting(UserGame::getAppid)
|
||||
.containsExactly(30L, 10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBacklogInOrder_LeavesOutALesserEditionOfALinkedSet() {
|
||||
jdbc.update("insert into user_game (user_id, appid, status, playtime_minutes) values (?, 40, 'BACKLOG', 100)", USER);
|
||||
jdbc.update("insert into game_link (appid, set_id) values (10, 1), (40, 1)");
|
||||
|
||||
assertThat(backlog(false)).containsExactly(20L, 30L, 40L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findBacklogInOrder_ReleasedLeavesOutAComingGame() {
|
||||
jdbc.update("insert into game_release (appid, release_date, fetched_at) values (20, current_date + 30, now())");
|
||||
|
||||
assertThat(backlog(true)).containsExactly(10L, 30L);
|
||||
assertThat(backlog(false)).containsExactly(10L, 20L, 30L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package core_service.modules.library.repo;
|
||||
|
||||
import core_service.shared.test.AbstractPersistenceTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* The trigger of V34 that keeps backlog_order.
|
||||
*/
|
||||
class BacklogOrderTriggerTest extends AbstractPersistenceTest {
|
||||
|
||||
private static final long USER = 1L;
|
||||
private static final long OTHER_USER = 2L;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
jdbc.update("delete from user_game");
|
||||
for (long appid = 1; appid <= 300; appid++) {
|
||||
jdbc.update("insert into steam_game (appid, name) values (?, ?) on conflict do nothing", appid, "Game " + appid);
|
||||
}
|
||||
}
|
||||
|
||||
private void own(long userId, long appid, String status) {
|
||||
jdbc.update("insert into user_game (user_id, appid, status) values (?, ?, ?)", userId, appid, status);
|
||||
}
|
||||
|
||||
private void setStatus(long appid, String status) {
|
||||
jdbc.update("update user_game set status = ? where user_id = ? and appid = ?", status, USER, appid);
|
||||
}
|
||||
|
||||
private List<Long> order(long userId) {
|
||||
return jdbc.queryForList("select appid from backlog_order where user_id = ? order by position, appid",
|
||||
Long.class, userId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertedBacklogGame_GoesToTheBottom() {
|
||||
own(USER, 1, "BACKLOG");
|
||||
own(USER, 2, "BACKLOG");
|
||||
own(OTHER_USER, 3, "BACKLOG");
|
||||
|
||||
assertThat(order(USER)).containsExactly(1L, 2L);
|
||||
assertThat(order(OTHER_USER)).containsExactly(3L);
|
||||
assertThat(jdbc.queryForObject("select position from backlog_order where user_id = ? and appid = 3",
|
||||
Long.class, OTHER_USER)).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertedPlayingGame_HasNoRow() {
|
||||
own(USER, 1, "PLAYING");
|
||||
|
||||
assertThat(order(USER)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void leavingBacklog_DeletesTheRow_AndComingBackPutsItAtTheBottom() {
|
||||
own(USER, 1, "BACKLOG");
|
||||
own(USER, 2, "BACKLOG");
|
||||
|
||||
setStatus(1, "FINISHED");
|
||||
assertThat(order(USER)).containsExactly(2L);
|
||||
|
||||
setStatus(1, "BACKLOG");
|
||||
assertThat(order(USER)).containsExactly(2L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hideAndRemove_DeleteTheRow_AndUnhidePutsItAtTheBottom() {
|
||||
own(USER, 1, "BACKLOG");
|
||||
own(USER, 2, "BACKLOG");
|
||||
own(USER, 3, "BACKLOG");
|
||||
|
||||
jdbc.update("update user_game set hidden_at = now() where user_id = ? and appid = 1", USER);
|
||||
jdbc.update("update user_game set removed_at = now() where user_id = ? and appid = 2", USER);
|
||||
assertThat(order(USER)).containsExactly(3L);
|
||||
|
||||
jdbc.update("update user_game set hidden_at = null where user_id = ? and appid = 1", USER);
|
||||
assertThat(order(USER)).containsExactly(3L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateWithoutStatusHideOrRemoveChange_KeepsThePosition() {
|
||||
own(USER, 1, "BACKLOG");
|
||||
own(USER, 2, "BACKLOG");
|
||||
jdbc.update("update backlog_order set position = 5 where user_id = ? and appid = 1", USER);
|
||||
|
||||
jdbc.update("update user_game set status = 'BACKLOG', notes = 'x', hidden_at = null where user_id = ? and appid = 1",
|
||||
USER);
|
||||
|
||||
assertThat(order(USER)).containsExactly(2L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void manyNewGamesAfterAReorder_KeepTheFirstPositions() {
|
||||
own(USER, 1, "BACKLOG");
|
||||
own(USER, 2, "BACKLOG");
|
||||
own(USER, 3, "BACKLOG");
|
||||
jdbc.update("update backlog_order set position = 3 where user_id = ? and appid = 1", USER);
|
||||
jdbc.update("update backlog_order set position = 2 where user_id = ? and appid = 2", USER);
|
||||
jdbc.update("update backlog_order set position = 1 where user_id = ? and appid = 3", USER);
|
||||
|
||||
for (long appid = 100; appid < 300; appid++) {
|
||||
own(USER, appid, "BACKLOG");
|
||||
}
|
||||
|
||||
List<Long> expected = new ArrayList<>(List.of(3L, 2L, 1L));
|
||||
for (long appid = 100; appid < 300; appid++) {
|
||||
expected.add(appid);
|
||||
}
|
||||
assertThat(order(USER)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingTheGame_DeletesTheOrderRow() {
|
||||
own(USER, 1, "BACKLOG");
|
||||
|
||||
jdbc.update("delete from user_game where user_id = ? and appid = 1", USER);
|
||||
|
||||
assertThat(order(USER)).isEmpty();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user