Public Access
fix(stats): count each set once by its set playtime
Played, never started, under one hour and oldest unplayed used the counted edition's own minutes, so a set played only on PS5 showed as never started. They and their drill-down lists now use the set total (user_game_entry_playtime). Refs #38. Nightly snapshot (#43) unchanged.
This commit is contained in:
1 parent
0550f3fe9c
commit
8f884040d8
8 files changed
+142
-12
No files matched your search
@@ -122,8 +122,10 @@ flowchart LR
|
|||||||
genres, moods, perfect games and the longest day list one entry per set, with the playtime of all its editions
|
genres, moods, perfect games and the longest day list one entry per set, with the playtime of all its editions
|
||||||
(`user_game_entry_member`, V43; its list shows that sum as `minutes`). Every playtime number (most played, genres,
|
(`user_game_entry_member`, V43; its list shows that sum as `minutes`). Every playtime number (most played, genres,
|
||||||
moods, the total playtime and moon trips, the drill-down lists) uses the set total, the view
|
moods, the total playtime and moon trips, the drill-down lists) uses the set total, the view
|
||||||
`user_game_entry_playtime` (V45), the same as the library card, sort and public profile. Platforms stay per edition:
|
`user_game_entry_playtime` (V45), the same as the library card, sort and public profile. So do "played", "never
|
||||||
a Steam + PlayStation set counts on both platforms.
|
started" and "under one hour": a set counts once, judged by its set total (also the dashboard "never played" and the
|
||||||
|
oldest unplayed game). Only the nightly trend snapshot `never_played` still uses the counted edition's own minutes.
|
||||||
|
Platforms stay per edition: a Steam + PlayStation set counts on both platforms.
|
||||||
- **Drill-down**: every countable number opens the games behind it. A `StatKey` (library), `CommunityKey` (landing page)
|
- **Drill-down**: every countable number opens the games behind it. A `StatKey` (library), `CommunityKey` (landing page)
|
||||||
or a Pile Points track/bonus row names a number; its SQL "game set" is the same text the number counts, so the
|
or a Pile Points track/bonus row names a number; its SQL "game set" is the same text the number counts, so the
|
||||||
list and the number cannot drift. 40 games per page (at most page 250), a fixed order per key. A profile visitor
|
list and the number cannot drift. 40 games per page (at most page 250), a fixed order per key. A profile visitor
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ public record UserStatsDto(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param playedGames games with a set total above 0
|
||||||
|
* @param neverStarted games with a set total of 0
|
||||||
* @param byStatus has every status, also those with 0 games
|
* @param byStatus has every status, also those with 0 games
|
||||||
*/
|
*/
|
||||||
public record Overview(long totalGames, long totalPlaytimeMinutes, long playedGames, long neverStarted,
|
public record Overview(long totalGames, long totalPlaytimeMinutes, long playedGames, long neverStarted,
|
||||||
@@ -56,6 +58,7 @@ public record UserStatsDto(
|
|||||||
/**
|
/**
|
||||||
* @param moonTrips the total playtime as flights to the moon and back (Apollo 11, 195.3 hours)
|
* @param moonTrips the total playtime as flights to the moon and back (Apollo 11, 195.3 hours)
|
||||||
* @param longestDay the day with the most playtime; null without history
|
* @param longestDay the day with the most playtime; null without history
|
||||||
|
* @param underOneHourGames games whose set total is 1 to 59 minutes
|
||||||
* @param underOneHourShare share of the started games with less than one hour; null when none is started
|
* @param underOneHourShare share of the started games with less than one hour; null when none is started
|
||||||
*/
|
*/
|
||||||
public record Habits(double moonTrips, DayMinutes longestDay, long underOneHourGames, Double underOneHourShare) {}
|
public record Habits(double moonTrips, DayMinutes longestDay, long underOneHourGames, Double underOneHourShare) {}
|
||||||
|
|||||||
@@ -123,7 +123,8 @@ public interface UserGameRepository extends JpaRepository<UserGame, UserGameId>
|
|||||||
nativeQuery = true)
|
nativeQuery = true)
|
||||||
List<RecentEntry> findEntriesPlayedInTwoWeeks(@Param("userId") Long userId, @Param("limit") int limit);
|
List<RecentEntry> findEntriesPlayedInTwoWeeks(@Param("userId") Long userId, @Param("limit") int limit);
|
||||||
|
|
||||||
@Query("select count(c) from CountedUserGame c where c.userId = :userId and c.playtimeMinutes = 0")
|
/** The entries whose set total (V45) is 0. */
|
||||||
|
@Query("select count(ep) from UserGameEntryPlaytime ep where ep.userId = :userId and ep.playtimeMinutes = 0")
|
||||||
long countListedNeverPlayed(@Param("userId") Long userId);
|
long countListedNeverPlayed(@Param("userId") Long userId);
|
||||||
|
|
||||||
@Query("select new core_service.modules.library.domain.StatusCount(c.status, count(c)) from CountedUserGame c " +
|
@Query("select new core_service.modules.library.domain.StatusCount(c.status, count(c)) from CountedUserGame c " +
|
||||||
|
|||||||
@@ -114,11 +114,11 @@ public class UserStatsRepository {
|
|||||||
private static final Map<StatKey, String> SETS = Map.ofEntries(
|
private static final Map<StatKey, String> SETS = Map.ofEntries(
|
||||||
Map.entry(StatKey.TOTAL, counted("true")),
|
Map.entry(StatKey.TOTAL, counted("true")),
|
||||||
Map.entry(StatKey.STATUS, counted("ug.status = :arg")),
|
Map.entry(StatKey.STATUS, counted("ug.status = :arg")),
|
||||||
Map.entry(StatKey.PLAYED, counted("ug.playtime_minutes > 0")),
|
Map.entry(StatKey.PLAYED, entries("ug.playtime_minutes > 0")),
|
||||||
Map.entry(StatKey.NEVER_STARTED, counted("ug.playtime_minutes = 0")),
|
Map.entry(StatKey.NEVER_STARTED, entries("ug.playtime_minutes = 0")),
|
||||||
Map.entry(StatKey.PILE, counted("ug.status = 'BACKLOG' and " + UserGameRepository.NOT_COMING)),
|
Map.entry(StatKey.PILE, counted("ug.status = 'BACKLOG' and " + UserGameRepository.NOT_COMING)),
|
||||||
Map.entry(StatKey.FINISHED_ALL, counted("ug.status in " + FINISHED)),
|
Map.entry(StatKey.FINISHED_ALL, counted("ug.status in " + FINISHED)),
|
||||||
Map.entry(StatKey.UNDER_ONE_HOUR, counted(UNDER_ONE_HOUR)),
|
Map.entry(StatKey.UNDER_ONE_HOUR, entries(UNDER_ONE_HOUR)),
|
||||||
Map.entry(StatKey.FINISHED_UNTRACKED, counted(UNTRACKED)),
|
Map.entry(StatKey.FINISHED_UNTRACKED, counted(UNTRACKED)),
|
||||||
Map.entry(StatKey.PILE_TIME_TO_BEAT, PILE_TTB_SET),
|
Map.entry(StatKey.PILE_TIME_TO_BEAT, PILE_TTB_SET),
|
||||||
Map.entry(StatKey.MOST_PLAYED, "select ug.appid, ug.playtime_minutes as minutes, " + NO_EVENT + ", " + NO_MOVE
|
Map.entry(StatKey.MOST_PLAYED, "select ug.appid, ug.playtime_minutes as minutes, " + NO_EVENT + ", " + NO_MOVE
|
||||||
@@ -168,9 +168,10 @@ public class UserStatsRepository {
|
|||||||
return jdbc.query("""
|
return jdbc.query("""
|
||||||
select ug.appid, sg.name, sg.image_url, ug.first_seen_at
|
select ug.appid, sg.name, sg.image_url, ug.first_seen_at
|
||||||
from counted_user_game ug join steam_game sg on sg.appid = ug.appid
|
from counted_user_game ug join steam_game sg on sg.appid = ug.appid
|
||||||
where %s and ug.status = 'BACKLOG' and ug.playtime_minutes = 0 and %s
|
join %s ep on ep.appid = ug.appid
|
||||||
|
where %s and ug.status = 'BACKLOG' and ep.playtime_minutes = 0 and %s
|
||||||
order by ug.first_seen_at, ug.appid limit 1
|
order by ug.first_seen_at, ug.appid limit 1
|
||||||
""".formatted(OF_USER, UserGameRepository.NOT_COMING), user(userId),
|
""".formatted(ENTRY_PLAYTIME, OF_USER, UserGameRepository.NOT_COMING), user(userId),
|
||||||
(rs, i) -> new UserStats.OldestGame(rs.getLong("appid"), rs.getString("name"),
|
(rs, i) -> new UserStats.OldestGame(rs.getLong("appid"), rs.getString("name"),
|
||||||
rs.getString("image_url"), rs.getTimestamp("first_seen_at").toInstant()))
|
rs.getString("image_url"), rs.getTimestamp("first_seen_at").toInstant()))
|
||||||
.stream().findFirst();
|
.stream().findFirst();
|
||||||
@@ -251,8 +252,8 @@ public class UserStatsRepository {
|
|||||||
return jdbc.queryForObject("""
|
return jdbc.queryForObject("""
|
||||||
select count(*) filter (where ug.playtime_minutes > 0) as started,
|
select count(*) filter (where ug.playtime_minutes > 0) as started,
|
||||||
count(*) filter (where %s) as under_one_hour
|
count(*) filter (where %s) as under_one_hour
|
||||||
from counted_user_game ug where %s
|
from %s ug
|
||||||
""".formatted(UNDER_ONE_HOUR, OF_USER), user(userId),
|
""".formatted(UNDER_ONE_HOUR, ENTRY_PLAYTIME), user(userId),
|
||||||
(rs, i) -> new UserStats.PlayedCounts(rs.getLong("started"), rs.getLong("under_one_hour")));
|
(rs, i) -> new UserStats.PlayedCounts(rs.getLong("started"), rs.getLong("under_one_hour")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +322,11 @@ public class UserStatsRepository {
|
|||||||
return games != null ? games : 0L;
|
return games != null ? games : 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The entries whose set total (V45) matches {@code where}; alias ug. */
|
||||||
|
private static String entries(String where) {
|
||||||
|
return "select ug.appid, %s from %s ug where %s".formatted(NO_STAT, ENTRY_PLAYTIME, where);
|
||||||
|
}
|
||||||
|
|
||||||
private static String counted(String where) {
|
private static String counted(String where) {
|
||||||
return "select ug.appid, %s from counted_user_game ug where %s and %s".formatted(NO_STAT, OF_USER, where);
|
return "select ug.appid, %s from counted_user_game ug where %s and %s".formatted(NO_STAT, OF_USER, where);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
void seed() {
|
void seed() {
|
||||||
jdbc.update("delete from game_link where set_id = 980002");
|
jdbc.update("delete from game_link where set_id = 980002");
|
||||||
|
jdbc.update("delete from user_psn_title where user_id = ?", USER_ID);
|
||||||
gameReleaseRepository.deleteAll();
|
gameReleaseRepository.deleteAll();
|
||||||
userGameRepository.deleteAll();
|
userGameRepository.deleteAll();
|
||||||
schemaMetaRepository.deleteAll();
|
schemaMetaRepository.deleteAll();
|
||||||
@@ -237,6 +238,21 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
|
|||||||
.andExpect(jsonPath("$.stats.topGames.mostPlayed[0].minutes").value(1000));
|
.andExpect(jsonPath("$.stats.topGames.mostPlayed[0].minutes").value(1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void publicProfile_HabitsCountsEqualTheOwnersStats() throws Exception {
|
||||||
|
jdbc.update("update user_game set playtime_minutes = 0 where user_id = ? and appid = 220", USER_ID);
|
||||||
|
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid, platform, playtime_minutes) "
|
||||||
|
+ "values (?, 'c1', 'STEAM_OWNED', 220, 'PS5', 40)", USER_ID);
|
||||||
|
var own = userStatsService.getStats(USER_ID, Set.of(StatGroup.HABITS));
|
||||||
|
|
||||||
|
assertEquals(1L, own.habits().underOneHourGames());
|
||||||
|
mockMvc.perform(get("/api/public/profiles/library")
|
||||||
|
.header("X-Profile-Ticket", ticket(USER_ID, List.of("HABITS"))))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.stats.habits.underOneHourGames").value(own.habits().underOneHourGames()))
|
||||||
|
.andExpect(jsonPath("$.stats.habits.underOneHourShare").value(own.habits().underOneHourShare()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void publicLibrary_OnlyTheSharedStatGroups() throws Exception {
|
void publicLibrary_OnlyTheSharedStatGroups() throws Exception {
|
||||||
mockMvc.perform(get("/api/public/profiles/library")
|
mockMvc.perform(get("/api/public/profiles/library")
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class StatGamesFlowTest extends AbstractMockedLibraryServicesTest {
|
|||||||
private static final long MANUAL_SWITCH = 5_000_010L;
|
private static final long MANUAL_SWITCH = 5_000_010L;
|
||||||
private static final long MANUAL_SWITCH_LOWER = 5_000_011L;
|
private static final long MANUAL_SWITCH_LOWER = 5_000_011L;
|
||||||
private static final long BACK_TO_PILE = 5_000_012L;
|
private static final long BACK_TO_PILE = 5_000_012L;
|
||||||
|
private static final long SET_PSN = 5_000_013L;
|
||||||
private static final int YEAR = LocalDate.now(ZoneId.of("Europe/Berlin")).getYear();
|
private static final int YEAR = LocalDate.now(ZoneId.of("Europe/Berlin")).getYear();
|
||||||
private static final JsonMapper JSON = JsonMapper.builder().build();
|
private static final JsonMapper JSON = JsonMapper.builder().build();
|
||||||
|
|
||||||
@@ -123,6 +124,7 @@ class StatGamesFlowTest extends AbstractMockedLibraryServicesTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void seed() {
|
void seed() {
|
||||||
|
jdbc.update("delete from user_psn_title where user_id in (?, ?)", USER, OTHER);
|
||||||
jdbc.update("delete from user_game where user_id in (?, ?)", USER, OTHER);
|
jdbc.update("delete from user_game where user_id in (?, ?)", USER, OTHER);
|
||||||
jdbc.update("delete from user_game_playtime_day where user_id in (?, ?)", USER, OTHER);
|
jdbc.update("delete from user_game_playtime_day where user_id in (?, ?)", USER, OTHER);
|
||||||
jdbc.update("delete from game_link where appid in (?, ?)", EDITION_MAIN, EDITION_SECOND);
|
jdbc.update("delete from game_link where appid in (?, ?)", EDITION_MAIN, EDITION_SECOND);
|
||||||
@@ -147,6 +149,9 @@ class StatGamesFlowTest extends AbstractMockedLibraryServicesTest {
|
|||||||
game(USER, MANUAL_SWITCH_LOWER, 70, "BACKLOG", "MANUAL", "switch");
|
game(USER, MANUAL_SWITCH_LOWER, 70, "BACKLOG", "MANUAL", "switch");
|
||||||
game(USER, BACK_TO_PILE, 10, "PLAYING", "STEAM", null);
|
game(USER, BACK_TO_PILE, 10, "PLAYING", "STEAM", null);
|
||||||
jdbc.update("update user_game set status = 'BACKLOG' where user_id = ? and appid = ?", USER, BACK_TO_PILE);
|
jdbc.update("update user_game set status = 'BACKLOG' where user_id = ? and appid = ?", USER, BACK_TO_PILE);
|
||||||
|
game(USER, SET_PSN, 0, "BACKLOG", "MANUAL", "Xbox");
|
||||||
|
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid, platform, playtime_minutes) "
|
||||||
|
+ "values (?, 'set-psn', 'STEAM_OWNED', ?, 'PS5', 120)", USER, SET_PSN);
|
||||||
game(OTHER, FINISHED_TRACKED, 4000, "FINISHED", "STEAM", null);
|
game(OTHER, FINISHED_TRACKED, 4000, "FINISHED", "STEAM", null);
|
||||||
|
|
||||||
jdbc.update("""
|
jdbc.update("""
|
||||||
@@ -220,6 +225,18 @@ class StatGamesFlowTest extends AbstractMockedLibraryServicesTest {
|
|||||||
assertThat(rows).extracting(r -> r.get("appid").asLong()).doesNotHaveDuplicates();
|
assertThat(rows).extracting(r -> r.get("appid").asLong()).doesNotHaveDuplicates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setPlayedOnPlayStation_IsPlayedNotNeverStarted() throws Exception {
|
||||||
|
JsonNode stats = stats();
|
||||||
|
List<Long> played = allRows("PLAYED", null).stream().map(r -> r.get("appid").asLong()).toList();
|
||||||
|
List<Long> never = allRows("NEVER_STARTED", null).stream().map(r -> r.get("appid").asLong()).toList();
|
||||||
|
|
||||||
|
assertThat(played).contains(SET_PSN);
|
||||||
|
assertThat(never).doesNotContain(SET_PSN);
|
||||||
|
assertThat(stats.at("/overview/playedGames").asLong() + stats.at("/overview/neverStarted").asLong())
|
||||||
|
.isEqualTo(stats.at("/overview/totalGames").asLong());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void mostPlayed_FirstFiveAreTheStatsTopGames() throws Exception {
|
void mostPlayed_FirstFiveAreTheStatsTopGames() throws Exception {
|
||||||
List<Long> top = new ArrayList<>();
|
List<Long> top = new ArrayList<>();
|
||||||
|
|||||||
@@ -631,6 +631,14 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
|||||||
assertEquals(1, userGameRepository.countListedNeverPlayed(USER_ID));
|
assertEquals(1, userGameRepository.countListedNeverPlayed(USER_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void countListedNeverPlayed_UsesTheSetTotal() {
|
||||||
|
userGameRepository.flush();
|
||||||
|
titlePlay("c9", 380L, "STEAM_OWNED", "PS5", 120);
|
||||||
|
|
||||||
|
assertEquals(1, userGameRepository.countListedNeverPlayed(USER_ID));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void findRandomBacklogGame_PicksOnlyOwnActiveBacklogGames() {
|
void findRandomBacklogGame_PicksOnlyOwnActiveBacklogGames() {
|
||||||
UserGame removed = userGameRepository.findById(new UserGameId(USER_ID, 700L)).orElseThrow();
|
UserGame removed = userGameRepository.findById(new UserGameId(USER_ID, 700L)).orElseThrow();
|
||||||
|
|||||||
@@ -179,12 +179,16 @@ class UserStatsRepositoryTest extends AbstractPersistenceTest {
|
|||||||
.containsExactly(620L, 220L);
|
.containsExactly(620L, 220L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void psnPlay(long appid, int minutes) {
|
||||||
|
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid, platform, playtime_minutes) "
|
||||||
|
+ "values (?, ?, 'STEAM_OWNED', ?, 'PS5', ?)", USER_ID, "c" + appid, appid, minutes);
|
||||||
|
}
|
||||||
|
|
||||||
private void seedEntryTotal() {
|
private void seedEntryTotal() {
|
||||||
userGameRepository.saveAllAndFlush(List.of(
|
userGameRepository.saveAllAndFlush(List.of(
|
||||||
game(220L, 100, GameStatus.PLAYING, Instant.now()),
|
game(220L, 100, GameStatus.PLAYING, Instant.now()),
|
||||||
game(620L, 300, GameStatus.PLAYING, Instant.now())));
|
game(620L, 300, GameStatus.PLAYING, Instant.now())));
|
||||||
jdbc.update("insert into user_psn_title (user_id, psn_key, state, appid, platform, playtime_minutes) "
|
psnPlay(220L, 500);
|
||||||
+ "values (?, 'c1', 'STEAM_OWNED', 220, 'PS5', 500)", USER_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -282,6 +286,79 @@ class UserStatsRepositoryTest extends AbstractPersistenceTest {
|
|||||||
assertThat(repository.playedCounts(USER_ID)).isEqualTo(new UserStats.PlayedCounts(2, 1));
|
assertThat(repository.playedCounts(USER_ID)).isEqualTo(new UserStats.PlayedCounts(2, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Long> appids(StatKey key) {
|
||||||
|
return repository.games(USER_ID, key, "", BERLIN, 0, 10).stream().map(UserStats.StatGame::appid).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void playedCounts_ASetPlayedOnlyOnPlayStationIsPlayed() {
|
||||||
|
userGameRepository.saveAllAndFlush(List.of(
|
||||||
|
game(220L, 0, GameStatus.BACKLOG, Instant.now()),
|
||||||
|
game(620L, 0, GameStatus.BACKLOG, Instant.now())));
|
||||||
|
psnPlay(220L, 120);
|
||||||
|
|
||||||
|
assertThat(repository.playedCounts(USER_ID)).isEqualTo(new UserStats.PlayedCounts(1, 0));
|
||||||
|
assertThat(repository.countGames(USER_ID, StatKey.PLAYED, "", BERLIN)).isEqualTo(1);
|
||||||
|
assertThat(appids(StatKey.PLAYED)).containsExactly(220L);
|
||||||
|
assertThat(repository.countGames(USER_ID, StatKey.NEVER_STARTED, "", BERLIN)).isEqualTo(1);
|
||||||
|
assertThat(appids(StatKey.NEVER_STARTED)).containsExactly(620L);
|
||||||
|
assertThat(repository.countGames(USER_ID, StatKey.UNDER_ONE_HOUR, "", BERLIN)).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void playedCounts_UnderOneHourIsJudgedByTheSetTotal() {
|
||||||
|
userGameRepository.saveAllAndFlush(List.of(
|
||||||
|
game(220L, 40, GameStatus.PLAYING, Instant.now()),
|
||||||
|
game(620L, 30, GameStatus.PLAYING, Instant.now()),
|
||||||
|
game(700L, 20, GameStatus.PLAYING, Instant.now())));
|
||||||
|
link(220L, 620L);
|
||||||
|
|
||||||
|
assertThat(repository.playedCounts(USER_ID)).isEqualTo(new UserStats.PlayedCounts(2, 1));
|
||||||
|
assertThat(appids(StatKey.UNDER_ONE_HOUR)).containsExactly(700L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void neverStarted_ASetWithEveryEdition0IsCountedOnce() {
|
||||||
|
userGameRepository.saveAllAndFlush(List.of(
|
||||||
|
game(220L, 0, GameStatus.BACKLOG, Instant.now()),
|
||||||
|
game(620L, 0, GameStatus.BACKLOG, Instant.now())));
|
||||||
|
link(220L, 620L);
|
||||||
|
|
||||||
|
assertThat(repository.countGames(USER_ID, StatKey.NEVER_STARTED, "", BERLIN)).isEqualTo(1);
|
||||||
|
assertThat(appids(StatKey.NEVER_STARTED)).hasSize(1);
|
||||||
|
assertThat(repository.playedCounts(USER_ID)).isEqualTo(new UserStats.PlayedCounts(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void playedCounts_ManualAndSteamSet() {
|
||||||
|
steamGameRepository.saveAndFlush(new SteamGame(8001L, "Manual", null, Instant.now()));
|
||||||
|
userGameRepository.saveAllAndFlush(List.of(
|
||||||
|
game(220L, 0, GameStatus.BACKLOG, Instant.now()),
|
||||||
|
UserGame.manual(USER_ID, 8001L, GameSource.MANUAL, "PS5", GameStatus.BACKLOG, 50, Instant.now())));
|
||||||
|
link(220L, 8001L);
|
||||||
|
|
||||||
|
assertThat(repository.playedCounts(USER_ID)).isEqualTo(new UserStats.PlayedCounts(1, 1));
|
||||||
|
assertThat(repository.countGames(USER_ID, StatKey.NEVER_STARTED, "", BERLIN)).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void playedCounts_NoGames() {
|
||||||
|
assertThat(repository.playedCounts(USER_ID)).isEqualTo(new UserStats.PlayedCounts(0, 0));
|
||||||
|
assertThat(repository.countGames(USER_ID, StatKey.PLAYED, "", BERLIN)).isZero();
|
||||||
|
assertThat(repository.countGames(USER_ID, StatKey.NEVER_STARTED, "", BERLIN)).isZero();
|
||||||
|
assertThat(repository.countGames(USER_ID, StatKey.UNDER_ONE_HOUR, "", BERLIN)).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void oldestUnplayed_LeavesOutAPileSetPlayedOnPlayStation() {
|
||||||
|
userGameRepository.saveAllAndFlush(List.of(
|
||||||
|
game(220L, 0, GameStatus.BACKLOG, Instant.EPOCH),
|
||||||
|
game(620L, 0, GameStatus.BACKLOG, Instant.parse("2026-01-01T00:00:00Z"))));
|
||||||
|
psnPlay(220L, 120);
|
||||||
|
|
||||||
|
assertThat(repository.oldestUnplayed(USER_ID).orElseThrow().appid()).isEqualTo(620L);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void finishedPerYear_CountsAGameOnceInTheYearItFirstGotFinished() {
|
void finishedPerYear_CountsAGameOnceInTheYearItFirstGotFinished() {
|
||||||
userGameRepository.saveAllAndFlush(List.of(
|
userGameRepository.saveAllAndFlush(List.of(
|
||||||
|
|||||||
Reference in new issue
Block a user