Public Access
feat(dev): add the local profile with recorded third-party answers
Profile "local" points Steam, Twitch/IGDB and speedrun.com to a WireMock container from compose.yaml (recordings in local/wiremock) and checks logins against the local Auth. The IGDB lookups now keep only the ids they asked for: an answer with another game made tags and release years drop the whole batch.
This commit is contained in:
1 parent
5a089b2082
commit
5c0c0c6a78
54 files changed
+1539
-8
No files matched your search
@@ -0,0 +1,104 @@
|
||||
package core_service.configuration;
|
||||
|
||||
import core_service.modules.igdb_adapter.api.dto.response.IgdbResponses;
|
||||
import core_service.modules.speedrun_adapter.api.dto.response.SpeedrunResponses;
|
||||
import core_service.modules.steam_adapter.api.dto.response.SteamGlobalAchievementPercentagesResponse;
|
||||
import core_service.modules.steam_adapter.api.dto.response.SteamOwnedGamesResponse;
|
||||
import core_service.modules.steam_adapter.api.dto.response.SteamPlayerAchievementsResponse;
|
||||
import core_service.modules.steam_adapter.api.dto.response.SteamPlayerSummaryResponse;
|
||||
import core_service.modules.steam_adapter.api.dto.response.SteamSchemaForGameResponse;
|
||||
import core_service.modules.steam_adapter.api.dto.response.SteamStoreItemsResponse;
|
||||
import core_service.modules.steam_adapter.api.dto.response.SteamWishlistResponse;
|
||||
import core_service.shared.test.AbstractUnitTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* The recorded answers of the local profile (local/wiremock) still fit the adapters' response types.
|
||||
*/
|
||||
class LocalMocksParseTest extends AbstractUnitTest {
|
||||
|
||||
private static final Path FILES = Path.of("local/wiremock/__files");
|
||||
private static final Path MAPPINGS = Path.of("local/wiremock/mappings");
|
||||
|
||||
// By file name prefix; no prefix starts another one.
|
||||
private static final Map<String, Class<?>> TYPES = Map.ofEntries(
|
||||
Map.entry("steam-owned-games", SteamOwnedGamesResponse.class),
|
||||
Map.entry("steam-player-summary", SteamPlayerSummaryResponse.class),
|
||||
Map.entry("steam-wishlist", SteamWishlistResponse.class),
|
||||
Map.entry("steam-store-items", SteamStoreItemsResponse.class),
|
||||
Map.entry("steam-schema-", SteamSchemaForGameResponse.class),
|
||||
Map.entry("steam-global-percentages-", SteamGlobalAchievementPercentagesResponse.class),
|
||||
Map.entry("steam-player-achievements-", SteamPlayerAchievementsResponse.class),
|
||||
Map.entry("twitch-token", IgdbResponses.Token.class),
|
||||
Map.entry("igdb-external-games", IgdbResponses.ExternalGame[].class),
|
||||
Map.entry("igdb-time-to-beats", IgdbResponses.TimeToBeat[].class),
|
||||
Map.entry("igdb-tags", IgdbResponses.GameTags[].class),
|
||||
Map.entry("igdb-game-", IgdbResponses.Game[].class),
|
||||
Map.entry("speedrun-games-", SpeedrunResponses.Games.class),
|
||||
Map.entry("speedrun-runs-", SpeedrunResponses.Runs.class));
|
||||
|
||||
private final JsonMapper jsonMapper = JsonMapper.builder().build();
|
||||
|
||||
@Test
|
||||
void everyMapping_PointsToAnExistingAnswer() throws IOException {
|
||||
List<Path> mappings;
|
||||
try (Stream<Path> listed = Files.list(MAPPINGS)) {
|
||||
mappings = listed.sorted().toList();
|
||||
}
|
||||
assertThat(mappings).isNotEmpty();
|
||||
|
||||
for (Path mapping : mappings) {
|
||||
JsonNode response = jsonMapper.readTree(mapping.toFile()).path("response");
|
||||
assertThat(response.has("bodyFileName") || response.has("jsonBody")).as(mapping.toString()).isTrue();
|
||||
if (response.has("bodyFileName")) {
|
||||
assertThat(FILES.resolve(response.get("bodyFileName").asString())).as(mapping.toString()).exists();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Every top-level field of an answer, and the first field (the id) of every list element, is not null.
|
||||
private static void assertFilled(String name, Object parsed) throws ReflectiveOperationException {
|
||||
if (parsed instanceof Object[] elements) {
|
||||
assertThat(elements).as(name).isNotEmpty();
|
||||
for (Object element : elements) {
|
||||
RecordComponent id = element.getClass().getRecordComponents()[0];
|
||||
assertThat(id.getAccessor().invoke(element)).as(name + " " + id.getName()).isNotNull();
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (RecordComponent component : parsed.getClass().getRecordComponents()) {
|
||||
assertThat(component.getAccessor().invoke(parsed)).as(name + " " + component.getName()).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyRecordedAnswer_ParsesIntoItsResponseType() throws IOException, ReflectiveOperationException {
|
||||
List<Path> files;
|
||||
try (Stream<Path> listed = Files.list(FILES)) {
|
||||
files = listed.sorted().toList();
|
||||
}
|
||||
assertThat(files).isNotEmpty();
|
||||
|
||||
for (Path file : files) {
|
||||
String name = file.getFileName().toString();
|
||||
Class<?> type = TYPES.entrySet().stream()
|
||||
.filter(entry -> name.startsWith(entry.getKey()))
|
||||
.map(Map.Entry::getValue)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("No response type for " + name));
|
||||
assertFilled(name, jsonMapper.readValue(file.toFile(), type));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,38 @@ class IgdbApiServiceTest extends AbstractUnitTest {
|
||||
assertThat(tags.get(620L).themes()).contains("Horror");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTimeToBeat_AnswersForAppsAndGamesNotAskedFor_AreLeftOut() {
|
||||
expectToken();
|
||||
igdb.expect(requestTo(containsString("/external_games")))
|
||||
.andRespond(withSuccess("[{\"game\": 72, \"uid\": \"620\"}, {\"game\": 73, \"uid\": \"999\"}]",
|
||||
MediaType.APPLICATION_JSON));
|
||||
igdb.expect(requestTo(containsString("/game_time_to_beats")))
|
||||
.andRespond(withSuccess("[{\"game_id\": 72, \"normally\": 3600}, {\"game_id\": 73, \"normally\": 7200}]",
|
||||
MediaType.APPLICATION_JSON));
|
||||
igdb.expect(requestTo(containsString("/game_time_to_beats")))
|
||||
.andRespond(withSuccess("[{\"game_id\": 1025, \"normally\": 3600}, {\"game_id\": 1026, \"normally\": 7200}]",
|
||||
MediaType.APPLICATION_JSON));
|
||||
|
||||
assertThat(service.getTimeToBeat(List.of(620L)).orElseThrow()).containsOnlyKeys(620L);
|
||||
assertThat(service.getTimeToBeatByGameIds(List.of(1025L)).orElseThrow()).containsOnlyKeys(1025L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGameTagsAndYears_AnswerWithAGameNotAskedFor_KeepTheAskedGames() {
|
||||
expectToken();
|
||||
for (int call = 0; call < 2; call++) {
|
||||
igdb.expect(requestTo(containsString("/external_games")))
|
||||
.andRespond(withSuccess("[{\"game\": 72, \"uid\": \"620\"}]", MediaType.APPLICATION_JSON));
|
||||
igdb.expect(requestTo(containsString("/games")))
|
||||
.andRespond(withSuccess("[{\"id\": 72, \"first_release_date\": 1149120000, \"themes\": [{\"name\": \"Horror\"}]},"
|
||||
+ " {\"id\": 999, \"first_release_date\": 1493942400}]", MediaType.APPLICATION_JSON));
|
||||
}
|
||||
|
||||
assertThat(service.getGameTags(List.of(620L)).orElseThrow().get(620L).themes()).contains("Horror");
|
||||
assertThat(service.getReleaseYears(List.of(620L))).contains(Map.of(620L, Set.of(2006)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getReleaseYears_SteamAppLinkedToTwoIgdbGames_GetsBothYears() {
|
||||
expectToken();
|
||||
|
||||
Reference in new issue
Block a user