diff --git a/src/main/java/core_service/modules/library/service/LibrarySyncWorker.java b/src/main/java/core_service/modules/library/service/LibrarySyncWorker.java index 47d86f1..b0a1d02 100644 --- a/src/main/java/core_service/modules/library/service/LibrarySyncWorker.java +++ b/src/main/java/core_service/modules/library/service/LibrarySyncWorker.java @@ -170,10 +170,12 @@ public class LibrarySyncWorker { private void upsertGame(SteamOwnedGamesResponse.Game g, Instant now) { Long appid = g.appid().longValue(); SteamGame existing = steamGameRepository.findById(appid).orElse(null); + // An unvetted app can come without a name, and the name column is NOT NULL. + boolean hasName = g.name() != null && !g.name().isBlank(); if (existing == null) { - steamGameRepository.save(new SteamGame(appid, g.name(), g.imgIconUrl(), now)); + steamGameRepository.save(new SteamGame(appid, hasName ? g.name() : "App " + appid, g.imgIconUrl(), now)); } else { - existing.setName(g.name()); + if (hasName) existing.setName(g.name()); existing.setImgIconUrl(g.imgIconUrl()); existing.setLastSeenAt(now); } diff --git a/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java b/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java index 9eaf921..2a74d54 100644 --- a/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java +++ b/src/main/java/core_service/modules/steam_adapter/service/SteamApiService.java @@ -65,6 +65,7 @@ public class SteamApiService { .queryParam("steamid", steamId) .queryParam("include_appinfo", "true") .queryParam("include_played_free_games", "true") + .queryParam("skip_unvetted_apps", 0) .queryParam("format", "json") .build()) .retrieve() @@ -145,6 +146,8 @@ public class SteamApiService { .queryParam("steamid", steamId) .queryParam("include_appinfo", 1) .queryParam("include_played_free_games", 1) + // Else Steam hides some owned apps, e.g. a game from a developer license. + .queryParam("skip_unvetted_apps", 0) .build()) .retrieve() .body(SteamOwnedGamesResponse.class); diff --git a/src/test/java/core_service/modules/library/service/LibrarySyncWorkerTest.java b/src/test/java/core_service/modules/library/service/LibrarySyncWorkerTest.java index 8af01c3..4c65876 100644 --- a/src/test/java/core_service/modules/library/service/LibrarySyncWorkerTest.java +++ b/src/test/java/core_service/modules/library/service/LibrarySyncWorkerTest.java @@ -2,6 +2,7 @@ package core_service.modules.library.service; 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.domain.UserSteamSync; @@ -34,10 +35,12 @@ import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; @@ -415,4 +418,30 @@ class LibrarySyncWorkerTest extends AbstractUnitTest { verify(timeToBeatService).refresh(List.of(220L, -1025L)); verify(moodService).refresh(List.of(220L, -1025L)); } + + @Test + void runSync_GameWithoutName_GetsTheAppidAsName() { + when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any())).thenReturn(1); + when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of( + new SteamOwnedGamesResponse.Game(2000001, null, 12, null, null, null), + game(90, 0))); + + worker.runSync(USER_ID, STEAM_ID); + + verify(steamGameRepository).save(argThat((SteamGame g) -> g.getAppid() == 2000001L && "App 2000001".equals(g.getName()))); + verify(steamGameRepository).save(argThat((SteamGame g) -> g.getAppid() == 220L)); + } + + @Test + void runSync_GameWithoutName_KeepsTheStoredName() { + SteamGame stored = new SteamGame(2000001L, "El Mucho", null, Instant.EPOCH); + when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any())).thenReturn(1); + when(steamGameRepository.findById(2000001L)).thenReturn(Optional.of(stored)); + when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of( + new SteamOwnedGamesResponse.Game(2000001, " ", 12, null, null, null))); + + worker.runSync(USER_ID, STEAM_ID); + + assertThat(stored.getName()).isEqualTo("El Mucho"); + } } diff --git a/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java b/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java index 495f19e..f13e7dd 100644 --- a/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java +++ b/src/test/java/core_service/modules/steam_adapter/service/SteamApiServiceTest.java @@ -124,6 +124,24 @@ class SteamApiServiceTest { assertThat(result.getFirst().name()).isEqualTo("Counter-Strike"); } + @Test + void getOwnedGames_shouldIncludeUnvettedApps() { + server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v1/"))) + .andExpect(queryParam("skip_unvetted_apps", "0")) + .andRespond(withSuccess("{\"response\":{\"games\":[{\"appid\":10,\"name\":\"El Mucho\"}]}}", MediaType.APPLICATION_JSON)); + + assertThat(steamApiService.getOwnedGames("76561197960435530")).hasSize(1); + } + + @Test + void getOwnedGamesCount_shouldCountUnvettedApps() { + server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v1/"))) + .andExpect(queryParam("skip_unvetted_apps", "0")) + .andRespond(withSuccess("{\"response\":{\"game_count\":1}}", MediaType.APPLICATION_JSON)); + + assertThat(steamApiService.getOwnedGamesCount("76561197960435530")).isEqualTo(1); + } + @Test void getOwnedGames_shouldReturnGamesEvenWithNullValues() { String steamId = "76561197960435530";