From a507b471b47898376643ccb270f2494a40378b59 Mon Sep 17 00:00:00 2001 From: anthorob Date: Thu, 10 Sep 2026 08:32:16 -0400 Subject: [PATCH] cleanup --- docs/contexts.md | 36 ++++---- docs/persistence.md | 44 ++++----- docs/tasks.md | 90 +++++++++---------- .../command/bot/ConfigureCommand.java | 2 +- .../core/command/UsageValidator.java | 2 - .../core/context/CommandContext.java | 2 +- .../core/scheduled/ScheduledTaskManager.java | 2 +- .../core/tasks/TaskManagerImpl.java | 2 +- .../bobert/netneutrality/http/WebModule.java | 52 +++-------- .../bootstrap/ApplicationContextTest.java | 33 ++----- .../bootstrap/ReflectionInjectionTest.java | 30 ++----- .../netneutrality/music/QueueManagerTest.java | 6 +- .../music/TrackSchedulerTest.java | 5 +- 13 files changed, 122 insertions(+), 184 deletions(-) diff --git a/docs/contexts.md b/docs/contexts.md index 042b117..9c9fac5 100644 --- a/docs/contexts.md +++ b/docs/contexts.md @@ -89,12 +89,12 @@ Abstract base for the [command flow](commands.md). It carries the parsed and exposes type-safe option access, e.g. `getOption(name, resolver)`. Concrete subclasses adapt a specific JDA event to this API. -| Class | Wraps | Notes | -|---|---|---| -| `SlashCommandContext` | `SlashCommandInteractionEvent` | Options already parsed by Discord; maps `OptionMapping` → `SlashCommandOption`. Interaction-based replies. | -| `MessageCommandContext` | `MessageReceivedEvent` (prefix) | Contains the **manual parser** that turns the raw message string into `CommandOption`s (mentions, integers, booleans, …). Message-based replies. | -| `MessageMenuContext` | `MessageContextInteractionEvent` | "Apps → …" action on a message; exposes the target `Message`. | -| `UserMenuContext` | `UserContextInteractionEvent` | "Apps → …" action on a user; exposes the target `User`. | +| Class | Wraps | Notes | +|-------------------------|----------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| `SlashCommandContext` | `SlashCommandInteractionEvent` | Options already parsed by Discord; maps `OptionMapping` → `SlashCommandOption`. Interaction-based replies. | +| `MessageCommandContext` | `MessageReceivedEvent` (prefix) | Contains the **manual parser** that turns the raw message string into `CommandOption`s (mentions, integers, booleans, …). Message-based replies. | +| `MessageMenuContext` | `MessageContextInteractionEvent` | "Apps → …" action on a message; exposes the target `Message`. | +| `UserMenuContext` | `UserContextInteractionEvent` | "Apps → …" action on a user; exposes the target `User`. | ## Task contexts @@ -106,11 +106,11 @@ Abstract base for the [task flow](tasks.md). Two things distinguish it: derived depends on the concrete subtype (see the table below and [tasks.md](tasks.md#the-task-id)). - **`contextData` map** — a small state bag used to carry values between steps of a multi-step flow. -| Class | Wraps | `getId()` derived from | -|---|---|---| -| `ButtonTaskContext` | `ButtonInteractionEvent` | `getComponentId()` of the clicked button | -| `ModalTaskContext` | `ModalInteractionEvent` | modal `customId` | -| `EmojiTaskContext` | `MessageReactionAddEvent` | composite `guildId:messageId` | +| Class | Wraps | `getId()` derived from | +|----------------------|------------------------------------|------------------------------------------------------------| +| `ButtonTaskContext` | `ButtonInteractionEvent` | `getComponentId()` of the clicked button | +| `ModalTaskContext` | `ModalInteractionEvent` | modal `customId` | +| `EmojiTaskContext` | `MessageReactionAddEvent` | composite `guildId:messageId` | | `MessageTaskContext` | `MessageReceivedEvent` (no prefix) | the id of the message being replied to (drives step tasks) | ## Reply mechanism & `AsyncContext` @@ -145,10 +145,10 @@ correct channel even when playback state changes without a fresh user interactio ## End-to-end summary -| Event | Context | Flow | Async result wraps | -|---|---|---|---| -| `SlashCommandInteractionEvent` | `SlashCommandContext` | Command | `InteractionHook` | -| `MessageReceivedEvent` (prefix) | `MessageCommandContext` | Command | `Message` | -| `ButtonInteractionEvent` | `ButtonTaskContext` | Task | `InteractionHook` | -| `MessageReactionAddEvent` | `EmojiTaskContext` | Task | `Message` | -| `ModalInteractionEvent` | `ModalTaskContext` | Task | `InteractionHook` | +| Event | Context | Flow | Async result wraps | +|---------------------------------|-------------------------|---------|--------------------| +| `SlashCommandInteractionEvent` | `SlashCommandContext` | Command | `InteractionHook` | +| `MessageReceivedEvent` (prefix) | `MessageCommandContext` | Command | `Message` | +| `ButtonInteractionEvent` | `ButtonTaskContext` | Task | `InteractionHook` | +| `MessageReactionAddEvent` | `EmojiTaskContext` | Task | `Message` | +| `ModalInteractionEvent` | `ModalTaskContext` | Task | `InteractionHook` | diff --git a/docs/persistence.md b/docs/persistence.md index eb4a698..8746c05 100644 --- a/docs/persistence.md +++ b/docs/persistence.md @@ -6,12 +6,12 @@ ## Stack -| Concern | Technology | -|---|---| -| ORM | Hibernate 7.1.9 (Jakarta JPA) | -| Engine | MariaDB 11 (per `docker-compose.yml`), MySQL Connector/J 9.7.0 driver, InnoDB | -| Pooling | HikariCP (via `hibernate-hikaricp`) | -| Migrations | Flyway 11.20.3 (`flyway-core` + `flyway-mysql`) | +| Concern | Technology | +|------------|-------------------------------------------------------------------------------| +| ORM | Hibernate 7.1.9 (Jakarta JPA) | +| Engine | MariaDB 11 (per `docker-compose.yml`), MySQL Connector/J 9.7.0 driver, InnoDB | +| Pooling | HikariCP (via `hibernate-hikaricp`) | +| Migrations | Flyway 11.20.3 (`flyway-core` + `flyway-mysql`) | Configuration is externalized via `Settings` (env vars / `bot.properties`): `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_USER`, `MYSQL_PASSWORD`, `MYSQL_DATABASE`. @@ -47,16 +47,16 @@ Repositories obtain sessions through the factory; transactional access uses help Entities live in `me.bobert.netneutrality.data.entity` and are prefixed with `O`. `OGuild` is the hub of a star-shaped model; most other tables reference `guild_id` with `ON DELETE CASCADE`. -| Entity | Table | Key | Relationships / notes | -|---|---|---|---| -| `OGuild` | `guilds` | `guild_id` (String) | Central entity; one-to-many with settings, blacklist, log messages, logging. | -| `OGuildSetting` | `guild_settings` | `id` (auto int) | Many-to-one `OGuild`; key/value guild settings. | -| `OBlacklist` | `blacklist` | `id` (auto int) | Many-to-one `OGuild`; blacklisted words. | -| `OLogging` | `logging` | `OLoggingId` (composite) | Composite key `guild_id` + `channel_id`; `@MapsId` to `OGuild`; debug/error log channel config. | -| `OLoggingId` | — (embeddable) | — | `@Embeddable`/`@IdClass` for the composite key. | -| `OLogMessage` | `log_message` | `id` (auto int) | Many-to-one `OGuild`; message logging history. | -| `OTask` | `tasks` | `id` (auto int) | [Persistable task](tasks.md#31-persistable-tasks) state; unique `task_id`, class name, JSON `payload`. | -| `OWebUser` | `web_users` | `id` (String) | Web dashboard user (tokens/expiry). *Present in schema & code but not listed in `hibernate.cfg.xml`.* | +| Entity | Table | Key | Relationships / notes | +|-----------------|------------------|--------------------------|--------------------------------------------------------------------------------------------------------| +| `OGuild` | `guilds` | `guild_id` (String) | Central entity; one-to-many with settings, blacklist, log messages, logging. | +| `OGuildSetting` | `guild_settings` | `id` (auto int) | Many-to-one `OGuild`; key/value guild settings. | +| `OBlacklist` | `blacklist` | `id` (auto int) | Many-to-one `OGuild`; blacklisted words. | +| `OLogging` | `logging` | `OLoggingId` (composite) | Composite key `guild_id` + `channel_id`; `@MapsId` to `OGuild`; debug/error log channel config. | +| `OLoggingId` | — (embeddable) | — | `@Embeddable`/`@IdClass` for the composite key. | +| `OLogMessage` | `log_message` | `id` (auto int) | Many-to-one `OGuild`; message logging history. | +| `OTask` | `tasks` | `id` (auto int) | [Persistable task](tasks.md#31-persistable-tasks) state; unique `task_id`, class name, JSON `payload`. | +| `OWebUser` | `web_users` | `id` (String) | Web dashboard user (tokens/expiry). *Present in schema & code but not listed in `hibernate.cfg.xml`.* | ### ER overview @@ -109,12 +109,12 @@ Services wrap repositories with business logic and **caching**. `GSettings` is an **enum** of the available per-guild configuration keys and their defaults: -| Setting | Default | -|---|---| -| `MUSIC_ROLE` | (none) | -| `MUSIC_CHANNEL` | (none) | -| `MUSIC_VOLUME` | `30` | -| `MUSIC_MAXPLAYLISTSIZE` | `20` | +| Setting | Default | +|-------------------------|---------| +| `MUSIC_ROLE` | (none) | +| `MUSIC_CHANNEL` | (none) | +| `MUSIC_VOLUME` | `30` | +| `MUSIC_MAXPLAYLISTSIZE` | `20` | `GuildSettingService` looks up an `OGuildSetting` row by the enum `name()`; if no row exists for a guild/setting, the enum's `defaultValue` is used. This is how `MusicCommand` obtains the guild's music diff --git a/docs/tasks.md b/docs/tasks.md index 01d1dd5..64ae0f8 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -12,15 +12,15 @@ sends a `ButtonInteractionEvent` whose id is `getComponentId()`. A `Task` with t inserted earlier (typically by the command that created the button), so the `TaskManager` can find the task and execute it. The system has since grown to cover several task subtypes: -| Subtype | Trigger | Id source | -|---|---|---| -| [Button task](#button-emoji-and-modal-tasks) | `ButtonInteractionEvent` | button `componentId` | -| [Modal task](#button-emoji-and-modal-tasks) | `ModalInteractionEvent` | modal `customId` | -| [Emoji task](#button-emoji-and-modal-tasks) | `MessageReactionAddEvent` | composite `guildId:messageId` | -| [Step task](#33-step-tasks) | replied-to message | last-message id (assigned dynamically) | -| [Built-in task](#32-built-in-tasks) | button with a hardcoded id | hardcoded id | -| [Persistable task](#31-persistable-tasks) | any of the above, restored from DB | as stored | -| [Scheduled task](#34-scheduled-tasks) | timer | n/a (time-based, not id-based) | +| Subtype | Trigger | Id source | +|----------------------------------------------|------------------------------------|----------------------------------------| +| [Button task](#button-emoji-and-modal-tasks) | `ButtonInteractionEvent` | button `componentId` | +| [Modal task](#button-emoji-and-modal-tasks) | `ModalInteractionEvent` | modal `customId` | +| [Emoji task](#button-emoji-and-modal-tasks) | `MessageReactionAddEvent` | composite `guildId:messageId` | +| [Step task](#33-step-tasks) | replied-to message | last-message id (assigned dynamically) | +| [Built-in task](#32-built-in-tasks) | button with a hardcoded id | hardcoded id | +| [Persistable task](#31-persistable-tasks) | any of the above, restored from DB | as stored | +| [Scheduled task](#34-scheduled-tasks) | timer | n/a (time-based, not id-based) | ## The `Task` id @@ -37,11 +37,11 @@ The id is the **primary lookup key** used by `TaskManager`. How it is derived de `Task` (interface) defines `execute(TaskContext)` and returns a **`Lifecycle`** value that tells the `TaskManager` what to do with the task afterwards: -| Lifecycle | Meaning | -|---|---| -| `BUILTIN` | Task is static and stays registered indefinitely. | -| `KEEP` | Keep the task in memory after execution (can run again). | -| `REMOVE_SELF` | Remove this task after execution (one-shot). | +| Lifecycle | Meaning | +|----------------------------|-------------------------------------------------------------------| +| `BUILTIN` | Task is static and stays registered indefinitely. | +| `KEEP` | Keep the task in memory after execution (can run again). | +| `REMOVE_SELF` | Remove this task after execution (one-shot). | | `REMOVE_SELF_AND_CHILDREN` | Remove this task and all of its [child tasks](#trait-interfaces). | ### Task base classes (`core.tasks.impl`) @@ -137,12 +137,12 @@ The file declares, for each persistable task class, the constructor parameters a Schema: -| Key | Meaning | -|---|---| -| `tasks` | array of task mappings | -| `class` | fully-qualified class name; **must** implement `PersistableTask` | -| `params` | ordered list of payload field names | -| `types` | parallel list of types for those fields | +| Key | Meaning | +|----------|------------------------------------------------------------------| +| `tasks` | array of task mappings | +| `class` | fully-qualified class name; **must** implement `PersistableTask` | +| `params` | ordered list of payload field names | +| `types` | parallel list of types for those fields | Supported types (`TaskFactory.ParamType`): `string` → `String`, `int` → `Integer`, `map` → `Map`. @@ -258,34 +258,34 @@ cancels them all on shutdown. Tasks can opt into extra behavior by implementing marker/behavior interfaces in `core.tasks.trait`: -| Trait | Effect | -|---|---| -| `Deletable` | After successful execution, `TaskManager` deletes the original message/interaction. | -| `NotAcknowledgeable` | Suppresses the default acknowledgement (the task sends its own response). | -| `WithButton` | The task can render itself as a JDA `Button` (`createButton()`). | -| `WithChildren` | The task tracks child tasks and can clean them up together (`REMOVE_SELF_AND_CHILDREN`). | -| `WithParent` | Links a child task to a parent; children share the parent's execution lock. | +| Trait | Effect | +|----------------------|------------------------------------------------------------------------------------------| +| `Deletable` | After successful execution, `TaskManager` deletes the original message/interaction. | +| `NotAcknowledgeable` | Suppresses the default acknowledgement (the task sends its own response). | +| `WithButton` | The task can render itself as a JDA `Button` (`createButton()`). | +| `WithChildren` | The task tracks child tasks and can clean them up together (`REMOVE_SELF_AND_CHILDREN`). | +| `WithParent` | Links a child task to a parent; children share the parent's execution lock. | ## Concrete task catalog -| Task | Package | Base type | Description | -|---|---|---|---| -| `EmojiReactionRoleTask` | `emoji` | `EmojiTask` (persistable) | Assign/remove a role when a user reacts with a mapped emoji. | -| `AcceptButton` | `generic` | `ButtonTask` | Generic "accept" button running a supplied function. | -| `CancelButton` | `generic` | `ButtonTask` | Generic "cancel" button running a supplied function. | -| `ReportIssueTask` | `git` | `ModalTask` | Modal to report a bug; creates a GitLab issue. | -| `BotInfo` | `help` | `AbstractBuiltInTask` | Show bot info + invite link. | -| `CommandHelp` | `help` | `AbstractBuiltInTask` | Show command help + the bot prefix. | -| `ReportBug` | `help` | `AbstractBuiltInTask` | Start a bug-report flow / provide the report link. | -| `LoadPlaylistTask` | `music` | `ButtonTask` (with children) | Present playlist/search results as choice buttons. | -| `LoadTrackFromPlaylistTask` | `music` | `ButtonTask` | Load one specific track from the results. | -| `EmojiRoleFirstStep` | `reaction` | `AbstractStepTask` | Ask for the target role id. | -| `EmojiRoleSecondStep` | `reaction` | `AbstractStepTask` | Ask for the emoji to map. | -| `EmojiRoleModify` | `reaction` | `AbstractStepTask` | Modify an existing emoji-role mapping. | -| `EmojiRoleRemoveStep` | `reaction` | `AbstractStepTask` | Remove an emoji-role mapping. | -| `MusicManagerCleanup` | `scheduled` | `ScheduledTask` | Evict unused per-guild music managers. | -| `MusicPlayerAutoDisconnect` | `scheduled` | `ScheduledTask` | Disconnect from voice when idle. | -| `TaskCleanup` | `scheduled` | `ScheduledTask` | Periodically remove expired non-persistable tasks. | +| Task | Package | Base type | Description | +|-----------------------------|-------------|------------------------------|--------------------------------------------------------------| +| `EmojiReactionRoleTask` | `emoji` | `EmojiTask` (persistable) | Assign/remove a role when a user reacts with a mapped emoji. | +| `AcceptButton` | `generic` | `ButtonTask` | Generic "accept" button running a supplied function. | +| `CancelButton` | `generic` | `ButtonTask` | Generic "cancel" button running a supplied function. | +| `ReportIssueTask` | `git` | `ModalTask` | Modal to report a bug; creates a GitLab issue. | +| `BotInfo` | `help` | `AbstractBuiltInTask` | Show bot info + invite link. | +| `CommandHelp` | `help` | `AbstractBuiltInTask` | Show command help + the bot prefix. | +| `ReportBug` | `help` | `AbstractBuiltInTask` | Start a bug-report flow / provide the report link. | +| `LoadPlaylistTask` | `music` | `ButtonTask` (with children) | Present playlist/search results as choice buttons. | +| `LoadTrackFromPlaylistTask` | `music` | `ButtonTask` | Load one specific track from the results. | +| `EmojiRoleFirstStep` | `reaction` | `AbstractStepTask` | Ask for the target role id. | +| `EmojiRoleSecondStep` | `reaction` | `AbstractStepTask` | Ask for the emoji to map. | +| `EmojiRoleModify` | `reaction` | `AbstractStepTask` | Modify an existing emoji-role mapping. | +| `EmojiRoleRemoveStep` | `reaction` | `AbstractStepTask` | Remove an emoji-role mapping. | +| `MusicManagerCleanup` | `scheduled` | `ScheduledTask` | Evict unused per-guild music managers. | +| `MusicPlayerAutoDisconnect` | `scheduled` | `ScheduledTask` | Disconnect from voice when idle. | +| `TaskCleanup` | `scheduled` | `ScheduledTask` | Periodically remove expired non-persistable tasks. | ### Worked example — emoji-role setup (step → persistable) diff --git a/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java index 32219c5..3a5b674 100644 --- a/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java +++ b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java @@ -1,4 +1,4 @@ -package me.bobert.netneutrality.command.basic; +package me.bobert.netneutrality.command.bot; /* * Copyright (c) anthorob * diff --git a/src/main/java/me/bobert/netneutrality/core/command/UsageValidator.java b/src/main/java/me/bobert/netneutrality/core/command/UsageValidator.java index 11836a3..5ba2501 100644 --- a/src/main/java/me/bobert/netneutrality/core/command/UsageValidator.java +++ b/src/main/java/me/bobert/netneutrality/core/command/UsageValidator.java @@ -14,8 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static java.util.function.Predicate.not; - public class UsageValidator { public boolean validate(CommandContext context, CommandContainer container) { diff --git a/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java b/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java index d3907c1..659c62e 100644 --- a/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java +++ b/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java @@ -100,7 +100,7 @@ public abstract class CommandContext extends AbstractContext { public CommandOption getOption(String name) { List options = getOptionsByName(name); - return options.isEmpty() ? null : options.get(0); + return options.isEmpty() ? null : options.getFirst(); } public List getOptionsByName(String name) { diff --git a/src/main/java/me/bobert/netneutrality/core/scheduled/ScheduledTaskManager.java b/src/main/java/me/bobert/netneutrality/core/scheduled/ScheduledTaskManager.java index e903c6c..a645664 100644 --- a/src/main/java/me/bobert/netneutrality/core/scheduled/ScheduledTaskManager.java +++ b/src/main/java/me/bobert/netneutrality/core/scheduled/ScheduledTaskManager.java @@ -89,7 +89,7 @@ public class ScheduledTaskManager implements Lifecycle { public void cancelAll() { logger.info("Cancelling all {} scheduled tasks", scheduledTasks.size()); - scheduledTasks.forEach((id, future) -> future.cancel(false)); + scheduledTasks.forEach((_, future) -> future.cancel(false)); scheduledTasks.clear(); } diff --git a/src/main/java/me/bobert/netneutrality/core/tasks/TaskManagerImpl.java b/src/main/java/me/bobert/netneutrality/core/tasks/TaskManagerImpl.java index 85ccf94..efe5837 100644 --- a/src/main/java/me/bobert/netneutrality/core/tasks/TaskManagerImpl.java +++ b/src/main/java/me/bobert/netneutrality/core/tasks/TaskManagerImpl.java @@ -182,7 +182,7 @@ public class TaskManagerImpl implements TaskManager { String lockId = task.getLockId(); ReentrantLock lock = (task instanceof PersistableTask || task instanceof BuiltInTask) ? null - : tasksLock.computeIfAbsent(lockId, k -> new ReentrantLock()); + : tasksLock.computeIfAbsent(lockId, _ -> new ReentrantLock()); try { if (lock != null) { diff --git a/src/main/java/me/bobert/netneutrality/http/WebModule.java b/src/main/java/me/bobert/netneutrality/http/WebModule.java index 13b3dc9..35e1fef 100644 --- a/src/main/java/me/bobert/netneutrality/http/WebModule.java +++ b/src/main/java/me/bobert/netneutrality/http/WebModule.java @@ -105,7 +105,7 @@ public class WebModule { try (Response response = client.newCall(request).execute()) { long elapsedTime = System.currentTimeMillis() - startTime; Sentry.addBreadcrumb(Breadcrumb.http(url, method.name(), response.code())); - String body = response.body() != null ? response.body().string() : null; + String body = response.body().string(); this.cachedResponse = new CachedResponse( response.code(), @@ -168,13 +168,13 @@ public class WebModule { } public boolean isSuccess() { - return cachedResponse != null && cachedResponse.getStatusCode() >= 200 && cachedResponse.getStatusCode() < 400; + return cachedResponse != null && cachedResponse.statusCode() >= 200 && cachedResponse.statusCode() < 400; } public static class Builder { private Request request; private final String url; - private String version; + private final String version; private Method method = Method.GET; private long timeout = 5; private TimeUnit timeUnit = TimeUnit.MINUTES; @@ -328,45 +328,19 @@ public class WebModule { } } - private static class CachedResponse { - private final int statusCode; - private final Headers headers; - private final long elapsedTime; - private final String body; - private final long expirationTime; - private final String errorMessage; - - public CachedResponse(int statusCode, Headers headers, long elapsedTime, String body, long expirationTime, String errorMessage) { - this.statusCode = statusCode; - this.headers = headers; - this.elapsedTime = elapsedTime; - this.body = body; - this.expirationTime = expirationTime; - this.errorMessage = errorMessage; - } + private record CachedResponse(int statusCode, Headers headers, long elapsedTime, String body, long expirationTime, + String errorMessage) { public boolean isExpired() { - return System.currentTimeMillis() > expirationTime; - } + return System.currentTimeMillis() > expirationTime; + } - public int getStatusCode() { - return statusCode; - } + public Optional getBody() { + return Optional.ofNullable(body); + } - public Headers getHeaders() { - return headers; + public Optional getErrorMessage() { + return Optional.ofNullable(errorMessage); + } } - - public long getElapsedTime() { - return elapsedTime; - } - - public Optional getBody() { - return Optional.ofNullable(body); - } - - public Optional getErrorMessage() { - return Optional.ofNullable(errorMessage); - } - } } diff --git a/src/test/java/me/bobert/netneutrality/bootstrap/ApplicationContextTest.java b/src/test/java/me/bobert/netneutrality/bootstrap/ApplicationContextTest.java index 63a6e39..988cbda 100644 --- a/src/test/java/me/bobert/netneutrality/bootstrap/ApplicationContextTest.java +++ b/src/test/java/me/bobert/netneutrality/bootstrap/ApplicationContextTest.java @@ -16,35 +16,20 @@ import static org.junit.jupiter.api.Assertions.assertNull; public class ApplicationContextTest { - private static final class RecordingComponent implements Lifecycle { - private final String name; - private final int priority; - private final List startLog; - private final List stopLog; - - private RecordingComponent(String name, int priority, List startLog, List stopLog) { - this.name = name; - this.priority = priority; - this.startLog = startLog; - this.stopLog = stopLog; - } + private record RecordingComponent(String name, int priority, List startLog, + List stopLog) implements Lifecycle { @Override - public void start() { - startLog.add(name); - } + public void start() { + startLog.add(name); + } - @Override - public void stop() { - stopLog.add(name); + @Override + public void stop() { + stopLog.add(name); + } } - @Override - public int priority() { - return priority; - } - } - @Test public void startsAscendingAndStopsDescending() throws Exception { List startLog = new ArrayList<>(); diff --git a/src/test/java/me/bobert/netneutrality/bootstrap/ReflectionInjectionTest.java b/src/test/java/me/bobert/netneutrality/bootstrap/ReflectionInjectionTest.java index eced4b3..f4e45b9 100644 --- a/src/test/java/me/bobert/netneutrality/bootstrap/ReflectionInjectionTest.java +++ b/src/test/java/me/bobert/netneutrality/bootstrap/ReflectionInjectionTest.java @@ -25,30 +25,16 @@ public class ReflectionInjectionTest { public interface Fixture { } - /** Dependency registered in the test context. */ - public static final class SampleDependency { - private final String label; - - public SampleDependency(String label) { - this.label = label; - } - - public String label() { - return label; - } + /** + * Dependency registered in the test context. + */ + public record SampleDependency(String label) { } - /** Fixture that requires a registered dependency via constructor injection. */ - public static final class InjectedFixture implements Fixture { - private final SampleDependency dependency; - - public InjectedFixture(SampleDependency dependency) { - this.dependency = dependency; - } - - public SampleDependency dependency() { - return dependency; - } + /** + * Fixture that requires a registered dependency via constructor injection. + */ + public record InjectedFixture(SampleDependency dependency) implements Fixture { } /** Fixture that only exposes a no-arg constructor (fallback path). */ diff --git a/src/test/java/me/bobert/netneutrality/music/QueueManagerTest.java b/src/test/java/me/bobert/netneutrality/music/QueueManagerTest.java index ddc8889..ff247b7 100644 --- a/src/test/java/me/bobert/netneutrality/music/QueueManagerTest.java +++ b/src/test/java/me/bobert/netneutrality/music/QueueManagerTest.java @@ -22,19 +22,17 @@ import static org.mockito.Mockito.*; public class QueueManagerTest { private AudioPlayer player; - private TrackScheduler scheduler; private MusicManager musicManager; private MusicManagerCallback callback; - private Guild guild; private QueueManager queueManager; @BeforeEach void setUp() { player = mock(AudioPlayer.class); - guild = mock(Guild.class); + Guild guild = mock(Guild.class); when(guild.getId()).thenReturn("guild1"); - scheduler = mock(TrackScheduler.class); + TrackScheduler scheduler = mock(TrackScheduler.class); musicManager = mock(MusicManager.class); callback = mock(MusicManagerCallback.class); diff --git a/src/test/java/me/bobert/netneutrality/music/TrackSchedulerTest.java b/src/test/java/me/bobert/netneutrality/music/TrackSchedulerTest.java index 676f915..2ec269f 100644 --- a/src/test/java/me/bobert/netneutrality/music/TrackSchedulerTest.java +++ b/src/test/java/me/bobert/netneutrality/music/TrackSchedulerTest.java @@ -17,8 +17,6 @@ import static org.mockito.Mockito.*; public class TrackSchedulerTest { private AudioPlayer player; - private MusicManager musicManager; - private QueueManager queueManager; private TrackScheduler scheduler; @BeforeEach @@ -27,12 +25,11 @@ public class TrackSchedulerTest { Guild guild = mock(Guild.class); when(guild.getId()).thenReturn("guild1"); - musicManager = mock(MusicManager.class); + MusicManager musicManager = mock(MusicManager.class); when(musicManager.getPlayer()).thenReturn(player); when(musicManager.getGuild()).thenReturn(guild); scheduler = new TrackScheduler(musicManager); - queueManager = scheduler.getQueueManager(); } @Test