Public Access
feat(library): show one entry per game in every list
Gitea #34 slice 2 of 3. Lists and stats count a set once. V43 views map an entry to its editions and platforms. Remove the hideDuplicates param; GameDto gets platforms[].
This commit is contained in:
1 parent
82d3b59165
commit
413381d404
23 files changed
+676
-87
No files matched your search
@@ -279,13 +279,26 @@ class GameLinkFlowTest extends AbstractIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void hideDuplicates_ListsOnlyTheEditionTheSetCounts() throws Exception {
|
||||
void games_ASetIsOneRowWithThePlatformsOfAllItsEditions() throws Exception {
|
||||
jdbc.update("update user_game set source = 'PSN', platform = 'PS5' where user_id = ? and appid = ?", USER_A, GAME_2);
|
||||
linkAs(Role.AUDITOR, GAME, GAME_2).andExpect(status().isNoContent());
|
||||
|
||||
mockMvc.perform(as(get("/api/library/games?hideDuplicates=true"), USER_A, Role.USER)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.totalElements").value(1));
|
||||
mockMvc.perform(as(get("/api/library/games"), USER_A, Role.USER)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.totalElements").value(2));
|
||||
.andExpect(jsonPath("$.totalElements").value(1))
|
||||
.andExpect(jsonPath("$.content[0].editions").value(2))
|
||||
.andExpect(jsonPath("$.content[0].platforms", hasSize(2)))
|
||||
.andExpect(jsonPath("$.content[0].platforms[0].source").value("PSN"))
|
||||
.andExpect(jsonPath("$.content[0].platforms[0].platform").value("PS5"))
|
||||
.andExpect(jsonPath("$.content[0].platforms[1].source").value("MANUAL"))
|
||||
.andExpect(jsonPath("$.content[0].platforms[1].platform").value("Switch"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void games_AnOldClientsHideDuplicatesParamChangesNothing() throws Exception {
|
||||
linkAs(Role.AUDITOR, GAME, GAME_2).andExpect(status().isNoContent());
|
||||
|
||||
mockMvc.perform(as(get("/api/library/games?hideDuplicates=false"), USER_A, Role.USER)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.totalElements").value(1));
|
||||
}
|
||||
|
||||
private void setSource(long appid, GameSource source) {
|
||||
@@ -301,10 +314,9 @@ class GameLinkFlowTest extends AbstractIntegrationTest {
|
||||
setSource(GAME_2, GameSource.PSN);
|
||||
linkAs(Role.AUDITOR, GAME, GAME_2).andExpect(status().isNoContent());
|
||||
|
||||
games("?source=PSN").andExpect(jsonPath("$.totalElements").value(2));
|
||||
games("?source=PSN&hideDuplicates=true").andExpect(jsonPath("$.totalElements").value(1));
|
||||
games("?source=PSN").andExpect(jsonPath("$.totalElements").value(1));
|
||||
games("?source=STEAM").andExpect(jsonPath("$.totalElements").value(0));
|
||||
games("?source=MANUAL,STEAM").andExpect(jsonPath("$.totalElements").value(2));
|
||||
games("?source=MANUAL,STEAM").andExpect(jsonPath("$.totalElements").value(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -426,7 +438,7 @@ class GameLinkFlowTest extends AbstractIntegrationTest {
|
||||
void sourceFilter_AfterASplitTheOtherEditionNoLongerMatches() throws Exception {
|
||||
setSource(GAME_2, GameSource.PSN);
|
||||
gameEditionService.linkSameGames(null);
|
||||
games("?source=PSN").andExpect(jsonPath("$.totalElements").value(2));
|
||||
games("?source=PSN").andExpect(jsonPath("$.totalElements").value(1));
|
||||
|
||||
splitAs(USER_A, GAME).andExpect(status().isNoContent());
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class LibraryControllerAchievementsTest extends AbstractMockedAdaptersTest {
|
||||
userAchievementMetaRepository.deleteAll();
|
||||
jdbc.update("delete from user_psn_trophy_set");
|
||||
jdbc.update("delete from psn_trophy_set");
|
||||
jdbc.update("delete from game_link");
|
||||
}
|
||||
|
||||
private void trophies(long appid, String setId, String platform, int total, int earned, boolean platinum) {
|
||||
@@ -146,6 +147,19 @@ class LibraryControllerAchievementsTest extends AbstractMockedAdaptersTest {
|
||||
.andExpect(jsonPath("$.closest[1].imageUrl").doesNotExist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void summary_TwoPerfectEditionsOfASetCountAsOnePerfectGame() throws Exception {
|
||||
game(10L, 10, 10, true);
|
||||
game(20L, 4, 4, true);
|
||||
game(30L, 4, 4, true);
|
||||
jdbc.update("insert into game_link (appid, set_id) values (10, 7001), (20, 7001)");
|
||||
|
||||
mockMvc.perform(get("/api/library/achievements/summary"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.startedGames").value(2))
|
||||
.andExpect(jsonPath("$.perfectGames").value(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void summary_TrophiesCount_APlatinumSetIsPerfectAndOneGameCountsOnce() throws Exception {
|
||||
game(10L, null, null, true);
|
||||
|
||||
@@ -53,6 +53,7 @@ class LibraryControllerActivityTest extends AbstractIntegrationTest {
|
||||
void seed() {
|
||||
userGameRepository.deleteAll();
|
||||
jdbc.update("delete from user_game_playtime_day");
|
||||
jdbc.update("delete from game_link where set_id = 980001");
|
||||
SteamGame repairedPortal = new SteamGame(620L, "Portal 2", null, REPAIRED_COVER, Instant.now());
|
||||
steamGameRepository.saveAll(List.of(
|
||||
new SteamGame(220L, "Half-Life 2", null, Instant.now()),
|
||||
@@ -90,6 +91,19 @@ class LibraryControllerActivityTest extends AbstractIntegrationTest {
|
||||
.andExpect(jsonPath("$.recentGames[1].imageUrl").doesNotExist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void activity_RecentGamesAreOneEntryPerSetWithTheSumOfItsEditions() throws Exception {
|
||||
jdbc.update("update user_game set playtime_2weeks = 50 where user_id = ? and appid = 700", USER_ID);
|
||||
jdbc.update("insert into game_link (appid, set_id) values (220, 980001), (700, 980001)");
|
||||
|
||||
mockMvc.perform(get("/api/library/activity").param("days", "7"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.recentGames", hasSize(2)))
|
||||
.andExpect(jsonPath("$.recentGames[0].appid").value(620))
|
||||
.andExpect(jsonPath("$.recentGames[1].appid").value(220))
|
||||
.andExpect(jsonPath("$.recentGames[1].playtime2Weeks").value(80));
|
||||
}
|
||||
|
||||
@Test
|
||||
void activity_DefaultsTo30DaysAndClampsTheRange() throws Exception {
|
||||
mockMvc.perform(get("/api/library/activity")).andExpect(jsonPath("$.days", hasSize(30)));
|
||||
|
||||
+12
@@ -76,6 +76,7 @@ class LibraryControllerPickerFriendsTest extends AbstractMockedLibraryServicesTe
|
||||
jdbc.update("delete from account_name where user_id = ?", id);
|
||||
}
|
||||
jdbc.update("delete from follow where user_id = ?", USER_ID);
|
||||
jdbc.update("delete from game_link where set_id = 960001");
|
||||
gameReleaseRepository.deleteAll();
|
||||
achievementMetaRepository.deleteAll();
|
||||
userGameRepository.deleteAll();
|
||||
@@ -124,6 +125,17 @@ class LibraryControllerPickerFriendsTest extends AbstractMockedLibraryServicesTe
|
||||
return ROW + ".games[?(@.appid == " + appid + ")].reason." + field;
|
||||
}
|
||||
|
||||
@Test
|
||||
void friends_ThePlayStationEditionOfAFriendMatchesTheSteamEditionOfTheCaller() throws Exception {
|
||||
mine(10L, GameStatus.BACKLOG, null);
|
||||
friend(MAX, "Max", null);
|
||||
friendGame(MAX, -10L, GameStatus.FINISHED, 100);
|
||||
jdbc.update("insert into game_link (appid, set_id) values (10, 960001), (-10, 960001)");
|
||||
|
||||
pick().andExpect(jsonPath(ROW + ".games[*].appid", contains(10)))
|
||||
.andExpect(jsonPath(reason(10, "friendName"), contains("Max")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void friends_FinishedAndPlayedGames_WithReason() throws Exception {
|
||||
mine(10L, GameStatus.BACKLOG, null);
|
||||
|
||||
@@ -93,6 +93,7 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
jdbc.update("delete from game_link where set_id = 980002");
|
||||
gameReleaseRepository.deleteAll();
|
||||
userGameRepository.deleteAll();
|
||||
schemaMetaRepository.deleteAll();
|
||||
@@ -114,6 +115,17 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
|
||||
new UserGame(99L, 220L, 5000, Instant.now(), Instant.now())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void publicLibrary_ListsASetOnce() throws Exception {
|
||||
jdbc.update("insert into game_link (appid, set_id) values (220, 980002), (700, 980002)");
|
||||
|
||||
mockMvc.perform(get("/api/public/profiles/library").header("X-Profile-Ticket", ticket(USER_ID)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.totalGames").value(2))
|
||||
.andExpect(jsonPath("$.playing", hasSize(0)))
|
||||
.andExpect(jsonPath("$.finished", hasSize(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void publicLibrary_ShowsStatsPlayingAndFinishedGamesButNoNotesOrPriority() throws Exception {
|
||||
mockMvc.perform(get("/api/public/profiles/library").header("X-Profile-Ticket", ticket(USER_ID)))
|
||||
|
||||
@@ -92,9 +92,9 @@ class StatGamesFlowTest extends AbstractMockedLibraryServicesTest {
|
||||
count("PILE", null, "/pile/backlogGames"),
|
||||
new Case("PILE_TIME_TO_BEAT", null, s -> s.at("/pile/gamesWithTimeToBeat").asLong(), "minutes",
|
||||
s -> s.at("/pile/normalMinutes").asLong()),
|
||||
new Case("GENRE", "RPG", s -> tagValue(s.at("/taste/genres"), "RPG", "games"), "playtimeMinutes",
|
||||
new Case("GENRE", "RPG", s -> tagValue(s.at("/taste/genres"), "RPG", "games"), "minutes",
|
||||
s -> tagValue(s.at("/taste/genres"), "RPG", "minutes")),
|
||||
new Case("MOOD", "CHILL", s -> tagValue(s.at("/taste/moods"), "CHILL", "games"), "playtimeMinutes",
|
||||
new Case("MOOD", "CHILL", s -> tagValue(s.at("/taste/moods"), "CHILL", "games"), "minutes",
|
||||
s -> tagValue(s.at("/taste/moods"), "CHILL", "minutes")),
|
||||
new Case("LONGEST_DAY", null, null, "minutes", s -> s.at("/habits/longestDay/minutes").asLong()),
|
||||
count("UNDER_ONE_HOUR", null, "/habits/underOneHourGames"),
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
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.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class GameEntryViewTest extends AbstractPersistenceTest {
|
||||
|
||||
private static final long USER = 1L;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
jdbc.update("delete from user_psn_title");
|
||||
jdbc.update("delete from game_link");
|
||||
jdbc.update("delete from user_game");
|
||||
for (long appid : List.of(-10L, 10L, 20L, 30L)) {
|
||||
jdbc.update("insert into steam_game (appid, name) values (?, ?) on conflict do nothing", appid, "Game " + appid);
|
||||
}
|
||||
}
|
||||
|
||||
private void set(long appid, long setId) {
|
||||
jdbc.update("insert into game_link (appid, set_id) values (?, ?)", appid, setId);
|
||||
}
|
||||
|
||||
private void own(long appid, String source, String platform, int playtime) {
|
||||
jdbc.update("insert into user_game (user_id, appid, source, platform, playtime_minutes, status) values (?, ?, ?, ?, ?, 'BACKLOG')",
|
||||
USER, appid, source, platform, playtime);
|
||||
}
|
||||
|
||||
private void title(String key, String state, long appid) {
|
||||
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid) values (?, ?, ?, ?)", USER, key, state, appid);
|
||||
}
|
||||
|
||||
private List<String> members() {
|
||||
return jdbc.queryForList("select appid || '>' || member_appid from user_game_entry_member where user_id = ? "
|
||||
+ "order by appid, member_appid", String.class, USER);
|
||||
}
|
||||
|
||||
private List<String> platforms(long appid) {
|
||||
return jdbc.queryForList("select member_appid || ':' || source || ':' || platform from user_game_entry_platform "
|
||||
+ "where user_id = ? and appid = ? order by member_appid, source", String.class, USER, appid);
|
||||
}
|
||||
|
||||
@Test
|
||||
void member_EntryHasItselfAndItsListedEditions() {
|
||||
own(10, "STEAM", null, 600);
|
||||
own(-10, "PSN", "PS5", 60);
|
||||
own(20, "STEAM", null, 5);
|
||||
set(10, 1);
|
||||
set(-10, 1);
|
||||
|
||||
assertThat(members()).containsExactly("10>-10", "10>10", "20>20");
|
||||
}
|
||||
|
||||
@Test
|
||||
void member_LeavesOutAHiddenEdition() {
|
||||
own(10, "STEAM", null, 600);
|
||||
own(-10, "PSN", "PS5", 60);
|
||||
set(10, 1);
|
||||
set(-10, 1);
|
||||
jdbc.update("update user_game set hidden_at = now() where appid = -10");
|
||||
|
||||
assertThat(members()).containsExactly("10>10");
|
||||
}
|
||||
|
||||
@Test
|
||||
void member_ASplitEditionIsItsOwnEntry() {
|
||||
own(10, "STEAM", null, 600);
|
||||
own(-10, "PSN", "PS5", 60);
|
||||
set(10, 1);
|
||||
set(-10, 1);
|
||||
jdbc.update("insert into user_game_split (user_id, appid) values (?, -10)", USER);
|
||||
|
||||
assertThat(members()).containsExactly("-10>-10", "10>10");
|
||||
}
|
||||
|
||||
@Test
|
||||
void platform_ASteamOwnedPsnTitleGivesAPsnPlatformOnTheSteamRow() {
|
||||
own(10, "STEAM", null, 600);
|
||||
title("c1", "STEAM_OWNED", 10);
|
||||
|
||||
assertThat(platforms(10)).containsExactly("10:PSN:", "10:STEAM:");
|
||||
}
|
||||
|
||||
@Test
|
||||
void platform_TwoTitlesOnOneKeyGiveOnePsnRow() {
|
||||
own(10, "STEAM", null, 600);
|
||||
title("c1", "STEAM_OWNED", 10);
|
||||
title("c2", "STEAM_OWNED", 10);
|
||||
|
||||
assertThat(platforms(10)).containsExactly("10:PSN:", "10:STEAM:");
|
||||
}
|
||||
|
||||
@Test
|
||||
void platform_ASetListsEveryEditionWithItsPlatform() {
|
||||
own(10, "STEAM", null, 600);
|
||||
own(-10, "PSN", "PS5", 60);
|
||||
set(10, 1);
|
||||
set(-10, 1);
|
||||
|
||||
assertThat(platforms(10)).containsExactly("-10:PSN:PS5", "10:STEAM:");
|
||||
}
|
||||
|
||||
@Test
|
||||
void platform_ADismissedTitleGivesNoPlatform() {
|
||||
own(10, "STEAM", null, 600);
|
||||
title("c1", "DISMISSED", 10);
|
||||
|
||||
assertThat(platforms(10)).containsExactly("10:STEAM:");
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ class GameLinkRepositoryTest extends AbstractPersistenceTest {
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
for (String table : List.of("game_link_request", "game_link_pair", "game_link", "game_edition", "user_game")) {
|
||||
for (String table : List.of("user_psn_title", "game_link_request", "game_link_pair", "game_link", "game_edition", "user_game")) {
|
||||
jdbc.update("delete from " + table);
|
||||
}
|
||||
for (long appid : List.of(100L, 200L, -7L, -8L, 300L)) {
|
||||
@@ -254,6 +254,47 @@ class GameLinkRepositoryTest extends AbstractPersistenceTest {
|
||||
assertThat(repository.linkedEditions(USER, 100)).isEmpty();
|
||||
}
|
||||
|
||||
private void ownOn(long appid, String source, String platform) {
|
||||
jdbc.update("insert into user_game (user_id, appid, status, source, platform) values (?, ?, 'BACKLOG', ?, ?)",
|
||||
USER, appid, source, platform);
|
||||
}
|
||||
|
||||
@Test
|
||||
void entryPlatforms_SteamFirstThenPlayStationThenManual_AndAPlayStationTitleWithoutNameIsDropped() {
|
||||
repository.link(100, 200);
|
||||
repository.link(100, -7);
|
||||
ownOn(100, "STEAM", null);
|
||||
ownOn(200, "PSN", "PS5");
|
||||
ownOn(-7, "MANUAL", "Switch");
|
||||
jdbc.update("update user_game set playtime_minutes = 500 where appid = 100");
|
||||
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid) values (?, 'c1', 'STEAM_OWNED', 100)", USER);
|
||||
|
||||
var platforms = repository.entryPlatforms(USER, List.of(100L, 300L));
|
||||
|
||||
assertThat(platforms.keySet()).containsExactly(100L);
|
||||
assertThat(platforms.get(100L)).extracting(p -> p.source() + ":" + p.platform())
|
||||
.containsExactly("STEAM:null", "PSN:PS5", "MANUAL:Switch");
|
||||
assertThat(repository.entryPlatforms(USER, List.of())).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void entryPlatforms_ASteamRowWithAPlayStationTitleHasBothPlatforms() {
|
||||
ownOn(100, "STEAM", null);
|
||||
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid) values (?, 'c1', 'STEAM_OWNED', 100)", USER);
|
||||
|
||||
assertThat(repository.entryPlatforms(USER, List.of(100L)).get(100L))
|
||||
.extracting(p -> p.source() + ":" + p.platform()).containsExactly("STEAM:null", "PSN:null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void entryPlatforms_AManualPlayStationRowWithALinkedTitleIsOnePlatform() {
|
||||
ownOn(-7, "MANUAL", "PS5");
|
||||
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid) values (?, 'c1', 'LINKED', -7)", USER);
|
||||
|
||||
assertThat(repository.entryPlatforms(USER, List.of(-7L)).get(-7L))
|
||||
.extracting(p -> p.source() + ":" + p.platform()).containsExactly("MANUAL:PS5");
|
||||
}
|
||||
|
||||
@Test
|
||||
void editionCounts_GivesTwoForBothKeysAndNothingForASingleOwnedKey() {
|
||||
repository.link(100, 200);
|
||||
|
||||
@@ -5,6 +5,7 @@ import core_service.modules.library.domain.GameSource;
|
||||
import core_service.modules.library.domain.GameTimeToBeat;
|
||||
import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.Mood;
|
||||
import core_service.modules.library.domain.RecentEntry;
|
||||
import core_service.modules.library.domain.StatusCount;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
@@ -81,7 +82,7 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
}
|
||||
|
||||
private List<Long> appids(GameStatus status, String query) {
|
||||
return userGameRepository.findOwnedGames(USER_ID, false, status, UserGameRepository.containsPattern(query), null, false, ALL_SOURCES, PAGE)
|
||||
return userGameRepository.findOwnedGames(USER_ID, false, status, UserGameRepository.containsPattern(query), null, ALL_SOURCES, PAGE)
|
||||
.map(UserGame::getAppid).getContent();
|
||||
}
|
||||
|
||||
@@ -210,7 +211,7 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
|
||||
@Test
|
||||
void findOwnedGames_CountsAllMatchesAcrossPages() {
|
||||
var page = userGameRepository.findOwnedGames(USER_ID, false, null, UserGameRepository.containsPattern("half"), null, false, ALL_SOURCES,
|
||||
var page = userGameRepository.findOwnedGames(USER_ID, false, null, UserGameRepository.containsPattern("half"), null, ALL_SOURCES,
|
||||
PageRequest.of(0, 1, Sort.by("appid")));
|
||||
|
||||
assertEquals(List.of(220L), page.map(UserGame::getAppid).getContent());
|
||||
@@ -218,7 +219,7 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
}
|
||||
|
||||
private List<Long> sortedAppids(Sort sort) {
|
||||
return userGameRepository.findOwnedGames(USER_ID, false, null, null, null, false, ALL_SOURCES, PageRequest.of(0, 20, UserGameRepository.querySort(sort)))
|
||||
return userGameRepository.findOwnedGames(USER_ID, false, null, null, null, ALL_SOURCES, PageRequest.of(0, 20, UserGameRepository.querySort(sort)))
|
||||
.map(UserGame::getAppid).getContent();
|
||||
}
|
||||
|
||||
@@ -269,7 +270,7 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
new GameTimeToBeat(620L, 300, 480, 900, Instant.now()),
|
||||
new GameTimeToBeat(380L, null, null, null, Instant.now())));
|
||||
|
||||
List<Long> shortGames = userGameRepository.findOwnedGames(USER_ID, false, null, null, 600, false, ALL_SOURCES, PAGE).map(UserGame::getAppid).getContent();
|
||||
List<Long> shortGames = userGameRepository.findOwnedGames(USER_ID, false, null, null, 600, ALL_SOURCES, PAGE).map(UserGame::getAppid).getContent();
|
||||
|
||||
assertEquals(List.of(620L), shortGames);
|
||||
}
|
||||
@@ -296,7 +297,7 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
hide(620L, 700L);
|
||||
|
||||
assertEquals(List.of(220L, 380L), appids(null, null));
|
||||
assertEquals(List.of(620L, 700L), userGameRepository.findOwnedGames(USER_ID, true, null, null, null, false, ALL_SOURCES,
|
||||
assertEquals(List.of(620L, 700L), userGameRepository.findOwnedGames(USER_ID, true, null, null, null, ALL_SOURCES,
|
||||
PageRequest.of(0, 20, Sort.by("appid"))).map(UserGame::getAppid).getContent());
|
||||
assertEquals(Set.of(620L, 700L), Set.copyOf(userGameRepository.findHiddenAppids(USER_ID)));
|
||||
}
|
||||
@@ -317,8 +318,8 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
assertEquals(Set.of(new StatusCount(GameStatus.PLAYING, 1), new StatusCount(GameStatus.BACKLOG, 1)),
|
||||
Set.copyOf(userGameRepository.countByStatus(USER_ID)));
|
||||
assertEquals(List.of(380L), userGameRepository.findBacklogAppids(USER_ID));
|
||||
assertEquals(List.of(220L), userGameRepository.findListedPlayedInTwoWeeks(USER_ID, PAGE)
|
||||
.stream().map(UserGame::getAppid).toList());
|
||||
assertEquals(List.of(220L), userGameRepository.findEntriesPlayedInTwoWeeks(USER_ID, 20)
|
||||
.stream().map(RecentEntry::getAppid).toList());
|
||||
assertEquals(List.of(220L), userGameRepository.findListedByStatusIn(USER_ID, List.of(GameStatus.PLAYING), PAGE)
|
||||
.stream().map(UserGame::getAppid).toList());
|
||||
assertEquals(Set.of(220L, 380L), Set.copyOf(userGameRepository.findAchievementProgress(USER_ID)
|
||||
@@ -370,7 +371,7 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
}
|
||||
|
||||
private List<Long> appidsOfSources(GameSource... sources) {
|
||||
return userGameRepository.findOwnedGames(USER_ID, false, null, null, null, false, Set.of(sources),
|
||||
return userGameRepository.findOwnedGames(USER_ID, false, null, null, null, Set.of(sources),
|
||||
PageRequest.of(0, 20, Sort.by("appid"))).map(UserGame::getAppid).getContent();
|
||||
}
|
||||
|
||||
@@ -421,19 +422,99 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkedBacklogEditions_CountOnceAndTheOnceFilterKeepsOne() {
|
||||
void linkedBacklogEditions_CountOnceAndTheListKeepsOne() {
|
||||
link(380L, 700L);
|
||||
userGameRepository.flush();
|
||||
|
||||
assertEquals(Set.of(new StatusCount(GameStatus.PLAYING, 2), new StatusCount(GameStatus.BACKLOG, 1)),
|
||||
Set.copyOf(userGameRepository.countByStatus(USER_ID)));
|
||||
assertEquals(1, userGameRepository.countListedNeverPlayed(USER_ID));
|
||||
assertEquals(List.of(220L, 380L, 620L), userGameRepository.findOwnedGames(USER_ID, false, null, null, null, true, ALL_SOURCES,
|
||||
PageRequest.of(0, 20, Sort.by("appid"))).map(UserGame::getAppid).getContent());
|
||||
assertEquals(List.of(220L, 380L, 620L, 700L), userGameRepository.findOwnedGames(USER_ID, false, null, null, null, false, ALL_SOURCES,
|
||||
assertEquals(List.of(220L, 380L, 620L), userGameRepository.findOwnedGames(USER_ID, false, null, null, null, ALL_SOURCES,
|
||||
PageRequest.of(0, 20, Sort.by("appid"))).map(UserGame::getAppid).getContent());
|
||||
}
|
||||
|
||||
private List<Long> listed(boolean hidden, String query) {
|
||||
return userGameRepository.findOwnedGames(USER_ID, hidden, null, UserGameRepository.containsPattern(query), null,
|
||||
ALL_SOURCES, PageRequest.of(0, 20, Sort.by("appid"))).map(UserGame::getAppid).getContent();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOwnedGames_ASetIsOneRow_TheCountedEdition() {
|
||||
link(220L, 380L);
|
||||
userGameRepository.flush();
|
||||
|
||||
assertEquals(List.of(220L, 620L, 700L), listed(false, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOwnedGames_TheNameOfTheLesserEditionFindsTheEntry() {
|
||||
link(220L, 380L);
|
||||
userGameRepository.flush();
|
||||
|
||||
assertEquals(List.of(220L), listed(false, "episode"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOwnedGames_BySource_FindsASteamRowWithAPlayStationTitle() {
|
||||
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid) values (?, 'c1', 'STEAM_OWNED', 620)", USER_ID);
|
||||
|
||||
assertEquals(List.of(620L), appidsOfSources(GameSource.PSN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOwnedGames_BySource_FindsAnEntryThroughItsLesserEdition() {
|
||||
link(220L, 380L);
|
||||
setSource(GameSource.PSN, 380L);
|
||||
|
||||
assertEquals(List.of(220L), appidsOfSources(GameSource.PSN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOwnedGames_Hidden_ListsEveryHiddenRow() {
|
||||
link(220L, 380L);
|
||||
hide(220L, 380L);
|
||||
|
||||
assertEquals(List.of(220L, 380L), listed(true, null));
|
||||
assertEquals(List.of(380L), listed(true, "episode"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findEntriesPlayedInTwoWeeks_SumsTheSetAndKeepsAnEntryPlayedOnlyOnTheLesserEdition() {
|
||||
link(220L, 380L);
|
||||
userGameRepository.findById(new UserGameId(USER_ID, 220L)).orElseThrow().setPlaytime2Weeks(30);
|
||||
userGameRepository.findById(new UserGameId(USER_ID, 380L)).orElseThrow().setPlaytime2Weeks(40);
|
||||
userGameRepository.findById(new UserGameId(USER_ID, 620L)).orElseThrow().setPlaytime2Weeks(50);
|
||||
userGameRepository.flush();
|
||||
|
||||
var recent = userGameRepository.findEntriesPlayedInTwoWeeks(USER_ID, 20);
|
||||
|
||||
assertEquals(List.of(220L, 620L), recent.stream().map(RecentEntry::getAppid).toList());
|
||||
assertEquals(70L, recent.getFirst().getPlaytime2Weeks());
|
||||
assertEquals(300L, recent.getFirst().getPlaytimeMinutes());
|
||||
|
||||
userGameRepository.findById(new UserGameId(USER_ID, 220L)).orElseThrow().setPlaytime2Weeks(0);
|
||||
userGameRepository.flush();
|
||||
assertEquals(List.of(620L, 220L), userGameRepository.findEntriesPlayedInTwoWeeks(USER_ID, 20)
|
||||
.stream().map(RecentEntry::getAppid).toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void countListed_CountsASetOnce() {
|
||||
link(220L, 380L);
|
||||
userGameRepository.flush();
|
||||
|
||||
assertEquals(3, userGameRepository.countListed(USER_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findListedByStatusIn_ListsASetOnce() {
|
||||
link(220L, 380L);
|
||||
userGameRepository.flush();
|
||||
|
||||
assertEquals(List.of(220L, 620L), userGameRepository.findListedByStatusIn(USER_ID, List.of(GameStatus.PLAYING), PAGE)
|
||||
.stream().map(UserGame::getAppid).sorted().toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void backlogEditionOfAPlayingSet_IsNeverPicked() {
|
||||
link(220L, 380L);
|
||||
|
||||
@@ -374,6 +374,81 @@ class UserStatsRepositoryTest extends AbstractPersistenceTest {
|
||||
assertThat(repository.games(USER_ID, StatKey.TOTAL, "", BERLIN, 0, 10)).hasSize(1);
|
||||
}
|
||||
|
||||
private void seedSet() {
|
||||
userGameRepository.saveAllAndFlush(List.of(
|
||||
game(220L, 300, GameStatus.PLAYING, Instant.now()),
|
||||
game(620L, 900, GameStatus.PLAYING, Instant.now()),
|
||||
game(700L, 500, GameStatus.PLAYING, Instant.now())));
|
||||
link(220L, 620L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mostPlayed_ASetIsOneRowWithTheSumOfItsEditions() {
|
||||
seedSet();
|
||||
|
||||
assertThat(repository.mostPlayed(USER_ID, 5)).extracting(UserStats.GameMinutes::minutes)
|
||||
.containsExactly(1200L, 500L);
|
||||
assertThat(repository.games(USER_ID, StatKey.MOST_PLAYED, "", BERLIN, 0, 10))
|
||||
.extracting(UserStats.StatGame::minutes).containsExactly(1200L, 500L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mostLoyal_CountsTheMonthsOfEveryEditionOfASet() {
|
||||
seedSet();
|
||||
addDay(220L, LocalDate.of(2026, 7, 1), 10);
|
||||
addDay(620L, LocalDate.of(2026, 8, 1), 10);
|
||||
addDay(620L, LocalDate.of(2026, 9, 1), 10);
|
||||
addDay(700L, LocalDate.of(2026, 7, 3), 30);
|
||||
addDay(700L, LocalDate.of(2026, 8, 3), 30);
|
||||
|
||||
UserStats.LoyalGame loyal = repository.mostLoyal(USER_ID).orElseThrow();
|
||||
|
||||
assertThat(loyal.months()).isEqualTo(3);
|
||||
assertThat(loyal.minutes()).isEqualTo(30);
|
||||
}
|
||||
|
||||
@Test
|
||||
void genresAndMoods_CountASetOnceWithTheSummedMinutes() {
|
||||
seedSet();
|
||||
for (long appid : List.of(220L, 620L, 700L)) {
|
||||
gameMoodsRepository.upsert(appid, new String[]{"ACTION"}, new String[]{"Shooter"}, false, Instant.now());
|
||||
}
|
||||
|
||||
assertThat(repository.genres(USER_ID, 8)).containsExactly(new UserStats.TagCount("Shooter", 2, 1700));
|
||||
assertThat(repository.moods(USER_ID)).containsExactly(new UserStats.TagCount("ACTION", 2, 1700));
|
||||
assertThat(repository.countGames(USER_ID, StatKey.GENRE, "Shooter", BERLIN)).isEqualTo(2);
|
||||
assertThat(repository.games(USER_ID, StatKey.GENRE, "Shooter", BERLIN, 0, 10))
|
||||
.extracting(UserStats.StatGame::minutes).containsExactly(1200L, 500L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void games_PerfectListsASetOnce() {
|
||||
seedSet();
|
||||
|
||||
assertThat(repository.countGames(USER_ID, StatKey.PERFECT, List.of(220L, 620L), BERLIN)).isEqualTo(1);
|
||||
assertThat(repository.games(USER_ID, StatKey.PERFECT, List.of(220L, 620L), BERLIN, 0, 10)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void games_LongestDayListsASetOnceWithTheSumOfItsEditions() {
|
||||
seedSet();
|
||||
LocalDate day = LocalDate.of(2026, 7, 1);
|
||||
addDay(220L, day, 100);
|
||||
addDay(620L, day, 100);
|
||||
|
||||
assertThat(repository.countGames(USER_ID, StatKey.LONGEST_DAY, day, BERLIN)).isEqualTo(1);
|
||||
assertThat(repository.games(USER_ID, StatKey.LONGEST_DAY, day, BERLIN, 0, 10))
|
||||
.extracting(UserStats.StatGame::minutes).containsExactly(200L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformStat_ASetCountsOnEveryPlatformItHas() {
|
||||
seedSet();
|
||||
|
||||
assertThat(repository.gamesPerPlatform(USER_ID)).containsExactly(new UserStats.TagCount("Steam", 3, 1700));
|
||||
assertThat(repository.games(USER_ID, StatKey.PLATFORM, "Steam", BERLIN, 0, 10)).hasSize(3);
|
||||
}
|
||||
|
||||
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)));
|
||||
|
||||
Reference in new issue
Block a user