Public Access
feat(sync): record what each Steam sync changed
Allows dashboard to show what the last sync changed. Migration V30 creates sync_change and sync_result tables. Sync records additions, removals, playtime, achievements, and status changes per game.
This commit is contained in:
1 parent
065048415c
commit
a8e208321f
23 files changed
+1058
-80
No files matched your search
@@ -17,7 +17,7 @@ class AccountCleanupRepositoryTest extends AbstractPersistenceTest {
|
||||
|
||||
private static final List<String> USER_TABLES = List.of("user_game_status_change", "user_game_achievement",
|
||||
"user_game_achievement_meta", "user_game_playtime_day", "wishlist_game", "backlog_snapshot",
|
||||
"user_steam_sync", "game_merge_undo_user", "user_game", "game_link_request", "account_name", "game_import", "reward_ledger",
|
||||
"sync_change", "sync_result", "user_steam_sync", "game_merge_undo_user", "user_game", "game_link_request", "account_name", "game_import", "reward_ledger",
|
||||
"reward_equipped");
|
||||
|
||||
@Autowired
|
||||
@@ -45,6 +45,8 @@ class AccountCleanupRepositoryTest extends AbstractPersistenceTest {
|
||||
jdbc.update("""
|
||||
insert into user_steam_sync (user_id, steam_id, user_seen_at) values (?, '76561198000000001', now())""",
|
||||
userId);
|
||||
jdbc.update("insert into sync_result (user_id, sync_started_at, started_by) values (?, now(), 'AUTO')", userId);
|
||||
jdbc.update("insert into sync_change (user_id, kind, appid) values (?, 'ADDED', 220)", userId);
|
||||
jdbc.update("insert into game_merge_undo_user (merge_id, user_id, combined) values (1, ?, false)", userId);
|
||||
jdbc.update("insert into game_link_request (user_id, appid_a, appid_b) values (?, 220, 620)", userId);
|
||||
jdbc.update("insert into account_name (user_id, display_name) values (?, 'lobo')", userId);
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package core_service.modules.library.api;
|
||||
|
||||
import core_service.modules.library.domain.GameAchievementSchemaMeta;
|
||||
import core_service.modules.library.domain.SyncTrigger;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.domain.UserGameAchievementMeta;
|
||||
@@ -153,7 +154,7 @@ class LibraryControllerAchievementsTest extends AbstractMockedAdaptersTest {
|
||||
when(steamApiService.getWishlist(anyString())).thenReturn(Optional.of(List.of()));
|
||||
when(steamApiService.getSchemaForGame(anyLong())).thenReturn(Optional.empty());
|
||||
|
||||
librarySyncWorker.runSyncNow(USER_ID, "76561198000000001");
|
||||
librarySyncWorker.runSyncNow(USER_ID, "76561198000000001", SyncTrigger.AUTO);
|
||||
|
||||
verify(steamApiService).getSchemaForGame(620L);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package core_service.modules.library.api;
|
||||
|
||||
import core_service.configuration.security.ClientHeaderFilter;
|
||||
import core_service.modules.library.domain.SyncTrigger;
|
||||
import core_service.modules.library.service.LibrarySyncWorker;
|
||||
import core_service.modules.steam_adapter.api.dto.response.SteamOwnedGamesResponse;
|
||||
import core_service.shared.test.AbstractMockedAdaptersTest;
|
||||
import core_service.shared.test.ContractTokens;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
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.web.servlet.MockMvc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Two real syncs with a mocked Steam. The dev profile authenticates a request without a token as user 1.
|
||||
*/
|
||||
@AutoConfigureMockMvc
|
||||
class SyncChangesFlowTest extends AbstractMockedAdaptersTest {
|
||||
|
||||
private static final long USER_ID = 1L;
|
||||
private static final String STEAM_ID = "76561198000000001";
|
||||
private static final int GROWN = 910_001;
|
||||
private static final int UNTOUCHED = 910_002;
|
||||
private static final int GONE = 910_003;
|
||||
private static final int HIDDEN = 910_004;
|
||||
private static final int NEW = 910_005;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private LibrarySyncWorker worker;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
void clean() {
|
||||
for (String table : List.of("sync_result", "user_game_playtime_day", "user_game", "user_steam_sync")) {
|
||||
jdbc.update("delete from " + table + " where user_id = ?", USER_ID);
|
||||
}
|
||||
jdbc.update("delete from steam_game where appid between 910001 and 910005");
|
||||
}
|
||||
|
||||
private static SteamOwnedGamesResponse.Game owned(int appid, int playtime) {
|
||||
return new SteamOwnedGamesResponse.Game(appid, "Game " + appid, playtime, "icon", true, playtime);
|
||||
}
|
||||
|
||||
private void sync(SyncTrigger trigger, SteamOwnedGamesResponse.Game... games) {
|
||||
when(steamApiService.getWishlist(anyString())).thenReturn(Optional.empty());
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(games));
|
||||
worker.runSyncNow(USER_ID, STEAM_ID, trigger);
|
||||
}
|
||||
|
||||
private void firstAndSecondSync() {
|
||||
sync(SyncTrigger.AUTO, owned(GROWN, 100), owned(UNTOUCHED, 0), owned(GONE, 5), owned(HIDDEN, 10));
|
||||
jdbc.update("update user_game set hidden_at = now() where user_id = ? and appid = ?", USER_ID, HIDDEN);
|
||||
sync(SyncTrigger.MANUAL, owned(GROWN, 160), owned(UNTOUCHED, 30), owned(HIDDEN, 40), owned(NEW, 15));
|
||||
}
|
||||
|
||||
@Test
|
||||
void firstSync_IsAnImportWithoutRows() throws Exception {
|
||||
sync(SyncTrigger.AUTO, owned(GROWN, 100), owned(UNTOUCHED, 0));
|
||||
|
||||
mockMvc.perform(get("/api/library/sync-changes"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.importedGames").value(2))
|
||||
.andExpect(jsonPath("$.startedBy").value("AUTO"))
|
||||
.andExpect(jsonPath("$.seen").value(false))
|
||||
.andExpect(jsonPath("$.changes", hasSize(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondSync_ListsWhatChanged_AndLeavesOutAHiddenGame() throws Exception {
|
||||
firstAndSecondSync();
|
||||
|
||||
mockMvc.perform(get("/api/library/sync-changes"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.startedBy").value("MANUAL"))
|
||||
.andExpect(jsonPath("$.importedGames").value(0))
|
||||
.andExpect(jsonPath("$.changes[?(@.kind=='ADDED')].appid").value(containsInAnyOrder(NEW)))
|
||||
.andExpect(jsonPath("$.changes[?(@.kind=='REMOVED')].appid").value(containsInAnyOrder(GONE)))
|
||||
.andExpect(jsonPath("$.changes[?(@.kind=='REMOVED')].name").value(containsInAnyOrder("Game " + GONE)))
|
||||
.andExpect(jsonPath("$.changes[?(@.kind=='PLAYTIME')].appid").value(containsInAnyOrder(GROWN, UNTOUCHED)))
|
||||
.andExpect(jsonPath("$.changes[?(@.kind=='PLAYTIME' && @.appid==" + GROWN + ")].amount")
|
||||
.value(containsInAnyOrder(60)))
|
||||
.andExpect(jsonPath("$.changes[?(@.kind=='STATUS')].appid").value(containsInAnyOrder(UNTOUCHED)))
|
||||
.andExpect(jsonPath("$.changes[?(@.kind=='STATUS')].fromStatus").value(containsInAnyOrder("BACKLOG")))
|
||||
.andExpect(jsonPath("$.changes[?(@.kind=='STATUS')].toStatus").value(containsInAnyOrder("PLAYING")))
|
||||
.andExpect(jsonPath("$.changes[?(@.appid==" + HIDDEN + ")]", hasSize(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void markSeen_SetsSeen_AndAStaleTimestampChangesNothing() throws Exception {
|
||||
firstAndSecondSync();
|
||||
String syncedAt = jdbc.queryForObject("select to_char(sync_started_at at time zone 'UTC', "
|
||||
+ "'YYYY-MM-DD\"T\"HH24:MI:SS.US\"Z\"') from sync_result where user_id = ?", String.class, USER_ID);
|
||||
|
||||
mockMvc.perform(put("/api/library/sync-changes/seen").header(ClientHeaderFilter.CLIENT_HEADER, "test")
|
||||
.contentType(MediaType.APPLICATION_JSON).content("{\"syncedAt\":\"2020-01-01T00:00:00Z\"}"))
|
||||
.andExpect(status().isNoContent());
|
||||
mockMvc.perform(get("/api/library/sync-changes")).andExpect(jsonPath("$.seen").value(false));
|
||||
|
||||
mockMvc.perform(put("/api/library/sync-changes/seen").header(ClientHeaderFilter.CLIENT_HEADER, "test")
|
||||
.contentType(MediaType.APPLICATION_JSON).content("{\"syncedAt\":\"" + syncedAt + "\"}"))
|
||||
.andExpect(status().isNoContent());
|
||||
mockMvc.perform(get("/api/library/sync-changes")).andExpect(jsonPath("$.seen").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void syncWithoutChanges_KeepsTheUnseenResult() throws Exception {
|
||||
firstAndSecondSync();
|
||||
|
||||
sync(SyncTrigger.AUTO, owned(GROWN, 160), owned(UNTOUCHED, 30), owned(HIDDEN, 40), owned(NEW, 15));
|
||||
|
||||
mockMvc.perform(get("/api/library/sync-changes"))
|
||||
.andExpect(jsonPath("$.startedBy").value("MANUAL"))
|
||||
.andExpect(jsonPath("$.changes[?(@.kind=='ADDED')].appid").value(containsInAnyOrder(NEW)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userWithoutAResult_GetsNoContent() throws Exception {
|
||||
firstAndSecondSync();
|
||||
|
||||
mockMvc.perform(get("/api/library/sync-changes")
|
||||
.header("Authorization", "Bearer " + ContractTokens.loginToken(77L)))
|
||||
.andExpect(status().isNoContent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void noSyncYet_GetsNoContent() throws Exception {
|
||||
mockMvc.perform(get("/api/library/sync-changes")).andExpect(status().isNoContent());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package core_service.modules.library.repo;
|
||||
|
||||
import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.SyncChange;
|
||||
import core_service.modules.library.domain.SyncChangeKind;
|
||||
import core_service.modules.library.domain.SyncChanges;
|
||||
import core_service.modules.library.domain.SyncTrigger;
|
||||
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.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Import(SyncChangeRepository.class)
|
||||
class SyncChangeRepositoryTest extends AbstractPersistenceTest {
|
||||
|
||||
private static final long USER_ID = 1L;
|
||||
private static final long OTHER_USER = 2L;
|
||||
private static final long HALF_LIFE = 220L;
|
||||
private static final long PORTAL = 620L;
|
||||
private static final long HIDDEN = 700L;
|
||||
private static final long REMOVED = 800L;
|
||||
private static final Instant FIRST_RUN = Instant.now().minusSeconds(3600).truncatedTo(ChronoUnit.MICROS);
|
||||
private static final Instant SECOND_RUN = FIRST_RUN.plusSeconds(60);
|
||||
|
||||
@Autowired
|
||||
private SyncChangeRepository repository;
|
||||
|
||||
@Autowired
|
||||
private UserGameRepository userGameRepository;
|
||||
|
||||
@Autowired
|
||||
private SteamGameRepository steamGameRepository;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
steamGameRepository.saveAllAndFlush(List.of(
|
||||
new SteamGame(HALF_LIFE, "Half-Life 2", null, Instant.now()),
|
||||
new SteamGame(PORTAL, "Portal 2", null, Instant.now()),
|
||||
new SteamGame(HIDDEN, "Playtest", null, Instant.now()),
|
||||
new SteamGame(REMOVED, "Refunded", null, Instant.now())));
|
||||
UserGame hidden = new UserGame(USER_ID, HIDDEN, 0, Instant.now(), Instant.now());
|
||||
hidden.setHiddenAt(Instant.now());
|
||||
UserGame removed = new UserGame(USER_ID, REMOVED, 0, Instant.now(), Instant.now());
|
||||
removed.setRemovedAt(Instant.now());
|
||||
userGameRepository.saveAllAndFlush(List.of(
|
||||
new UserGame(USER_ID, HALF_LIFE, 0, Instant.now(), Instant.now()),
|
||||
new UserGame(USER_ID, PORTAL, 0, Instant.now(), Instant.now()),
|
||||
hidden, removed));
|
||||
}
|
||||
|
||||
private void claim(long userId, Instant startedAt) {
|
||||
jdbc.update("""
|
||||
insert into user_steam_sync (user_id, steam_id, sync_started_at) values (?, '76561198000000001', ?)
|
||||
on conflict (user_id) do update set sync_started_at = excluded.sync_started_at""",
|
||||
userId, Timestamp.from(startedAt));
|
||||
}
|
||||
|
||||
private int rows() {
|
||||
return jdbc.queryForObject("select count(*) from sync_change where user_id = ?", Integer.class, USER_ID);
|
||||
}
|
||||
|
||||
private void markSeenNow() {
|
||||
jdbc.update("update sync_result set seen_at = now() where user_id = ?", USER_ID);
|
||||
}
|
||||
|
||||
private SyncChanges stored() {
|
||||
return repository.find(USER_ID).orElseThrow();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replace_DropsTheOldRowsAndResetsSeenAt() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of(SyncChange.added(HALF_LIFE)));
|
||||
markSeenNow();
|
||||
claim(USER_ID, SECOND_RUN);
|
||||
|
||||
boolean written = repository.replace(USER_ID, SECOND_RUN, SyncTrigger.MANUAL, 0,
|
||||
List.of(SyncChange.removed(PORTAL)));
|
||||
|
||||
assertThat(written).isTrue();
|
||||
SyncChanges result = stored();
|
||||
assertThat(result.syncedAt()).isEqualTo(SECOND_RUN);
|
||||
assertThat(result.startedBy()).isEqualTo(SyncTrigger.MANUAL);
|
||||
assertThat(result.seen()).isFalse();
|
||||
assertThat(result.changes()).extracting(e -> e.change()).containsExactly(SyncChange.removed(PORTAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replace_EmptyResultOverAnUnseenOne_KeepsTheUnseenResultAndItsRows() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of(SyncChange.added(HALF_LIFE)));
|
||||
claim(USER_ID, SECOND_RUN);
|
||||
|
||||
boolean written = repository.replace(USER_ID, SECOND_RUN, SyncTrigger.MANUAL, 0, List.of());
|
||||
|
||||
assertThat(written).isFalse();
|
||||
SyncChanges result = stored();
|
||||
assertThat(result.syncedAt()).isEqualTo(FIRST_RUN);
|
||||
assertThat(result.changes()).extracting(e -> e.change()).containsExactly(SyncChange.added(HALF_LIFE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replace_EmptyResultOverASeenOne_WritesAnEmptyUnseenResult() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of(SyncChange.added(HALF_LIFE)));
|
||||
markSeenNow();
|
||||
claim(USER_ID, SECOND_RUN);
|
||||
|
||||
boolean written = repository.replace(USER_ID, SECOND_RUN, SyncTrigger.MANUAL, 0, List.of());
|
||||
|
||||
assertThat(written).isTrue();
|
||||
SyncChanges result = stored();
|
||||
assertThat(result.syncedAt()).isEqualTo(SECOND_RUN);
|
||||
assertThat(result.seen()).isFalse();
|
||||
assertThat(result.changes()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyManualSyncReplacesAnUnseenEmptyAutoResult() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of());
|
||||
claim(USER_ID, SECOND_RUN);
|
||||
|
||||
repository.replace(USER_ID, SECOND_RUN, SyncTrigger.MANUAL, 0, List.of());
|
||||
|
||||
SyncChanges result = stored();
|
||||
assertThat(result.syncedAt()).isEqualTo(SECOND_RUN);
|
||||
assertThat(result.startedBy()).isEqualTo(SyncTrigger.MANUAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replace_NoResultYet_WritesAnEmptyUnseenResult() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
|
||||
assertThat(repository.replace(USER_ID, FIRST_RUN, SyncTrigger.MANUAL, 0, List.of())).isTrue();
|
||||
|
||||
assertThat(stored().seen()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replace_ImportOverAnUnseenResult_ReplacesIt() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of(SyncChange.added(HALF_LIFE)));
|
||||
claim(USER_ID, SECOND_RUN);
|
||||
|
||||
assertThat(repository.replace(USER_ID, SECOND_RUN, SyncTrigger.AUTO, 5, List.of())).isTrue();
|
||||
|
||||
SyncChanges result = stored();
|
||||
assertThat(result.importedGames()).isEqualTo(5);
|
||||
assertThat(result.changes()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replace_NonEmptyResultOverAnUnseenOne_ReplacesIt() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of(SyncChange.added(HALF_LIFE)));
|
||||
claim(USER_ID, SECOND_RUN);
|
||||
|
||||
assertThat(repository.replace(USER_ID, SECOND_RUN, SyncTrigger.AUTO, 0, List.of(SyncChange.removed(PORTAL)))).isTrue();
|
||||
|
||||
assertThat(stored().changes()).extracting(e -> e.change()).containsExactly(SyncChange.removed(PORTAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replace_AnotherRunHoldsTheClaim_WritesNothing() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of(SyncChange.added(HALF_LIFE)));
|
||||
claim(USER_ID, SECOND_RUN);
|
||||
|
||||
boolean written = repository.replace(USER_ID, FIRST_RUN.plusSeconds(1), SyncTrigger.MANUAL, 0,
|
||||
List.of(SyncChange.removed(PORTAL)));
|
||||
|
||||
assertThat(written).isFalse();
|
||||
assertThat(stored().syncedAt()).isEqualTo(FIRST_RUN);
|
||||
assertThat(rows()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findStatusMovesOfThisTransaction_ReturnsOnlyMovesOfTheSync() {
|
||||
// Same transaction as the seed: the inserts (from null) are in the log with this now() too.
|
||||
jdbc.update("update user_game set status = 'PLAYING' where user_id = ? and appid = ?", USER_ID, HALF_LIFE);
|
||||
jdbc.update("update user_game set status = 'DROPPED', status_set_by_user = true where user_id = ? and appid = ?",
|
||||
USER_ID, PORTAL);
|
||||
|
||||
assertThat(repository.findStatusMovesOfThisTransaction(USER_ID))
|
||||
.containsExactly(SyncChange.status(HALF_LIFE, GameStatus.BACKLOG, GameStatus.PLAYING));
|
||||
}
|
||||
|
||||
@Test
|
||||
void add_WithoutAResult_WritesNothing() {
|
||||
repository.add(USER_ID, SyncChange.achievements(HALF_LIFE, 2));
|
||||
|
||||
assertThat(rows()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void add_WithAResult_AppendsTheRow() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of());
|
||||
|
||||
repository.add(USER_ID, SyncChange.status(HALF_LIFE, GameStatus.PLAYING, GameStatus.FINISHED));
|
||||
|
||||
assertThat(stored().changes()).extracting(e -> e.change())
|
||||
.containsExactly(SyncChange.status(HALF_LIFE, GameStatus.PLAYING, GameStatus.FINISHED));
|
||||
}
|
||||
|
||||
@Test
|
||||
void find_HidesAHiddenGameKeepsARemovedOneAndSumsTheUnlocks() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of(
|
||||
SyncChange.removed(REMOVED), SyncChange.added(HIDDEN), SyncChange.playtime(PORTAL, 30)));
|
||||
repository.add(USER_ID, SyncChange.achievements(HALF_LIFE, 2));
|
||||
repository.add(USER_ID, SyncChange.achievements(HALF_LIFE, 3));
|
||||
|
||||
List<SyncChanges.Entry> entries = stored().changes();
|
||||
|
||||
assertThat(entries).extracting(e -> e.change()).containsExactlyInAnyOrder(
|
||||
SyncChange.removed(REMOVED), SyncChange.playtime(PORTAL, 30), SyncChange.achievements(HALF_LIFE, 5));
|
||||
assertThat(entries).filteredOn(e -> e.change().kind() == SyncChangeKind.REMOVED)
|
||||
.extracting(SyncChanges.Entry::name).containsExactly("Refunded");
|
||||
}
|
||||
|
||||
@Test
|
||||
void find_NoResult_IsEmpty() {
|
||||
assertThat(repository.find(OTHER_USER)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void markSeen_ForTheResult_SetsSeen() {
|
||||
claim(USER_ID, FIRST_RUN);
|
||||
repository.replace(USER_ID, FIRST_RUN, SyncTrigger.AUTO, 0, List.of());
|
||||
|
||||
assertThat(repository.markSeen(USER_ID, FIRST_RUN)).isEqualTo(1);
|
||||
|
||||
assertThat(stored().seen()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void markSeen_WithAnOldSyncedAt_ChangesNothing() {
|
||||
claim(USER_ID, SECOND_RUN);
|
||||
repository.replace(USER_ID, SECOND_RUN, SyncTrigger.AUTO, 0, List.of());
|
||||
|
||||
assertThat(repository.markSeen(USER_ID, FIRST_RUN)).isZero();
|
||||
|
||||
assertThat(stored().seen()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,14 @@ package core_service.modules.library.service;
|
||||
|
||||
import core_service.modules.library.domain.GameAchievementSchema;
|
||||
import core_service.modules.library.domain.GameAchievementSchemaMeta;
|
||||
import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.SyncChange;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.domain.UserGameAchievement;
|
||||
import core_service.modules.library.domain.UserGameAchievementMeta;
|
||||
import core_service.modules.library.repo.GameAchievementSchemaMetaRepository;
|
||||
import core_service.modules.library.repo.GameAchievementSchemaRepository;
|
||||
import core_service.modules.library.repo.SyncChangeRepository;
|
||||
import core_service.modules.library.repo.UserGameAchievementMetaRepository;
|
||||
import core_service.modules.library.repo.UserGameAchievementRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
@@ -66,13 +69,15 @@ class AchievementSyncServiceTest extends AbstractUnitTest {
|
||||
private UserGameAchievementMetaRepository userAchievementMetaRepository;
|
||||
@Mock
|
||||
private SteamApiService steamApiService;
|
||||
@Mock
|
||||
private SyncChangeRepository syncChangeRepository;
|
||||
|
||||
private AchievementSyncService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new AchievementSyncService(userGameRepository, schemaRepository, schemaMetaRepository,
|
||||
userAchievementRepository, userAchievementMetaRepository, steamApiService,
|
||||
userAchievementRepository, userAchievementMetaRepository, steamApiService, syncChangeRepository,
|
||||
mock(PlatformTransactionManager.class), 30, 0);
|
||||
}
|
||||
|
||||
@@ -375,7 +380,7 @@ class AchievementSyncServiceTest extends AbstractUnitTest {
|
||||
void syncLibrary_TwoUsersInParallel_ShareOneSteamBudget() throws Exception {
|
||||
long pauseMillis = 300;
|
||||
AchievementSyncService paced = new AchievementSyncService(userGameRepository, schemaRepository, schemaMetaRepository,
|
||||
userAchievementRepository, userAchievementMetaRepository, steamApiService,
|
||||
userAchievementRepository, userAchievementMetaRepository, steamApiService, syncChangeRepository,
|
||||
mock(PlatformTransactionManager.class), 30, pauseMillis);
|
||||
when(userGameRepository.findListedSteamGames(1L)).thenReturn(List.of(new UserGame(1L, 620L, 60, Instant.now(), Instant.now())));
|
||||
when(userGameRepository.findListedSteamGames(2L)).thenReturn(List.of(new UserGame(2L, 620L, 60, Instant.now(), Instant.now())));
|
||||
@@ -415,4 +420,79 @@ class AchievementSyncServiceTest extends AbstractUnitTest {
|
||||
|
||||
verifyNoInteractions(steamApiService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void syncLibrary_MoreUnlocksThanTheEarlierVisibleRefresh_AddsTheDifference() {
|
||||
library(List.of(game(620L, 150)), List.of(schema(620L, 10)), List.of(unlocks(620L, 3, 120)));
|
||||
when(steamApiService.getPlayerAchievements(STEAM_ID, 620L)).thenReturn(visible("A", "B", "C", "D", "E"));
|
||||
|
||||
service.syncLibrary(USER_ID, STEAM_ID);
|
||||
|
||||
verify(syncChangeRepository).add(USER_ID, SyncChange.achievements(620L, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void syncLibrary_FirstRefreshOfAGame_AddsNoUnlockRow() {
|
||||
library(List.of(game(620L, 150)), List.of(schema(620L, 10)), List.of());
|
||||
when(steamApiService.getPlayerAchievements(STEAM_ID, 620L)).thenReturn(visible("A", "B", "C", "D", "E"));
|
||||
|
||||
service.syncLibrary(USER_ID, STEAM_ID);
|
||||
|
||||
verify(syncChangeRepository, never()).add(anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void syncLibrary_EarlierRefreshWasHidden_AddsNoUnlockRow() {
|
||||
UserGameAchievementMeta hidden = new UserGameAchievementMeta(USER_ID, 620L, Instant.now(), false, 0, null);
|
||||
library(List.of(game(620L, 150)), List.of(schema(620L, 10)), List.of(hidden));
|
||||
when(steamApiService.getPlayerAchievements(STEAM_ID, 620L)).thenReturn(visible("A", "B"));
|
||||
|
||||
service.syncLibrary(USER_ID, STEAM_ID);
|
||||
|
||||
verify(syncChangeRepository, never()).add(anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void syncLibrary_SameUnlockCount_AddsNoUnlockRow() {
|
||||
library(List.of(game(620L, 150)), List.of(schema(620L, 10)), List.of(unlocks(620L, 2, 120)));
|
||||
when(steamApiService.getPlayerAchievements(STEAM_ID, 620L)).thenReturn(visible("A", "B"));
|
||||
|
||||
service.syncLibrary(USER_ID, STEAM_ID);
|
||||
|
||||
verify(syncChangeRepository, never()).add(anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void syncLibrary_EndingUnlocked_AddsAFinishedMove() {
|
||||
library(List.of(game(620L, 120)), List.of(schema(620L, 1)), List.of(unlocks(620L, 1, 120)));
|
||||
endingOf620(true);
|
||||
when(userGameRepository.finishIfUntouched(USER_ID, 620L)).thenReturn(1);
|
||||
|
||||
service.syncLibrary(USER_ID, STEAM_ID);
|
||||
|
||||
verify(syncChangeRepository).add(USER_ID, SyncChange.status(620L, GameStatus.PLAYING, GameStatus.FINISHED));
|
||||
}
|
||||
|
||||
@Test
|
||||
void syncLibrary_EndingAlreadyHandled_AddsNoMove() {
|
||||
library(List.of(game(620L, 120)), List.of(schema(620L, 1)), List.of(unlocks(620L, 1, 120)));
|
||||
endingOf620(true);
|
||||
when(userGameRepository.finishIfUntouched(USER_ID, 620L)).thenReturn(0);
|
||||
|
||||
service.syncLibrary(USER_ID, STEAM_ID);
|
||||
|
||||
verify(syncChangeRepository, never()).add(anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshUserIfStale_AddsNoSyncRow() {
|
||||
when(userAchievementMetaRepository.findByUserIdAndAppid(USER_ID, 620L))
|
||||
.thenReturn(Optional.of(new UserGameAchievementMeta(USER_ID, 620L, Instant.now().minus(Duration.ofDays(2)), true, 1, 60)));
|
||||
when(steamApiService.getPlayerAchievements(STEAM_ID, 620L)).thenReturn(visible("A", "B", "C"));
|
||||
|
||||
service.refreshUserIfStale(USER_ID, STEAM_ID, 620L, 90, Duration.ofHours(1));
|
||||
|
||||
verify(userAchievementMetaRepository).save(any());
|
||||
verifyNoInteractions(syncChangeRepository);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package core_service.modules.library.service;
|
||||
|
||||
import core_service.modules.library.domain.SyncTrigger;
|
||||
import core_service.modules.library.domain.UserSteamSync;
|
||||
import core_service.modules.library.repo.BacklogSnapshotRepository;
|
||||
import core_service.modules.library.repo.UserSteamSyncRepository;
|
||||
@@ -73,7 +74,7 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
|
||||
syncService.syncIfStale(USER_ID, STEAM_ID);
|
||||
|
||||
verify(worker).runSync(USER_ID, STEAM_ID);
|
||||
verify(worker).runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -82,7 +83,7 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
|
||||
syncService.syncIfStale(USER_ID, STEAM_ID);
|
||||
|
||||
verify(worker, never()).runSync(anyLong(), anyString());
|
||||
verify(worker, never()).runSync(anyLong(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -91,7 +92,7 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
|
||||
syncService.syncIfStale(USER_ID, STEAM_ID);
|
||||
|
||||
verify(worker).runSync(USER_ID, STEAM_ID);
|
||||
verify(worker).runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -100,7 +101,7 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
|
||||
syncService.syncIfStale(USER_ID, STEAM_ID);
|
||||
|
||||
verify(worker).runSync(USER_ID, STEAM_ID);
|
||||
verify(worker).runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,7 +110,7 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
|
||||
syncService.syncIfStale(USER_ID, STEAM_ID);
|
||||
|
||||
verify(worker, never()).runSync(anyLong(), anyString());
|
||||
verify(worker, never()).runSync(anyLong(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,7 +119,7 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
|
||||
syncService.syncIfStale(USER_ID, STEAM_ID);
|
||||
|
||||
verify(worker, never()).runSync(anyLong(), anyString());
|
||||
verify(worker, never()).runSync(anyLong(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -130,7 +131,7 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
syncService.syncAllNightly();
|
||||
|
||||
InOrder order = inOrder(worker, backlogSnapshotRepository, finishedEstimateService);
|
||||
order.verify(worker).runSyncNow(1L, "76561198000000001");
|
||||
order.verify(worker).runSyncNow(1L, "76561198000000001", SyncTrigger.AUTO);
|
||||
order.verify(backlogSnapshotRepository).writeAll(today);
|
||||
order.verify(finishedEstimateService).fillMissing();
|
||||
}
|
||||
@@ -149,7 +150,7 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
void syncAllNightly_ASyncThrows_TheSnapshotIsStillWritten() {
|
||||
when(userSteamSyncRepository.findByUserSeenAtAfter(any())).thenReturn(List.of(
|
||||
new UserSteamSync(1L, "76561198000000001")));
|
||||
doThrow(new IllegalStateException("db down")).when(worker).runSyncNow(anyLong(), anyString());
|
||||
doThrow(new IllegalStateException("db down")).when(worker).runSyncNow(anyLong(), anyString(), any());
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> syncService.syncAllNightly());
|
||||
|
||||
@@ -164,9 +165,9 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
|
||||
syncService.syncAllNightly();
|
||||
|
||||
verify(worker).runSyncNow(1L, "76561198000000001");
|
||||
verify(worker).runSyncNow(2L, "76561198005679790");
|
||||
verify(worker, never()).runSync(anyLong(), anyString());
|
||||
verify(worker).runSyncNow(1L, "76561198000000001", SyncTrigger.AUTO);
|
||||
verify(worker).runSyncNow(2L, "76561198005679790", SyncTrigger.AUTO);
|
||||
verify(worker, never()).runSync(anyLong(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -186,4 +187,13 @@ class LibrarySyncServiceTest extends AbstractUnitTest {
|
||||
|
||||
verify(userSteamSyncRepository, times(1)).touchUserSeen(eq(USER_ID), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceSync_StartsAManualSync() {
|
||||
when(userSteamSyncRepository.findById(USER_ID)).thenReturn(Optional.empty());
|
||||
|
||||
syncService.forceSync(USER_ID, STEAM_ID);
|
||||
|
||||
verify(worker).runSync(USER_ID, STEAM_ID, SyncTrigger.MANUAL);
|
||||
}
|
||||
}
|
||||
+7
-6
@@ -1,6 +1,7 @@
|
||||
package core_service.modules.library.service;
|
||||
|
||||
import core_service.modules.library.domain.UserSteamSync;
|
||||
import core_service.modules.library.domain.SyncTrigger;
|
||||
import core_service.modules.library.repo.GameSpeedrunRepository;
|
||||
import core_service.modules.speedrun_adapter.service.SpeedrunApiService;
|
||||
import core_service.modules.library.repo.UserSteamSyncRepository;
|
||||
@@ -66,7 +67,7 @@ class LibrarySyncWorkerConcurrencyTest extends AbstractMockedAdaptersTest {
|
||||
new SpeedrunApiService.Game("hl2", "Half-Life 2", 2004, "https://www.speedrun.com/hl2"))));
|
||||
when(speedrunApiService.hasVerifiedRuns("hl2")).thenReturn(Optional.of(true));
|
||||
|
||||
worker.runSyncNow(USER_ID, STEAM_ID);
|
||||
worker.runSyncNow(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(speedrunApiService).hasVerifiedRuns("hl2");
|
||||
assertThat(gameSpeedrunRepository.findById(APPID)).isPresent();
|
||||
@@ -86,12 +87,12 @@ class LibrarySyncWorkerConcurrencyTest extends AbstractMockedAdaptersTest {
|
||||
return List.of(game("icon"));
|
||||
});
|
||||
|
||||
CompletableFuture<Void> first = CompletableFuture.runAsync(() -> worker.runSyncNow(USER_ID, STEAM_ID));
|
||||
CompletableFuture<Void> first = CompletableFuture.runAsync(() -> worker.runSyncNow(USER_ID, STEAM_ID, SyncTrigger.AUTO));
|
||||
assertThat(steamAsked.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(userSteamSyncRepository.findById(USER_ID).orElseThrow().getLastSyncStatus())
|
||||
.isEqualTo(UserSteamSync.Status.RUNNING);
|
||||
CompletableFuture<Void> second = CompletableFuture.runAsync(() -> {
|
||||
worker.runSyncNow(USER_ID, STEAM_ID);
|
||||
worker.runSyncNow(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
secondDone.countDown();
|
||||
});
|
||||
CompletableFuture.allOf(first, second).get(30, TimeUnit.SECONDS);
|
||||
@@ -107,7 +108,7 @@ class LibrarySyncWorkerConcurrencyTest extends AbstractMockedAdaptersTest {
|
||||
// img_icon_url is VARCHAR(512).
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(game("x".repeat(600))));
|
||||
|
||||
worker.runSyncNow(USER_ID, STEAM_ID);
|
||||
worker.runSyncNow(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
UserSteamSync row = userSteamSyncRepository.findById(USER_ID).orElseThrow();
|
||||
assertThat(row.getLastSyncStatus()).isEqualTo(UserSteamSync.Status.FAILED);
|
||||
@@ -134,12 +135,12 @@ class LibrarySyncWorkerConcurrencyTest extends AbstractMockedAdaptersTest {
|
||||
return List.of(game("icon"));
|
||||
});
|
||||
|
||||
CompletableFuture<Void> first = CompletableFuture.runAsync(() -> worker.runSyncNow(USER_ID, STEAM_ID));
|
||||
CompletableFuture<Void> first = CompletableFuture.runAsync(() -> worker.runSyncNow(USER_ID, STEAM_ID, SyncTrigger.AUTO));
|
||||
assertThat(firstAsked.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
// The first run hangs past running-stale-minutes, so the next sync takes over.
|
||||
jdbcTemplate.update("UPDATE user_steam_sync SET sync_started_at = NOW() - INTERVAL '10 minutes' WHERE user_id = ?",
|
||||
USER_ID);
|
||||
CompletableFuture<Void> second = CompletableFuture.runAsync(() -> worker.runSyncNow(USER_ID, STEAM_ID));
|
||||
CompletableFuture<Void> second = CompletableFuture.runAsync(() -> worker.runSyncNow(USER_ID, STEAM_ID, SyncTrigger.AUTO));
|
||||
assertThat(secondAsked.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
releaseFirst.countDown();
|
||||
first.get(30, TimeUnit.SECONDS);
|
||||
|
||||
@@ -4,12 +4,15 @@ import core_service.modules.library.domain.CorrectionField;
|
||||
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.SyncChange;
|
||||
import core_service.modules.library.domain.SyncTrigger;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.domain.UserSteamSync;
|
||||
import core_service.modules.library.domain.WishlistGame;
|
||||
import core_service.modules.library.repo.GameCorrectionRepository;
|
||||
import core_service.modules.library.repo.PlaytimeDayRepository;
|
||||
import core_service.modules.library.repo.SteamGameRepository;
|
||||
import core_service.modules.library.repo.SyncChangeRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
import core_service.modules.library.repo.UserSteamSyncRepository;
|
||||
import core_service.modules.library.repo.WishlistGameRepository;
|
||||
@@ -108,6 +111,9 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
@Mock
|
||||
private UserSteamSyncRepository userSteamSyncRepository;
|
||||
|
||||
@Mock
|
||||
private SyncChangeRepository syncChangeRepository;
|
||||
|
||||
@Mock
|
||||
private PlaytimeDayRepository playtimeDayRepository;
|
||||
|
||||
@@ -143,7 +149,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
steamReturns(game(100, 0), null);
|
||||
when(userSteamSyncRepository.markFinished(eq(USER_ID), any(), eq("OK"), any())).thenReturn(0);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verifyNoInteractions(eventPublisher);
|
||||
}
|
||||
@@ -153,7 +159,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of());
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(userGameRepository, never()).softDeleteMissingSince(anyLong(), any(), any());
|
||||
verify(userSteamSyncRepository).markFinished(eq(USER_ID), any(), eq("FAILED"), any());
|
||||
@@ -167,7 +173,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of());
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(userGameRepository).softDeleteMissingSince(eq(USER_ID), any(), any());
|
||||
verify(userSteamSyncRepository).markFinished(eq(USER_ID), any(), eq("FAILED"), any());
|
||||
@@ -180,7 +186,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenThrow(new IllegalStateException("Steam down"));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(userGameRepository).softDeleteMissingSince(eq(USER_ID), any(), any());
|
||||
verify(userSteamSyncRepository).markFinished(eq(USER_ID), any(), eq("FAILED"), any());
|
||||
@@ -192,7 +198,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(
|
||||
game(0, 0)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(userGameRepository).softDeleteMissingSince(eq(USER_ID), any(), any());
|
||||
verify(userSteamSyncRepository).markFinished(eq(USER_ID), any(), eq("OK"), any());
|
||||
@@ -206,7 +212,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(
|
||||
game(playtime, 0)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
ArgumentCaptor<UserGame> saved = ArgumentCaptor.forClass(UserGame.class);
|
||||
verify(userGameRepository).save(saved.capture());
|
||||
@@ -221,7 +227,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(userGameRepository.findByUserId(USER_ID)).thenReturn(List.of(manual));
|
||||
steamReturns(game(90, 30), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
assertEquals(GameSource.STEAM, manual.getSource());
|
||||
assertNull(manual.getPlatform());
|
||||
@@ -237,7 +243,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(userGameRepository.findByUserId(USER_ID)).thenReturn(List.of(manual));
|
||||
steamReturns(game(90, 30), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository, never()).addMinutes(anyLong(), anyLong(), any(), anyInt());
|
||||
}
|
||||
@@ -251,7 +257,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(
|
||||
game(90, 0)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
assertEquals(GameStatus.DROPPED, existing.getStatus());
|
||||
assertEquals(90, existing.getPlaytimeMinutes());
|
||||
@@ -263,7 +269,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(game(600, 120), game(600, 120)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(userGameRepository).save(any(UserGame.class));
|
||||
verify(playtimeDayRepository).addMinutes(USER_ID, 220L, today(), 120);
|
||||
@@ -274,7 +280,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
owned(100);
|
||||
steamReturns(game(160, 60), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository).addMinutes(USER_ID, 220L, today(), 60);
|
||||
}
|
||||
@@ -284,7 +290,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
UserGame existing = owned(0);
|
||||
steamReturns(game(30, 30), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(userGameRepository).playFromPileIfUntouched(USER_ID, List.of(220L));
|
||||
// Never through the entity: a status the user saves during the sync would be overwritten.
|
||||
@@ -296,7 +302,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
owned(100);
|
||||
steamReturns(game(100, 0), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(userGameRepository, never()).playFromPileIfUntouched(anyLong(), any());
|
||||
}
|
||||
@@ -306,7 +312,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
owned(0);
|
||||
steamReturns(game(5000, 0), null);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(userGameRepository, never()).playFromPileIfUntouched(anyLong(), any());
|
||||
}
|
||||
@@ -316,7 +322,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
owned(100);
|
||||
steamReturns(game(90, 0), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository, never()).addMinutes(anyLong(), anyLong(), any(), anyInt());
|
||||
}
|
||||
@@ -325,7 +331,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
void runSync_NewGameAfterEarlierSyncs_RecordsItsWholePlaytime() {
|
||||
steamReturns(game(45, 45), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository).addMinutes(USER_ID, 220L, today(), 45);
|
||||
}
|
||||
@@ -334,7 +340,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
void runSync_NewGameWithOldPlaytime_RecordsOnlyTheLastTwoWeeks() {
|
||||
steamReturns(game(2378, 30), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository).addMinutes(USER_ID, 220L, today(), 30);
|
||||
}
|
||||
@@ -343,7 +349,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
void runSync_NewGameNotPlayedForTwoWeeks_RecordsNothingAndLeavesThePileAlone() {
|
||||
steamReturns(game(2378, 0), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository, never()).addMinutes(anyLong(), anyLong(), any(), anyInt());
|
||||
verify(userGameRepository, never()).playFromPileIfUntouched(anyLong(), any());
|
||||
@@ -354,7 +360,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
owned(100);
|
||||
steamReturns(game(1000, 60), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository).addMinutes(USER_ID, 220L, today(), 60);
|
||||
}
|
||||
@@ -364,7 +370,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
owned(100);
|
||||
steamReturns(game(160, 0), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository, never()).addMinutes(anyLong(), anyLong(), any(), anyInt());
|
||||
verify(userGameRepository).playFromPileIfUntouched(USER_ID, List.of(220L));
|
||||
@@ -374,7 +380,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
void runSync_FirstSync_IsOnlyTheBaseline() {
|
||||
steamReturns(game(5000, 60), null);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository, never()).addMinutes(anyLong(), anyLong(), any(), anyInt());
|
||||
}
|
||||
@@ -384,7 +390,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
owned(100);
|
||||
steamReturns(game(5000, 60), OTHER_STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository, never()).addMinutes(anyLong(), anyLong(), any(), anyInt());
|
||||
}
|
||||
@@ -397,8 +403,8 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(), List.of(game(5000, 60)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(playtimeDayRepository, never()).addMinutes(anyLong(), anyLong(), any(), anyInt());
|
||||
verify(userSteamSyncRepository).markHistoryBaseline(USER_ID, STEAM_ID);
|
||||
@@ -409,7 +415,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
steamReturns(game(0, 0), STEAM_ID);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of());
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(userSteamSyncRepository, never()).markHistoryBaseline(anyLong(), any());
|
||||
}
|
||||
@@ -419,7 +425,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
UserGame existing = owned(100);
|
||||
steamReturns(game(160, 75), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
assertEquals(75, existing.getPlaytime2Weeks());
|
||||
}
|
||||
@@ -435,7 +441,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(steamApiService.getStoreItems(List.of(1257610L))).thenReturn(Map.of(
|
||||
1257610L, new SteamApiService.StoreItem("Prison Architect 2", "7,49€", "29,99€", 75)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
ArgumentCaptor<List<WishlistGame>> saved = ArgumentCaptor.forClass(List.class);
|
||||
InOrder order = inOrder(wishlistGameRepository);
|
||||
@@ -455,7 +461,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
steamReturns(game(0, 0), STEAM_ID);
|
||||
when(steamApiService.getWishlist(STEAM_ID)).thenReturn(Optional.empty());
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(wishlistGameRepository, never()).deleteByUserId(anyLong());
|
||||
verify(userSteamSyncRepository).markFinished(eq(USER_ID), any(), eq("OK"), any());
|
||||
@@ -467,7 +473,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of());
|
||||
when(steamApiService.getWishlist(STEAM_ID)).thenReturn(Optional.of(List.of(new SteamApiService.WishlistItem(1257610L, null))));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(wishlistGameRepository).saveAll(anyList());
|
||||
verify(userSteamSyncRepository).markFinished(eq(USER_ID), any(), eq("FAILED"), any());
|
||||
@@ -482,7 +488,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(wishlistGameRepository.findForUser(USER_ID)).thenReturn(List.of(
|
||||
new WishlistGame(USER_ID, 1257610L, "Prison Architect 2", null, "29,99€", null, 0)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
ArgumentCaptor<List<WishlistGame>> saved = ArgumentCaptor.forClass(List.class);
|
||||
verify(wishlistGameRepository).saveAll(saved.capture());
|
||||
@@ -495,7 +501,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
steamReturns(game(0, 0), OTHER_STEAM_ID);
|
||||
when(steamApiService.getWishlist(STEAM_ID)).thenReturn(Optional.empty());
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(wishlistGameRepository).deleteByUserId(USER_ID);
|
||||
}
|
||||
@@ -506,7 +512,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of());
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(wishlistGameRepository).deleteByUserId(USER_ID);
|
||||
}
|
||||
@@ -515,7 +521,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
void runSync_RefreshesTheTimeToBeatOfTheOwnedGames() {
|
||||
steamReturns(game(0, 0), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(timeToBeatService).refresh(List.of(220L));
|
||||
}
|
||||
@@ -525,7 +531,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
steamReturns(game(0, 0), STEAM_ID);
|
||||
when(userGameRepository.findActiveManualAppids(USER_ID)).thenReturn(List.of(-1025L));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(timeToBeatService).refresh(List.of(220L, -1025L));
|
||||
verify(moodService).refresh(List.of(220L, -1025L));
|
||||
@@ -539,7 +545,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
hidden.setHiddenAt(hiddenAt);
|
||||
steamReturns(game(90, 30), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
assertEquals(hiddenAt, hidden.getHiddenAt());
|
||||
assertEquals(90, hidden.getPlaytimeMinutes());
|
||||
@@ -551,7 +557,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
steamReturns(game(0, 0), STEAM_ID);
|
||||
when(userGameRepository.findHiddenAppids(USER_ID)).thenReturn(List.of(220L));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(timeToBeatService).refresh(List.of());
|
||||
verify(moodService).refresh(List.of());
|
||||
@@ -564,7 +570,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
new SteamOwnedGamesResponse.Game(2000001, null, 12, null, null, null),
|
||||
game(90, 0)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(steamGameRepository).save(argThat((SteamGame g) -> g.getAppid() == 2000001L && "App 2000001".equals(g.getName())));
|
||||
verify(steamGameRepository).save(argThat((SteamGame g) -> g.getAppid() == 220L));
|
||||
@@ -578,7 +584,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(
|
||||
new SteamOwnedGamesResponse.Game(2000001, " ", 12, null, null, null)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
assertThat(stored.getName()).isEqualTo("El Mucho");
|
||||
}
|
||||
@@ -589,7 +595,7 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(steamGameRepository.findAllById(List.of(220L))).thenReturn(List.of(stored));
|
||||
steamReturns(game(10, 0), null);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
// The update keeps an auditor's name (SteamGameRepositoryTest); the entity itself is not renamed.
|
||||
verify(steamGameRepository).setSourceName(220L, "Half-Life 2");
|
||||
@@ -603,8 +609,103 @@ class LibrarySyncWorkerTest extends AbstractUnitTest {
|
||||
when(gameCorrectionRepository.findCorrectedAppids(eq(CorrectionField.NAME), any())).thenReturn(Set.of(220L));
|
||||
steamReturns(game(10, 0), null);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID);
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
verify(steamGameRepository, never()).setSourceName(anyLong(), anyString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<SyncChange> replacedChanges(SyncTrigger trigger, int imported) {
|
||||
ArgumentCaptor<List<SyncChange>> changes = ArgumentCaptor.forClass(List.class);
|
||||
verify(syncChangeRepository).replace(eq(USER_ID), any(), eq(trigger), eq(imported), changes.capture());
|
||||
return changes.getValue();
|
||||
}
|
||||
|
||||
private static SteamOwnedGamesResponse.Game gameOf(int appid, int playtime) {
|
||||
return new SteamOwnedGamesResponse.Game(appid, "Game " + appid, playtime, "icon", true, playtime);
|
||||
}
|
||||
|
||||
@Test
|
||||
void runSync_AfterEarlierSync_RecordsAddedRemovedAndPlaytimeWithTheTrigger() {
|
||||
UserGame grown = new UserGame(USER_ID, 220L, 100, Instant.EPOCH, Instant.EPOCH);
|
||||
UserGame gone = new UserGame(USER_ID, 300L, 10, Instant.EPOCH, Instant.EPOCH);
|
||||
UserGame alreadyRemoved = new UserGame(USER_ID, 400L, 10, Instant.EPOCH, Instant.EPOCH);
|
||||
alreadyRemoved.setRemovedAt(Instant.EPOCH);
|
||||
UserGame byHand = UserGame.manual(USER_ID, 500L, "PS5", GameStatus.PLAYING, 60, Instant.EPOCH);
|
||||
when(userGameRepository.findByUserId(USER_ID)).thenReturn(List.of(grown, gone, alreadyRemoved, byHand));
|
||||
when(userSteamSyncRepository.findForClaim(USER_ID)).thenReturn(Optional.of(syncRow(STEAM_ID, STEAM_ID)));
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(gameOf(220, 160), gameOf(600, 5)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.MANUAL);
|
||||
|
||||
assertThat(replacedChanges(SyncTrigger.MANUAL, 0)).containsExactlyInAnyOrder(
|
||||
SyncChange.removed(300L), SyncChange.added(600L), SyncChange.playtime(220L, 60));
|
||||
}
|
||||
|
||||
@Test
|
||||
void runSync_StatusMovesOfTheSave_AreRecorded() {
|
||||
owned(100);
|
||||
steamReturns(game(160, 60), STEAM_ID);
|
||||
when(syncChangeRepository.findStatusMovesOfThisTransaction(USER_ID))
|
||||
.thenReturn(List.of(SyncChange.status(220L, GameStatus.BACKLOG, GameStatus.PLAYING)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
assertThat(replacedChanges(SyncTrigger.AUTO, 0)).containsExactlyInAnyOrder(
|
||||
SyncChange.playtime(220L, 60), SyncChange.status(220L, GameStatus.BACKLOG, GameStatus.PLAYING));
|
||||
}
|
||||
|
||||
@Test
|
||||
void runSync_ReAddedGame_IsAddedAgain() {
|
||||
UserGame removed = new UserGame(USER_ID, 220L, 100, Instant.EPOCH, Instant.EPOCH);
|
||||
removed.setRemovedAt(Instant.EPOCH);
|
||||
when(userGameRepository.findByUserId(USER_ID)).thenReturn(List.of(removed));
|
||||
steamReturns(game(100, 0), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
assertThat(replacedChanges(SyncTrigger.AUTO, 0)).containsExactly(SyncChange.added(220L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void runSync_FirstSync_StoresOnlyTheCountOfImportedGames() {
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of(gameOf(220, 100), gameOf(300, 0)));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
assertThat(replacedChanges(SyncTrigger.AUTO, 2)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void runSync_GameAddedByHandIsNowOnSteam_IsNeitherAddedNorPlaytime() {
|
||||
UserGame manual = UserGame.manual(USER_ID, 220L, "PS5", GameStatus.PLAYING, 60, Instant.EPOCH);
|
||||
when(userGameRepository.findByUserId(USER_ID)).thenReturn(List.of(manual));
|
||||
steamReturns(game(90, 30), STEAM_ID);
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.AUTO);
|
||||
|
||||
assertThat(replacedChanges(SyncTrigger.AUTO, 0)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void runSync_SteamAnswersNoGames_KeepsTheOldResult() {
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenReturn(List.of());
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.MANUAL);
|
||||
|
||||
verifyNoInteractions(syncChangeRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void runSync_SteamFails_KeepsTheOldResult() {
|
||||
when(userSteamSyncRepository.tryClaimRun(eq(USER_ID), eq(STEAM_ID), any(), any())).thenReturn(1);
|
||||
when(steamApiService.getOwnedGames(STEAM_ID)).thenThrow(new IllegalStateException("Steam down"));
|
||||
|
||||
worker.runSync(USER_ID, STEAM_ID, SyncTrigger.MANUAL);
|
||||
|
||||
verifyNoInteractions(syncChangeRepository);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user