Replace GitLab integration with Gitea in issue reporting
Migrated issue reporting functionality from GitLab to Gitea. Updated environment variables, dependencies, and configuration references to support Gitea. Introduced `GiteaClient` for communication with the Gitea API and refactored related code and tests to align with the new integration.
This commit is contained in:
1 parent
a23e3843ed
commit
abdd2d1656
11 files changed
+194
-89
No files matched your search
@@ -10,11 +10,10 @@ import me.bobert.netneutrality.core.command.CommandExecutionResult;
|
||||
import me.bobert.netneutrality.core.command.CommandOptionsBuilder;
|
||||
import me.bobert.netneutrality.core.command.impl.AbstractCommand;
|
||||
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 java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import me.bobert.netneutrality.core.tasks.TaskManager;
|
||||
import me.bobert.netneutrality.utils.SettingsProvider;
|
||||
|
||||
@@ -72,15 +71,16 @@ public class ReportIssueCommand extends AbstractCommand {
|
||||
|
||||
@Override
|
||||
public CommandExecutionResult execute(CommandContext context) {
|
||||
String gitlabHost = settings.get("GITLAB_HOST");
|
||||
String gitlabToken = settings.get("GITLAB_TOKEN");
|
||||
String projectId = settings.get("GITLAB_PROJECT_ID");
|
||||
String host = settings.get("GITEA_HOST");
|
||||
String token = settings.get("GITEA_TOKEN");
|
||||
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.");
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ import net.dv8tion.jda.api.interactions.modals.ModalMapping;
|
||||
import net.dv8tion.jda.api.modals.Modal;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.gitlab4j.api.GitLabApi;
|
||||
import org.gitlab4j.api.GitLabApiException;
|
||||
import org.gitlab4j.api.models.Issue;
|
||||
import me.bobert.netneutrality.utils.GiteaClient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ReportIssueTask extends ModalTask {
|
||||
private static final Logger logger = LogManager.getLogger(ReportIssueTask.class);
|
||||
@@ -72,14 +72,16 @@ public class ReportIssueTask extends ModalTask {
|
||||
public Lifecycle execute(TaskContext context) {
|
||||
context.ephemeral();
|
||||
|
||||
String gitlabHost = settings.get("GITLAB_HOST");
|
||||
String gitlabToken = settings.get("GITLAB_TOKEN");
|
||||
String projectId = settings.get("GITLAB_PROJECT_ID");
|
||||
String host = settings.get("GITEA_HOST");
|
||||
String token = settings.get("GITEA_TOKEN");
|
||||
String owner = settings.get("GITEA_OWNER");
|
||||
String repo = settings.get("GITEA_REPO");
|
||||
|
||||
if (gitlabHost == null || gitlabHost.isBlank()
|
||||
|| gitlabToken == null || gitlabToken.isBlank()
|
||||
|| projectId == null || projectId.isBlank()) {
|
||||
logger.error("Gitlab is not configured in the bot settings, can't execute task");
|
||||
if (host == null || host.isBlank()
|
||||
|| token == null || token.isBlank()
|
||||
|| owner == null || owner.isBlank()
|
||||
|| 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.");
|
||||
return null;
|
||||
}
|
||||
@@ -93,8 +95,7 @@ public class ReportIssueTask extends ModalTask {
|
||||
}
|
||||
|
||||
User user = context.getDiscordUser();
|
||||
try (GitLabApi gitLabApi = new GitLabApi(gitlabHost, gitlabToken)) {
|
||||
gitLabApi.setRequestTimeout(1000, 5000);
|
||||
try {
|
||||
|
||||
String title = String.format("Bug report: %s", modalTitle.getAsString());
|
||||
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" +
|
||||
"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.");
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,57 +6,85 @@ package me.bobert.netneutrality.command.git;
|
||||
* https://discord.gg/qUa6zNFZsd
|
||||
*/
|
||||
|
||||
import org.gitlab4j.api.GitLabApi;
|
||||
import org.gitlab4j.api.IssuesApi;
|
||||
import org.gitlab4j.api.models.Issue;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import me.bobert.netneutrality.utils.GiteaClient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ReportIssueCommandTest {
|
||||
private GitLabApi mockGitLabApi;
|
||||
private IssuesApi mockIssuesApi;
|
||||
private HttpServer server;
|
||||
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
|
||||
void setup() {
|
||||
mockGitLabApi = mock(GitLabApi.class);
|
||||
mockIssuesApi = mock(IssuesApi.class);
|
||||
void setup() throws IOException {
|
||||
server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
||||
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
|
||||
void testCreateIssueMocked() throws Exception {
|
||||
int projectId = 123456;
|
||||
String modalTitle = "Bug report: mocked bug report";
|
||||
String modalDescription = "This is a mocked description";
|
||||
String userName = "TestUser";
|
||||
String userId = UUID.randomUUID().toString();
|
||||
String sentryId = UUID.randomUUID().toString();
|
||||
|
||||
String expectedTitle = String.format("Bug report: %s", modalTitle);
|
||||
String expectedDescription = "Issue reported from user of bot\n\n" +
|
||||
"User: " + userName + String.format("[%s]", userId) + "\n\n" +
|
||||
"Last Sentry ID: " + sentryId + "\n\n" +
|
||||
"Description from user: \n\n" + modalDescription;
|
||||
|
||||
Issue mockedIssue = new Issue();
|
||||
mockedIssue.setTitle(expectedTitle);
|
||||
mockedIssue.setDescription(expectedDescription);
|
||||
|
||||
when(mockIssuesApi.createIssue(eq(projectId), eq(expectedTitle), eq(expectedDescription))).thenReturn(mockedIssue);
|
||||
|
||||
Issue created = mockGitLabApi.getIssuesApi().createIssue(projectId, expectedTitle, expectedDescription);
|
||||
assertNotNull(created);
|
||||
assertEquals(expectedTitle, created.getTitle());
|
||||
assertEquals(expectedDescription, created.getDescription());
|
||||
|
||||
verify(mockIssuesApi).createIssue(projectId, expectedTitle, expectedDescription);
|
||||
void createIssueReturnsHtmlUrl() throws IOException {
|
||||
assertEquals("http://x/o/r/issues/1", client().createIssue("t", "b"));
|
||||
assertEquals("token tok", lastAuth.get());
|
||||
assertTrue(lastBody.get().contains("\"title\":\"t\""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonCreatedStatusThrows() {
|
||||
status = 401;
|
||||
assertThrows(IOException.class, () -> client().createIssue("t", "b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void malformedJsonThrows() {
|
||||
response = "not json";
|
||||
assertThrows(IOException.class, () -> client().createIssue("t", "b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingHtmlUrlThrows() {
|
||||
response = "{}";
|
||||
assertThrows(IOException.class, () -> client().createIssue("t", "b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pingWorksAndFailsWhenDown() {
|
||||
assertTrue(client().ping());
|
||||
server.stop(0);
|
||||
assertFalse(client().ping());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user