cleanup
This commit is contained in:
1 parent
868dbea8df
commit
a507b471b4
13 files changed
+122
-184
No files matched your search
+18
-18
@@ -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` |
|
||||
+22
-22
@@ -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
|
||||
|
||||
+45
-45
@@ -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)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.bobert.netneutrality.command.basic;
|
||||
package me.bobert.netneutrality.command.bot;
|
||||
/*
|
||||
* Copyright (c) anthorob
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -100,7 +100,7 @@ public abstract class CommandContext extends AbstractContext {
|
||||
|
||||
public CommandOption getOption(String name) {
|
||||
List<CommandOption> options = getOptionsByName(name);
|
||||
return options.isEmpty() ? null : options.get(0);
|
||||
return options.isEmpty() ? null : options.getFirst();
|
||||
}
|
||||
|
||||
public List<CommandOption> getOptionsByName(String name) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<String> getBody() {
|
||||
return Optional.ofNullable(body);
|
||||
}
|
||||
|
||||
public Headers getHeaders() {
|
||||
return headers;
|
||||
public Optional<String> getErrorMessage() {
|
||||
return Optional.ofNullable(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public long getElapsedTime() {
|
||||
return elapsedTime;
|
||||
}
|
||||
|
||||
public Optional<String> getBody() {
|
||||
return Optional.ofNullable(body);
|
||||
}
|
||||
|
||||
public Optional<String> getErrorMessage() {
|
||||
return Optional.ofNullable(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> startLog;
|
||||
private final List<String> stopLog;
|
||||
|
||||
private RecordingComponent(String name, int priority, List<String> startLog, List<String> stopLog) {
|
||||
this.name = name;
|
||||
this.priority = priority;
|
||||
this.startLog = startLog;
|
||||
this.stopLog = stopLog;
|
||||
}
|
||||
private record RecordingComponent(String name, int priority, List<String> startLog,
|
||||
List<String> 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<String> startLog = new ArrayList<>();
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user