Public Access
feat(library): let auditors merge duplicate games
An auditor merges a game added by hand into its Steam game or another game added by hand (V21). Every user's row moves; a user with both rows keeps one, with the status, rating and notes set by hand. game_alias sends later adds to the kept key, and admin_audit logs who merged what. The game search shows games already in ToBePlayed first.
This commit is contained in:
1 parent
3c2f40036d
commit
5a089b2082
14 files changed
+804
-44
No files matched your search
@@ -0,0 +1,313 @@
|
||||
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.GameSource;
|
||||
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.GameCorrectionRepository;
|
||||
import core_service.modules.library.repo.GameMoodsRepository;
|
||||
import core_service.modules.library.repo.GameTimeToBeatRepository;
|
||||
import core_service.modules.library.repo.SteamGameRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
import core_service.modules.library.repo.UserStatsRepository;
|
||||
import core_service.modules.library.domain.UserStats;
|
||||
import core_service.shared.security.Role;
|
||||
import core_service.shared.test.AbstractIntegrationTest;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
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.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Merges through the real (non-dev) filter chain, so the role in the token decides.
|
||||
*/
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles(value = "prod", inheritProfiles = false)
|
||||
class GameMergeFlowTest extends AbstractIntegrationTest {
|
||||
|
||||
private static final long AUDITOR_ID = 4711L;
|
||||
private static final long OTHER_USER = 4712L;
|
||||
private static final long STEAM = 1_900_100L;
|
||||
private static final long STEAM_2 = 1_900_200L;
|
||||
private static final long MANUAL = -1_900_007L;
|
||||
private static final long MANUAL_2 = -1_900_008L;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private SteamGameRepository steamGameRepository;
|
||||
|
||||
@Autowired
|
||||
private UserGameRepository userGameRepository;
|
||||
|
||||
@Autowired
|
||||
private GameMoodsRepository gameMoodsRepository;
|
||||
|
||||
@Autowired
|
||||
private GameTimeToBeatRepository gameTimeToBeatRepository;
|
||||
|
||||
@Autowired
|
||||
private GameCorrectionRepository gameCorrectionRepository;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@Autowired
|
||||
private UserStatsRepository userStatsRepository;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
jdbc.update("delete from game_alias");
|
||||
jdbc.update("delete from admin_audit");
|
||||
gameCorrectionRepository.deleteAll();
|
||||
userGameRepository.deleteAll();
|
||||
gameMoodsRepository.deleteAll();
|
||||
gameTimeToBeatRepository.deleteAll();
|
||||
for (long appid : List.of(STEAM, STEAM_2, MANUAL, MANUAL_2)) {
|
||||
steamGameRepository.findById(appid).ifPresent(steamGameRepository::delete);
|
||||
}
|
||||
steamGameRepository.save(new SteamGame(STEAM, "Hades", "icon", Instant.now()));
|
||||
steamGameRepository.save(new SteamGame(STEAM_2, "Hades II", "icon", Instant.now()));
|
||||
steamGameRepository.save(new SteamGame(MANUAL, "Hades (Switch)", null, Instant.now()));
|
||||
steamGameRepository.save(new SteamGame(MANUAL_2, "Hades Nintendo", null, Instant.now()));
|
||||
}
|
||||
|
||||
private static String token(Role role) {
|
||||
return Jwts.builder()
|
||||
.subject("auditor@example.com")
|
||||
.claim("userId", String.valueOf(AUDITOR_ID))
|
||||
.claim("role", role.name())
|
||||
.expiration(new Date(System.currentTimeMillis() + 60_000))
|
||||
.signWith(TEST_KEYS.getPrivate(), Jwts.SIG.EdDSA)
|
||||
.compact();
|
||||
}
|
||||
|
||||
private ResultActions merge(Role role, long appid, long otherAppid) throws Exception {
|
||||
return mockMvc.perform(post("/api/library/games/" + appid + "/merge")
|
||||
.header("Authorization", "Bearer " + token(role)).header(ClientHeaderFilter.CLIENT_HEADER, "test")
|
||||
.contentType(MediaType.APPLICATION_JSON).content("{\"otherAppid\": " + otherAppid + "}"));
|
||||
}
|
||||
|
||||
private UserGame manualRow(long userId, long appid, GameStatus status) {
|
||||
UserGame game = UserGame.manual(userId, appid, "Switch", status, 600, Instant.now());
|
||||
return userGameRepository.save(game);
|
||||
}
|
||||
|
||||
private UserGame row(long userId, long appid) {
|
||||
return userGameRepository.findById(new UserGameId(userId, appid)).orElseThrow();
|
||||
}
|
||||
|
||||
private boolean hasRow(long userId, long appid) {
|
||||
return userGameRepository.findById(new UserGameId(userId, appid)).isPresent();
|
||||
}
|
||||
|
||||
private Long aliasOf(long appid) {
|
||||
List<Long> found = jdbc.queryForList("select to_appid from game_alias where from_appid = ?", Long.class, appid);
|
||||
return found.isEmpty() ? null : found.getFirst();
|
||||
}
|
||||
|
||||
/** Each row as "actor action appid targetAppid detail", oldest first. */
|
||||
private List<String> audit() {
|
||||
return jdbc.query("select * from admin_audit order by id", (rs, i) -> rs.getLong("actor_user_id") + " "
|
||||
+ rs.getString("action") + " " + rs.getLong("appid") + " " + rs.getLong("target_appid") + " "
|
||||
+ rs.getString("detail"));
|
||||
}
|
||||
|
||||
private int logRows(long userId, long appid) {
|
||||
Integer count = jdbc.queryForObject("select count(*) from user_game_status_change where user_id = ? and appid = ?",
|
||||
Integer.class, userId, appid);
|
||||
return count != null ? count : 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
void manualIntoSteam_MovesTheRowWithItsHistoryAndDropsTheManualCatalogRows() throws Exception {
|
||||
UserGame manual = manualRow(AUDITOR_ID, MANUAL, GameStatus.PLAYING);
|
||||
manual.setNotes("on the couch");
|
||||
userGameRepository.save(manual);
|
||||
userGameRepository.save(new UserGame(OTHER_USER, STEAM, 120, Instant.now(), Instant.now()));
|
||||
gameMoodsRepository.upsert(MANUAL, new String[]{"ACTION"}, new String[]{"Roguelike"}, false, Instant.now());
|
||||
int historyBefore = logRows(AUDITOR_ID, MANUAL);
|
||||
|
||||
merge(Role.AUDITOR, MANUAL, STEAM).andExpect(status().isOk()).andExpect(jsonPath("$.appid").value(STEAM));
|
||||
|
||||
UserGame moved = row(AUDITOR_ID, STEAM);
|
||||
assertThat(moved.getSource()).isEqualTo(GameSource.MANUAL);
|
||||
assertThat(moved.getPlatform()).isEqualTo("Switch");
|
||||
assertThat(moved.getStatus()).isEqualTo(GameStatus.PLAYING);
|
||||
assertThat(moved.getNotes()).isEqualTo("on the couch");
|
||||
assertThat(moved.getPlaytimeMinutes()).isEqualTo(600);
|
||||
assertThat(logRows(AUDITOR_ID, STEAM)).isEqualTo(historyBefore);
|
||||
assertThat(hasRow(AUDITOR_ID, MANUAL)).isFalse();
|
||||
assertThat(row(OTHER_USER, STEAM).getPlaytimeMinutes()).isEqualTo(120);
|
||||
assertThat(steamGameRepository.findById(MANUAL)).isEmpty();
|
||||
assertThat(gameMoodsRepository.findById(MANUAL)).isEmpty();
|
||||
assertThat(aliasOf(MANUAL)).isEqualTo(STEAM);
|
||||
assertThat(audit()).containsExactly(AUDITOR_ID + " GAME_MERGE " + MANUAL + " " + STEAM
|
||||
+ " \"Hades (Switch)\" into \"Hades\": 1 moved, 0 combined");
|
||||
}
|
||||
|
||||
@Test
|
||||
void userWithBothRows_KeepsTheSteamRowWithTheNewerHandSetFields() throws Exception {
|
||||
UserGame steam = new UserGame(AUDITOR_ID, STEAM, 300, Instant.now(), Instant.now());
|
||||
steam.setStatus(GameStatus.PLAYING);
|
||||
steam.setStatusSetByUser(true);
|
||||
steam.setRating(4);
|
||||
steam.setPriority(GamePriority.HIGH);
|
||||
userGameRepository.save(steam);
|
||||
// Saved later, so its status change by the user is the newer one.
|
||||
UserGame manual = manualRow(AUDITOR_ID, MANUAL, GameStatus.FINISHED);
|
||||
manual.setNotes("beat it on Switch");
|
||||
userGameRepository.save(manual);
|
||||
int historyBefore = logRows(AUDITOR_ID, STEAM) + logRows(AUDITOR_ID, MANUAL);
|
||||
|
||||
merge(Role.AUDITOR, MANUAL, STEAM).andExpect(status().isOk());
|
||||
|
||||
UserGame kept = row(AUDITOR_ID, STEAM);
|
||||
// Both histories, and no entry for the status the merge took over.
|
||||
assertThat(logRows(AUDITOR_ID, STEAM)).isEqualTo(historyBefore);
|
||||
assertThat(kept.getStatus()).isEqualTo(GameStatus.FINISHED);
|
||||
assertThat(kept.isStatusSetByUser()).isTrue();
|
||||
assertThat(kept.getNotes()).isEqualTo("beat it on Switch");
|
||||
assertThat(kept.getRating()).isEqualTo(4);
|
||||
assertThat(kept.getPriority()).isEqualTo(GamePriority.HIGH);
|
||||
assertThat(kept.getSource()).isEqualTo(GameSource.STEAM);
|
||||
assertThat(kept.getPlatform()).isNull();
|
||||
assertThat(kept.getPlaytimeMinutes()).isEqualTo(300);
|
||||
assertThat(hasRow(AUDITOR_ID, MANUAL)).isFalse();
|
||||
assertThat(logRows(AUDITOR_ID, MANUAL)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void userWithBothRows_SteamStatusChosenLater_KeepsTheSteamStatus() throws Exception {
|
||||
manualRow(AUDITOR_ID, MANUAL, GameStatus.FINISHED);
|
||||
UserGame steam = new UserGame(AUDITOR_ID, STEAM, 300, Instant.now(), Instant.now());
|
||||
steam.setStatus(GameStatus.DROPPED);
|
||||
steam.setStatusSetByUser(true);
|
||||
userGameRepository.save(steam);
|
||||
|
||||
merge(Role.AUDITOR, STEAM, MANUAL).andExpect(status().isOk()).andExpect(jsonPath("$.appid").value(STEAM));
|
||||
|
||||
assertThat(row(AUDITOR_ID, STEAM).getStatus()).isEqualTo(GameStatus.DROPPED);
|
||||
assertThat(hasRow(AUDITOR_ID, MANUAL)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void userWithBothRows_SteamCopyGone_KeepsTheCopyAddedByHand() throws Exception {
|
||||
UserGame steam = new UserGame(AUDITOR_ID, STEAM, 300, Instant.now(), Instant.now());
|
||||
steam.setRemovedAt(Instant.now());
|
||||
userGameRepository.save(steam);
|
||||
manualRow(AUDITOR_ID, MANUAL, GameStatus.PLAYING);
|
||||
|
||||
merge(Role.AUDITOR, MANUAL, STEAM).andExpect(status().isOk());
|
||||
|
||||
UserGame kept = row(AUDITOR_ID, STEAM);
|
||||
assertThat(kept.getRemovedAt()).isNull();
|
||||
assertThat(kept.getSource()).isEqualTo(GameSource.MANUAL);
|
||||
assertThat(kept.getPlatform()).isEqualTo("Switch");
|
||||
assertThat(kept.getPlaytimeMinutes()).isEqualTo(600);
|
||||
assertThat(kept.getStatus()).isEqualTo(GameStatus.PLAYING);
|
||||
}
|
||||
|
||||
@Test
|
||||
void userWithBothRows_NoHistory_TheStatusSetByHandWins() throws Exception {
|
||||
userGameRepository.save(new UserGame(AUDITOR_ID, STEAM, 300, Instant.now(), Instant.now()));
|
||||
manualRow(AUDITOR_ID, MANUAL, GameStatus.FINISHED);
|
||||
// Rows from before V14 have no status history.
|
||||
jdbc.update("delete from user_game_status_change where user_id = ?", AUDITOR_ID);
|
||||
|
||||
merge(Role.AUDITOR, MANUAL, STEAM).andExpect(status().isOk());
|
||||
|
||||
UserGame kept = row(AUDITOR_ID, STEAM);
|
||||
assertThat(kept.getStatus()).isEqualTo(GameStatus.FINISHED);
|
||||
assertThat(kept.isStatusSetByUser()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void userWithBothRows_HiddenSteamCopyGone_KeepsTheCopyAddedByHandShown() throws Exception {
|
||||
UserGame steam = new UserGame(AUDITOR_ID, STEAM, 300, Instant.now(), Instant.now());
|
||||
steam.setHiddenAt(Instant.now());
|
||||
steam.setRemovedAt(Instant.now());
|
||||
userGameRepository.save(steam);
|
||||
manualRow(AUDITOR_ID, MANUAL, GameStatus.PLAYING);
|
||||
|
||||
merge(Role.AUDITOR, MANUAL, STEAM).andExpect(status().isOk());
|
||||
|
||||
assertThat(row(AUDITOR_ID, STEAM).getHiddenAt()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void chain_StaysFlat() throws Exception {
|
||||
manualRow(AUDITOR_ID, MANUAL, GameStatus.BACKLOG);
|
||||
|
||||
merge(Role.AUDITOR, MANUAL, MANUAL_2).andExpect(status().isOk()).andExpect(jsonPath("$.appid").value(MANUAL_2));
|
||||
merge(Role.AUDITOR, MANUAL_2, STEAM).andExpect(status().isOk()).andExpect(jsonPath("$.appid").value(STEAM));
|
||||
|
||||
assertThat(aliasOf(MANUAL)).isEqualTo(STEAM);
|
||||
assertThat(aliasOf(MANUAL_2)).isEqualTo(STEAM);
|
||||
assertThat(hasRow(AUDITOR_ID, STEAM)).isTrue();
|
||||
// The audit keeps the first step, which the flat alias no longer shows.
|
||||
assertThat(audit()).hasSize(2).first().asString().contains(MANUAL + " " + MANUAL_2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void badInput_IsRefused() throws Exception {
|
||||
merge(Role.AUDITOR, STEAM, STEAM_2).andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").value("STEAM_GAMES"));
|
||||
merge(Role.AUDITOR, MANUAL, MANUAL).andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").value("SAME_GAME"));
|
||||
merge(Role.AUDITOR, MANUAL, 999_999_999L).andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.code").value("GAME_NOT_FOUND"));
|
||||
mockMvc.perform(post("/api/library/games/" + MANUAL + "/merge")
|
||||
.header("Authorization", "Bearer " + token(Role.AUDITOR)).header(ClientHeaderFilter.CLIENT_HEADER, "test")
|
||||
.contentType(MediaType.APPLICATION_JSON).content("{}"))
|
||||
.andExpect(status().isBadRequest());
|
||||
assertThat(steamGameRepository.findById(MANUAL)).isPresent();
|
||||
assertThat(audit()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void roles_OnlyAuditorsAndAdminsMerge() throws Exception {
|
||||
merge(Role.USER, MANUAL, STEAM).andExpect(status().isForbidden());
|
||||
assertThat(steamGameRepository.findById(MANUAL)).isPresent();
|
||||
merge(Role.PLATFORM_ADMIN, MANUAL, STEAM).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void userWithBothRows_FinishedByHandBefore_KeepsTheFinishInItsYear() throws Exception {
|
||||
manualRow(AUDITOR_ID, MANUAL, GameStatus.BACKLOG);
|
||||
jdbc.update("update user_game set status = 'FINISHED' where user_id = ? and appid = ?", AUDITOR_ID, MANUAL);
|
||||
jdbc.update("update user_game_status_change set changed_at = timestamptz '2025-06-01 12:00:00+00' "
|
||||
+ "where user_id = ? and appid = ?", AUDITOR_ID, MANUAL);
|
||||
// The sync's own row: BACKLOG, not set by the user.
|
||||
userGameRepository.save(new UserGame(AUDITOR_ID, STEAM, 0, Instant.now(), Instant.now()));
|
||||
ZoneId zone = ZoneId.of("Europe/Berlin");
|
||||
assertThat(userStatsRepository.finishedPerYear(AUDITOR_ID, zone)).containsExactly(new UserStats.YearCount(2025, 1));
|
||||
|
||||
merge(Role.AUDITOR, MANUAL, STEAM).andExpect(status().isOk());
|
||||
|
||||
assertThat(row(AUDITOR_ID, STEAM).getStatus()).isEqualTo(GameStatus.FINISHED);
|
||||
assertThat(userStatsRepository.finishedPerYear(AUDITOR_ID, zone)).containsExactly(new UserStats.YearCount(2025, 1));
|
||||
assertThat(userStatsRepository.pileMoves(AUDITOR_ID, Instant.now().minus(1, ChronoUnit.DAYS)).done()).isZero();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ 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.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@@ -25,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@@ -65,12 +67,17 @@ class ManualGameControllerTest extends AbstractIntegrationTest {
|
||||
@Autowired
|
||||
private GameTimeToBeatRepository gameTimeToBeatRepository;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@MockitoBean
|
||||
private IgdbApiService igdbApiService;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
jdbc.update("delete from game_alias");
|
||||
userGameRepository.deleteAll();
|
||||
jdbc.update("delete from steam_game where appid in (?, ?, ?, ?)", MERGED_INTO, -1_900_400L, -555_001L, -555_002L);
|
||||
gameTimeToBeatRepository.deleteAll();
|
||||
when(igdbApiService.isEnabled()).thenReturn(true);
|
||||
when(igdbApiService.getGame(119388)).thenReturn(Optional.of(ZELDA));
|
||||
@@ -231,8 +238,7 @@ class ManualGameControllerTest extends AbstractIntegrationTest {
|
||||
ZELDA.name(), 2023, null, List.of("Switch"), 2_000_000L))));
|
||||
|
||||
mockMvc.perform(get("/api/library/game-search").param("q", "zelda"))
|
||||
.andExpect(jsonPath("$.results[0].appid").value(2_000_000))
|
||||
.andExpect(jsonPath("$.results[0].inLibrary").value(true));
|
||||
.andExpect(jsonPath("$.results[?(@.appid == 2000000)].inLibrary", contains(true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -359,4 +365,99 @@ class ManualGameControllerTest extends AbstractIntegrationTest {
|
||||
mockMvc.perform(get("/api/library/games/-119388").cookie(login))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
private static final long MERGED_INTO = 1_900_300L;
|
||||
|
||||
private void mergeZeldaIntoSteam() {
|
||||
steamGameRepository.save(new SteamGame(MERGED_INTO, "Zelda on Steam", "icon", Instant.now()));
|
||||
// The merge deletes the catalog row of the merged key.
|
||||
jdbc.update("delete from steam_game where appid = ?", -119388L);
|
||||
jdbc.update("insert into game_alias (from_appid, to_appid) values (?, ?)", -119388L, MERGED_INTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
void add_MergedGame_LandsOnTheGameItWasMergedInto() throws Exception {
|
||||
mergeZeldaIntoSteam();
|
||||
|
||||
add(119388, "Switch").andExpect(status().isCreated()).andExpect(jsonPath("$.appid").value(MERGED_INTO));
|
||||
|
||||
assertEquals(GameSource.MANUAL, stored(MERGED_INTO).getSource());
|
||||
assertEquals("Zelda on Steam", steamGameRepository.findById(MERGED_INTO).orElseThrow().getName());
|
||||
assertTrue(userGameRepository.findById(new UserGameId(USER_ID, -119388L)).isEmpty());
|
||||
add(119388, "Switch").andExpect(status().isConflict());
|
||||
}
|
||||
|
||||
@Test
|
||||
void add_GameMergedIntoAnotherManualGame_KeepsTheTargetName() throws Exception {
|
||||
steamGameRepository.save(new SteamGame(-1_900_400L, "Zelda TotK", null, Instant.now()));
|
||||
jdbc.update("insert into game_alias (from_appid, to_appid) values (?, ?)", -119388L, -1_900_400L);
|
||||
|
||||
add(119388, "Switch").andExpect(status().isCreated()).andExpect(jsonPath("$.appid").value(-1_900_400L));
|
||||
|
||||
assertEquals("Zelda TotK", steamGameRepository.findById(-1_900_400L).orElseThrow().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void add_MergedGameIgdbLaterFindsOnSteam_IsStillInTheLibrary() throws Exception {
|
||||
steamGameRepository.save(new SteamGame(-1_900_400L, "Zelda TotK", null, Instant.now()));
|
||||
jdbc.update("insert into game_alias (from_appid, to_appid) values (?, ?)", -119388L, -1_900_400L);
|
||||
userGameRepository.save(UserGame.manual(USER_ID, -1_900_400L, "Switch", GameStatus.BACKLOG, 0, Instant.now()));
|
||||
when(igdbApiService.getGame(119388)).thenReturn(Optional.of(new IgdbApiService.Game(119388, ZELDA.name(),
|
||||
2023, null, List.of("Switch"), 2_000_000L)));
|
||||
|
||||
add(119388, "Switch").andExpect(status().isConflict());
|
||||
|
||||
assertTrue(userGameRepository.findById(new UserGameId(USER_ID, 2_000_000L)).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_MergedGame_IsTheTargetAndInTheLibrary() throws Exception {
|
||||
mergeZeldaIntoSteam();
|
||||
userGameRepository.save(new UserGame(USER_ID, MERGED_INTO, 60, Instant.now(), Instant.now()));
|
||||
when(igdbApiService.searchGames("zelda")).thenReturn(Optional.of(List.of(ZELDA)));
|
||||
|
||||
mockMvc.perform(get("/api/library/game-search").param("q", "zelda"))
|
||||
.andExpect(jsonPath("$.results.length()").value(1))
|
||||
.andExpect(jsonPath("$.results[0].appid").value(MERGED_INTO))
|
||||
.andExpect(jsonPath("$.results[0].name").value("Zelda on Steam"))
|
||||
.andExpect(jsonPath("$.results[0].inLibrary").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_SteamGameWithADuplicateAddedByHand_OffersBothKeys() throws Exception {
|
||||
steamGameRepository.save(new SteamGame(MERGED_INTO, "Zelda on Steam", "icon", Instant.now()));
|
||||
steamGameRepository.save(new SteamGame(-1_900_400L, "Zelda TotK", null, Instant.now()));
|
||||
when(igdbApiService.searchGames("zelda totk")).thenReturn(Optional.of(List.of(
|
||||
new IgdbApiService.Game(1_900_400, "Zelda TotK", 2023, null, List.of("Switch"), MERGED_INTO))));
|
||||
|
||||
mockMvc.perform(get("/api/library/game-search").param("q", "zelda totk"))
|
||||
.andExpect(jsonPath("$.results[*].appid", contains((int) MERGED_INTO, -1_900_400)))
|
||||
.andExpect(jsonPath("$.results[*].inCatalog", contains(true, true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_GamesInToBePlayedComeFirst() throws Exception {
|
||||
steamGameRepository.save(new SteamGame(-555_001L, "Blue Prince (corrected)", null, Instant.now()));
|
||||
steamGameRepository.save(new SteamGame(-555_002L, "Princess Farmer", null, Instant.now()));
|
||||
IgdbApiService.Game persia = new IgdbApiService.Game(777, "Prince of Persia", 2008, null, List.of("PS3"), null);
|
||||
IgdbApiService.Game bluePrince = new IgdbApiService.Game(555_001, "Blue Prince", 2025,
|
||||
"https://images.igdb.com/igdb/image/upload/t_screenshot_med/bp.jpg", List.of("PS5"), null);
|
||||
IgdbApiService.Game other = new IgdbApiService.Game(778, "Prince of Persia 2", 2009, null, List.of("PS3"), null);
|
||||
when(igdbApiService.searchGames("prince")).thenReturn(Optional.of(List.of(persia, bluePrince, other)));
|
||||
|
||||
mockMvc.perform(get("/api/library/game-search").param("q", "prince"))
|
||||
.andExpect(jsonPath("$.results.length()").value(4))
|
||||
.andExpect(jsonPath("$.results[0].appid").value(-555_001))
|
||||
.andExpect(jsonPath("$.results[0].name").value("Blue Prince (corrected)"))
|
||||
.andExpect(jsonPath("$.results[0].imageUrl").value(bluePrince.imageUrl()))
|
||||
.andExpect(jsonPath("$.results[0].platforms[0]").value("PS5"))
|
||||
.andExpect(jsonPath("$.results[0].inCatalog").value(true))
|
||||
.andExpect(jsonPath("$.results[1].appid").value(-555_002))
|
||||
.andExpect(jsonPath("$.results[1].inCatalog").value(true))
|
||||
.andExpect(jsonPath("$.results[1].igdbId").value(555_002))
|
||||
.andExpect(jsonPath("$.results[1].platforms").isEmpty())
|
||||
.andExpect(jsonPath("$.results[2].appid").value(-777))
|
||||
.andExpect(jsonPath("$.results[2].inCatalog").value(false))
|
||||
.andExpect(jsonPath("$.results[3].appid").value(-778));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import core_service.modules.igdb_adapter.service.IgdbApiService;
|
||||
import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGameId;
|
||||
import core_service.modules.library.repo.GameMergeRepository;
|
||||
import core_service.modules.library.repo.SteamGameRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
import core_service.shared.test.AbstractUnitTest;
|
||||
@@ -40,6 +41,9 @@ class ManualGameServiceTest extends AbstractUnitTest {
|
||||
@Mock
|
||||
private SteamGameRepository steamGameRepository;
|
||||
|
||||
@Mock
|
||||
private GameMergeRepository mergeRepository;
|
||||
|
||||
@Mock
|
||||
private TimeToBeatService timeToBeatService;
|
||||
|
||||
@@ -53,7 +57,7 @@ class ManualGameServiceTest extends AbstractUnitTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new ManualGameService(igdbApiService, userGameRepository, steamGameRepository, timeToBeatService,
|
||||
service = new ManualGameService(igdbApiService, userGameRepository, steamGameRepository, mergeRepository, timeToBeatService,
|
||||
moodService, transactionManager);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user