Compare commits
2
Commits
a23e3843ed
...
bedc8b9f02
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bedc8b9f02 | ||
|
|
abdd2d1656 |
No files matched your search
+4
-3
@@ -25,6 +25,7 @@ NETNEUTRALITY_WEBPO_URL=http://webpo:8080
|
|||||||
NETNEUTRALITY_WEBPO_PASSWORD=
|
NETNEUTRALITY_WEBPO_PASSWORD=
|
||||||
|
|
||||||
NETNEUTRALITY_SENTRY_DSN=
|
NETNEUTRALITY_SENTRY_DSN=
|
||||||
GITLAB_HOST=
|
GITEA_HOST=
|
||||||
GITLAB_TOKEN=
|
GITEA_TOKEN=
|
||||||
GITLAB_PROJECT_ID=
|
GITEA_OWNER=
|
||||||
|
GITEA_REPO=
|
||||||
@@ -25,6 +25,7 @@ NETNEUTRALITY_LAVALINK_PASSWORD=youshallnotpass
|
|||||||
NETNEUTRALITY_LAVALINK_NAME=main
|
NETNEUTRALITY_LAVALINK_NAME=main
|
||||||
|
|
||||||
NETNEUTRALITY_SENTRY_DSN=
|
NETNEUTRALITY_SENTRY_DSN=
|
||||||
GITLAB_HOST=
|
GITEA_HOST=
|
||||||
GITLAB_TOKEN=
|
GITEA_TOKEN=
|
||||||
GITLAB_PROJECT_ID=
|
GITEA_OWNER=
|
||||||
|
GITEA_REPO=
|
||||||
@@ -52,7 +52,6 @@ infrastructure.
|
|||||||
| JDBC driver | `com.mysql:mysql-connector-j` | 9.7.0 |
|
| JDBC driver | `com.mysql:mysql-connector-j` | 9.7.0 |
|
||||||
| DB migrations | `org.flywaydb:flyway-core` / `flyway-mysql` | 11.20.3 |
|
| DB migrations | `org.flywaydb:flyway-core` / `flyway-mysql` | 11.20.3 |
|
||||||
| Error tracking | `io.sentry:sentry` | 8.41.0 |
|
| Error tracking | `io.sentry:sentry` | 8.41.0 |
|
||||||
| GitLab integration | `org.gitlab4j:gitlab4j-api` | 6.2.0 |
|
|
||||||
| HTTP client | `com.squareup.okhttp3:okhttp-jvm` | 5.3.2 |
|
| HTTP client | `com.squareup.okhttp3:okhttp-jvm` | 5.3.2 |
|
||||||
| JSON | `org.json:json` + `com.google.code.gson:gson` | — |
|
| JSON | `org.json:json` + `com.google.code.gson:gson` | — |
|
||||||
| Classpath scanning | `org.reflections:reflections` | 0.10.2 |
|
| Classpath scanning | `org.reflections:reflections` | 0.10.2 |
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
# Commands
|
# Commands
|
||||||
|
|
||||||
**Packages:** `me.bobert.netneutrality.framework.command` (framework),
|
**Packages:** `me.bobert.netneutrality.framework.command` (framework),
|
||||||
`me.bobert.netneutrality.feature.*.command` (concrete commands)
|
`me.bobert.netneutrality.feature.*.command` (concrete commands)
|
||||||
@@ -139,7 +139,7 @@ Concrete commands live under `me.bobert.netneutrality.feature.*.command`, groupe
|
|||||||
| `PingCommand` | `basic` | Slash | Reply with the gateway ping. |
|
| `PingCommand` | `basic` | Slash | Reply with the gateway ping. |
|
||||||
| `ConfigureCommand` | `bot` | Slash | Configure the bot for the server (subcommands). |
|
| `ConfigureCommand` | `bot` | Slash | Configure the bot for the server (subcommands). |
|
||||||
| `CryptoPriceCommand` | `crypto` | Slash | Fetch cryptocurrency prices. |
|
| `CryptoPriceCommand` | `crypto` | Slash | Fetch cryptocurrency prices. |
|
||||||
| `ReportIssueCommand` | `git` | Slash | Report a bug (opens the GitLab report flow). |
|
| `ReportIssueCommand` | `git` | Slash | Report a bug (opens the Gitea report flow). |
|
||||||
| `PlayCommand` | `music` | Slash | Search/queue and start playback. |
|
| `PlayCommand` | `music` | Slash | Search/queue and start playback. |
|
||||||
| `QueueCommand` / `ViewQueueCommand` | `music` | Slash | View/manage the queue. |
|
| `QueueCommand` / `ViewQueueCommand` | `music` | Slash | View/manage the queue. |
|
||||||
| `SkipCommand` / `VoteSkipCommand` | `music` | Slash | Skip (or vote-skip) the current track. |
|
| `SkipCommand` / `VoteSkipCommand` | `music` | Slash | Skip (or vote-skip) the current track. |
|
||||||
|
|||||||
+4
-5
@@ -1,4 +1,4 @@
|
|||||||
# Tasks
|
# Tasks
|
||||||
|
|
||||||
**Packages:** `me.bobert.netneutrality.framework.task` (framework),
|
**Packages:** `me.bobert.netneutrality.framework.task` (framework),
|
||||||
`me.bobert.netneutrality.feature.*.task` (concrete tasks)
|
`me.bobert.netneutrality.feature.*.task` (concrete tasks)
|
||||||
@@ -249,7 +249,7 @@ Tasks can opt into extra behavior by implementing marker/behavior interfaces in
|
|||||||
| `EmojiReactionRoleTask` | `emoji` | `EmojiTask` (persistable) | Assign/remove a role when a user reacts with a mapped emoji. |
|
| `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. |
|
| `AcceptButton` | `generic` | `ButtonTask` | Generic "accept" button running a supplied function. |
|
||||||
| `CancelButton` | `generic` | `ButtonTask` | Generic "cancel" 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. |
|
| `ReportIssueTask` | `git` | `ModalTask` | Modal to report a bug; creates a Gitea issue. |
|
||||||
| `BotInfo` | `help` | `AbstractBuiltInTask` | Show bot info + invite link. |
|
| `BotInfo` | `help` | `AbstractBuiltInTask` | Show bot info + invite link. |
|
||||||
| `CommandHelp` | `help` | `AbstractBuiltInTask` | Show command help + the bot prefix. |
|
| `CommandHelp` | `help` | `AbstractBuiltInTask` | Show command help + the bot prefix. |
|
||||||
| `ReportBug` | `help` | `AbstractBuiltInTask` | Start a bug-report flow / provide the report link. |
|
| `ReportBug` | `help` | `AbstractBuiltInTask` | Start a bug-report flow / provide the report link. |
|
||||||
@@ -275,12 +275,11 @@ Tasks can opt into extra behavior by implementing marker/behavior interfaces in
|
|||||||
on the next startup via the
|
on the next startup via the
|
||||||
[`PersistableTaskRegistry`](#persistabletaskregistry--discovery-and-restore).
|
[`PersistableTaskRegistry`](#persistabletaskregistry--discovery-and-restore).
|
||||||
|
|
||||||
### Worked example — GitLab bug report (modal)
|
### Worked example — Gitea bug report (modal)
|
||||||
|
|
||||||
1. `ReportIssueTask.replyToInteraction` opens a modal with **title** and **description** fields.
|
1. `ReportIssueTask.replyToInteraction` opens a modal with **title** and **description** fields.
|
||||||
2. On submit, `ModalTaskContext` routes back to the task; `execute` reads the field values.
|
2. On submit, `ModalTaskContext` routes back to the task; `execute` reads the field values.
|
||||||
3. The task connects to GitLab (using `GITLAB_HOST` / `GITLAB_TOKEN` / `GITLAB_PROJECT_ID` from
|
3. The task calls the Gitea API via `GiteaClient` (using `GITEA_HOST` / `GITEA_TOKEN` /`r`n `GITEA_OWNER` / `GITEA_REPO` from configuration) and creates an issue, then returns `Lifecycle.REMOVE_SELF`.
|
||||||
configuration) and creates an issue, then returns `Lifecycle.REMOVE_SELF`.
|
|
||||||
|
|
||||||
### Worked example — music results (parent/children)
|
### Worked example — music results (parent/children)
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ An example configuration file is `bot.properties.example`. Key configuration val
|
|||||||
| `NETNEUTRALITY_API_PORT` | Port for the [Web API](web-api.md) (default `9000`). |
|
| `NETNEUTRALITY_API_PORT` | Port for the [Web API](web-api.md) (default `9000`). |
|
||||||
| `MYSQL_*` | Database connection settings (see [persistence.md](persistence.md)). |
|
| `MYSQL_*` | Database connection settings (see [persistence.md](persistence.md)). |
|
||||||
| `NETNEUTRALITY_SENTRY_DSN` | Enables Sentry when present. |
|
| `NETNEUTRALITY_SENTRY_DSN` | Enables Sentry when present. |
|
||||||
| `GITLAB_HOST` / `GITLAB_TOKEN` / `GITLAB_PROJECT_ID` | GitLab issue reporting ([`ReportIssueTask`](tasks.md#worked-example--gitlab-bug-report-modal)). |
|
| `GITEA_HOST` / `GITEA_TOKEN` / `GITEA_OWNER` / `GITEA_REPO` | Gitea issue reporting ([`ReportIssueTask`](tasks.md#worked-example--gitea-bug-report-modal)). Token needs `write:issue`. |
|
||||||
| `NETNEUTRALITY_LAVALINK_URI` / `_PASSWORD` / `_NAME` | Connection settings for the [Lavalink node](music.md#settings). |
|
| `NETNEUTRALITY_LAVALINK_URI` / `_PASSWORD` / `_NAME` | Connection settings for the [Lavalink node](music.md#settings). |
|
||||||
| `NETNEUTRALITY_PO_TOKEN` / `NETNEUTRALITY_VISITOR_DATA` / `NETNEUTRALITY_CIPHER_*` | YouTube auth/cipher settings, consumed by the node via `lavalink/application.yml`. |
|
| `NETNEUTRALITY_PO_TOKEN` / `NETNEUTRALITY_VISITOR_DATA` / `NETNEUTRALITY_CIPHER_*` | YouTube auth/cipher settings, consumed by the node via `lavalink/application.yml`. |
|
||||||
|
|
||||||
|
|||||||
@@ -272,18 +272,6 @@
|
|||||||
<artifactId>sentry</artifactId>
|
<artifactId>sentry</artifactId>
|
||||||
<version>8.41.0</version>
|
<version>8.41.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.gitlab4j</groupId>
|
|
||||||
<artifactId>gitlab4j-api</artifactId>
|
|
||||||
<version>6.2.0</version>
|
|
||||||
<exclusions>
|
|
||||||
<!-- Clashes with the Servlet API used by Javalin's Jetty 12 in the shaded jar. -->
|
|
||||||
<exclusion>
|
|
||||||
<groupId>jakarta.servlet</groupId>
|
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.fellbaum</groupId>
|
<groupId>net.fellbaum</groupId>
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
|||||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||||
import me.bobert.netneutrality.core.command.impl.AbstractCommand;
|
import me.bobert.netneutrality.core.command.impl.AbstractCommand;
|
||||||
import me.bobert.netneutrality.core.context.CommandContext;
|
import me.bobert.netneutrality.core.context.CommandContext;
|
||||||
import me.bobert.netneutrality.http.WebModule;
|
import me.bobert.netneutrality.utils.GiteaClient;
|
||||||
import me.bobert.netneutrality.tasks.git.ReportIssueTask;
|
import me.bobert.netneutrality.tasks.git.ReportIssueTask;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||||
|
|
||||||
@@ -72,15 +71,16 @@ public class ReportIssueCommand extends AbstractCommand {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandExecutionResult execute(CommandContext context) {
|
public CommandExecutionResult execute(CommandContext context) {
|
||||||
String gitlabHost = settings.get("GITLAB_HOST");
|
String host = settings.get("GITEA_HOST");
|
||||||
String gitlabToken = settings.get("GITLAB_TOKEN");
|
String token = settings.get("GITEA_TOKEN");
|
||||||
String projectId = settings.get("GITLAB_PROJECT_ID");
|
String owner = settings.get("GITEA_OWNER");
|
||||||
|
String repo = settings.get("GITEA_REPO");
|
||||||
|
|
||||||
if (gitlabHost == null || gitlabToken == null || projectId == null) {
|
if (host == null || token == null || owner == null || repo == null) {
|
||||||
return CommandExecutionResult.error("Command is not configured, failed to create issue.");
|
return CommandExecutionResult.error("Command is not configured, failed to create issue.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new WebModule.Builder(gitlabHost).ping().timeout(2, TimeUnit.SECONDS).build().request().isSuccess()) {
|
if (new GiteaClient(host, token, owner, repo).ping()) {
|
||||||
return CommandExecutionResult.task(new ReportIssueTask(taskManager, context, settings));
|
return CommandExecutionResult.task(new ReportIssueTask(taskManager, context, settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ import net.dv8tion.jda.api.interactions.modals.ModalMapping;
|
|||||||
import net.dv8tion.jda.api.modals.Modal;
|
import net.dv8tion.jda.api.modals.Modal;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.gitlab4j.api.GitLabApi;
|
import me.bobert.netneutrality.utils.GiteaClient;
|
||||||
import org.gitlab4j.api.GitLabApiException;
|
|
||||||
import org.gitlab4j.api.models.Issue;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class ReportIssueTask extends ModalTask {
|
public class ReportIssueTask extends ModalTask {
|
||||||
private static final Logger logger = LogManager.getLogger(ReportIssueTask.class);
|
private static final Logger logger = LogManager.getLogger(ReportIssueTask.class);
|
||||||
@@ -72,14 +72,16 @@ public class ReportIssueTask extends ModalTask {
|
|||||||
public Lifecycle execute(TaskContext context) {
|
public Lifecycle execute(TaskContext context) {
|
||||||
context.ephemeral();
|
context.ephemeral();
|
||||||
|
|
||||||
String gitlabHost = settings.get("GITLAB_HOST");
|
String host = settings.get("GITEA_HOST");
|
||||||
String gitlabToken = settings.get("GITLAB_TOKEN");
|
String token = settings.get("GITEA_TOKEN");
|
||||||
String projectId = settings.get("GITLAB_PROJECT_ID");
|
String owner = settings.get("GITEA_OWNER");
|
||||||
|
String repo = settings.get("GITEA_REPO");
|
||||||
|
|
||||||
if (gitlabHost == null || gitlabHost.isBlank()
|
if (host == null || host.isBlank()
|
||||||
|| gitlabToken == null || gitlabToken.isBlank()
|
|| token == null || token.isBlank()
|
||||||
|| projectId == null || projectId.isBlank()) {
|
|| owner == null || owner.isBlank()
|
||||||
logger.error("Gitlab is not configured in the bot settings, can't execute task");
|
|| repo == null || repo.isBlank()) {
|
||||||
|
logger.error("Gitea is not configured in the bot settings, can't execute task");
|
||||||
context.reply("Command is not configured, failed to create issue.");
|
context.reply("Command is not configured, failed to create issue.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -93,8 +95,7 @@ public class ReportIssueTask extends ModalTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
User user = context.getDiscordUser();
|
User user = context.getDiscordUser();
|
||||||
try (GitLabApi gitLabApi = new GitLabApi(gitlabHost, gitlabToken)) {
|
try {
|
||||||
gitLabApi.setRequestTimeout(1000, 5000);
|
|
||||||
|
|
||||||
String title = String.format("Bug report: %s", modalTitle.getAsString());
|
String title = String.format("Bug report: %s", modalTitle.getAsString());
|
||||||
String description = "Issue reported from user of bot\n\n" +
|
String description = "Issue reported from user of bot\n\n" +
|
||||||
@@ -102,11 +103,11 @@ public class ReportIssueTask extends ModalTask {
|
|||||||
"Last Sentry ID: " + Sentry.getLastEventId() + "\n\n" +
|
"Last Sentry ID: " + Sentry.getLastEventId() + "\n\n" +
|
||||||
"Description from user: \n\n" + modalDescription.getAsString();
|
"Description from user: \n\n" + modalDescription.getAsString();
|
||||||
|
|
||||||
Issue issue = gitLabApi.getIssuesApi().createIssue(projectId, title, description);
|
String url = new GiteaClient(host, token, owner, repo).createIssue(title, description);
|
||||||
|
|
||||||
context.reply(String.format("Issue was successfully created: %s", issue.getWebUrl()));
|
context.reply(String.format("Issue was successfully created: %s", url));
|
||||||
|
|
||||||
} catch (GitLabApiException e) {
|
} catch (IOException e) {
|
||||||
context.reply("Failed to create issue.");
|
context.reply("Failed to create issue.");
|
||||||
LogHandler.log(e);
|
LogHandler.log(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package me.bobert.netneutrality.utils;
|
||||||
|
/*
|
||||||
|
* Copyright (c) anthorob
|
||||||
|
*
|
||||||
|
* https://discord.gg/qUa6zNFZsd
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class GiteaClient {
|
||||||
|
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||||
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
private final String host;
|
||||||
|
private final String token;
|
||||||
|
private final String owner;
|
||||||
|
private final String repo;
|
||||||
|
private final OkHttpClient client;
|
||||||
|
|
||||||
|
public GiteaClient(String host, String token, String owner, String repo) {
|
||||||
|
this(host, token, owner, repo, new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(1))
|
||||||
|
.readTimeout(Duration.ofSeconds(5))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GiteaClient(String host, String token, String owner, String repo, OkHttpClient client) {
|
||||||
|
Objects.requireNonNull(host, "host");
|
||||||
|
this.host = host.endsWith("/") ? host.replaceAll("/+$", "") : host;
|
||||||
|
this.token = Objects.requireNonNull(token, "token");
|
||||||
|
this.owner = Objects.requireNonNull(owner, "owner");
|
||||||
|
this.repo = Objects.requireNonNull(repo, "repo");
|
||||||
|
this.client = Objects.requireNonNull(client, "client");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean ping() {
|
||||||
|
Request request = new Request.Builder().url(host + "/api/v1/version").get().build();
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
return response.isSuccessful();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an issue and returns its html url.
|
||||||
|
*
|
||||||
|
* @throws IOException on network failure, non-201 response or malformed response
|
||||||
|
*/
|
||||||
|
public String createIssue(String title, String body) throws IOException {
|
||||||
|
Map<String, String> payload = new LinkedHashMap<>();
|
||||||
|
payload.put("title", title);
|
||||||
|
payload.put("body", body);
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(host + "/api/v1/repos/" + owner + "/" + repo + "/issues")
|
||||||
|
.header("Authorization", "token " + token)
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.post(RequestBody.create(MAPPER.writeValueAsString(payload), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
if (response.code() != 201) {
|
||||||
|
throw new IOException("Gitea returned HTTP " + response.code() + " when creating issue");
|
||||||
|
}
|
||||||
|
if (response.body() == null) {
|
||||||
|
throw new IOException("Gitea returned an empty response");
|
||||||
|
}
|
||||||
|
JsonNode url = MAPPER.readTree(response.body().string()).get("html_url");
|
||||||
|
if (url == null || !url.isTextual()) {
|
||||||
|
throw new IOException("Gitea response did not contain html_url");
|
||||||
|
}
|
||||||
|
return url.asText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<hibernate-configuration>
|
<hibernate-configuration>
|
||||||
<session-factory>
|
<session-factory>
|
||||||
|
|
||||||
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
|
<property name="hibernate.dialect">org.hibernate.dialect.MariaDBDialect</property>
|
||||||
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
|
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
|
||||||
<property name="hibernate.connection.provider_class">org.hibernate.hikaricp.internal.HikariCPConnectionProvider</property>
|
<property name="hibernate.connection.provider_class">org.hibernate.hikaricp.internal.HikariCPConnectionProvider</property>
|
||||||
<!-- The schema is owned by Flyway; Hibernate only checks that the entities match it. -->
|
<!-- The schema is owned by Flyway; Hibernate only checks that the entities match it. -->
|
||||||
|
|||||||
@@ -6,57 +6,85 @@ package me.bobert.netneutrality.command.git;
|
|||||||
* https://discord.gg/qUa6zNFZsd
|
* https://discord.gg/qUa6zNFZsd
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.gitlab4j.api.GitLabApi;
|
import com.sun.net.httpserver.HttpServer;
|
||||||
import org.gitlab4j.api.IssuesApi;
|
import me.bobert.netneutrality.utils.GiteaClient;
|
||||||
import org.gitlab4j.api.models.Issue;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
class ReportIssueCommandTest {
|
class ReportIssueCommandTest {
|
||||||
private GitLabApi mockGitLabApi;
|
private HttpServer server;
|
||||||
private IssuesApi mockIssuesApi;
|
private final AtomicReference<String> lastAuth = new AtomicReference<>();
|
||||||
|
private final AtomicReference<String> lastBody = new AtomicReference<>();
|
||||||
|
private volatile int status = 201;
|
||||||
|
private volatile String response = "{\"html_url\":\"http://x/o/r/issues/1\"}";
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setup() {
|
void setup() throws IOException {
|
||||||
mockGitLabApi = mock(GitLabApi.class);
|
server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||||
mockIssuesApi = mock(IssuesApi.class);
|
server.createContext("/api/v1/version", ex -> {
|
||||||
|
ex.sendResponseHeaders(200, -1);
|
||||||
|
ex.close();
|
||||||
|
});
|
||||||
|
server.createContext("/api/v1/repos/o/r/issues", ex -> {
|
||||||
|
lastAuth.set(ex.getRequestHeaders().getFirst("Authorization"));
|
||||||
|
lastBody.set(new String(ex.getRequestBody().readAllBytes(), StandardCharsets.UTF_8));
|
||||||
|
byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
|
||||||
|
ex.sendResponseHeaders(status, bytes.length);
|
||||||
|
ex.getResponseBody().write(bytes);
|
||||||
|
ex.close();
|
||||||
|
});
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
|
||||||
when(mockGitLabApi.getIssuesApi()).thenReturn(mockIssuesApi);
|
@AfterEach
|
||||||
|
void teardown() {
|
||||||
|
server.stop(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GiteaClient client() {
|
||||||
|
return new GiteaClient("http://127.0.0.1:" + server.getAddress().getPort() + "/", "tok", "o", "r");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCreateIssueMocked() throws Exception {
|
void createIssueReturnsHtmlUrl() throws IOException {
|
||||||
int projectId = 123456;
|
assertEquals("http://x/o/r/issues/1", client().createIssue("t", "b"));
|
||||||
String modalTitle = "Bug report: mocked bug report";
|
assertEquals("token tok", lastAuth.get());
|
||||||
String modalDescription = "This is a mocked description";
|
assertTrue(lastBody.get().contains("\"title\":\"t\""));
|
||||||
String userName = "TestUser";
|
}
|
||||||
String userId = UUID.randomUUID().toString();
|
|
||||||
String sentryId = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
String expectedTitle = String.format("Bug report: %s", modalTitle);
|
@Test
|
||||||
String expectedDescription = "Issue reported from user of bot\n\n" +
|
void nonCreatedStatusThrows() {
|
||||||
"User: " + userName + String.format("[%s]", userId) + "\n\n" +
|
status = 401;
|
||||||
"Last Sentry ID: " + sentryId + "\n\n" +
|
assertThrows(IOException.class, () -> client().createIssue("t", "b"));
|
||||||
"Description from user: \n\n" + modalDescription;
|
}
|
||||||
|
|
||||||
Issue mockedIssue = new Issue();
|
@Test
|
||||||
mockedIssue.setTitle(expectedTitle);
|
void malformedJsonThrows() {
|
||||||
mockedIssue.setDescription(expectedDescription);
|
response = "not json";
|
||||||
|
assertThrows(IOException.class, () -> client().createIssue("t", "b"));
|
||||||
|
}
|
||||||
|
|
||||||
when(mockIssuesApi.createIssue(eq(projectId), eq(expectedTitle), eq(expectedDescription))).thenReturn(mockedIssue);
|
@Test
|
||||||
|
void missingHtmlUrlThrows() {
|
||||||
|
response = "{}";
|
||||||
|
assertThrows(IOException.class, () -> client().createIssue("t", "b"));
|
||||||
|
}
|
||||||
|
|
||||||
Issue created = mockGitLabApi.getIssuesApi().createIssue(projectId, expectedTitle, expectedDescription);
|
@Test
|
||||||
assertNotNull(created);
|
void pingWorksAndFailsWhenDown() {
|
||||||
assertEquals(expectedTitle, created.getTitle());
|
assertTrue(client().ping());
|
||||||
assertEquals(expectedDescription, created.getDescription());
|
server.stop(0);
|
||||||
|
assertFalse(client().ping());
|
||||||
verify(mockIssuesApi).createIssue(projectId, expectedTitle, expectedDescription);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user