feat: remove AnimalApi cache + !ratingoverview for super-admin
- AnimalApi: drop Caffeine URL cache (30s TTL made !neko return the same photo) - RatingsRepository.getExtremeRating(guild, type, excluded, highest) with NOT IN - !ratingoverview (super-admin only): per-category max/min, excluding bots + super-admin - RatingsRepositoryTest: 7 new tests for getExtremeRating
This commit is contained in:
parent
bf14e03b21
commit
ec25d9e8d3
6 changed files with 242 additions and 35 deletions
32
TODO.md
32
TODO.md
|
|
@ -253,7 +253,7 @@ feature/economy/
|
||||||
- [x] Красивые level-up embed с прогресс-баром
|
- [x] Красивые level-up embed с прогресс-баром
|
||||||
|
|
||||||
### Приоритет 3
|
### Приоритет 3
|
||||||
- [ ] Статистика сервера — графики, CSV export
|
- [x] Статистика сервера — графики, CSV export — ✅ `!serverstats [csv|chart]`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -263,15 +263,15 @@ feature/economy/
|
||||||
- [x] `!catperson` / `!dogperson` / `!foxperson` — «Какой ты кот/пёс/лис»: % + роль + картинка (детерминировано ID)
|
- [x] `!catperson` / `!dogperson` / `!foxperson` — «Какой ты кот/пёс/лис»: % + роль + картинка (детерминировано ID)
|
||||||
- [x] `!shibe` / `!birb` — картинки шиба-ину и птичек
|
- [x] `!shibe` / `!birb` — картинки шиба-ину и птичек
|
||||||
- [x] `!neko` / `!waifu` / `!catboy` — аниме-картинки (неко, вайфу, кошко-мальчик)
|
- [x] `!neko` / `!waifu` / `!catboy` — аниме-картинки (неко, вайфу, кошко-мальчик)
|
||||||
- [ ] Факты: `!catfact` / `!dogfact` — факт + картинка (catfact.ninja, dog facts API)
|
- [x] Факты: `!catfact` / `!dogfact` — факт + картинка (catfact.ninja, dog facts API)
|
||||||
- [ ] `!joke` — анекдоты (jokeapi.dev, категории Any/Programming/Misc)
|
- [x] `!joke` — анекдоты (jokeapi.dev, категории Any/Programming/Misc)
|
||||||
- [ ] `!chuck` — цитаты Чака Норриса (api.chucknorris.io)
|
- [x] `!chuck` — цитаты Чака Норриса (api.chucknorris.io)
|
||||||
- [ ] `!inspire` — мотивационные цитаты (zenquotes.io)
|
- [x] `!inspire` — мотивационные цитаты (zenquotes.io)
|
||||||
- [ ] `!apod` — астрономическая картинка дня NASA (api.nasa.gov, бесплатный ключ)
|
- [x] `!apod` — астрономическая картинка дня NASA (api.nasa.gov, DEMO_KEY)
|
||||||
- [ ] `!iss` — где сейчас МКС (api.wheretheiss.at)
|
- [x] `!iss` — где сейчас МКС (api.wheretheiss.at)
|
||||||
- [ ] `!space` — кто сейчас в космосе (open-notify.org)
|
- [x] `!space` — кто сейчас в космосе (open-notify.org)
|
||||||
- [ ] `!xkcd` — свежий стрип (xkcd.com/api)
|
- [x] `!xkcd` — свежий стрип (xkcd.com/api)
|
||||||
- [ ] `!meme <subreddit>` — расширить мем по сабреддиту
|
- [x] `!meme <subreddit>` — расширить мем по сабреддиту
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -296,6 +296,7 @@ feature/economy/
|
||||||
|
|
||||||
### Оптимизации (2026-08-19)
|
### Оптимизации (2026-08-19)
|
||||||
- [x] `AnimalApi` — Caffeine-кэш успешных URL (TTL 30 с): повторные `!neko`/`!catboy`/зоо-викторины отвечают мгновенно
|
- [x] `AnimalApi` — Caffeine-кэш успешных URL (TTL 30 с): повторные `!neko`/`!catboy`/зоо-викторины отвечают мгновенно
|
||||||
|
- [x] `AnimalApi` — кэш полностью убран (2026-08-19): из-за него `!neko` 30с отдавал одну и ту же фотку
|
||||||
- [x] `MemeCommand` — HttpURLConnection (без таймаутов, мог висеть) → OkHttp 6с/8с
|
- [x] `MemeCommand` — HttpURLConnection (без таймаутов, мог висеть) → OkHttp 6с/8с
|
||||||
- [ ] `Database` — включить `PRAGMA journal_mode = WAL` (сейчас нет — блокировки при конкурентной записи)
|
- [ ] `Database` — включить `PRAGMA journal_mode = WAL` (сейчас нет — блокировки при конкурентной записи)
|
||||||
- [ ] NekosLifeApi — таймауты/ретраи вынести в конфиг (10с → 8с, retry 3 → 2)
|
- [ ] NekosLifeApi — таймауты/ретраи вынести в конфиг (10с → 8с, retry 3 → 2)
|
||||||
|
|
@ -462,15 +463,16 @@ feature/economy/
|
||||||
- [ ] F14: MetricsCollector.java — разбить по типам метрик
|
- [ ] F14: MetricsCollector.java — разбить по типам метрик
|
||||||
- [ ] F15: WarnCommand.java — вынести auto-mute в отдельный сервис
|
- [ ] F15: WarnCommand.java — вынести auto-mute в отдельный сервис
|
||||||
- [x] F16: BotInitializer.java — вынести фабрики ботов — ✅ 162→135 строк
|
- [x] F16: BotInitializer.java — вынести фабрики ботов — ✅ 162→135 строк
|
||||||
|
- [x] F02-04: QueueSlashHandler, MusicBotControlHandler, QueueCommands — вынести общие части — ✅ вынесены при реорге music
|
||||||
- [ ] Остальные: разбить по SOLID
|
- [ ] Остальные: разбить по SOLID
|
||||||
|
|
||||||
### Этап 11: ≤6 файлов в папке
|
### Этап 11: ≤6 файлов в папке
|
||||||
|
|
||||||
- [ ] P1: Entertainment команды — группировать по смыслу (coin/dice → `random/`, gay/iq/pp → `rating/`)
|
- [x] P1: Entertainment команды — уже сгруппированы (choice/, random/; rating-команды в feature/rating/)
|
||||||
- [ ] P2: Social команды — объединить в `SocialCommand` с параметром
|
- [x] P2: Social команды — уже объединены (SocialCommand + 3 отдельных), 4 файла
|
||||||
- [ ] P4: Level — разбить на commands/, service/, repository/
|
- [x] P4: Level — уже разбит на commands/, service/, repository/, domain/
|
||||||
- [ ] P5: Moderation — разбить по поддоменам (commands/punish, commands/utils, warn)
|
- [x] P5: Moderation — уже разбит по поддоменам (punish, utils, warn, caselookup, log, repository)
|
||||||
- [ ] P6: Music — разбить на commands/, player/, lavalink/
|
- [x] P6: Music — ✅ commands/filter|control|queue, listener/bot|select, manage/player|filter, util/embed|voice|query (все папки ≤6)
|
||||||
|
|
||||||
### Этап 12: Тесты
|
### Этап 12: Тесты
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
@ -20,18 +18,15 @@ import org.slf4j.LoggerFactory;
|
||||||
/**
|
/**
|
||||||
* Клиент к публичным API животных и аниме-картинок. Для каждого вида пробует эндпоинты по порядку и
|
* Клиент к публичным API животных и аниме-картинок. Для каждого вида пробует эндпоинты по порядку и
|
||||||
* возвращает первый успешный URL картинки. Все запросы проходят через {@link ApiRequestQueue} —
|
* возвращает первый успешный URL картинки. Все запросы проходят через {@link ApiRequestQueue} —
|
||||||
* ответы изолированы и не могут подмешаться друг к другу. Успешные URL кэшируются на короткое
|
* ответы изолированы и не могут подмешаться друг к другу.
|
||||||
* время, чтобы повторные команды отвечали мгновенно и не перегружали внешние API.
|
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class AnimalApi {
|
public class AnimalApi {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AnimalApi.class);
|
private static final Logger logger = LoggerFactory.getLogger(AnimalApi.class);
|
||||||
private static final String NEKOS_BASE = "https://nekos.life/api/v2/img/";
|
private static final String NEKOS_BASE = "https://nekos.life/api/v2/img/";
|
||||||
private static final long CACHE_TTL_SECONDS = 30;
|
|
||||||
|
|
||||||
private final ApiRequestQueue queue;
|
private final ApiRequestQueue queue;
|
||||||
private final OkHttpClient client;
|
private final OkHttpClient client;
|
||||||
private final Cache<String, String> cache;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AnimalApi(final ApiRequestQueue queue) {
|
public AnimalApi(final ApiRequestQueue queue) {
|
||||||
|
|
@ -41,24 +36,11 @@ public class AnimalApi {
|
||||||
AnimalApi(final ApiRequestQueue queue, final OkHttpClient client) {
|
AnimalApi(final ApiRequestQueue queue, final OkHttpClient client) {
|
||||||
this.queue = queue;
|
this.queue = queue;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.cache =
|
|
||||||
Caffeine.newBuilder()
|
|
||||||
.maximumSize(64)
|
|
||||||
.expireAfterWrite(CACHE_TTL_SECONDS, TimeUnit.SECONDS)
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Возвращает URL картинки для вида или null, если все эндпоинты недоступны. */
|
/** Возвращает URL картинки для вида или null, если все эндпоинты недоступны. */
|
||||||
public String fetchImage(final AnimalKind kind) {
|
public String fetchImage(final AnimalKind kind) {
|
||||||
String cached = cache.getIfPresent(kind.name());
|
return queue.submit(() -> fetchImageBlocking(kind));
|
||||||
if (cached != null) {
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
String url = queue.submit(() -> fetchImageBlocking(kind));
|
|
||||||
if (url != null) {
|
|
||||||
cache.put(kind.name(), url);
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String fetchImageBlocking(final AnimalKind kind) {
|
private String fetchImageBlocking(final AnimalKind kind) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
package creator.loki.feature.rating.commands.board;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import creator.loki.command.Command;
|
||||||
|
import creator.loki.command.CommandCategories;
|
||||||
|
import creator.loki.command.CommandContext;
|
||||||
|
import creator.loki.config.BotConfig;
|
||||||
|
import creator.loki.embed.EmbedFactory;
|
||||||
|
import creator.loki.feature.rating.repository.RatingsRepository;
|
||||||
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
|
|
||||||
|
/** Максимум и минимум по каждой категории рейтинга (только для супер-админа, без ботов). */
|
||||||
|
public class RatingOverviewCommand implements Command {
|
||||||
|
|
||||||
|
private static final List<String> TYPES =
|
||||||
|
List.of("ship", "gay", "iq", "pp", "mustache", "fat", "boykisser");
|
||||||
|
|
||||||
|
private final RatingsRepository ratingsRepository;
|
||||||
|
private final BotConfig config;
|
||||||
|
|
||||||
|
public RatingOverviewCommand(RatingsRepository ratingsRepository, BotConfig config) {
|
||||||
|
this.ratingsRepository = ratingsRepository;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "ratingoverview";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Максимумы и минимумы рейтингов по всем категориям (только для супер-админа)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsage() {
|
||||||
|
return "ratingoverview";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCategory() {
|
||||||
|
return CommandCategories.SUPER_ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(final CommandContext context) {
|
||||||
|
if (!config.getSuperAdminId().equals(context.getAuthor().getUser().getId())) {
|
||||||
|
context
|
||||||
|
.getChannel()
|
||||||
|
.sendMessage("❌ Команда доступна только супер-администратору бота.")
|
||||||
|
.queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Guild guild = context.getGuild();
|
||||||
|
if (guild == null) {
|
||||||
|
context.getChannel().sendMessage("❌ Команда работает только на сервере.").queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> excluded = new HashSet<>();
|
||||||
|
excluded.add(config.getSuperAdminId());
|
||||||
|
for (Member member : guild.getMembers()) {
|
||||||
|
if (member.getUser().isBot()) {
|
||||||
|
excluded.add(member.getUser().getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder description = new StringBuilder();
|
||||||
|
for (String type : TYPES) {
|
||||||
|
RatingsRepository.RatingEntry max =
|
||||||
|
ratingsRepository.getExtremeRating(guild.getId(), type, excluded, true);
|
||||||
|
RatingsRepository.RatingEntry min =
|
||||||
|
ratingsRepository.getExtremeRating(guild.getId(), type, excluded, false);
|
||||||
|
if (max == null || min == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
description
|
||||||
|
.append("**")
|
||||||
|
.append(type)
|
||||||
|
.append("**\n🏆 <@")
|
||||||
|
.append(max.userId())
|
||||||
|
.append("> — **")
|
||||||
|
.append(max.value())
|
||||||
|
.append("**\n📉 <@")
|
||||||
|
.append(min.userId())
|
||||||
|
.append("> — **")
|
||||||
|
.append(min.value())
|
||||||
|
.append("**\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description.isEmpty()) {
|
||||||
|
context
|
||||||
|
.getChannel()
|
||||||
|
.sendMessage("❌ Пока никто не имеет рейтингов. Сначала `!generateratings`.")
|
||||||
|
.queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context
|
||||||
|
.getChannel()
|
||||||
|
.sendMessageEmbeds(
|
||||||
|
EmbedFactory.create()
|
||||||
|
.withTitle("📊 Сводка рейтингов")
|
||||||
|
.withDescription(description.toString())
|
||||||
|
.withFooter("Без учёта ботов и супер-админа")
|
||||||
|
.build())
|
||||||
|
.queue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,8 @@ import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -152,6 +154,40 @@ public class RatingsRepository {
|
||||||
return getRatingBoard(guildId, ratingType, limit, false);
|
return getRatingBoard(guildId, ratingType, limit, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Крайнее значение рейтинга (максимум или минимум) без учёта исключённых пользователей. */
|
||||||
|
public RatingEntry getExtremeRating(
|
||||||
|
final String guildId,
|
||||||
|
final String ratingType,
|
||||||
|
final Collection<String> excludedUserIds,
|
||||||
|
final boolean highest) {
|
||||||
|
StringBuilder sql =
|
||||||
|
new StringBuilder(
|
||||||
|
"SELECT user_id, value FROM ratings WHERE guild_id = ? AND rating_type = ?");
|
||||||
|
List<String> params = new ArrayList<>(List.of(guildId, ratingType));
|
||||||
|
if (excludedUserIds != null && !excludedUserIds.isEmpty()) {
|
||||||
|
sql.append(" AND user_id NOT IN (")
|
||||||
|
.append(String.join(",", Collections.nCopies(excludedUserIds.size(), "?")))
|
||||||
|
.append(")");
|
||||||
|
params.addAll(excludedUserIds);
|
||||||
|
}
|
||||||
|
sql.append(" ORDER BY value ").append(highest ? "DESC" : "ASC").append(" LIMIT 1");
|
||||||
|
|
||||||
|
try (PreparedStatement stmt = database.prepare(sql.toString())) {
|
||||||
|
for (int i = 0; i < params.size(); i++) {
|
||||||
|
stmt.setString(i + 1, params.get(i));
|
||||||
|
}
|
||||||
|
try (ResultSet rs = stmt.executeQuery()) {
|
||||||
|
if (rs.next()) {
|
||||||
|
return new RatingEntry(rs.getString("user_id"), rs.getInt("value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.error("Ошибка получения крайнего рейтинга {} для сервера {}", ratingType, guildId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private List<RatingEntry> getRatingBoard(
|
private List<RatingEntry> getRatingBoard(
|
||||||
final String guildId, final String ratingType, final int limit, final boolean descending) {
|
final String guildId, final String ratingType, final int limit, final boolean descending) {
|
||||||
String sql =
|
String sql =
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import creator.loki.feature.info.commands.ServerStatsCommand;
|
||||||
import creator.loki.feature.metrics.MetricsCollector;
|
import creator.loki.feature.metrics.MetricsCollector;
|
||||||
import creator.loki.feature.metrics.commands.MetricsCommand;
|
import creator.loki.feature.metrics.commands.MetricsCommand;
|
||||||
import creator.loki.feature.rating.commands.board.RatingBoardCommand;
|
import creator.loki.feature.rating.commands.board.RatingBoardCommand;
|
||||||
|
import creator.loki.feature.rating.commands.board.RatingOverviewCommand;
|
||||||
import creator.loki.feature.rating.repository.RatingsRepository;
|
import creator.loki.feature.rating.repository.RatingsRepository;
|
||||||
import creator.loki.feature.reputation.MinusRepCommand;
|
import creator.loki.feature.reputation.MinusRepCommand;
|
||||||
import creator.loki.feature.reputation.RepCommand;
|
import creator.loki.feature.reputation.RepCommand;
|
||||||
|
|
@ -81,6 +82,7 @@ public class UtilityCommandRegistrar implements CommandRegistrar {
|
||||||
registry.register(new GenerateRatingsCommand(ratingsRepository, config));
|
registry.register(new GenerateRatingsCommand(ratingsRepository, config));
|
||||||
registry.register(new RatingBoardCommand(RatingBoardCommand.Flavor.TOP, ratingsRepository));
|
registry.register(new RatingBoardCommand(RatingBoardCommand.Flavor.TOP, ratingsRepository));
|
||||||
registry.register(new RatingBoardCommand(RatingBoardCommand.Flavor.BOTTOM, ratingsRepository));
|
registry.register(new RatingBoardCommand(RatingBoardCommand.Flavor.BOTTOM, ratingsRepository));
|
||||||
|
registry.register(new RatingOverviewCommand(ratingsRepository, config));
|
||||||
|
|
||||||
registry.register(new RepCommand(reputationRepository, cooldownManager));
|
registry.register(new RepCommand(reputationRepository, cooldownManager));
|
||||||
registry.register(new MinusRepCommand(reputationRepository, cooldownManager));
|
registry.register(new MinusRepCommand(reputationRepository, cooldownManager));
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import creator.loki.database.Database;
|
import creator.loki.database.Database;
|
||||||
|
|
@ -259,4 +260,74 @@ class RatingsRepositoryTest {
|
||||||
assertTrue(repository.getBottomRatings("guild1", "iq", 10).isEmpty());
|
assertTrue(repository.getBottomRatings("guild1", "iq", 10).isEmpty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class Extreme {
|
||||||
|
@Test
|
||||||
|
void highestWithoutExclusions() {
|
||||||
|
repository.setRating("guild1", "user1", "iq", 100);
|
||||||
|
repository.setRating("guild1", "user2", "iq", 150);
|
||||||
|
|
||||||
|
var max = repository.getExtremeRating("guild1", "iq", List.of(), true);
|
||||||
|
assertEquals("user2", max.userId());
|
||||||
|
assertEquals(150, max.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lowestWithoutExclusions() {
|
||||||
|
repository.setRating("guild1", "user1", "iq", 100);
|
||||||
|
repository.setRating("guild1", "user2", "iq", 50);
|
||||||
|
|
||||||
|
var min = repository.getExtremeRating("guild1", "iq", List.of(), false);
|
||||||
|
assertEquals("user2", min.userId());
|
||||||
|
assertEquals(50, min.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void highestSkipsExcludedUsers() {
|
||||||
|
repository.setRating("guild1", "user1", "iq", 100);
|
||||||
|
repository.setRating("guild1", "bot1", "iq", 200);
|
||||||
|
repository.setRating("guild1", "admin", "iq", 180);
|
||||||
|
|
||||||
|
var max = repository.getExtremeRating("guild1", "iq", List.of("bot1", "admin"), true);
|
||||||
|
assertEquals("user1", max.userId());
|
||||||
|
assertEquals(100, max.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lowestSkipsExcludedUsers() {
|
||||||
|
repository.setRating("guild1", "user1", "iq", 5);
|
||||||
|
repository.setRating("guild1", "bot1", "iq", 1);
|
||||||
|
repository.setRating("guild1", "admin", "iq", 3);
|
||||||
|
|
||||||
|
var min = repository.getExtremeRating("guild1", "iq", List.of("bot1", "admin"), false);
|
||||||
|
assertEquals("user1", min.userId());
|
||||||
|
assertEquals(5, min.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsNullWhenAllExcluded() {
|
||||||
|
repository.setRating("guild1", "user1", "iq", 100);
|
||||||
|
|
||||||
|
var max = repository.getExtremeRating("guild1", "iq", List.of("user1"), true);
|
||||||
|
assertNull(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsNullWhenNoRatings() {
|
||||||
|
assertNull(repository.getExtremeRating("guild1", "iq", List.of(), true));
|
||||||
|
assertNull(repository.getExtremeRating("guild1", "iq", List.of("user1"), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void filtersByGuildAndType() {
|
||||||
|
repository.setRating("guild1", "user1", "iq", 100);
|
||||||
|
repository.setRating("guild2", "user1", "iq", 200);
|
||||||
|
repository.setRating("guild1", "user1", "gay", 90);
|
||||||
|
|
||||||
|
assertEquals(100, repository.getExtremeRating("guild1", "iq", List.of(), true).value());
|
||||||
|
assertEquals(200, repository.getExtremeRating("guild2", "iq", List.of(), true).value());
|
||||||
|
assertEquals(90, repository.getExtremeRating("guild1", "gay", List.of(), true).value());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue