Public Access
feat(library): summary endpoint with status counts and a backlog pick
GET /api/library/summary returns the game count per status, the never-started count and a random backlog game. exclude=<appid> makes a new pick differ from the current one.
This commit is contained in:
1 parent
474a5dd570
commit
0482fb0c88
7 files changed
+216
-11
No files matched your search
@@ -0,0 +1,100 @@
|
||||
package core_service.modules.library.api;
|
||||
|
||||
import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.repo.SteamGameRepository;
|
||||
import core_service.modules.library.repo.UserGameRepository;
|
||||
import core_service.shared.test.AbstractIntegrationTest;
|
||||
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.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* The dev profile authenticates every request as user 1.
|
||||
*/
|
||||
@AutoConfigureMockMvc
|
||||
class LibraryControllerSummaryTest extends AbstractIntegrationTest {
|
||||
|
||||
private static final long USER_ID = 1L;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private SteamGameRepository steamGameRepository;
|
||||
|
||||
@Autowired
|
||||
private UserGameRepository userGameRepository;
|
||||
|
||||
@BeforeEach
|
||||
void clean() {
|
||||
userGameRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void summary_EmptyLibrary_HasAllStatusesWithZeroAndNoSuggestion() throws Exception {
|
||||
mockMvc.perform(get("/api/library/summary"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.total").value(0))
|
||||
.andExpect(jsonPath("$.neverPlayed").value(0))
|
||||
.andExpect(jsonPath("$.byStatus.BACKLOG").value(0))
|
||||
.andExpect(jsonPath("$.byStatus.DROPPED").value(0))
|
||||
.andExpect(jsonPath("$.suggestion").value(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void summary_CountsStatusesAndSuggestsABacklogGame() throws Exception {
|
||||
steamGameRepository.saveAll(List.of(
|
||||
new SteamGame(220L, "Half-Life 2", "icon", Instant.now()),
|
||||
new SteamGame(620L, "Portal 2", null, Instant.now())));
|
||||
UserGame finished = new UserGame(USER_ID, 620L, 600, Instant.now(), Instant.now());
|
||||
finished.setStatus(GameStatus.FINISHED);
|
||||
userGameRepository.saveAll(List.of(new UserGame(USER_ID, 220L, 0, Instant.now(), Instant.now()), finished));
|
||||
|
||||
mockMvc.perform(get("/api/library/summary"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.total").value(2))
|
||||
.andExpect(jsonPath("$.neverPlayed").value(1))
|
||||
.andExpect(jsonPath("$.byStatus.BACKLOG").value(1))
|
||||
.andExpect(jsonPath("$.byStatus.FINISHED").value(1))
|
||||
.andExpect(jsonPath("$.byStatus.PLAYING").value(0))
|
||||
.andExpect(jsonPath("$.suggestion.appid").value(220))
|
||||
.andExpect(jsonPath("$.suggestion.name").value("Half-Life 2"))
|
||||
.andExpect(jsonPath("$.suggestion.status").value("BACKLOG"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void summary_Exclude_SuggestsAnotherBacklogGame() throws Exception {
|
||||
steamGameRepository.saveAll(List.of(
|
||||
new SteamGame(220L, "Half-Life 2", null, Instant.now()),
|
||||
new SteamGame(620L, "Portal 2", null, Instant.now())));
|
||||
userGameRepository.saveAll(List.of(
|
||||
new UserGame(USER_ID, 220L, 0, Instant.now(), Instant.now()),
|
||||
new UserGame(USER_ID, 620L, 0, Instant.now(), Instant.now())));
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
mockMvc.perform(get("/api/library/summary").param("exclude", "220"))
|
||||
.andExpect(jsonPath("$.suggestion.appid").value(620));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void summary_ExcludeTheOnlyBacklogGame_HasNoSuggestion() throws Exception {
|
||||
steamGameRepository.save(new SteamGame(220L, "Half-Life 2", null, Instant.now()));
|
||||
userGameRepository.save(new UserGame(USER_ID, 220L, 0, Instant.now(), Instant.now()));
|
||||
|
||||
mockMvc.perform(get("/api/library/summary").param("exclude", "220"))
|
||||
.andExpect(jsonPath("$.suggestion").value(nullValue()));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package core_service.modules.library.repo;
|
||||
|
||||
import core_service.modules.library.domain.GameStatus;
|
||||
import core_service.modules.library.domain.StatusCount;
|
||||
import core_service.modules.library.domain.SteamGame;
|
||||
import core_service.modules.library.domain.UserGame;
|
||||
import core_service.modules.library.domain.UserGameId;
|
||||
@@ -18,9 +19,11 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
|
||||
@@ -98,6 +101,47 @@ class UserGameRepositoryTest extends AbstractPersistenceTest {
|
||||
assertNull(UserGameRepository.containsPattern(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void countByStatus_CountsOnlyOwnActiveGames() {
|
||||
UserGame dropped = userGameRepository.findById(new UserGameId(USER_ID, 380L)).orElseThrow();
|
||||
dropped.setStatus(GameStatus.DROPPED);
|
||||
UserGame removed = userGameRepository.findById(new UserGameId(USER_ID, 700L)).orElseThrow();
|
||||
removed.setRemovedAt(Instant.now());
|
||||
userGameRepository.flush();
|
||||
|
||||
assertEquals(Set.of(new StatusCount(GameStatus.PLAYING, 2), new StatusCount(GameStatus.DROPPED, 1)),
|
||||
Set.copyOf(userGameRepository.countByStatus(USER_ID)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void countNeverPlayed_CountsOnlyOwnActiveGamesWithoutPlaytime() {
|
||||
UserGame removed = userGameRepository.findById(new UserGameId(USER_ID, 700L)).orElseThrow();
|
||||
removed.setRemovedAt(Instant.now());
|
||||
userGameRepository.save(new UserGame(2L, 380L, 0, Instant.now(), Instant.now()));
|
||||
userGameRepository.flush();
|
||||
|
||||
assertEquals(1, userGameRepository.countByUserIdAndRemovedAtIsNullAndPlaytimeMinutes(USER_ID, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findRandomBacklogGame_PicksOnlyOwnActiveBacklogGames() {
|
||||
UserGame removed = userGameRepository.findById(new UserGameId(USER_ID, 700L)).orElseThrow();
|
||||
removed.setRemovedAt(Instant.now());
|
||||
userGameRepository.flush();
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
assertEquals(380L, userGameRepository.findRandomBacklogGame(USER_ID, -1).orElseThrow().getAppid());
|
||||
}
|
||||
assertTrue(userGameRepository.findRandomBacklogGame(99L, -1).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findRandomBacklogGame_SkipsTheExcludedGame() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
assertEquals(700L, userGameRepository.findRandomBacklogGame(USER_ID, 380L).orElseThrow().getAppid());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
void syncFlushKeepsBacklogEditCommittedMeanwhile() {
|
||||
|
||||
Reference in new issue
Block a user