Public Access
refactor: item 29 cleanup
C14 C16 C17 C18 C20 A11 A15 X10 X12 X15: dead Steam count code, GameNames, steamIdText, one query per game for the finished estimate, no API versioning config, open-in-view off, CORS from APP_FRONTEND_URL, non-root image, prune only up to the highest known id.
This commit is contained in:
1 parent
504c935b27
commit
34d64e7215
23 files changed
+116
-188
No files matched your search
@@ -17,7 +17,7 @@ public class CacheConfig {
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager("steamGameCount", "publicStats", "steamPlayerSummary",
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager("publicStats", "steamPlayerSummary",
|
||||
"igdbSearch");
|
||||
cacheManager.setCaffeine(Caffeine.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -90,13 +91,15 @@ public class AccountCleanupRepository {
|
||||
}
|
||||
|
||||
/**
|
||||
* Users that have rows in Core but are not in the list.
|
||||
* Users that have rows in Core but are not in the list. Ids above the highest known one are left out: Auth's
|
||||
* ids never come back, so such a user registered after Auth read the list.
|
||||
*
|
||||
* @param knownUserIds not empty
|
||||
*/
|
||||
public List<Long> findOrphans(Collection<Long> knownUserIds) {
|
||||
String union = String.join(" union ", ORPHAN_SOURCES.stream().map(table -> "select user_id from " + table).toList());
|
||||
return jdbc.queryForList("select user_id from (" + union + ") u where user_id not in (:ids) order by user_id",
|
||||
ids(knownUserIds), Long.class);
|
||||
return jdbc.queryForList("select user_id from (" + union + ") u where user_id not in (:ids) "
|
||||
+ "and user_id < :highest order by user_id",
|
||||
ids(knownUserIds).addValue("highest", Collections.max(knownUserIds)), Long.class);
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -87,11 +88,8 @@ public class LibraryController {
|
||||
@GetMapping("/games-count")
|
||||
public ResponseEntity<?> getGamesCount(@AuthenticationPrincipal UserPrincipal principal) {
|
||||
Long userId = principal.getIdAsLong();
|
||||
if (principal.steamId() != null) {
|
||||
syncService.syncIfStale(userId, String.valueOf(principal.steamId()));
|
||||
} else {
|
||||
syncService.stopNightlySync(userId);
|
||||
}
|
||||
principal.steamIdText().ifPresentOrElse(steamId -> syncService.syncIfStale(userId, steamId),
|
||||
() -> syncService.stopNightlySync(userId));
|
||||
long count = queryService.countOwnedGames(userId);
|
||||
return ResponseEntity.ok(Map.of("count", count, "hasSteamId", principal.steamId() != null));
|
||||
}
|
||||
@@ -108,9 +106,7 @@ public class LibraryController {
|
||||
@RequestParam(defaultValue = "false") boolean hidden
|
||||
) {
|
||||
Long userId = principal.getIdAsLong();
|
||||
if (principal.steamId() != null) {
|
||||
syncService.syncIfStale(userId, String.valueOf(principal.steamId()));
|
||||
}
|
||||
principal.steamIdText().ifPresent(steamId -> syncService.syncIfStale(userId, steamId));
|
||||
|
||||
int safeSize = ALLOWED_SIZES.contains(size) ? size : DEFAULT_SIZE;
|
||||
int safePage = Math.max(0, page);
|
||||
@@ -132,11 +128,12 @@ public class LibraryController {
|
||||
|
||||
@PostMapping("/sync")
|
||||
public ResponseEntity<?> forceSync(@AuthenticationPrincipal UserPrincipal principal) {
|
||||
if (principal.steamId() == null) {
|
||||
Optional<String> steamId = principal.steamIdText();
|
||||
if (steamId.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(new ErrorResponse("NO_STEAM_ID", "User has no Steam ID"));
|
||||
}
|
||||
try {
|
||||
syncService.forceSync(principal.getIdAsLong(), String.valueOf(principal.steamId()));
|
||||
syncService.forceSync(principal.getIdAsLong(), steamId.get());
|
||||
return ResponseEntity.accepted().body(Map.of("status", "started"));
|
||||
} catch (SyncCooldownException e) {
|
||||
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
|
||||
@@ -152,8 +149,7 @@ public class LibraryController {
|
||||
public ResponseEntity<?> getGameDetail(@AuthenticationPrincipal UserPrincipal principal,
|
||||
@PathVariable("appid") Long appid) {
|
||||
Long userId = principal.getIdAsLong();
|
||||
String steamId = principal.steamId() != null ? String.valueOf(principal.steamId()) : null;
|
||||
return gameDetailService.getGameDetail(userId, steamId, appid)
|
||||
return gameDetailService.getGameDetail(userId, principal.steamIdText().orElse(null), appid)
|
||||
.<ResponseEntity<?>>map(ResponseEntity::ok)
|
||||
.orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body(GAME_NOT_FOUND));
|
||||
@@ -310,9 +306,7 @@ public class LibraryController {
|
||||
int safeCount = Math.clamp(count, 1, MAX_PICKER_GAMES);
|
||||
Integer budget = maxMinutes != null && maxMinutes > 0 ? maxMinutes : null;
|
||||
Long userId = principal.getIdAsLong();
|
||||
if (principal.steamId() != null) {
|
||||
syncService.syncIfStale(userId, String.valueOf(principal.steamId()));
|
||||
}
|
||||
principal.steamIdText().ifPresent(steamId -> syncService.syncIfStale(userId, steamId));
|
||||
return ResponseEntity.ok(queryService.pickBacklogGames(userId, budget, mood, excluded, safeCount));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package core_service.modules.library.domain;
|
||||
|
||||
import java.text.Normalizer;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Game names as the matches compare them (import lines, speedrun.com hits).
|
||||
*/
|
||||
public final class GameNames {
|
||||
|
||||
public static final String TRADEMARK_SIGNS = "[™®©]";
|
||||
|
||||
private GameNames() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lower case, without accents, ™ ® © and punctuation, words split by one space.
|
||||
*/
|
||||
public static String normalize(String name) {
|
||||
// Before NFKD, which turns ™ into "TM".
|
||||
String withoutMarks = name.replaceAll(TRADEMARK_SIGNS, "");
|
||||
String plain = Normalizer.normalize(withoutMarks, Normalizer.Form.NFKD).replaceAll("\\p{M}", "");
|
||||
return plain.toLowerCase(Locale.ROOT).replaceAll("[^\\p{L}\\p{N}]+", " ").trim();
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,15 @@ import core_service.modules.library.domain.UserGameAchievement;
|
||||
import core_service.modules.library.domain.UserGameAchievementId;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface UserGameAchievementRepository extends JpaRepository<UserGameAchievement, UserGameAchievementId> {
|
||||
|
||||
List<UserGameAchievement> findByUserIdAndAppid(Long userId, Long appid);
|
||||
|
||||
List<UserGameAchievement> findByAppidAndApiNameAndUserIdInAndUnlockedTrueAndUnlockedAtNotNull(Long appid,
|
||||
String apiName, Collection<Long> userIds);
|
||||
|
||||
void deleteByUserIdAndAppid(Long userId, Long appid);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -39,15 +40,17 @@ public class FinishedEstimateService {
|
||||
for (Map.Entry<Long, List<FinishedWithoutDate>> app : byApp.entrySet()) {
|
||||
Optional<EndingAchievement> ending = EndingAchievement.of(schemaRepository.findByAppid(app.getKey()));
|
||||
if (ending.isEmpty()) continue;
|
||||
String apiName = ending.get().achievement().getApiName();
|
||||
// One query per game, not per user: most candidates never unlocked the ending and come back every night.
|
||||
// With the user ids, the primary key (user_id, appid, api_name) serves it.
|
||||
List<Long> userIds = app.getValue().stream().map(FinishedWithoutDate::userId).toList();
|
||||
Map<Long, Instant> unlockByUser = userAchievementRepository
|
||||
.findByAppidAndApiNameAndUserIdInAndUnlockedTrueAndUnlockedAtNotNull(app.getKey(),
|
||||
ending.get().achievement().getApiName(), userIds).stream()
|
||||
.collect(Collectors.toMap(UserGameAchievement::getUserId, UserGameAchievement::getUnlockedAt));
|
||||
for (FinishedWithoutDate game : app.getValue()) {
|
||||
Optional<UserGameAchievement> unlock = userAchievementRepository
|
||||
.findByUserIdAndAppid(game.userId(), game.appid()).stream()
|
||||
.filter(a -> a.isUnlocked() && a.getUnlockedAt() != null && a.getApiName().equals(apiName))
|
||||
.findFirst();
|
||||
if (unlock.isPresent()) {
|
||||
dated += userGameRepository.setFinishedAtEstimate(game.userId(), game.appid(),
|
||||
unlock.get().getUnlockedAt());
|
||||
Instant unlockedAt = unlockByUser.get(game.userId());
|
||||
if (unlockedAt != null) {
|
||||
dated += userGameRepository.setFinishedAtEstimate(game.userId(), game.appid(), unlockedAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package core_service.modules.library.service;
|
||||
|
||||
import core_service.modules.library.domain.GameNames;
|
||||
import core_service.modules.library.domain.GameImport;
|
||||
import core_service.modules.library.domain.IgdbGame;
|
||||
import core_service.modules.library.repo.GameImportRepository;
|
||||
@@ -195,7 +196,7 @@ public class GameImportWorker {
|
||||
* Case, accents, punctuation and marks like ™ ignored, as in the speedrun.com match.
|
||||
*/
|
||||
static boolean sameName(String a, String b) {
|
||||
return SpeedrunMatch.normalize(a).equals(SpeedrunMatch.normalize(b));
|
||||
return GameNames.normalize(a).equals(GameNames.normalize(b));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -212,11 +212,9 @@ public class LibraryQueryService {
|
||||
|
||||
/**
|
||||
* @param profileStats the stat group names of the profile ticket
|
||||
*/
|
||||
@Public
|
||||
/**
|
||||
* @param sectionNames see {@link ProfileSection#shown}; a hidden section runs no query
|
||||
*/
|
||||
@Public
|
||||
public PublicLibraryDto getPublicLibrary(Long userId, Collection<String> profileStats, Collection<String> sectionNames) {
|
||||
Set<ProfileSection> sections = ProfileSection.shown(sectionNames);
|
||||
boolean summary = sections.contains(ProfileSection.SUMMARY);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package core_service.modules.library.service;
|
||||
|
||||
import core_service.modules.library.domain.GameNames;
|
||||
import core_service.modules.speedrun_adapter.service.SpeedrunApiService;
|
||||
|
||||
import java.text.Normalizer;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -21,7 +20,6 @@ final class SpeedrunMatch {
|
||||
static final String WEBLINK_PREFIX = "https://www.speedrun.com/";
|
||||
|
||||
// speedrun.com tells remakes apart by the year: "Prey (2017)" next to "Prey".
|
||||
private static final String TRADEMARK_SIGNS = "[™®©]";
|
||||
private static final Pattern YEAR_SUFFIX = Pattern.compile("^(.*?)\\s*\\((\\d{4})\\)$");
|
||||
// The column sizes of game_speedrun.
|
||||
private static final int MAX_GAME_ID_LENGTH = 16;
|
||||
@@ -64,24 +62,14 @@ final class SpeedrunMatch {
|
||||
* The name for speedrun.com's search, which does not find "DOOM Eternal" for "DOOM Eternal™".
|
||||
*/
|
||||
static String searchText(String name) {
|
||||
return name.replaceAll(TRADEMARK_SIGNS, "").trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lower case, without accents, ™ ® © and punctuation, words split by one space.
|
||||
*/
|
||||
static String normalize(String name) {
|
||||
// Before NFKD, which turns ™ into "TM".
|
||||
String withoutMarks = name.replaceAll(TRADEMARK_SIGNS, "");
|
||||
String plain = Normalizer.normalize(withoutMarks, Normalizer.Form.NFKD).replaceAll("\\p{M}", "");
|
||||
return plain.toLowerCase(Locale.ROOT).replaceAll("[^\\p{L}\\p{N}]+", " ").trim();
|
||||
return name.replaceAll(GameNames.TRADEMARK_SIGNS, "").trim();
|
||||
}
|
||||
|
||||
private static NameAndYear split(String name) {
|
||||
Matcher matcher = YEAR_SUFFIX.matcher(name.trim());
|
||||
return matcher.matches()
|
||||
? new NameAndYear(normalize(matcher.group(1)), Integer.parseInt(matcher.group(2)))
|
||||
: new NameAndYear(normalize(name), null);
|
||||
? new NameAndYear(GameNames.normalize(matcher.group(1)), Integer.parseInt(matcher.group(2)))
|
||||
: new NameAndYear(GameNames.normalize(name), null);
|
||||
}
|
||||
|
||||
private record NameAndYear(String name, Integer year) {}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package core_service.modules.steam_adapter.api;
|
||||
|
||||
import core_service.modules.steam_adapter.service.SteamApiService;
|
||||
import core_service.shared.security.UserPrincipal;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/steam")
|
||||
@RequiredArgsConstructor
|
||||
public class SteamController {
|
||||
|
||||
private final SteamApiService steamApiService;
|
||||
|
||||
@GetMapping("/games-count")
|
||||
public ResponseEntity<?> getGamesCount(@AuthenticationPrincipal UserPrincipal principal) {
|
||||
if (principal.steamId() == null) {
|
||||
return ResponseEntity.ok(Map.of("count", 0, "hasSteamId", false));
|
||||
}
|
||||
|
||||
int count = steamApiService.getOwnedGamesCount(String.valueOf(principal.steamId()));
|
||||
return ResponseEntity.ok(Map.of("count", count, "hasSteamId", true));
|
||||
}
|
||||
}
|
||||
-1
@@ -11,7 +11,6 @@ public record SteamOwnedGamesResponse(
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record OwnedGamesResponse(
|
||||
@JsonProperty("game_count") Integer gameCount,
|
||||
@JsonProperty("games") List<Game> games
|
||||
) {}
|
||||
|
||||
|
||||
@@ -51,51 +51,6 @@ public class SteamApiService {
|
||||
@Value("${app.steam.store-country:DE}")
|
||||
private String storeCountry;
|
||||
|
||||
@Cacheable(value = "steamGameCount", key = "#steamId")
|
||||
@RateLimiter(name = "steamApi")
|
||||
@Retry(name = "steamApiOnce", fallbackMethod = "noGamesCount")
|
||||
public int getOwnedGamesCount(String steamId) {
|
||||
if (apiKey == null || apiKey.isEmpty() || apiKey.equals("${STEAM_API_KEY}")) {
|
||||
log.error("Steam API Key is not configured correctly!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
SteamOwnedGamesResponse response = steamRestClient.get()
|
||||
.uri(uriBuilder -> uriBuilder
|
||||
.path("/IPlayerService/GetOwnedGames/v1/")
|
||||
.queryParam("key", apiKey)
|
||||
.queryParam("steamid", steamId)
|
||||
.queryParam("include_appinfo", "true")
|
||||
.queryParam("include_played_free_games", "true")
|
||||
.queryParam("skip_unvetted_apps", 0)
|
||||
.queryParam("format", "json")
|
||||
.build())
|
||||
.retrieve()
|
||||
.body(SteamOwnedGamesResponse.class);
|
||||
|
||||
if (response != null && response.response() != null) {
|
||||
Integer count = response.response().gameCount();
|
||||
if (count == null) {
|
||||
if (response.response().games() != null) {
|
||||
int size = response.response().games().size();
|
||||
log.info("game_count was null, but games list has {} entries for SteamID: {}", size, steamId);
|
||||
return size;
|
||||
}
|
||||
log.warn("Steam API returned an empty response object for SteamID: {}. Check if the ID is correct and 'Game details' are Public.", steamId);
|
||||
return 0;
|
||||
}
|
||||
log.info("Successfully fetched {} games for SteamID: {}", count, steamId);
|
||||
return count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The fallbacks run after the last try; the callers keep their "Steam failed" rule.
|
||||
private int noGamesCount(String steamId, Exception e) {
|
||||
log.error("Error communicating with Steam API for game count of SteamID: {}", steamId, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Asked on every page load for the avatar in the header.
|
||||
@Cacheable(value = "steamPlayerSummary", key = "#steamId", unless = "#result == null")
|
||||
@RateLimiter(name = "steamApi")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package core_service.shared.security;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @param role the active role of this login ("view as"), set by Auth
|
||||
@@ -20,6 +21,13 @@ public record UserPrincipal(
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Steam ID as the Steam API and user_steam_sync take it; empty, never "null", without one.
|
||||
*/
|
||||
public Optional<String> steamIdText() {
|
||||
return Optional.ofNullable(steamId).map(String::valueOf);
|
||||
}
|
||||
|
||||
public long getIdAsLong() {
|
||||
return Long.parseLong(id);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ security:
|
||||
jwks-url: "http://localhost:8080/.well-known/jwks.json"
|
||||
|
||||
app:
|
||||
cors:
|
||||
allowed-origins: "http://localhost:5173"
|
||||
frontend-url: "http://localhost:5173"
|
||||
steam:
|
||||
api-key: local
|
||||
base-url: "http://localhost:8089/steam"
|
||||
|
||||
@@ -7,7 +7,7 @@ spring:
|
||||
deserialization:
|
||||
accept-float-as-int: false
|
||||
jpa:
|
||||
open-in-view: true
|
||||
open-in-view: false
|
||||
flyway:
|
||||
enabled: true
|
||||
baseline-on-migrate: true
|
||||
@@ -15,12 +15,6 @@ spring:
|
||||
threads:
|
||||
virtual:
|
||||
enabled: true
|
||||
mvc:
|
||||
apiversion:
|
||||
use:
|
||||
query-parameter: version
|
||||
default: "1.0"
|
||||
supported: 1.0, 2,0
|
||||
|
||||
server:
|
||||
port: 8090
|
||||
@@ -42,8 +36,11 @@ app:
|
||||
# Filled from the CI run by the Dockerfile.
|
||||
commit: ${APP_BUILD_COMMIT:dev}
|
||||
time: ${APP_BUILD_TIME:}
|
||||
# The frontend; FRONTEND_HOST in the Deployment compose file. The prod host as default: an image that starts before
|
||||
# the compose file sets the env var keeps working.
|
||||
frontend-url: ${APP_FRONTEND_URL:https://tbp.andreas-dahm.eu}
|
||||
cors:
|
||||
allowed-origins: "https://tbp.andreas-dahm.eu"
|
||||
allowed-origins: ${app.frontend-url}
|
||||
steam:
|
||||
api-key: ${STEAM_API_KEY}
|
||||
base-url: https://api.steampowered.com
|
||||
|
||||
@@ -111,10 +111,16 @@ class AccountCleanupRepositoryTest extends AbstractPersistenceTest {
|
||||
void findOrphans_ReturnsUsersNotInTheList() {
|
||||
repository.addDeleted(List.of(7L));
|
||||
|
||||
assertThat(repository.findOrphans(List.of(2L))).containsExactly(1L, 7L);
|
||||
assertThat(repository.findOrphans(List.of(2L, 8L))).containsExactly(1L, 7L);
|
||||
assertThat(repository.findOrphans(List.of(1L, 2L, 7L))).isEmpty();
|
||||
}
|
||||
|
||||
// Auth reads its ids before the call: a user who registers in between has a higher id and is no orphan.
|
||||
@Test
|
||||
void findOrphans_SkipsUsersAboveTheHighestKnownId() {
|
||||
assertThat(repository.findOrphans(List.of(1L))).isEmpty();
|
||||
}
|
||||
|
||||
// A new table with user rows must be in the purge list, or "Delete my account" leaves its rows behind.
|
||||
@Test
|
||||
void tables_CoverEveryTableWithAUserId() {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package core_service.modules.library.domain;
|
||||
|
||||
import core_service.shared.test.AbstractUnitTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class GameNamesTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void normalize_DropsCaseAccentsTrademarksAndPunctuation() {
|
||||
assertThat(GameNames.normalize(" Pokémon™: Let's Go, Pikachu!® ")).isEqualTo("pokemon let s go pikachu");
|
||||
assertThat(GameNames.normalize("The Witcher 3: Wild Hunt")).isEqualTo(GameNames.normalize("The Witcher® 3 - Wild Hunt"));
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,6 @@ class SpeedrunMatchTest extends AbstractUnitTest {
|
||||
private static final SpeedrunApiService.Game PREY_2006 = hit("prey", "Prey", 2006);
|
||||
private static final SpeedrunApiService.Game PREY_2017 = hit("prey2017", "Prey (2017)", 2017);
|
||||
|
||||
@Test
|
||||
void normalize_DropsCaseAccentsTrademarksAndPunctuation() {
|
||||
assertThat(SpeedrunMatch.normalize(" Pokémon™: Let's Go, Pikachu!® ")).isEqualTo("pokemon let s go pikachu");
|
||||
assertThat(SpeedrunMatch.normalize("The Witcher 3: Wild Hunt")).isEqualTo(SpeedrunMatch.normalize("The Witcher® 3 - Wild Hunt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void candidates_YearSuffixTellsTheRemakeApart() {
|
||||
assertThat(SpeedrunMatch.candidates("Prey", Set.of(2017), List.of(PREY_2006, PREY_2017))).containsExactly(PREY_2017);
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ class SteamApiServiceCacheTest extends AbstractIntegrationTest {
|
||||
|
||||
@Test
|
||||
void getOwnedGames_TransientSteamError_IsTriedAgain() {
|
||||
SteamOwnedGamesResponse games = new SteamOwnedGamesResponse(new SteamOwnedGamesResponse.OwnedGamesResponse(1,
|
||||
SteamOwnedGamesResponse games = new SteamOwnedGamesResponse(new SteamOwnedGamesResponse.OwnedGamesResponse(
|
||||
List.of(new SteamOwnedGamesResponse.Game(220, "Half-Life 2", 60, null, true, 0))));
|
||||
when(steamRestClient.get().uri(any(Function.class)).retrieve().body(SteamOwnedGamesResponse.class))
|
||||
.thenAnswer(invocation -> {
|
||||
|
||||
@@ -45,27 +45,6 @@ class SteamApiServiceTest {
|
||||
ReflectionTestUtils.setField(steamApiService, "apiKey", "test-api-key");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOwnedGamesCount_shouldReturnCount() {
|
||||
String steamId = "76561197960435530";
|
||||
String jsonResponse = """
|
||||
{
|
||||
"response": {
|
||||
"game_count": 42
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v1/")))
|
||||
.andExpect(queryParam("steamid", steamId))
|
||||
.andExpect(queryParam("key", "test-api-key"))
|
||||
.andRespond(withSuccess(jsonResponse, MediaType.APPLICATION_JSON));
|
||||
|
||||
int result = steamApiService.getOwnedGamesCount(steamId);
|
||||
|
||||
assertThat(result).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPlayerSummary_shouldReturnPlayer() {
|
||||
String steamId = "76561197960435530";
|
||||
@@ -135,15 +114,6 @@ class SteamApiServiceTest {
|
||||
assertThat(steamApiService.getOwnedGames("76561197960435530")).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOwnedGamesCount_shouldCountUnvettedApps() {
|
||||
server.expect(requestTo(containsString("/IPlayerService/GetOwnedGames/v1/")))
|
||||
.andExpect(queryParam("skip_unvetted_apps", "0"))
|
||||
.andRespond(withSuccess("{\"response\":{\"game_count\":1}}", MediaType.APPLICATION_JSON));
|
||||
|
||||
assertThat(steamApiService.getOwnedGamesCount("76561197960435530")).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOwnedGames_shouldReturnGamesEvenWithNullValues() {
|
||||
String steamId = "76561197960435530";
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package core_service.shared.test;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
|
||||
|
||||
/**
|
||||
* Base class for WebMvc tests.
|
||||
* Slices the web layer.
|
||||
*/
|
||||
@Tag("WebMvcTest")
|
||||
@WebMvcTest
|
||||
public abstract class AbstractWebMvcTest {
|
||||
}
|
||||
Reference in new issue
Block a user