From 73ff8a86059a6feaa578a096eef17fa1be3f3e21 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell <54124162+gmitch215@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:29:41 -0600 Subject: [PATCH 01/51] Draft Unit Testing --- build.gradle | 4 + src/main/java/io/codemc/bot/CodeMCBot.java | 2 +- .../codemc/bot/config/TestConfigHandler.java | 127 ++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/codemc/bot/config/TestConfigHandler.java diff --git a/build.gradle b/build.gradle index b88f1f3..373eb3d 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ repositories { maven { url = 'https://repo.codemc.io/repository/codemc' } maven { url = 'https://repo.codemc.io/repository/codemc' } maven { url = 'https://m2.chew.pro/releases' } + maven { url = 'https://m2.coly.dev/releases' } } dependencies { @@ -51,6 +52,9 @@ dependencies { implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '3.5.0' implementation group: 'org.jetbrains.exposed', name: 'exposed-core', version: '0.56.0' implementation group: 'org.jetbrains.exposed', name: 'exposed-jdbc', version: '0.56.0' + + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.11.3' + testImplementation group: 'dev.coly', name: 'JDATesting', version: '0.7.0' } artifacts { diff --git a/src/main/java/io/codemc/bot/CodeMCBot.java b/src/main/java/io/codemc/bot/CodeMCBot.java index 3d39550..6c9174f 100644 --- a/src/main/java/io/codemc/bot/CodeMCBot.java +++ b/src/main/java/io/codemc/bot/CodeMCBot.java @@ -43,7 +43,7 @@ public class CodeMCBot{ private final Logger logger = LoggerFactory.getLogger(CodeMCBot.class); - private final ConfigHandler configHandler = new ConfigHandler(); + private ConfigHandler configHandler = new ConfigHandler(); public static void main(String[] args){ try{ diff --git a/src/test/java/io/codemc/bot/config/TestConfigHandler.java b/src/test/java/io/codemc/bot/config/TestConfigHandler.java new file mode 100644 index 0000000..87ae673 --- /dev/null +++ b/src/test/java/io/codemc/bot/config/TestConfigHandler.java @@ -0,0 +1,127 @@ +package io.codemc.bot.config; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TestConfigHandler extends ConfigHandler { + + private static final Map TEST_CONFIG = Stream.of( + "bot_token test_token", + "server 405915656039694336", + "channels.request_access 1233971297185431582", + "channels.accepted_requests 784119059138478080", + "channels.rejected_requests 800423355551449098", + "author_role 405918641859723294", + "allowed_roles.applications.accept 405917902865170453,659568973079379971,1233971297185431582", + "allowed_roles.applications.deny 405917902865170453,659568973079379971,1233971297185431582", + "allowed_roles.commands.application 405917902865170453,659568973079379971,1233971297185431582", + "allowed_roles.commands.codemc 405917902865170453,659568973079379971", + "allowed_roles.commands.disable 405917902865170453", + "allowed_roles.commands.msg 405917902865170453", + "allowed_roles.commands.reload 405917902865170453", + "users.owner 204232208049766400", + "users.co_owners 143088571656437760,282975975954710528", + "messages.accepted Accepted", + "messages.denied Rejected", + // Configuration + "jenkins.url http://localhost:8080", + "jenkins.username admin", + "jenkins.token 00000000000000000000000000000000", + + "nexus.url http://localhost:8081", + "nexus.username admin", + "nexus.password password", + + "database.service mariadb", + "database.host localhost", + "database.port 3306", + "database.database test", + "database.username admin", + "database.password password", + "github token" + ).map(s -> s.split("\\s", 1)).collect(Collectors.toMap(s -> s[0], s -> s[1])); + + private final Logger logger = LoggerFactory.getLogger(TestConfigHandler.class); + + + @Override + public boolean loadConfig() { + return true; + } + + @Override + public boolean reloadConfig() { + return true; + } + + @Override + public String getString(Object... path) { + String[] args = Arrays.stream(path).map(Object::toString).toArray(String[]::new); + String key = String.join(".", args); + if (TEST_CONFIG.containsKey(key)) { + return TEST_CONFIG.get(key); + } + logger.warn(() -> "Missing test config value for key: " + key); + return null; + } + + @Override + public long getLong(Object... path) { + String value = getString(path); + if (value == null) { + return -1L; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException ex) { + logger.warn(() -> "Invalid long value for key: " + String.join(".", Arrays.stream(path).map(Object::toString).toArray(String[]::new))); + return -1L; + } + } + + @Override + public int getInt(Object... path) { + String value = getString(path); + if (value == null) { + return -1; + } + try { + return Integer.parseInt(value); + } catch (NumberFormatException ex) { + logger.warn(() -> "Invalid int value for key: " + String.join(".", Arrays.stream(path).map(Object::toString).toArray(String[]::new))); + return -1; + } + } + + @Override + public List getStringList(Object... path) { + String value = getString(path); + if (value == null) { + return null; + } + return Arrays.asList(value.split(",")); + } + + @Override + public List getLongList(Object... path) { + String value = getString(path); + if (value == null) { + return null; + } + try { + return Arrays.stream(value.split(",")).map(Long::parseLong).toList(); + } catch (NumberFormatException ex) { + logger.warn(() -> "Invalid long list value for key: " + String.join(".", Arrays.stream(path).map(Object::toString).toArray(String[]::new))); + return null; + } + } + + + +} From fa68c8f92ba24f5e0840201d4657f7c24744c619 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 15:23:28 +0000 Subject: [PATCH 02/51] Update Workflows --- .github/test.sh | 5 ++ .github/workflows/build.yml | 103 +++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 .github/test.sh diff --git a/.github/test.sh b/.github/test.sh new file mode 100644 index 0000000..c62df44 --- /dev/null +++ b/.github/test.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Setup API Server +git clone https://github.com/CodeMC/API build/tmp/CodeMC-API +./build/tmp/CodeMC-API/.github/test.sh \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c27c40..66355c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build Project on: [push, pull_request, workflow_dispatch] jobs: - build: + setup: runs-on: ubuntu-latest timeout-minutes: 30 @@ -21,5 +21,104 @@ jobs: run: chmod +x ./gradlew - name: Gradle Information run: ./gradlew tasks project dependencies + + build: + needs: setup + runs-on: ubuntu-latest + timeout-minutes: 30 + + name: Gradle Build + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + - name: Change Permissions + run: chmod +x ./gradlew - name: Gradle Build - run: ./gradlew build \ No newline at end of file + run: ./gradlew build + + test: + needs: build + runs-on: ubuntu-latest + timeout-minutes: 30 + + name: Gradle Test + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + - name: Change Permissions + run: chmod +x ./gradlew + - name: Setup Servers + run: bash .github/test.sh + - name: Gradle Test + run: ./gradlew test + - name: Stop Servers + run: bash .github/stop.sh + - name: Stop Servers + if: success() || failure() + run: | + rm -rf /tmp/admin.password + + docker stop jenkins-rest + docker rm jenkins-rest + + docker stop nexus-rest + docker rm nexus-rest + + docker stop mariadb + docker rm mariadb + + docker network rm codemc + - name: Archive Test Reports + uses: actions/upload-artifact@v4 + with: + name: test-report + path: build/reports/tests/test/ + - name: Collect JaCoCo Report + if: ${{ github.event_name != 'pull_request' }} + id: jacoco_reporter + uses: PavanMudigonda/jacoco-reporter@v5.0 + with: + coverage_results_path: build/jacoco.xml + coverage_report_name: Code Coverage + github_token: ${{ secrets.GITHUB_TOKEN }} + skip_check_run: false + minimum_coverage: 85 + fail_below_threshold: false + publish_only_summary: false + + - name: Print JaCoCo Report + if: ${{ github.event_name != 'pull_request' }} + run: | + echo "| Outcome | Value |" >> $GITHUB_STEP_SUMMARY + echo "| --- | --- |" >> $GITHUB_STEP_SUMMARY + echo "| Code Coverage % | ${{ steps.jacoco_reporter.outputs.coverage_percentage }} |" >> $GITHUB_STEP_SUMMARY + echo "| :heavy_check_mark: Number of Lines Covered | ${{ steps.jacoco_reporter.outputs.covered_lines }} |" >> $GITHUB_STEP_SUMMARY + echo "| :x: Number of Lines Missed | ${{ steps.jacoco_reporter.outputs.missed_lines }} |" >> $GITHUB_STEP_SUMMARY + echo "| Total Number of Lines | ${{ steps.jacoco_reporter.outputs.total_lines }} |" >> $GITHUB_STEP_SUMMARY + + - name: Upload Code Coverage Artifacts (Push) + if: ${{ github.event_name != 'pull_request' }} + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: "*/coverage-results.md" + + - name: Upload Code Coverage Artifacts (Pull Request) + if: ${{ github.event_name == 'pull_request' }} + uses: madrapps/jacoco-report@v1.7.1 + with: + paths: build/jacoco.xml + token: ${{ secrets.GITHUB_TOKEN }} + pass-emoji: ✅ + min-coverage-overall: 85 + min-coverage-changed-files: 90 \ No newline at end of file From f638f5ae0c05597bd42b967498180b5443e2d8e1 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 15:23:55 +0000 Subject: [PATCH 03/51] Rename to `Mock` Classes --- .../java/io/codemc/bot/MockCodeMCBot.java | 28 +++++++++++++++++++ ...figHandler.java => MockConfigHandler.java} | 7 ++--- 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 src/test/java/io/codemc/bot/MockCodeMCBot.java rename src/test/java/io/codemc/bot/config/{TestConfigHandler.java => MockConfigHandler.java} (95%) diff --git a/src/test/java/io/codemc/bot/MockCodeMCBot.java b/src/test/java/io/codemc/bot/MockCodeMCBot.java new file mode 100644 index 0000000..9c59c78 --- /dev/null +++ b/src/test/java/io/codemc/bot/MockCodeMCBot.java @@ -0,0 +1,28 @@ +package io.codemc.bot; + +import org.slf4j.LoggerFactory; + +import io.codemc.bot.config.MockConfigHandler; + +public class MockCodeMCBot extends CodeMCBot { + + public static MockCodeMCBot INSTANCE; + + MockCodeMCBot() { + if (INSTANCE != null) { + throw new IllegalStateException("Cannot create multiple instances of TestCodeMCBot"); + } + + INSTANCE = this; + logger = LoggerFactory.getLogger(MockCodeMCBot.class); + configHandler = new MockConfigHandler(); + } + + @Override + void start() { + logger.info("Starting test bot..."); + + validateConfig(); + initializeAPI(); + } +} diff --git a/src/test/java/io/codemc/bot/config/TestConfigHandler.java b/src/test/java/io/codemc/bot/config/MockConfigHandler.java similarity index 95% rename from src/test/java/io/codemc/bot/config/TestConfigHandler.java rename to src/test/java/io/codemc/bot/config/MockConfigHandler.java index 87ae673..259e1d4 100644 --- a/src/test/java/io/codemc/bot/config/TestConfigHandler.java +++ b/src/test/java/io/codemc/bot/config/MockConfigHandler.java @@ -9,7 +9,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public class TestConfigHandler extends ConfigHandler { +public class MockConfigHandler extends ConfigHandler { private static final Map TEST_CONFIG = Stream.of( "bot_token test_token", @@ -43,11 +43,10 @@ public class TestConfigHandler extends ConfigHandler { "database.port 3306", "database.database test", "database.username admin", - "database.password password", - "github token" + "database.password password" ).map(s -> s.split("\\s", 1)).collect(Collectors.toMap(s -> s[0], s -> s[1])); - private final Logger logger = LoggerFactory.getLogger(TestConfigHandler.class); + private final Logger logger = LoggerFactory.getLogger(MockConfigHandler.class); @Override From 0925194f279867d2a1db2c29cbb5f433ee949229 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 15:24:28 +0000 Subject: [PATCH 04/51] Separate Bot Stages into Methods --- src/main/java/io/codemc/bot/CodeMCBot.java | 71 ++++++++++++++-------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/src/main/java/io/codemc/bot/CodeMCBot.java b/src/main/java/io/codemc/bot/CodeMCBot.java index 6c9174f..5e07409 100644 --- a/src/main/java/io/codemc/bot/CodeMCBot.java +++ b/src/main/java/io/codemc/bot/CodeMCBot.java @@ -34,6 +34,8 @@ import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.utils.MemberCachePolicy; + +import org.jetbrains.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,8 +44,11 @@ public class CodeMCBot{ - private final Logger logger = LoggerFactory.getLogger(CodeMCBot.class); - private ConfigHandler configHandler = new ConfigHandler(); + @VisibleForTesting + Logger logger = LoggerFactory.getLogger(CodeMCBot.class); + + @VisibleForTesting + ConfigHandler configHandler = new ConfigHandler(); public static void main(String[] args){ try{ @@ -53,7 +58,37 @@ public static void main(String[] args){ } } - private void start() throws LoginException{ + @VisibleForTesting + void start() throws LoginException{ + validateConfig(); + + String token = configHandler.getString("bot_token"); + long owner = configHandler.getLong("users", "owner"); + long guildId = configHandler.getLong("server"); + + CommandClientBuilder clientBuilder = new CommandClientBuilder().setActivity(null).forceGuildOnly(guildId); + + clientBuilder.setOwnerId(owner); + + List coOwners = configHandler.getLongList("users", "co_owners"); + + if(coOwners != null && !coOwners.isEmpty()){ + logger.info("Adding {} Co-Owner(s) to the bot.", coOwners.size()); + // Annoying, but setCoOwnerIds has no overload with a Collection... + long[] coOwnerIds = new long[coOwners.size()]; + for(int i = 0; i < coOwnerIds.length; i++){ + coOwnerIds[i] = coOwners.get(i); + } + + clientBuilder.setCoOwnerIds(coOwnerIds); + } + + initializeAPI(); + login(clientBuilder, token); + } + + @VisibleForTesting + final void validateConfig() { if(!configHandler.loadConfig()){ logger.warn("Unable to load config.json! See previous logs for any errors."); System.exit(1); @@ -67,37 +102,21 @@ private void start() throws LoginException{ return; } - long owner = configHandler.getLong("users", "owner"); - if(owner == -1L){ + if(configHandler.getLong("users", "owner") == -1L){ logger.warn("Unable to retrieve Owner ID. This value is required!"); System.exit(1); return; } - long guildId = configHandler.getLong("server"); - if(guildId == -1L){ + if(configHandler.getLong("server") == -1L){ logger.warn("Unable to retrieve Server ID. This value is required!"); System.exit(1); return; } - - CommandClientBuilder clientBuilder = new CommandClientBuilder().setActivity(null).forceGuildOnly(guildId); - - clientBuilder.setOwnerId(owner); - - List coOwners = configHandler.getLongList("users", "co_owners"); - - if(coOwners != null && !coOwners.isEmpty()){ - logger.info("Adding {} Co-Owner(s) to the bot.", coOwners.size()); - // Annoying, but setCoOwnerIds has no overload with a Collection... - long[] coOwnerIds = new long[coOwners.size()]; - for(int i = 0; i < coOwnerIds.length; i++){ - coOwnerIds[i] = coOwners.get(i); - } - - clientBuilder.setCoOwnerIds(coOwnerIds); - } + } + @VisibleForTesting + final void initializeAPI() { logger.info("Initializing API..."); JenkinsConfig jenkins = new JenkinsConfig( configHandler.getString("jenkins", "url"), @@ -143,7 +162,9 @@ private void start() throws LoginException{ logger.warn("GitHub API Token is empty! This may cause issues with GitHub API requests."); else logger.info("GitHub API Token set"); - + } + + private void login(CommandClientBuilder clientBuilder, String token) throws LoginException{ logger.info("Adding commands..."); clientBuilder.addSlashCommands( new CmdApplication(this), From 76fa5bf432b3c1e353c8b8adb8cb04dad7cd9479 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 19:54:45 +0000 Subject: [PATCH 05/51] Add Test `config.json`, Update Mock Bot --- .../java/io/codemc/bot/MockCodeMCBot.java | 35 +++-- .../codemc/bot/config/MockConfigHandler.java | 126 ------------------ .../codemc/bot/config/TestConfigHandler.java | 64 +++++++++ src/test/resources/config.json | 84 ++++++++++++ 4 files changed, 175 insertions(+), 134 deletions(-) delete mode 100644 src/test/java/io/codemc/bot/config/MockConfigHandler.java create mode 100644 src/test/java/io/codemc/bot/config/TestConfigHandler.java create mode 100644 src/test/resources/config.json diff --git a/src/test/java/io/codemc/bot/MockCodeMCBot.java b/src/test/java/io/codemc/bot/MockCodeMCBot.java index 9c59c78..fd017c3 100644 --- a/src/test/java/io/codemc/bot/MockCodeMCBot.java +++ b/src/test/java/io/codemc/bot/MockCodeMCBot.java @@ -1,21 +1,40 @@ package io.codemc.bot; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + import org.slf4j.LoggerFactory; -import io.codemc.bot.config.MockConfigHandler; +import io.codemc.bot.config.ConfigHandler; public class MockCodeMCBot extends CodeMCBot { - public static MockCodeMCBot INSTANCE; + public static MockCodeMCBot INSTANCE = new MockCodeMCBot(); - MockCodeMCBot() { - if (INSTANCE != null) { - throw new IllegalStateException("Cannot create multiple instances of TestCodeMCBot"); + private MockCodeMCBot() { + logger = LoggerFactory.getLogger(MockCodeMCBot.class); + configHandler = new ConfigHandler(); + configHandler.loadConfig(); + + // Set Nexus Password + try { + File file = new File("/tmp/admin.password"); + if (file.exists()) { + String password = Files.readString(file.toPath()); + configHandler.set(password, "nexus", "password"); + } + } catch (IOException e) { + logger.error("Failed to read Nexus password from file", e); } - INSTANCE = this; - logger = LoggerFactory.getLogger(MockCodeMCBot.class); - configHandler = new MockConfigHandler(); + // Set GitHub Token + String token = System.getenv("GITHUB_TOKEN"); + if (token != null && !token.isEmpty()) { + configHandler.set(System.getenv("GITHUB_TOKEN"), "github"); + } + + start(); } @Override diff --git a/src/test/java/io/codemc/bot/config/MockConfigHandler.java b/src/test/java/io/codemc/bot/config/MockConfigHandler.java deleted file mode 100644 index 259e1d4..0000000 --- a/src/test/java/io/codemc/bot/config/MockConfigHandler.java +++ /dev/null @@ -1,126 +0,0 @@ -package io.codemc.bot.config; - -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class MockConfigHandler extends ConfigHandler { - - private static final Map TEST_CONFIG = Stream.of( - "bot_token test_token", - "server 405915656039694336", - "channels.request_access 1233971297185431582", - "channels.accepted_requests 784119059138478080", - "channels.rejected_requests 800423355551449098", - "author_role 405918641859723294", - "allowed_roles.applications.accept 405917902865170453,659568973079379971,1233971297185431582", - "allowed_roles.applications.deny 405917902865170453,659568973079379971,1233971297185431582", - "allowed_roles.commands.application 405917902865170453,659568973079379971,1233971297185431582", - "allowed_roles.commands.codemc 405917902865170453,659568973079379971", - "allowed_roles.commands.disable 405917902865170453", - "allowed_roles.commands.msg 405917902865170453", - "allowed_roles.commands.reload 405917902865170453", - "users.owner 204232208049766400", - "users.co_owners 143088571656437760,282975975954710528", - "messages.accepted Accepted", - "messages.denied Rejected", - // Configuration - "jenkins.url http://localhost:8080", - "jenkins.username admin", - "jenkins.token 00000000000000000000000000000000", - - "nexus.url http://localhost:8081", - "nexus.username admin", - "nexus.password password", - - "database.service mariadb", - "database.host localhost", - "database.port 3306", - "database.database test", - "database.username admin", - "database.password password" - ).map(s -> s.split("\\s", 1)).collect(Collectors.toMap(s -> s[0], s -> s[1])); - - private final Logger logger = LoggerFactory.getLogger(MockConfigHandler.class); - - - @Override - public boolean loadConfig() { - return true; - } - - @Override - public boolean reloadConfig() { - return true; - } - - @Override - public String getString(Object... path) { - String[] args = Arrays.stream(path).map(Object::toString).toArray(String[]::new); - String key = String.join(".", args); - if (TEST_CONFIG.containsKey(key)) { - return TEST_CONFIG.get(key); - } - logger.warn(() -> "Missing test config value for key: " + key); - return null; - } - - @Override - public long getLong(Object... path) { - String value = getString(path); - if (value == null) { - return -1L; - } - try { - return Long.parseLong(value); - } catch (NumberFormatException ex) { - logger.warn(() -> "Invalid long value for key: " + String.join(".", Arrays.stream(path).map(Object::toString).toArray(String[]::new))); - return -1L; - } - } - - @Override - public int getInt(Object... path) { - String value = getString(path); - if (value == null) { - return -1; - } - try { - return Integer.parseInt(value); - } catch (NumberFormatException ex) { - logger.warn(() -> "Invalid int value for key: " + String.join(".", Arrays.stream(path).map(Object::toString).toArray(String[]::new))); - return -1; - } - } - - @Override - public List getStringList(Object... path) { - String value = getString(path); - if (value == null) { - return null; - } - return Arrays.asList(value.split(",")); - } - - @Override - public List getLongList(Object... path) { - String value = getString(path); - if (value == null) { - return null; - } - try { - return Arrays.stream(value.split(",")).map(Long::parseLong).toList(); - } catch (NumberFormatException ex) { - logger.warn(() -> "Invalid long list value for key: " + String.join(".", Arrays.stream(path).map(Object::toString).toArray(String[]::new))); - return null; - } - } - - - -} diff --git a/src/test/java/io/codemc/bot/config/TestConfigHandler.java b/src/test/java/io/codemc/bot/config/TestConfigHandler.java new file mode 100644 index 0000000..8000084 --- /dev/null +++ b/src/test/java/io/codemc/bot/config/TestConfigHandler.java @@ -0,0 +1,64 @@ +package io.codemc.bot.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.codemc.bot.MockCodeMCBot; + +public class TestConfigHandler { + + private final ConfigHandler handler = MockCodeMCBot.INSTANCE.getConfigHandler(); + + @Test + public void testGetString() { + assertEquals(handler.getString("bot_token"), "TOKEN"); + assertEquals(handler.getString("github"), "token"); + + assertEquals(handler.getString("jenkins", "username"), "admin"); + assertEquals(handler.getString("jenkins", "url"), "https://ci.codemc.io/"); + + assertEquals(handler.getString("database", "database"), "test"); + assertEquals(handler.getString("database", "host"), "localhost"); + assertEquals(handler.getString("database", "username"), "admin"); + + assertNotEquals(handler.getString("nexus", "password"), "unset"); + } + + @Test + public void testGetLong() { + assertEquals(handler.getLong("server"), 405915656039694336L); + assertEquals(handler.getLong("author_role"), 405918641859723294L); + + assertEquals(handler.getLong("uesrs", "owner"), 204232208049766400L); + assertEquals(handler.getLong("channels", "request_access"), 1233971297185431582L); + assertEquals(handler.getLong("channels", "accepted_requests"), 784119059138478080L); + assertEquals(handler.getLong("channels", "rejected_requests"), 800423355551449098L); + } + + @Test + public void testGetStringList() { + assertEquals(handler.getStringList("messages", "accepted").size(), 4); + assertEquals(handler.getStringList("messages", "accepted").get(0), "Your request has been **accepted**!"); + + assertEquals(handler.getStringList("messages", "denied").size(), 4); + assertEquals(handler.getStringList("messages", "denied").get(0), "Your request has been **rejected**!"); + } + + @Test + public void testGetLongList() { + assertEquals(handler.getLongList("allowed_roles", "applications", "accept").size(), 3); + assertEquals(handler.getLongList("allowed_roles", "applications", "accept").get(0), 405917902865170453L); + + assertEquals(handler.getLongList("allowed_roles", "applications", "deny").size(), 3); + assertEquals(handler.getLongList("allowed_roles", "applications", "deny").get(0), 405917902865170453L); + } + + @Test + public void testGetInt() { + assertEquals(handler.getInt("database", "port"), 3306); + } + +} diff --git a/src/test/resources/config.json b/src/test/resources/config.json new file mode 100644 index 0000000..cadb3cf --- /dev/null +++ b/src/test/resources/config.json @@ -0,0 +1,84 @@ +{ + "bot_token": "TOKEN", + "server": 405915656039694336, + "channels": { + "request_access": 1233971297185431582, + "accepted_requests": 784119059138478080, + "rejected_requests": 800423355551449098 + }, + "author_role": 405918641859723294, + "allowed_roles": { + "applications": { + "accept": [ + 405917902865170453, + 659568973079379971, + 1233971297185431582 + ], + "deny": [ + 405917902865170453, + 659568973079379971, + 1233971297185431582 + ] + }, + "commands": { + "application": [ + 405917902865170453, + 659568973079379971, + 1233971297185431582 + ], + "codemc": [ + 405917902865170453, + 659568973079379971 + ], + "disable": [ + 405917902865170453 + ], + "msg": [ + 405917902865170453 + ], + "reload": [ + 405917902865170453 + ] + } + }, + "users": { + "owner": 204232208049766400, + "co_owners": [ + 143088571656437760, + 282975975954710528 + ] + }, + "messages": { + "accepted": [ + "Your request has been **accepted**!", + "You will now be able to login with your GitHub Account and access the approved Repository on the CI.", + "", + "Remember to [visit our Documentation](https://docs.codemc.io) and [Read our FAQ](https://docs.codemc.io/faq) to learn how to setup automatic builds!" + ], + "denied": [ + "Your request has been **rejected**!", + "The reason for the denial is stated below.", + "", + "You may re-apply unless mentioned otherwise in the Reason." + ] + }, + "jenkins": { + "url": "http://localhost:8080", + "username": "admin", + "token": "00000000000000000000000000000000" + }, + "nexus": { + "url": "http://localhost:8081", + "username": "admin", + "password": "unset" + }, + "database": { + "service": "mariadb", + "host": "localhost", + "port": 3306, + "database": "test", + "username": "admin", + "password": "password" + }, + "github": "unset" +} \ No newline at end of file From c7c8ca4854727676ffbf8c512b52aa25896e1fe4 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 19:55:33 +0000 Subject: [PATCH 06/51] Update build.yml --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 66355c6..4de9a17 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,8 +61,8 @@ jobs: run: bash .github/test.sh - name: Gradle Test run: ./gradlew test - - name: Stop Servers - run: bash .github/stop.sh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Stop Servers if: success() || failure() run: | From 69242ebb49add1583ff604770c04ef1ebf984174 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 19:55:42 +0000 Subject: [PATCH 07/51] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 79785c4..25cc2bf 100644 --- a/.gitignore +++ b/.gitignore @@ -158,4 +158,4 @@ build/ .vscode/ # Copied configuration -config.json \ No newline at end of file +/config.json \ No newline at end of file From fd166df4f82a822ec08114c991a0469d583fbfe4 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 19:56:14 +0000 Subject: [PATCH 08/51] Add Test Task & JaCoCo Configuration --- build.gradle | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/build.gradle b/build.gradle index 373eb3d..f794e06 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ plugins { id "java" id "application" id "com.github.johnrengelman.shadow" version "8.1.1" + id "jacoco" } group 'io.codemc' @@ -57,6 +58,26 @@ dependencies { testImplementation group: 'dev.coly', name: 'JDATesting', version: '0.7.0' } +tasks { + test { + useJUnitPlatform() + } + + jacocoTestReport { + dependsOn test + + reports { + csv.required = false + + xml.required = true + xml.outputLocation = layout.buildDirectory.file("jacoco.xml").get().asFile + + html.required = true + html.outputLocation = layout.buildDirectory.dir("jacocoHtml").get().asFile + } + } +} + artifacts { archives(shadowJar) } From 7215d71d4757c44f151aac64d4cc780551d7ca81 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 19:56:37 +0000 Subject: [PATCH 09/51] Improve Null Safety, Add More Logging --- .../codemc/bot/commands/CmdApplication.java | 25 +++++++++++++------ .../io/codemc/bot/commands/CmdCodeMC.java | 4 +++ .../io/codemc/bot/config/ConfigHandler.java | 11 ++++++++ .../codemc/bot/listeners/ButtonListener.java | 13 +++++++++- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/codemc/bot/commands/CmdApplication.java b/src/main/java/io/codemc/bot/commands/CmdApplication.java index 45df218..e94d9d6 100644 --- a/src/main/java/io/codemc/bot/commands/CmdApplication.java +++ b/src/main/java/io/codemc/bot/commands/CmdApplication.java @@ -30,6 +30,7 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.MessageEmbed.Footer; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; import net.dv8tion.jda.api.exceptions.ErrorHandler; @@ -88,28 +89,36 @@ public static void handle(CodeMCBot bot, InteractionHook hook, Guild guild, long } MessageEmbed embed = embeds.get(0); - if(embed.getFooter() == null || embed.getFields().isEmpty()){ + Footer footer = embed.getFooter(); + if(footer == null || embed.getFields().isEmpty()){ CommandUtil.EmbedReply.from(hook).error("Embed does not have a footer or any Embed Fields.").send(); return; } - - String userId = embed.getFooter().getText().trim(); - if(userId == null || userId.isEmpty()){ + + String footerText = footer.getText(); + if(footerText == null || footerText.isEmpty()){ CommandUtil.EmbedReply.from(hook).error("Embed does not have a valid footer.").send(); return; } - + + String userId = footerText.trim(); String userLink = null; String repoLink = null; for(MessageEmbed.Field field : embed.getFields()){ - if(field.getName() == null || field.getValue() == null) + String name = field.getName(); + if(name == null || field.getValue() == null) continue; - if(field.getName().equalsIgnoreCase("user/organisation:")){ + if(name.equalsIgnoreCase("user/organisation:")){ userLink = field.getValue(); }else - if(field.getName().equalsIgnoreCase("repository:")){ + if(name.equalsIgnoreCase("repository:")){ String link = field.getValue(); + if (link == null || link.isEmpty()) { + CommandUtil.EmbedReply.from(hook).error("Repository field is empty!").send(); + return; + } + String url = link.substring(link.indexOf("(") + 1, link.indexOf(")")); repoLink = url.isEmpty() ? link : url; diff --git a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java index 895845f..a5091f9 100644 --- a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java +++ b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java @@ -234,6 +234,10 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g long id = dbUser.getDiscord(); Member user = guild.getMemberById(id); + if (user == null) { + CommandUtil.EmbedReply.from(hook).success("Successfully removed " + username + " from the CodeMC Services!").send(); + return; + } Role authorRole = guild.getRoleById(bot.getConfigHandler().getLong("author_role")); if (authorRole == null) { diff --git a/src/main/java/io/codemc/bot/config/ConfigHandler.java b/src/main/java/io/codemc/bot/config/ConfigHandler.java index 14aec2f..c0d18bb 100644 --- a/src/main/java/io/codemc/bot/config/ConfigHandler.java +++ b/src/main/java/io/codemc/bot/config/ConfigHandler.java @@ -36,12 +36,14 @@ public class ConfigHandler{ private final Logger logger = LoggerFactory.getLogger(ConfigHandler.class); private final File file = new File("./config.json"); + private boolean loaded = false; private ConfigurationNode node = null; public ConfigHandler(){} public boolean loadConfig(){ + if (loaded) return reloadConfig(); logger.info("Loading config.json..."); if(!file.exists()){ @@ -59,6 +61,7 @@ public boolean loadConfig(){ } } + loaded = true; return reloadConfig(); } @@ -102,4 +105,12 @@ public List getStringList(Object... path){ return Collections.emptyList(); } } + + public void set(Object value, Object... path){ + try{ + node.node(path).set(value); + }catch(SerializationException ex){ + logger.error("Unable to set value in Configuration!", ex); + } + } } diff --git a/src/main/java/io/codemc/bot/listeners/ButtonListener.java b/src/main/java/io/codemc/bot/listeners/ButtonListener.java index 28ac7e9..ba1e548 100644 --- a/src/main/java/io/codemc/bot/listeners/ButtonListener.java +++ b/src/main/java/io/codemc/bot/listeners/ButtonListener.java @@ -32,12 +32,15 @@ import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; import net.dv8tion.jda.api.interactions.modals.Modal; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; public class ButtonListener extends ListenerAdapter{ private final CodeMCBot bot; + private final Logger logger = LoggerFactory.getLogger(ButtonListener.class); public ButtonListener(CodeMCBot bot){ this.bot = bot; @@ -61,6 +64,7 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event){ if(acceptApplicationRoles.isEmpty() || denyApplicationRoles.isEmpty()){ CommandUtil.EmbedReply.from(event).error("No roles for accepting or denying applications set!").send(); + logger.error("No roles for accepting or denying applications set!"); return; } @@ -69,8 +73,15 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event){ CommandUtil.EmbedReply.from(event).error("Cannot get Member from Server!").send(); return; } + + String id = event.getButton().getId(); + if (id == null) { + CommandUtil.EmbedReply.from(event).error("Received Button Interaction with no ID!").send(); + logger.error("Received Button Interaction with no ID!"); + return; + } - String[] values = event.getButton().getId().split(":"); + String[] values = id.split(":"); if(values.length < 4 || !values[0].equals("application")){ CommandUtil.EmbedReply.from(event).error("Received non-application button event!").send(); return; From f0049fb7c161c61c123867ad03cbe0736839cdec Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 13:59:11 -0600 Subject: [PATCH 10/51] Fix Build Workflow --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4de9a17..1c26785 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,10 +39,10 @@ jobs: - name: Change Permissions run: chmod +x ./gradlew - name: Gradle Build - run: ./gradlew build + run: ./gradlew build -x test test: - needs: build + needs: setup runs-on: ubuntu-latest timeout-minutes: 30 @@ -121,4 +121,4 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} pass-emoji: ✅ min-coverage-overall: 85 - min-coverage-changed-files: 90 \ No newline at end of file + min-coverage-changed-files: 90 From 86c6f8b01296f962157a24967fb075fc7fa267db Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Mon, 18 Nov 2024 14:00:46 -0600 Subject: [PATCH 11/51] Update test.sh --- .github/test.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/test.sh b/.github/test.sh index c62df44..a2b7d27 100644 --- a/.github/test.sh +++ b/.github/test.sh @@ -2,4 +2,7 @@ # Setup API Server git clone https://github.com/CodeMC/API build/tmp/CodeMC-API -./build/tmp/CodeMC-API/.github/test.sh \ No newline at end of file +chmod +x ./build/tmp/CodeMC-API/.github/test.sh + +# Run API Server +./build/tmp/CodeMC-API/.github/test.sh From 9bbfd14d8aeed4934eaee0d8a32804f7d52da135 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell <54124162+gmitch215@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:02:25 -0600 Subject: [PATCH 12/51] Add Mockito Dependency --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index f794e06..dc9bf68 100644 --- a/build.gradle +++ b/build.gradle @@ -56,6 +56,7 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.11.3' testImplementation group: 'dev.coly', name: 'JDATesting', version: '0.7.0' + testImplementation group: 'org.mockito', name: 'mockito-core', version: '5.14.2' } tasks { From 47d4c94bc19ae4f071cfaceb644b01a2e1747407 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell <54124162+gmitch215@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:02:58 -0600 Subject: [PATCH 13/51] Remove Redundant Initializers --- src/main/java/io/codemc/bot/commands/CmdApplication.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/codemc/bot/commands/CmdApplication.java b/src/main/java/io/codemc/bot/commands/CmdApplication.java index e94d9d6..f4b7f7b 100644 --- a/src/main/java/io/codemc/bot/commands/CmdApplication.java +++ b/src/main/java/io/codemc/bot/commands/CmdApplication.java @@ -263,7 +263,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g } try { - long messageId = -1L; + long messageId; if (message.contains("-")) messageId = Long.parseLong(message.split("-")[1]); else @@ -309,7 +309,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g } try { - long messageId = -1L; + long messageId; if (message.contains("-")) messageId = Long.parseLong(message.split("-")[1]); else From a14dfbd3513ca97eee26b450bacbc4f3fbb561b9 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell <54124162+gmitch215@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:03:23 -0600 Subject: [PATCH 14/51] Create MockJDA, TestAPIUtil --- src/test/java/io/codemc/bot/MockJDA.java | 114 ++++++++++++++++ .../codemc/bot/config/TestConfigHandler.java | 47 ++++--- .../java/io/codemc/bot/utils/TestAPIUtil.java | 123 ++++++++++++++++++ 3 files changed, 260 insertions(+), 24 deletions(-) create mode 100644 src/test/java/io/codemc/bot/MockJDA.java create mode 100644 src/test/java/io/codemc/bot/utils/TestAPIUtil.java diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java new file mode 100644 index 0000000..d27b62d --- /dev/null +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -0,0 +1,114 @@ +package io.codemc.bot; + +import dev.coly.jdat.JDAObjects; +import dev.coly.util.Callback; +import io.codemc.bot.config.ConfigHandler; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.UserSnowflake; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.InteractionType; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MockJDA { + + private static final ConfigHandler CONFIG = MockCodeMCBot.INSTANCE.getConfigHandler(); + + public static final Guild GUILD = mockGuild(); + + public static final TextChannel REQUEST_CHANNEL = mockChannel("request_access"); + public static final TextChannel ACCEPTED_CHANNEL = mockChannel("accepted_requests"); + public static final TextChannel REJECTED_CHANNEL = mockChannel("rejected_requests"); + public static final List CHANNELS = List.of(REQUEST_CHANNEL, ACCEPTED_CHANNEL, REJECTED_CHANNEL); + + public static final Role ADMINISTRATOR = mockRole("Administrator", 405917902865170453L, 4); + public static final Role MAINTAINER = mockRole("Maintainer", 659568973079379971L, 3); + public static final Role REVIEWER = mockRole("Reviewer", 1233971297185431582L, 2); + public static final Role AUTHOR = mockRole("Author", CONFIG.getLong("author_role"), 1); + public static final List ROLES = List.of(ADMINISTRATOR, MAINTAINER, REVIEWER, AUTHOR); + + public static InteractionHook mockInteractionHook(Member user, MessageChannel channel, InteractionType type) { + InteractionHook hook = mock(InteractionHook.class); + when(hook.getJDA()).thenReturn(JDAObjects.getJDA()); + when(hook.getExpirationTimestamp()).thenReturn(0L); + + Interaction interaction = mock(Interaction.class); + when(interaction.getJDA()).thenReturn(JDAObjects.getJDA()); + when(interaction.getChannel()).thenReturn(channel); + when(interaction.getChannelIdLong()).thenReturn(channel.getIdLong()); + when(interaction.getMember()).thenReturn(user); + when(interaction.getUser()).thenReturn(user.getUser()); + when(interaction.getGuild()).thenReturn(GUILD); + when(interaction.getTypeRaw()).thenReturn(type.getKey()); + + when(hook.getInteraction()).thenReturn(interaction); + return hook; + } + + public static TextChannel mockChannel(String configName) { + long id = CONFIG.getLong("channels", configName); + return (TextChannel) JDAObjects.getMessageChannel(configName.replace('_', '-'), id, Callback.single()); + } + + private static Guild mockGuild() { + Guild guild = mock(Guild.class); + long serverId = CONFIG.getLong("server"); + + when(guild.getIdLong()).thenReturn(serverId); + when(guild.getJDA()).thenReturn(JDAObjects.getJDA()); + + when(guild.addRoleToMember(any(UserSnowflake.class), any(Role.class))).thenAnswer(inv -> { + Member member = JDAObjects.getMember(inv.getArgument(0), "0000"); + Role role = inv.getArgument(1); + + member.getRoles().add(role); + return null; + }); + when(guild.getRoleById(any(Long.class))).thenAnswer(inv -> { + long id = inv.getArgument(0); + return ROLES.stream().filter(role -> role.getIdLong() == id).findFirst().orElse(null); + }); + when(guild.getChannelById(any(), any(Long.class))).thenAnswer(inv -> { + long id = inv.getArgument(1); + return CHANNELS.stream().filter(channel -> channel.getIdLong() == id).findFirst().orElse(null); + }); + + return guild; + } + + public static Member mockMember(String username) { + Member member = JDAObjects.getMember(username, "0000"); + + when(member.getGuild()).thenReturn(GUILD); + + List roles = new ArrayList<>(); + when(member.getRoles()).thenReturn(roles); + + return member; + } + + private static Role mockRole(String name, long id, int position) { + Role role = mock(Role.class); + + when(role.getJDA()).thenReturn(JDAObjects.getJDA()); + when(role.getName()).thenReturn(name); + when(role.getIdLong()).thenReturn(id); + when(role.getGuild()).thenReturn(GUILD); + when(role.getColorRaw()).thenReturn(0); + when(role.getPosition()).thenReturn(position); + when(role.getPositionRaw()).thenReturn(0); + + return role; + } + +} diff --git a/src/test/java/io/codemc/bot/config/TestConfigHandler.java b/src/test/java/io/codemc/bot/config/TestConfigHandler.java index 8000084..ea43ca7 100644 --- a/src/test/java/io/codemc/bot/config/TestConfigHandler.java +++ b/src/test/java/io/codemc/bot/config/TestConfigHandler.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import io.codemc.bot.MockCodeMCBot; @@ -14,51 +13,51 @@ public class TestConfigHandler { @Test public void testGetString() { - assertEquals(handler.getString("bot_token"), "TOKEN"); - assertEquals(handler.getString("github"), "token"); + assertEquals("TOKEN", handler.getString("bot_token")); + assertEquals("token", handler.getString("github")); - assertEquals(handler.getString("jenkins", "username"), "admin"); - assertEquals(handler.getString("jenkins", "url"), "https://ci.codemc.io/"); + assertEquals("admin", handler.getString("jenkins", "username")); + assertEquals("https://ci.codemc.io/", handler.getString("jenkins", "url")); - assertEquals(handler.getString("database", "database"), "test"); - assertEquals(handler.getString("database", "host"), "localhost"); - assertEquals(handler.getString("database", "username"), "admin"); + assertEquals("test", handler.getString("database", "database")); + assertEquals("localhost", handler.getString("database", "host")); + assertEquals("admin", handler.getString("database", "username")); - assertNotEquals(handler.getString("nexus", "password"), "unset"); + assertNotEquals("unset", handler.getString("nexus", "password")); } @Test public void testGetLong() { - assertEquals(handler.getLong("server"), 405915656039694336L); - assertEquals(handler.getLong("author_role"), 405918641859723294L); + assertEquals(405915656039694336L, handler.getLong("server")); + assertEquals(405918641859723294L, handler.getLong("author_role")); - assertEquals(handler.getLong("uesrs", "owner"), 204232208049766400L); - assertEquals(handler.getLong("channels", "request_access"), 1233971297185431582L); - assertEquals(handler.getLong("channels", "accepted_requests"), 784119059138478080L); - assertEquals(handler.getLong("channels", "rejected_requests"), 800423355551449098L); + assertEquals(204232208049766400L, handler.getLong("users", "owner")); + assertEquals(1233971297185431582L, handler.getLong("channels", "request_access")); + assertEquals(784119059138478080L, handler.getLong("channels", "accepted_requests")); + assertEquals(800423355551449098L, handler.getLong("channels", "rejected_requests")); } @Test public void testGetStringList() { - assertEquals(handler.getStringList("messages", "accepted").size(), 4); - assertEquals(handler.getStringList("messages", "accepted").get(0), "Your request has been **accepted**!"); + assertEquals(4, handler.getStringList("messages", "accepted").size()); + assertEquals("Your request has been **accepted**!", handler.getStringList("messages", "accepted").get(0)); - assertEquals(handler.getStringList("messages", "denied").size(), 4); - assertEquals(handler.getStringList("messages", "denied").get(0), "Your request has been **rejected**!"); + assertEquals(4, handler.getStringList("messages", "denied").size()); + assertEquals("Your request has been **rejected**!", handler.getStringList("messages", "denied").get(0)); } @Test public void testGetLongList() { - assertEquals(handler.getLongList("allowed_roles", "applications", "accept").size(), 3); - assertEquals(handler.getLongList("allowed_roles", "applications", "accept").get(0), 405917902865170453L); + assertEquals(3, handler.getLongList("allowed_roles", "applications", "accept").size()); + assertEquals(405917902865170453L, handler.getLongList("allowed_roles", "applications", "accept").get(0)); - assertEquals(handler.getLongList("allowed_roles", "applications", "deny").size(), 3); - assertEquals(handler.getLongList("allowed_roles", "applications", "deny").get(0), 405917902865170453L); + assertEquals(3, handler.getLongList("allowed_roles", "applications", "deny").size()); + assertEquals(405917902865170453L, handler.getLongList("allowed_roles", "applications", "deny").get(0)); } @Test public void testGetInt() { - assertEquals(handler.getInt("database", "port"), 3306); + assertEquals(3306, handler.getInt("database", "port")); } } diff --git a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java new file mode 100644 index 0000000..ef36ef6 --- /dev/null +++ b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java @@ -0,0 +1,123 @@ +package io.codemc.bot.utils; + +import io.codemc.api.jenkins.JenkinsAPI; +import io.codemc.api.nexus.NexusAPI; +import io.codemc.bot.MockJDA; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.InteractionType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestAPIUtil { + + @BeforeAll + public static void ping() { + assertTrue(NexusAPI.ping()); + assertTrue(JenkinsAPI.ping()); + } + + @Test + public void testCreatePassword() { + String p1 = APIUtil.newPassword(); + assertEquals(APIUtil.PASSWORD_SIZE, p1.length()); + + String p2 = APIUtil.newPassword(); + assertEquals(APIUtil.PASSWORD_SIZE, p2.length()); + assertNotEquals(p1, p2); + } + + @Test + public void testIsGroup() { + // Tests if the username is a GitHub Organization + assertTrue(APIUtil.isGroup("CodeMC")); + assertTrue(APIUtil.isGroup("Team-Inceptus")); + + assertFalse(APIUtil.isGroup("gmitch215")); + assertFalse(APIUtil.isGroup("sgdc3")); + assertFalse(APIUtil.isGroup("Andre601")); + + assertFalse(APIUtil.isGroup("_")); + assertFalse(APIUtil.isGroup("-1")); + } + + @Test + public void testNexus() { + String user1 = "gmitch215"; + Member u1 = MockJDA.mockMember(user1); + String p1 = APIUtil.newPassword(); + InteractionHook h1 = MockJDA.mockInteractionHook(u1, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + assertTrue(APIUtil.createNexus(h1, user1, p1)); + assertNotNull(NexusAPI.getNexusUser(user1)); + assertNotNull(NexusAPI.getNexusRepository(user1)); + + assertTrue(NexusAPI.deleteNexus(user1)); + assertNull(NexusAPI.getNexusUser(user1)); + assertNull(NexusAPI.getNexusRepository(user1)); + + String user2 = "CodeMC"; + Member u2 = MockJDA.mockMember(user2); + String p2 = APIUtil.newPassword(); + InteractionHook h2 = MockJDA.mockInteractionHook(u2, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + assertTrue(APIUtil.createNexus(h2, user2, p2)); + assertNotNull(NexusAPI.getNexusUser(user2)); + assertNotNull(NexusAPI.getNexusRepository(user2)); + + assertTrue(NexusAPI.deleteNexus(user2)); + assertNull(NexusAPI.getNexusUser(user2)); + assertNull(NexusAPI.getNexusRepository(user2)); + } + + @Test + public void testJenkins() { + String user1 = "gmitch215"; + String j1 = "SocketMC"; + Member u1 = MockJDA.mockMember(user1); + String p1 = APIUtil.newPassword(); + InteractionHook h1 = MockJDA.mockInteractionHook(u1, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + assertTrue(APIUtil.createJenkinsJob(h1, user1, p1, j1, "https://github.com/gmitch215/SocketMC")); + assertNotNull(JenkinsAPI.getJenkinsUser(user1)); + assertNotNull(JenkinsAPI.getJobInfo(user1, j1)); + + assertTrue(JenkinsAPI.deleteJob(user1, j1)); + assertNull(JenkinsAPI.getJobInfo(user1, j1)); + assertTrue(JenkinsAPI.deleteUser(user1)); + assertNull(JenkinsAPI.getJenkinsUser(user1)); + + String user2 = "CodeMC"; + String j2 = "Bot"; + Member u2 = MockJDA.mockMember(user2); + String p2 = APIUtil.newPassword(); + InteractionHook h2 = MockJDA.mockInteractionHook(u2, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + assertTrue(APIUtil.createJenkinsJob(h2, user2, p2, j2, "https://github.com/CodeMC/Bot")); + assertNotNull(JenkinsAPI.getJenkinsUser(user2)); + assertNotNull(JenkinsAPI.getJobInfo(user2, j2)); + + assertTrue(JenkinsAPI.deleteJob(user2, j2)); + assertNull(JenkinsAPI.getJobInfo(user2, j2)); + assertTrue(JenkinsAPI.deleteUser(user2)); + assertNull(JenkinsAPI.getJenkinsUser(user2)); + } + + @Test + public void testChangePassword() { + String username = "CodeMC"; + String job = "API"; + Member user = MockJDA.mockMember(username); + InteractionHook hook = MockJDA.mockInteractionHook(user, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + String oldPassword = APIUtil.newPassword(); + assertTrue(APIUtil.createNexus(hook, username, oldPassword)); + assertTrue(APIUtil.createJenkinsJob(hook, username, oldPassword, job, "https://github.com/CodeMC/API")); + + String newPassword = APIUtil.newPassword(); + assertTrue(APIUtil.changePassword(hook, username, newPassword)); + } + +} From 9b51a60525198eecd1dc2dc15053d16a18b1fb42 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell <54124162+gmitch215@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:03:45 -0600 Subject: [PATCH 15/51] Fix Server Script --- .github/test.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/test.sh b/.github/test.sh index a2b7d27..e365e1a 100644 --- a/.github/test.sh +++ b/.github/test.sh @@ -5,4 +5,6 @@ git clone https://github.com/CodeMC/API build/tmp/CodeMC-API chmod +x ./build/tmp/CodeMC-API/.github/test.sh # Run API Server -./build/tmp/CodeMC-API/.github/test.sh +cd build/tmp/CodeMC-API +/.github/test.sh +cd ../../../ From 1ab4905410c8d7a8b59fd0aab7448d294353b7a1 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 19 Nov 2024 11:05:59 -0600 Subject: [PATCH 16/51] Update test.sh --- .github/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test.sh b/.github/test.sh index e365e1a..6750695 100644 --- a/.github/test.sh +++ b/.github/test.sh @@ -6,5 +6,5 @@ chmod +x ./build/tmp/CodeMC-API/.github/test.sh # Run API Server cd build/tmp/CodeMC-API -/.github/test.sh +./.github/test.sh cd ../../../ From b55351f0a2d7b03dc445872afd196661b543c743 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 19 Nov 2024 19:26:08 +0000 Subject: [PATCH 17/51] Enable Standard Logging --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index dc9bf68..b4e6a3e 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,13 @@ dependencies { tasks { test { useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + showStandardStreams = true + } + + finalizedBy jacocoTestReport } jacocoTestReport { From 0999ab2a2d6a52f1014747ec99ee13f35b543484 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 19 Nov 2024 19:26:20 +0000 Subject: [PATCH 18/51] Split Configuration Loading --- src/main/java/io/codemc/bot/CodeMCBot.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/codemc/bot/CodeMCBot.java b/src/main/java/io/codemc/bot/CodeMCBot.java index 5e07409..44e000a 100644 --- a/src/main/java/io/codemc/bot/CodeMCBot.java +++ b/src/main/java/io/codemc/bot/CodeMCBot.java @@ -60,6 +60,7 @@ public static void main(String[] args){ @VisibleForTesting void start() throws LoginException{ + loadConfig(); validateConfig(); String token = configHandler.getString("bot_token"); @@ -87,14 +88,18 @@ void start() throws LoginException{ login(clientBuilder, token); } - @VisibleForTesting - final void validateConfig() { + private void loadConfig() { if(!configHandler.loadConfig()){ logger.warn("Unable to load config.json! See previous logs for any errors."); System.exit(1); return; } + logger.info("Loaded config.json"); + } + + @VisibleForTesting + public final void validateConfig() { String token = configHandler.getString("bot_token"); if(token == null || token.isEmpty()){ logger.warn("Received invalid Bot Token!"); @@ -143,7 +148,7 @@ final void initializeAPI() { boolean jenkinsPing = JenkinsAPI.ping(); if (!jenkinsPing) { - logger.error("Failed to connect to Jenkins at {}!", jenkins.getUrl()); + logger.error("Failed to connect to Jenkins at '{}'!", jenkins.getUrl()); System.exit(1); return; } @@ -151,7 +156,7 @@ final void initializeAPI() { boolean nexusPing = NexusAPI.ping(); if (!nexusPing) { - logger.error("Failed to connect to Nexus at {}!", nexus.getUrl()); + logger.error("Failed to connect to Nexus at '{}'!", nexus.getUrl()); System.exit(1); return; } From 9686f662164a45da4c439b29991c3049665d34ed Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 19 Nov 2024 19:26:37 +0000 Subject: [PATCH 19/51] Fix JDA Mocking --- src/test/java/io/codemc/bot/MockJDA.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index d27b62d..73c9237 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -3,6 +3,7 @@ import dev.coly.jdat.JDAObjects; import dev.coly.util.Callback; import io.codemc.bot.config.ConfigHandler; +import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Role; @@ -24,6 +25,7 @@ public class MockJDA { private static final ConfigHandler CONFIG = MockCodeMCBot.INSTANCE.getConfigHandler(); + public static final JDA JDA = JDAObjects.getJDA(); public static final Guild GUILD = mockGuild(); public static final TextChannel REQUEST_CHANNEL = mockChannel("request_access"); @@ -39,15 +41,13 @@ public class MockJDA { public static InteractionHook mockInteractionHook(Member user, MessageChannel channel, InteractionType type) { InteractionHook hook = mock(InteractionHook.class); - when(hook.getJDA()).thenReturn(JDAObjects.getJDA()); + when(hook.getJDA()).thenReturn(JDA); when(hook.getExpirationTimestamp()).thenReturn(0L); Interaction interaction = mock(Interaction.class); - when(interaction.getJDA()).thenReturn(JDAObjects.getJDA()); + when(interaction.getJDA()).thenReturn(JDA); when(interaction.getChannel()).thenReturn(channel); - when(interaction.getChannelIdLong()).thenReturn(channel.getIdLong()); when(interaction.getMember()).thenReturn(user); - when(interaction.getUser()).thenReturn(user.getUser()); when(interaction.getGuild()).thenReturn(GUILD); when(interaction.getTypeRaw()).thenReturn(type.getKey()); @@ -65,7 +65,9 @@ private static Guild mockGuild() { long serverId = CONFIG.getLong("server"); when(guild.getIdLong()).thenReturn(serverId); - when(guild.getJDA()).thenReturn(JDAObjects.getJDA()); + when(guild.getJDA()).thenReturn(JDA); + when(guild.getTextChannels()).thenReturn(CHANNELS); + when(guild.getRoles()).thenReturn(ROLES); when(guild.addRoleToMember(any(UserSnowflake.class), any(Role.class))).thenAnswer(inv -> { Member member = JDAObjects.getMember(inv.getArgument(0), "0000"); @@ -100,7 +102,7 @@ public static Member mockMember(String username) { private static Role mockRole(String name, long id, int position) { Role role = mock(Role.class); - when(role.getJDA()).thenReturn(JDAObjects.getJDA()); + when(role.getJDA()).thenReturn(JDA); when(role.getName()).thenReturn(name); when(role.getIdLong()).thenReturn(id); when(role.getGuild()).thenReturn(GUILD); From d132747a08ab1a101c6c6a8db9d90c961cdac5a4 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 19 Nov 2024 19:27:07 +0000 Subject: [PATCH 20/51] Fix Incorrect Tests --- .../io/codemc/bot/config/TestConfigHandler.java | 3 +-- .../java/io/codemc/bot/utils/TestAPIUtil.java | 15 +++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/codemc/bot/config/TestConfigHandler.java b/src/test/java/io/codemc/bot/config/TestConfigHandler.java index ea43ca7..31d7ab9 100644 --- a/src/test/java/io/codemc/bot/config/TestConfigHandler.java +++ b/src/test/java/io/codemc/bot/config/TestConfigHandler.java @@ -14,10 +14,9 @@ public class TestConfigHandler { @Test public void testGetString() { assertEquals("TOKEN", handler.getString("bot_token")); - assertEquals("token", handler.getString("github")); assertEquals("admin", handler.getString("jenkins", "username")); - assertEquals("https://ci.codemc.io/", handler.getString("jenkins", "url")); + assertEquals("http://localhost:8080", handler.getString("jenkins", "url")); assertEquals("test", handler.getString("database", "database")); assertEquals("localhost", handler.getString("database", "host")); diff --git a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java index ef36ef6..99cac94 100644 --- a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java +++ b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java @@ -2,6 +2,7 @@ import io.codemc.api.jenkins.JenkinsAPI; import io.codemc.api.nexus.NexusAPI; +import io.codemc.bot.MockCodeMCBot; import io.codemc.bot.MockJDA; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.interactions.InteractionHook; @@ -15,6 +16,8 @@ public class TestAPIUtil { @BeforeAll public static void ping() { + MockCodeMCBot.INSTANCE.validateConfig(); + assertTrue(NexusAPI.ping()); assertTrue(JenkinsAPI.ping()); } @@ -81,13 +84,13 @@ public void testJenkins() { InteractionHook h1 = MockJDA.mockInteractionHook(u1, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); assertTrue(APIUtil.createJenkinsJob(h1, user1, p1, j1, "https://github.com/gmitch215/SocketMC")); - assertNotNull(JenkinsAPI.getJenkinsUser(user1)); + assertFalse(JenkinsAPI.getJenkinsUser(user1).isEmpty()); assertNotNull(JenkinsAPI.getJobInfo(user1, j1)); assertTrue(JenkinsAPI.deleteJob(user1, j1)); assertNull(JenkinsAPI.getJobInfo(user1, j1)); assertTrue(JenkinsAPI.deleteUser(user1)); - assertNull(JenkinsAPI.getJenkinsUser(user1)); + assertTrue(JenkinsAPI.getJenkinsUser(user1).isEmpty()); String user2 = "CodeMC"; String j2 = "Bot"; @@ -96,13 +99,13 @@ public void testJenkins() { InteractionHook h2 = MockJDA.mockInteractionHook(u2, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); assertTrue(APIUtil.createJenkinsJob(h2, user2, p2, j2, "https://github.com/CodeMC/Bot")); - assertNotNull(JenkinsAPI.getJenkinsUser(user2)); + assertFalse(JenkinsAPI.getJenkinsUser(user2).isEmpty()); assertNotNull(JenkinsAPI.getJobInfo(user2, j2)); assertTrue(JenkinsAPI.deleteJob(user2, j2)); assertNull(JenkinsAPI.getJobInfo(user2, j2)); assertTrue(JenkinsAPI.deleteUser(user2)); - assertNull(JenkinsAPI.getJenkinsUser(user2)); + assertTrue(JenkinsAPI.getJenkinsUser(user2).isEmpty()); } @Test @@ -118,6 +121,10 @@ public void testChangePassword() { String newPassword = APIUtil.newPassword(); assertTrue(APIUtil.changePassword(hook, username, newPassword)); + + assertTrue(JenkinsAPI.deleteJob(username, job)); + assertTrue(JenkinsAPI.deleteUser(username)); + assertTrue(NexusAPI.deleteNexus(username)); } } From e1d9d5fee9ee6bc524ec3ebf6f1070cfb5909cf2 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 26 Nov 2024 02:39:35 +0000 Subject: [PATCH 21/51] Add Unit Tests for `/codemc jenkins` and `/codemc nexus` --- .../io/codemc/bot/commands/CmdCodeMC.java | 72 +++-- .../java/io/codemc/bot/utils/APIUtil.java | 28 +- .../java/io/codemc/bot/utils/CommandUtil.java | 27 +- .../java/io/codemc/bot/MockCodeMCBot.java | 19 ++ src/test/java/io/codemc/bot/MockJDA.java | 305 +++++++++++++++++- .../io/codemc/bot/commands/TestCmdCodeMC.java | 74 +++++ .../bot/commands/TestCommandListener.java | 27 ++ .../codemc/bot/config/TestConfigHandler.java | 6 + .../java/io/codemc/bot/utils/TestAPIUtil.java | 6 + 9 files changed, 522 insertions(+), 42 deletions(-) create mode 100644 src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java create mode 100644 src/test/java/io/codemc/bot/commands/TestCommandListener.java diff --git a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java index a5091f9..f61e49d 100644 --- a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java +++ b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java @@ -41,6 +41,8 @@ import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; import net.dv8tion.jda.api.requests.ErrorResponse; + +import org.jetbrains.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,7 +53,7 @@ public class CmdCodeMC extends BotCommand { - private static final Logger LOGGER = LoggerFactory.getLogger(CmdCodeMC.class); + static final Logger LOGGER = LoggerFactory.getLogger(CmdCodeMC.class); public CmdCodeMC(CodeMCBot bot) { super(bot); @@ -80,7 +82,8 @@ public void withModalReply(SlashCommandEvent event) { public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild guild, Member member) { } - private static class Jenkins extends BotCommand { + @VisibleForTesting + static class Jenkins extends BotCommand { public Jenkins(CodeMCBot bot) { super(bot); @@ -90,7 +93,6 @@ public Jenkins(CodeMCBot bot) { this.options = List.of( new OptionData(OptionType.STRING, "job", "The Jenkins Job Location to fetch. I.e. \"CodeMC/API\".").setRequired(true) - ); } @@ -106,15 +108,23 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g return; } + MessageEmbed embed = createInfoEmbed(job); + if (embed == null) { + CommandUtil.EmbedReply.from(hook).error("Failed to fetch Jenkins Job Info!").send(); + return; + } + + hook.editOriginalEmbeds(embed).queue(); + } + + @VisibleForTesting + MessageEmbed createInfoEmbed(String job) { String jenkinsUrl = bot.getConfigHandler().getString("jenkins", "url"); String username = job.split("/")[0]; String jobName = job.split("/")[1]; JenkinsJob info = JenkinsAPI.getJobInfo(username, jobName); - if (info == null) { - CommandUtil.EmbedReply.from(hook).error("Failed to fetch Jenkins Job Info!").send(); - return; - } + if (info == null) return null; EmbedBuilder embed = CommandUtil.getEmbed() .setTitle(job, info.getUrl()) @@ -133,12 +143,13 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g if (info.getLastStableBuild() != null) embed.addField("Last Stable Build", info.getLastStableBuild().toString(), false); - - hook.editOriginalEmbeds(embed.build()).queue(); + + return embed.build(); } } - private static class Nexus extends BotCommand { + @VisibleForTesting + static class Nexus extends BotCommand { public Nexus(CodeMCBot bot) { super(bot); @@ -164,14 +175,22 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g return; } + MessageEmbed embed = createInfoEmbed(user); + if (embed == null) { + CommandUtil.EmbedReply.from(hook).error("Failed to fetch Nexus Repository Info!").send(); + return; + } + + hook.sendMessageEmbeds(embed).queue(); + } + + @VisibleForTesting + MessageEmbed createInfoEmbed(String user) { String nexusUrl = bot.getConfigHandler().getString("nexus", "url"); String repository = user.toLowerCase(); JsonObject info = NexusAPI.getNexusRepository(repository); - if (info == null) { - CommandUtil.EmbedReply.from(hook).error("Failed to fetch Nexus Repository Info!").send(); - return; - } + if (info == null) return null; String format = ((JsonPrimitive) info.get("format")).getContent(); String type = ((JsonPrimitive) info.get("type")).getContent(); @@ -183,12 +202,13 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g .addField("Type", type, true) .setTimestamp(Instant.now()) .build(); - - hook.sendMessageEmbeds(embed).queue(); + + return embed; } } - private static class Remove extends BotCommand { + @VisibleForTesting + static class Remove extends BotCommand { public Remove(CodeMCBot bot) { super(bot); @@ -270,7 +290,8 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g } } - private static class Validate extends BotCommand{ + @VisibleForTesting + static class Validate extends BotCommand{ public Validate(CodeMCBot bot) { super(bot); @@ -344,7 +365,8 @@ private boolean validate(InteractionHook hook, String username, AtomicInteger co } } - private static class Link extends BotCommand{ + @VisibleForTesting + static class Link extends BotCommand{ public Link(CodeMCBot bot) { super(bot); @@ -400,7 +422,8 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g } } - private static class Unlink extends BotCommand { + @VisibleForTesting + static class Unlink extends BotCommand { public Unlink(CodeMCBot bot) { super(bot); @@ -447,7 +470,8 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g } } - private static class ChangePassword extends BotCommand { + @VisibleForTesting + static class ChangePassword extends BotCommand { public ChangePassword(CodeMCBot bot) { super(bot); @@ -509,7 +533,8 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g } } - private static class CreateUser extends BotCommand{ + @VisibleForTesting + static class CreateUser extends BotCommand{ public CreateUser(CodeMCBot bot) { super(bot); @@ -577,7 +602,8 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g } } - private static class DeleteUser extends BotCommand{ + @VisibleForTesting + static class DeleteUser extends BotCommand{ public DeleteUser(CodeMCBot bot) { super(bot); diff --git a/src/main/java/io/codemc/bot/utils/APIUtil.java b/src/main/java/io/codemc/bot/utils/APIUtil.java index 38ed493..d50ab30 100644 --- a/src/main/java/io/codemc/bot/utils/APIUtil.java +++ b/src/main/java/io/codemc/bot/utils/APIUtil.java @@ -45,9 +45,10 @@ public static boolean createNexus(InteractionHook hook, String username, String public static boolean createJenkinsJob(InteractionHook hook, String username, String password, String project, String repoLink) { if (!JenkinsAPI.getJenkinsUser(username).isEmpty()) { - CommandUtil.EmbedReply.from(hook) - .error("Jenkins User for " + username + " already exists!") - .send(); + if (hook != null) + CommandUtil.EmbedReply.from(hook) + .error("Jenkins User for " + username + " already exists!") + .send(); LOGGER.error("Jenkins User for {} already exists!", username); return false; @@ -55,9 +56,10 @@ public static boolean createJenkinsJob(InteractionHook hook, String username, St boolean userSuccess = JenkinsAPI.createJenkinsUser(username, password, isGroup(username)); if (!userSuccess) { - CommandUtil.EmbedReply.from(hook) - .error("Failed to create Jenkins User for " + username + "!") - .send(); + if (hook != null) + CommandUtil.EmbedReply.from(hook) + .error("Failed to create Jenkins User for " + username + "!") + .send(); LOGGER.error("Failed to create Jenkins User for {}!", username); return false; @@ -66,9 +68,10 @@ public static boolean createJenkinsJob(InteractionHook hook, String username, St boolean freestyle = JenkinsAPI.isFreestyle(repoLink); boolean jobSuccess = JenkinsAPI.createJenkinsJob(username, project, repoLink, freestyle); if (!jobSuccess) { - CommandUtil.EmbedReply.from(hook) - .error("Failed to create Jenkins Job '" + project + "' for " + username + "!") - .send(); + if (hook != null) + CommandUtil.EmbedReply.from(hook) + .error("Failed to create Jenkins Job '" + project + "' for " + username + "!") + .send(); LOGGER.error("Failed to create Jenkins Job '{}' for {}!", project, username); return false; @@ -76,9 +79,10 @@ public static boolean createJenkinsJob(InteractionHook hook, String username, St boolean triggerBuild = JenkinsAPI.triggerBuild(username, project); if (!triggerBuild) { - CommandUtil.EmbedReply.from(hook) - .error("Failed to trigger Jenkins Build for " + username + "!") - .send(); + if (hook != null) + CommandUtil.EmbedReply.from(hook) + .error("Failed to trigger Jenkins Build for " + username + "!") + .send(); LOGGER.error("Failed to trigger Jenkins Build for {}!", username); return false; diff --git a/src/main/java/io/codemc/bot/utils/CommandUtil.java b/src/main/java/io/codemc/bot/utils/CommandUtil.java index 55aa5b5..c2a3f4e 100644 --- a/src/main/java/io/codemc/bot/utils/CommandUtil.java +++ b/src/main/java/io/codemc/bot/utils/CommandUtil.java @@ -22,6 +22,7 @@ import com.jagrosh.jdautilities.command.SlashCommandEvent; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionHook; @@ -49,6 +50,14 @@ public static boolean hasRole(Member member, List roleIds){ .findFirst() .orElse(null) != null; } + + public static MessageEmbed embedError(String... lines){ + return EmbedReply.empty().error(lines).build(); + } + + public static MessageEmbed embedSuccess(String... lines){ + return EmbedReply.empty().success(lines).build(); + } public static class EmbedReply { @@ -62,6 +71,10 @@ private EmbedReply(T type){ public static EmbedReply from(T type){ return new EmbedReply<>(type); } + + public static EmbedReply empty() { + return new EmbedReply<>(null); + } public EmbedReply success(String... lines){ builder.setDescription(String.join("\n", lines)) @@ -83,19 +96,25 @@ public EmbedReply error(String... lines){ .setColor(0xFF0000); return this; } + + public MessageEmbed build() { + return builder.build(); + } public void send(){ + if(type == null) return; + if(type instanceof SlashCommandEvent commandEvent){ - commandEvent.replyEmbeds(builder.build()).setEphemeral(true).queue(); + commandEvent.replyEmbeds(build()).setEphemeral(true).queue(); }else if(type instanceof ModalInteractionEvent modalEvent){ - modalEvent.replyEmbeds(builder.build()).setEphemeral(true).queue(); + modalEvent.replyEmbeds(build()).setEphemeral(true).queue(); }else if(type instanceof ButtonInteractionEvent buttonEvent){ - buttonEvent.replyEmbeds(builder.build()).setEphemeral(true).queue(); + buttonEvent.replyEmbeds(build()).setEphemeral(true).queue(); }else if(type instanceof InteractionHook hook){ - hook.editOriginal(EmbedBuilder.ZERO_WIDTH_SPACE).setEmbeds(builder.build()).queue(); + hook.editOriginal(EmbedBuilder.ZERO_WIDTH_SPACE).setEmbeds(build()).queue(); }else{ LOG.error("Received unknown Type {} for EmbedReply!", type); } diff --git a/src/test/java/io/codemc/bot/MockCodeMCBot.java b/src/test/java/io/codemc/bot/MockCodeMCBot.java index fd017c3..967b59b 100644 --- a/src/test/java/io/codemc/bot/MockCodeMCBot.java +++ b/src/test/java/io/codemc/bot/MockCodeMCBot.java @@ -6,7 +6,10 @@ import org.slf4j.LoggerFactory; +import io.codemc.api.jenkins.JenkinsAPI; +import io.codemc.api.nexus.NexusAPI; import io.codemc.bot.config.ConfigHandler; +import io.codemc.bot.utils.APIUtil; public class MockCodeMCBot extends CodeMCBot { @@ -44,4 +47,20 @@ void start() { validateConfig(); initializeAPI(); } + + public void create(String username, String job) { + String link = "https://github.com/" + username + "/" + job; + if (JenkinsAPI.getJenkinsUser(username).isEmpty()) { + String password = APIUtil.newPassword(); + APIUtil.createJenkinsJob(null, username, password, job, link); + APIUtil.createNexus(null, username, password); + } else { + JenkinsAPI.createJenkinsJob(username, job, link, JenkinsAPI.isFreestyle(link)); + } + } + + public void delete(String username) { + JenkinsAPI.deleteUser(username); + NexusAPI.deleteNexus(username); + } } diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index 73c9237..cb98356 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -2,22 +2,54 @@ import dev.coly.jdat.JDAObjects; import dev.coly.util.Callback; +import io.codemc.bot.commands.BotCommand; +import io.codemc.bot.commands.TestCommandListener; import io.codemc.bot.config.ConfigHandler; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.IMentionable; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.UserSnowflake; +import net.dv8tion.jda.api.entities.Message.Attachment; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; import net.dv8tion.jda.api.interactions.Interaction; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.InteractionType; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.requests.restaction.MessageEditAction; +import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; +import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; +import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import net.dv8tion.jda.api.utils.messages.MessageRequest; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import org.junit.jupiter.api.Assertions; + +import com.jagrosh.jdautilities.command.SlashCommandEvent; + +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -25,6 +57,10 @@ public class MockJDA { private static final ConfigHandler CONFIG = MockCodeMCBot.INSTANCE.getConfigHandler(); + private static final Map messages = new HashMap<>(); + private static final Map embeds = new HashMap<>(); + private static long CURRENT_ID = 0; + public static final JDA JDA = JDAObjects.getJDA(); public static final Guild GUILD = mockGuild(); @@ -40,19 +76,65 @@ public class MockJDA { public static final List ROLES = List.of(ADMINISTRATOR, MAINTAINER, REVIEWER, AUTHOR); public static InteractionHook mockInteractionHook(Member user, MessageChannel channel, InteractionType type) { + return mockInteractionHook(mockInteraction(user, channel, type)); + } + + public static InteractionHook mockInteractionHook(Interaction interaction) { InteractionHook hook = mock(InteractionHook.class); when(hook.getJDA()).thenReturn(JDA); when(hook.getExpirationTimestamp()).thenReturn(0L); + when(hook.getIdLong()).thenReturn(CURRENT_ID); + + when(hook.getInteraction()).thenReturn(interaction); + + MessageChannel channel = interaction.getMessageChannel(); + + when(hook.editOriginal(anyString())).thenAnswer(inv -> { + String content = inv.getArgument(0); + messages.put(hook.getIdLong(), content); + return mockReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel)); + }); + when(hook.editOriginalEmbeds(any(MessageEmbed[].class))).thenAnswer(inv -> { + Object obj = inv.getArgument(0); + if (obj instanceof MessageEmbed[] allEmbeds) + embeds.put(hook.getIdLong(), allEmbeds); + else + embeds.put(hook.getIdLong(), new MessageEmbed[] { (MessageEmbed) obj }); + + return mockReply(WebhookMessageEditAction.class, hook, mockMessage(null, Arrays.asList(embeds.get(hook.getIdLong())), channel)); + }); + + when(hook.sendMessageEmbeds(any(), any(MessageEmbed[].class))).thenAnswer(inv -> { + MessageEmbed first = inv.getArgument(0); + + List embeds = new ArrayList<>(); + embeds.add(first); + if (inv.getArguments().length > 1) { + Object obj = inv.getArgument(1); + + if (obj instanceof MessageEmbed[] allEmbeds) + embeds.addAll(Arrays.asList(allEmbeds)); + else + embeds.add((MessageEmbed) obj); + } + + return mockReply(WebhookMessageCreateAction.class, hook, mockMessage(null, embeds, channel)); + }); + + return hook; + } + + public static Interaction mockInteraction(Member user, MessageChannel channel, InteractionType type) { Interaction interaction = mock(Interaction.class); when(interaction.getJDA()).thenReturn(JDA); when(interaction.getChannel()).thenReturn(channel); when(interaction.getMember()).thenReturn(user); when(interaction.getGuild()).thenReturn(GUILD); when(interaction.getTypeRaw()).thenReturn(type.getKey()); + when(interaction.getIdLong()).thenReturn(CURRENT_ID); - when(hook.getInteraction()).thenReturn(interaction); - return hook; + return interaction; } public static TextChannel mockChannel(String configName) { @@ -60,6 +142,30 @@ public static TextChannel mockChannel(String configName) { return (TextChannel) JDAObjects.getMessageChannel(configName.replace('_', '-'), id, Callback.single()); } + public static Message mockMessage(String content, MessageChannel channel) { + Message message = JDAObjects.getMessage(content, channel); + messages.put(message.getIdLong(), content); + + when(message.getIdLong()).thenReturn(CURRENT_ID); + when(message.getGuild()).thenReturn(GUILD); + + when(message.editMessage(anyString())).thenAnswer(inv -> { + messages.put(message.getIdLong(), inv.getArgument(0)); + return mockReply(MessageEditAction.class, mockInteractionHook(message.getMember(), channel, InteractionType.COMMAND), message); + }); + + return message; + } + + public static Message mockMessage(String content, List embeds, MessageChannel channel) { + Message message = mockMessage(content, channel); + MockJDA.embeds.put(message.getIdLong(), embeds.toArray(new MessageEmbed[0])); + + when(message.getEmbeds()).thenReturn(embeds); + + return message; + } + private static Guild mockGuild() { Guild guild = mock(Guild.class); long serverId = CONFIG.getLong("server"); @@ -70,7 +176,7 @@ private static Guild mockGuild() { when(guild.getRoles()).thenReturn(ROLES); when(guild.addRoleToMember(any(UserSnowflake.class), any(Role.class))).thenAnswer(inv -> { - Member member = JDAObjects.getMember(inv.getArgument(0), "0000"); + Member member = inv.getArgument(0); Role role = inv.getArgument(1); member.getRoles().add(role); @@ -113,4 +219,197 @@ private static Role mockRole(String name, long id, int position) { return role; } + private static void assertEmbeds(long id, List expectedOutputs, boolean ignoreTimestamp) { + MessageEmbed[] embeds = MockJDA.embeds.get(id); + + assertEquals(expectedOutputs.size(), embeds.length, "Number of embeds"); + + int i = 0; + for (MessageEmbed embed : embeds) { + MessageEmbed expectedOutput = expectedOutputs.get(i); + assertEmbed(embed, expectedOutput, ignoreTimestamp); + i++; + } + } + + public static void assertEmbed(MessageEmbed embed, MessageEmbed expectedOutput, boolean ignoreTimestamp) { + Assertions.assertEquals(expectedOutput.getTitle(), embed.getTitle()); + Assertions.assertEquals(expectedOutput.getColor(), embed.getColor()); + Assertions.assertEquals(expectedOutput.getDescription(), embed.getDescription()); + Assertions.assertEquals(expectedOutput.getUrl(), embed.getUrl()); + if (expectedOutput.getAuthor() != null) + Assertions.assertEquals(expectedOutput.getAuthor().getName(), Objects.requireNonNull(embed.getAuthor()).getName()); + + if (expectedOutput.getFooter() != null) { + Assertions.assertEquals(expectedOutput.getFooter().getText(), Objects.requireNonNull(embed.getFooter()).getText()); + Assertions.assertEquals(expectedOutput.getFooter().getIconUrl(), Objects.requireNonNull(embed.getFooter()).getIconUrl()); + } + + if (expectedOutput.getImage() != null) + Assertions.assertEquals(expectedOutput.getImage().getUrl(), Objects.requireNonNull(embed.getImage()).getUrl()); + + if (expectedOutput.getThumbnail() != null) + Assertions.assertEquals(expectedOutput.getThumbnail().getUrl(), Objects.requireNonNull(embed.getThumbnail()).getUrl()); + + int i = 0; + for (MessageEmbed.Field field : embed.getFields()) { + try { + Assertions.assertEquals(expectedOutput.getFields().get(i).getName(), field.getName()); + Assertions.assertEquals(expectedOutput.getFields().get(i).getValue(), field.getValue()); + Assertions.assertEquals(expectedOutput.getFields().get(i).isInline(), field.isInline()); + } catch (IndexOutOfBoundsException e) { + Assertions.fail("Too many fields in embed: " + field.getName() + " - '" + field.getValue() + "'"); + } + i++; + } + + if (!ignoreTimestamp) + Assertions.assertEquals(expectedOutput.getTimestamp(), embed.getTimestamp()); + } + + public static void assertSlashCommandEvent(TestCommandListener listener, Map options, MessageEmbed... outputs) { + BotCommand command = listener.getCommand(); + + SlashCommandEvent event = mockSlashCommandEvent(REQUEST_CHANNEL, command, options); + listener.onEvent(event); + + assertEmbeds(event.getIdLong(), Arrays.asList(outputs), true); + CURRENT_ID++; + } + + public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, BotCommand command, Map options) { + SlashCommandEvent event = mock(SlashCommandEvent.class); + when(event.getName()).thenAnswer(invocation -> command.getName()); + when(event.getSubcommandName()).thenAnswer(invocation -> command.getName()); + when(event.getSubcommandGroup()).thenAnswer(invocation -> command.getSubcommandGroup()); + when(event.getChannel()).thenAnswer(invocation -> channel); + when(event.getGuild()).thenAnswer(invocation -> GUILD); + when(event.getIdLong()).thenReturn(CURRENT_ID); + + Member user = mockMember("User"); + GUILD.addRoleToMember(user, AUTHOR); + GUILD.addRoleToMember(user, REVIEWER); + GUILD.addRoleToMember(user, MAINTAINER); + GUILD.addRoleToMember(user, ADMINISTRATOR); + + when(event.getMember()).thenReturn(user); + + when(event.getOption(anyString())).thenAnswer(invocation -> { + OptionMapping mapping = mock(OptionMapping.class); + Object option = options.get(invocation.getArgument(0)); + + when(mapping.getAsAttachment()).thenReturn((Attachment) option); + when(mapping.getAsString()).thenReturn((String) option); + when(mapping.getAsBoolean()).thenReturn((Boolean) option); + when(mapping.getAsLong()).thenReturn((Long) option); + when(mapping.getAsInt()).thenReturn((Integer) option); + when(mapping.getAsDouble()).thenReturn((Double) option); + when(mapping.getAsMentionable()).thenReturn((IMentionable) option); + when(mapping.getAsRole()).thenReturn((Role) option); + when(mapping.getAsUser()).thenReturn((User) option); + when(mapping.getAsMember()).thenReturn((Member) option); + when(mapping.getAsChannel()).thenReturn((GuildChannelUnion) option); + + return mapping; + }); + when(event.getOption(anyString(), any(), any())).thenAnswer(inv -> { + String key = inv.getArgument(0); + Object def = inv.getArgument(1); + + return options.containsKey(key) ? options.get(key) : def; + }); + + when(event.reply(anyString())).thenAnswer(invocation -> + mockReply(mockMessage(invocation.getArgument(0), channel))); + when(event.reply(any(MessageCreateData.class))).thenAnswer(invocation -> + mockReply(mockMessage(invocation.getArgument(0, MessageCreateData.class).getContent(), channel))); + when(event.replyEmbeds(anyList())).thenAnswer(invocation -> + mockReply(mockMessage(null, invocation.getArgument(0), channel))); + when(event.replyEmbeds(any(MessageEmbed.class), any(MessageEmbed[].class))).thenAnswer(invocation -> { + List embeds = invocation.getArguments().length == 1 ? new ArrayList<>() : Arrays.asList(invocation.getArgument(1)); + embeds.add(invocation.getArgument(0)); + return mockReply(mockMessage(null, embeds, channel)); + }); + + when(event.deferReply()).thenAnswer(invocation -> + mockReply(mockMessage(null, channel)) + ); + + when(event.deferReply(any(Boolean.class))).thenAnswer(invocation -> + mockReply(mockMessage(null, channel)) + ); + + return event; + } + + private static ReplyCallbackAction mockReply(Message message) { + ReplyCallbackAction action = mock(ReplyCallbackAction.class); + + messages.put(message.getIdLong(), message.getContentRaw()); + embeds.put(message.getIdLong(), message.getEmbeds().toArray(new MessageEmbed[0])); + + when(action.getEmbeds()).thenAnswer(inv -> { + return embeds.get(message.getIdLong()); + }); + + when(action.setEmbeds(anyCollection())).thenAnswer(inv -> { + Collection embed = inv.getArgument(0); + embeds.put(message.getIdLong(), embed.toArray(new MessageEmbed[0])); + return action; + }); + + when(action.addEmbeds(anyCollection())).thenAnswer(inv -> { + List total = new ArrayList<>(); + total.addAll(Arrays.asList(embeds.get(message.getIdLong()))); + total.addAll(inv.getArgument(0)); + embeds.put(message.getIdLong(), total.toArray(new MessageEmbed[0])); + return action; + }); + + doAnswer(inv -> { + Consumer hookConsumer = inv.getArgument(0); + hookConsumer.accept(mockInteractionHook(message.getMember(), message.getChannel(), InteractionType.COMMAND)); + return null; + }).when(action).queue(any()); + + when(action.setEphemeral(anyBoolean())).thenAnswer(invocation -> { + when(message.isEphemeral()).thenReturn(true); + return action; + }); + + return action; + } + + private static & RestAction> T mockReply(Class clazz, InteractionHook hook, Message message) { + T action = mock(clazz); + + doAnswer(inv -> { + Consumer hookConsumer = inv.getArgument(0); + hookConsumer.accept(hook); + return null; + }).when(action).queue(any()); + + when(action.getEmbeds()).thenAnswer(inv -> { + return embeds.get(message.getIdLong()); + }); + + when(action.setEmbeds(anyCollection())).thenAnswer(inv -> { + Collection embed = inv.getArgument(0); + embeds.put(message.getIdLong(), embed.toArray(new MessageEmbed[0])); + return action; + }); + + when(action.setEmbeds(any(MessageEmbed[].class))).thenAnswer(inv -> { + Object obj = inv.getArgument(0); + + if (obj instanceof MessageEmbed[] allEmbeds) + embeds.put(message.getIdLong(), allEmbeds); + else + embeds.put(message.getIdLong(), new MessageEmbed[] { (MessageEmbed) obj }); + return action; + }); + + return action; + } + } diff --git a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java new file mode 100644 index 0000000..026f671 --- /dev/null +++ b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java @@ -0,0 +1,74 @@ +package io.codemc.bot.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import io.codemc.bot.commands.CmdCodeMC.Jenkins; +import io.codemc.bot.commands.CmdCodeMC.Nexus; +import io.codemc.bot.utils.CommandUtil; + +public class TestCmdCodeMC { + + private static CmdCodeMC command; + + @BeforeAll + public static void init() { + command = new CmdCodeMC(MockCodeMCBot.INSTANCE); + + MockCodeMCBot.INSTANCE.create("CodeMC", "Bot"); + MockCodeMCBot.INSTANCE.create("CodeMC", "API"); + } + + @AfterAll + public static void cleanup() { + MockCodeMCBot.INSTANCE.delete("CodeMC"); + } + + @Test + @DisplayName("Test /codemc jenkins") + public void testJenkins() { + Jenkins jenkins = (Jenkins) command.getChildren()[0]; + + assertEquals("jenkins", jenkins.getName()); + assertEquals(1, jenkins.getOptions().size()); + + TestCommandListener listener = new TestCommandListener(jenkins); + + MockJDA.assertSlashCommandEvent(listener, Map.of("job", "CodeMC/Bot"), jenkins.createInfoEmbed("CodeMC/Bot")); + MockJDA.assertSlashCommandEvent(listener, Map.of("job", "CodeMC/API"), jenkins.createInfoEmbed("CodeMC/API")); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Jenkins Job provided!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("job", ""), CommandUtil.embedError("Invalid Jenkins Job provided!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("job", "InvalidJobName"), CommandUtil.embedError("Invalid Jenkins Job provided!")); + + MockJDA.assertSlashCommandEvent(listener, Map.of("job", "Inexistent/Inexistent"), CommandUtil.embedError("Failed to fetch Jenkins Job Info!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("job", "CodeMC/Inexistent"), CommandUtil.embedError("Failed to fetch Jenkins Job Info!")); + } + + @Test + @DisplayName("Test /codemc nexus") + public void testNexus() { + Nexus nexus = (Nexus) command.getChildren()[1]; + + assertEquals("nexus", nexus.getName()); + assertEquals(1, nexus.getOptions().size()); + + TestCommandListener listener = new TestCommandListener(nexus); + + MockJDA.assertSlashCommandEvent(listener, Map.of("user", "CodeMC"), nexus.createInfoEmbed("CodeMC")); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Username provided!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("user", ""), CommandUtil.embedError("Invalid Username provided!")); + + MockJDA.assertSlashCommandEvent(listener, Map.of("user", "Inexistent"), CommandUtil.embedError("Failed to fetch Nexus Repository Info!")); + } + +} diff --git a/src/test/java/io/codemc/bot/commands/TestCommandListener.java b/src/test/java/io/codemc/bot/commands/TestCommandListener.java new file mode 100644 index 0000000..f771b61 --- /dev/null +++ b/src/test/java/io/codemc/bot/commands/TestCommandListener.java @@ -0,0 +1,27 @@ +package io.codemc.bot.commands; + +import com.jagrosh.jdautilities.command.SlashCommand; +import com.jagrosh.jdautilities.command.SlashCommandEvent; + +import net.dv8tion.jda.api.events.GenericEvent; +import net.dv8tion.jda.api.hooks.EventListener; + +public class TestCommandListener implements EventListener { + + private final BotCommand command; + + public TestCommandListener(SlashCommand command) { + this.command = (BotCommand) command; + } + + public BotCommand getCommand() { + return command; + } + + @Override + public void onEvent(GenericEvent event) { + if (event instanceof SlashCommandEvent commandEvent) + command.execute(commandEvent); + } + +} \ No newline at end of file diff --git a/src/test/java/io/codemc/bot/config/TestConfigHandler.java b/src/test/java/io/codemc/bot/config/TestConfigHandler.java index 31d7ab9..3858c1e 100644 --- a/src/test/java/io/codemc/bot/config/TestConfigHandler.java +++ b/src/test/java/io/codemc/bot/config/TestConfigHandler.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import io.codemc.bot.MockCodeMCBot; @@ -12,6 +13,7 @@ public class TestConfigHandler { private final ConfigHandler handler = MockCodeMCBot.INSTANCE.getConfigHandler(); @Test + @DisplayName("Test ConfigHandler#getString") public void testGetString() { assertEquals("TOKEN", handler.getString("bot_token")); @@ -26,6 +28,7 @@ public void testGetString() { } @Test + @DisplayName("Test ConfigHandler#getLong") public void testGetLong() { assertEquals(405915656039694336L, handler.getLong("server")); assertEquals(405918641859723294L, handler.getLong("author_role")); @@ -37,6 +40,7 @@ public void testGetLong() { } @Test + @DisplayName("Test ConfigHandler#getStringList") public void testGetStringList() { assertEquals(4, handler.getStringList("messages", "accepted").size()); assertEquals("Your request has been **accepted**!", handler.getStringList("messages", "accepted").get(0)); @@ -46,6 +50,7 @@ public void testGetStringList() { } @Test + @DisplayName("Test ConfigHandler#getLongList") public void testGetLongList() { assertEquals(3, handler.getLongList("allowed_roles", "applications", "accept").size()); assertEquals(405917902865170453L, handler.getLongList("allowed_roles", "applications", "accept").get(0)); @@ -55,6 +60,7 @@ public void testGetLongList() { } @Test + @DisplayName("Test ConfigHandler#getInt") public void testGetInt() { assertEquals(3306, handler.getInt("database", "port")); } diff --git a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java index 99cac94..d914d43 100644 --- a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java +++ b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java @@ -8,6 +8,7 @@ import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.InteractionType; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -23,6 +24,7 @@ public static void ping() { } @Test + @DisplayName("Test APIUtil#newPassword") public void testCreatePassword() { String p1 = APIUtil.newPassword(); assertEquals(APIUtil.PASSWORD_SIZE, p1.length()); @@ -33,6 +35,7 @@ public void testCreatePassword() { } @Test + @DisplayName("Test APIUtil#isGroup") public void testIsGroup() { // Tests if the username is a GitHub Organization assertTrue(APIUtil.isGroup("CodeMC")); @@ -47,6 +50,7 @@ public void testIsGroup() { } @Test + @DisplayName("Test APIUtil#createNexus") public void testNexus() { String user1 = "gmitch215"; Member u1 = MockJDA.mockMember(user1); @@ -76,6 +80,7 @@ public void testNexus() { } @Test + @DisplayName("Test APIUtil#createJenkinsJob") public void testJenkins() { String user1 = "gmitch215"; String j1 = "SocketMC"; @@ -109,6 +114,7 @@ public void testJenkins() { } @Test + @DisplayName("Test APIUtil#changePassword") public void testChangePassword() { String username = "CodeMC"; String job = "API"; From 7bb7911cc698902dfdf3392498951c059cdf9611 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 26 Nov 2024 02:46:17 +0000 Subject: [PATCH 22/51] Create Unit Tests for `CommandUtil#hasRole` and `CommandUtil.EmbedReply#build` --- .../io/codemc/bot/utils/TestCommandUtil.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/test/java/io/codemc/bot/utils/TestCommandUtil.java diff --git a/src/test/java/io/codemc/bot/utils/TestCommandUtil.java b/src/test/java/io/codemc/bot/utils/TestCommandUtil.java new file mode 100644 index 0000000..f5c7626 --- /dev/null +++ b/src/test/java/io/codemc/bot/utils/TestCommandUtil.java @@ -0,0 +1,58 @@ +package io.codemc.bot.utils; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.MessageEmbed; + +public class TestCommandUtil { + + private static final long AUTHOR_ROLE = MockCodeMCBot.INSTANCE.getConfigHandler().getLong("author_role"); + private static final long ADMIN_ROLE = 405917902865170453L; + private static final long MAINTAINER_ROLE = 659568973079379971L; + + @Test + @DisplayName("Test CommandUtil#hasRole") + public void testHasRole() { + Member m1 = MockJDA.mockMember("gmitch215"); + + assertFalse(CommandUtil.hasRole(m1, List.of(AUTHOR_ROLE))); + + MockJDA.GUILD.addRoleToMember(m1, MockJDA.AUTHOR); + + assertTrue(CommandUtil.hasRole(m1, List.of(AUTHOR_ROLE))); + + Member m2 = MockJDA.mockMember("sgdc3"); + + assertFalse(CommandUtil.hasRole(m2, List.of(ADMIN_ROLE))); + assertFalse(CommandUtil.hasRole(m2, List.of(MAINTAINER_ROLE))); + + MockJDA.GUILD.addRoleToMember(m2, MockJDA.ADMINISTRATOR); + MockJDA.GUILD.addRoleToMember(m2, MockJDA.MAINTAINER); + + assertTrue(CommandUtil.hasRole(m2, List.of(ADMIN_ROLE, MAINTAINER_ROLE))); + } + + @Test + @DisplayName("Test CommandUtil.EmbedReply#build") + public void testEmbedReply() { + CommandUtil.EmbedReply r1 = CommandUtil.EmbedReply.empty(); + MessageEmbed m1 = r1.success("Success!").build(); + + MockJDA.assertEmbed(m1, CommandUtil.embedSuccess("Success!"), true); + + CommandUtil.EmbedReply r2 = CommandUtil.EmbedReply.empty(); + MessageEmbed m2 = r2.error("Error!").build(); + + MockJDA.assertEmbed(m2, CommandUtil.embedError("Error!"), true); + } + +} From e39f4c4c8776c2aec744165cb36a7fc13580f126 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 26 Nov 2024 03:00:48 +0000 Subject: [PATCH 23/51] Add Unit Testing for `/submit` --- src/test/java/io/codemc/bot/MockJDA.java | 29 +++++++++++++- .../io/codemc/bot/commands/TestCmdSubmit.java | 38 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/codemc/bot/commands/TestCmdSubmit.java diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index cb98356..f000b4a 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -22,10 +22,12 @@ import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.InteractionType; import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.modals.Modal; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.MessageEditAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; +import net.dv8tion.jda.api.requests.restaction.interactions.ModalCallbackAction; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import net.dv8tion.jda.api.utils.messages.MessageRequest; @@ -63,6 +65,7 @@ public class MockJDA { public static final JDA JDA = JDAObjects.getJDA(); public static final Guild GUILD = mockGuild(); + public static Modal CURRENT_MODAL = null; public static final TextChannel REQUEST_CHANNEL = mockChannel("request_access"); public static final TextChannel ACCEPTED_CHANNEL = mockChannel("accepted_requests"); @@ -221,6 +224,7 @@ private static Role mockRole(String name, long id, int position) { private static void assertEmbeds(long id, List expectedOutputs, boolean ignoreTimestamp) { MessageEmbed[] embeds = MockJDA.embeds.get(id); + if (embeds == null && expectedOutputs.isEmpty()) return; assertEquals(expectedOutputs.size(), embeds.length, "Number of embeds"); @@ -273,7 +277,9 @@ public static void assertSlashCommandEvent(TestCommandListener listener, Map { + if (options == null) return null; + OptionMapping mapping = mock(OptionMapping.class); Object option = options.get(invocation.getArgument(0)); @@ -313,6 +321,8 @@ public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, Bo return mapping; }); when(event.getOption(anyString(), any(), any())).thenAnswer(inv -> { + if (options == null) return null; + String key = inv.getArgument(0); Object def = inv.getArgument(1); @@ -339,6 +349,11 @@ public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, Bo mockReply(mockMessage(null, channel)) ); + when(event.replyModal(any())).thenAnswer(inv -> { + CURRENT_MODAL = inv.getArgument(0); + return mockModalReply(user, channel); + }); + return event; } @@ -380,6 +395,18 @@ private static ReplyCallbackAction mockReply(Message message) { return action; } + private static ModalCallbackAction mockModalReply(Member user, MessageChannel channel) { + ModalCallbackAction action = mock(ModalCallbackAction.class); + + doAnswer(inv -> { + Consumer hookConsumer = inv.getArgument(0); + hookConsumer.accept(mockInteractionHook(user, channel, InteractionType.MODAL_SUBMIT)); + return null; + }).when(action).queue(any()); + + return action; + } + private static & RestAction> T mockReply(Class clazz, InteractionHook hook, Message message) { T action = mock(clazz); diff --git a/src/test/java/io/codemc/bot/commands/TestCmdSubmit.java b/src/test/java/io/codemc/bot/commands/TestCmdSubmit.java new file mode 100644 index 0000000..6235e30 --- /dev/null +++ b/src/test/java/io/codemc/bot/commands/TestCmdSubmit.java @@ -0,0 +1,38 @@ +package io.codemc.bot.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; + +public class TestCmdSubmit { + + private static CmdSubmit command; + + @BeforeAll + public static void init() { + command = new CmdSubmit(MockCodeMCBot.INSTANCE); + } + + @Test + @DisplayName("Test /submit") + public void testSubmit() { + assertEquals("submit", command.getName()); + assertEquals(0, command.getOptions().size()); + assertTrue(command.hasModalReply); + + TestCommandListener listener = new TestCommandListener(command); + + MockJDA.assertSlashCommandEvent(listener, null); + assertNotNull(MockJDA.CURRENT_MODAL); + assertEquals("submit", MockJDA.CURRENT_MODAL.getId()); + assertEquals(4, MockJDA.CURRENT_MODAL.getComponents().size()); + } + +} From 9a52bcb71c6f21190675d04bbf3cee76c0b3f214 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 26 Nov 2024 03:01:06 +0000 Subject: [PATCH 24/51] Upgrade to Gradle 8.11.1 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 94113f2..e2847c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 84683ad6ca1c5265fae8ca8ad6a2d654a0d7867b Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 3 Dec 2024 17:33:05 +0000 Subject: [PATCH 25/51] Fix Username Collision --- .../java/io/codemc/bot/utils/TestAPIUtil.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java index d914d43..b9bfc42 100644 --- a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java +++ b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java @@ -52,7 +52,7 @@ public void testIsGroup() { @Test @DisplayName("Test APIUtil#createNexus") public void testNexus() { - String user1 = "gmitch215"; + String user1 = "TestNexus1"; Member u1 = MockJDA.mockMember(user1); String p1 = APIUtil.newPassword(); InteractionHook h1 = MockJDA.mockInteractionHook(u1, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); @@ -65,7 +65,7 @@ public void testNexus() { assertNull(NexusAPI.getNexusUser(user1)); assertNull(NexusAPI.getNexusRepository(user1)); - String user2 = "CodeMC"; + String user2 = "TestNexus2"; Member u2 = MockJDA.mockMember(user2); String p2 = APIUtil.newPassword(); InteractionHook h2 = MockJDA.mockInteractionHook(u2, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); @@ -82,8 +82,8 @@ public void testNexus() { @Test @DisplayName("Test APIUtil#createJenkinsJob") public void testJenkins() { - String user1 = "gmitch215"; - String j1 = "SocketMC"; + String user1 = "TestJenkins1"; + String j1 = "Job"; Member u1 = MockJDA.mockMember(user1); String p1 = APIUtil.newPassword(); InteractionHook h1 = MockJDA.mockInteractionHook(u1, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); @@ -97,8 +97,8 @@ public void testJenkins() { assertTrue(JenkinsAPI.deleteUser(user1)); assertTrue(JenkinsAPI.getJenkinsUser(user1).isEmpty()); - String user2 = "CodeMC"; - String j2 = "Bot"; + String user2 = "TestJenkins2"; + String j2 = "Job"; Member u2 = MockJDA.mockMember(user2); String p2 = APIUtil.newPassword(); InteractionHook h2 = MockJDA.mockInteractionHook(u2, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); @@ -116,8 +116,8 @@ public void testJenkins() { @Test @DisplayName("Test APIUtil#changePassword") public void testChangePassword() { - String username = "CodeMC"; - String job = "API"; + String username = "TestChangePassword"; + String job = "Job"; Member user = MockJDA.mockMember(username); InteractionHook hook = MockJDA.mockInteractionHook(user, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); From 8342f25239f300d038af191d4b3ae34e46a12d43 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 3 Dec 2024 17:45:12 +0000 Subject: [PATCH 26/51] Add `trigger` Parameter to Jenkins Build --- .../java/io/codemc/bot/utils/APIUtil.java | 22 ++++++++++--------- .../codemc/bot/utils/ApplicationHandler.java | 2 +- .../java/io/codemc/bot/MockCodeMCBot.java | 2 +- src/test/java/io/codemc/bot/MockJDA.java | 2 +- .../java/io/codemc/bot/utils/TestAPIUtil.java | 6 ++--- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/codemc/bot/utils/APIUtil.java b/src/main/java/io/codemc/bot/utils/APIUtil.java index d50ab30..6c82bdf 100644 --- a/src/main/java/io/codemc/bot/utils/APIUtil.java +++ b/src/main/java/io/codemc/bot/utils/APIUtil.java @@ -43,7 +43,7 @@ public static boolean createNexus(InteractionHook hook, String username, String return true; } - public static boolean createJenkinsJob(InteractionHook hook, String username, String password, String project, String repoLink) { + public static boolean createJenkinsJob(InteractionHook hook, String username, String password, String project, String repoLink, boolean trigger) { if (!JenkinsAPI.getJenkinsUser(username).isEmpty()) { if (hook != null) CommandUtil.EmbedReply.from(hook) @@ -77,15 +77,17 @@ public static boolean createJenkinsJob(InteractionHook hook, String username, St return false; } - boolean triggerBuild = JenkinsAPI.triggerBuild(username, project); - if (!triggerBuild) { - if (hook != null) - CommandUtil.EmbedReply.from(hook) - .error("Failed to trigger Jenkins Build for " + username + "!") - .send(); - - LOGGER.error("Failed to trigger Jenkins Build for {}!", username); - return false; + if (trigger) { + boolean triggerBuild = JenkinsAPI.triggerBuild(username, project); + if (!triggerBuild) { + if (hook != null) + CommandUtil.EmbedReply.from(hook) + .error("Failed to trigger Jenkins Build for " + username + "!") + .send(); + + LOGGER.error("Failed to trigger Jenkins Build for {}!", username); + return false; + } } LOGGER.info("Successfully created Jenkins Job '{}' for {}!", project, username); diff --git a/src/main/java/io/codemc/bot/utils/ApplicationHandler.java b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java index 8d3908d..71fd17f 100644 --- a/src/main/java/io/codemc/bot/utils/ApplicationHandler.java +++ b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java @@ -166,7 +166,7 @@ public static void handle(CodeMCBot bot, InteractionHook hook, Guild guild, long if(accepted){ String password = APIUtil.newPassword(); - boolean jenkinsSuccess = APIUtil.createJenkinsJob(hook, username, password, repoName, repoLink); + boolean jenkinsSuccess = APIUtil.createJenkinsJob(hook, username, password, repoName, repoLink, true); boolean nexusSuccess = APIUtil.createNexus(hook, username, password); if(!jenkinsSuccess || ! nexusSuccess) diff --git a/src/test/java/io/codemc/bot/MockCodeMCBot.java b/src/test/java/io/codemc/bot/MockCodeMCBot.java index 967b59b..14939ae 100644 --- a/src/test/java/io/codemc/bot/MockCodeMCBot.java +++ b/src/test/java/io/codemc/bot/MockCodeMCBot.java @@ -52,7 +52,7 @@ public void create(String username, String job) { String link = "https://github.com/" + username + "/" + job; if (JenkinsAPI.getJenkinsUser(username).isEmpty()) { String password = APIUtil.newPassword(); - APIUtil.createJenkinsJob(null, username, password, job, link); + APIUtil.createJenkinsJob(null, username, password, job, link, false); APIUtil.createNexus(null, username, password); } else { JenkinsAPI.createJenkinsJob(username, job, link, JenkinsAPI.isFreestyle(link)); diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index f000b4a..8956219 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -255,7 +255,7 @@ public static void assertEmbed(MessageEmbed embed, MessageEmbed expectedOutput, if (expectedOutput.getThumbnail() != null) Assertions.assertEquals(expectedOutput.getThumbnail().getUrl(), Objects.requireNonNull(embed.getThumbnail()).getUrl()); - int i = 0; + int i = 0; for (MessageEmbed.Field field : embed.getFields()) { try { Assertions.assertEquals(expectedOutput.getFields().get(i).getName(), field.getName()); diff --git a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java index b9bfc42..47b0240 100644 --- a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java +++ b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java @@ -88,7 +88,7 @@ public void testJenkins() { String p1 = APIUtil.newPassword(); InteractionHook h1 = MockJDA.mockInteractionHook(u1, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); - assertTrue(APIUtil.createJenkinsJob(h1, user1, p1, j1, "https://github.com/gmitch215/SocketMC")); + assertTrue(APIUtil.createJenkinsJob(h1, user1, p1, j1, "https://github.com/gmitch215/SocketMC", false)); assertFalse(JenkinsAPI.getJenkinsUser(user1).isEmpty()); assertNotNull(JenkinsAPI.getJobInfo(user1, j1)); @@ -103,7 +103,7 @@ public void testJenkins() { String p2 = APIUtil.newPassword(); InteractionHook h2 = MockJDA.mockInteractionHook(u2, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); - assertTrue(APIUtil.createJenkinsJob(h2, user2, p2, j2, "https://github.com/CodeMC/Bot")); + assertTrue(APIUtil.createJenkinsJob(h2, user2, p2, j2, "https://github.com/CodeMC/Bot", false)); assertFalse(JenkinsAPI.getJenkinsUser(user2).isEmpty()); assertNotNull(JenkinsAPI.getJobInfo(user2, j2)); @@ -123,7 +123,7 @@ public void testChangePassword() { String oldPassword = APIUtil.newPassword(); assertTrue(APIUtil.createNexus(hook, username, oldPassword)); - assertTrue(APIUtil.createJenkinsJob(hook, username, oldPassword, job, "https://github.com/CodeMC/API")); + assertTrue(APIUtil.createJenkinsJob(hook, username, oldPassword, job, "https://github.com/CodeMC/API", false)); String newPassword = APIUtil.newPassword(); assertTrue(APIUtil.changePassword(hook, username, newPassword)); From b0b9a5954f492a1f59b53926009a619d899a0ea0 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 3 Dec 2024 17:45:15 +0000 Subject: [PATCH 27/51] Add Tests for `/codemc remove` and `/codemc validate` --- .../io/codemc/bot/commands/TestCmdCodeMC.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java index 026f671..b8cf2c5 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java @@ -1,6 +1,10 @@ package io.codemc.bot.commands; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Map; @@ -9,10 +13,15 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import io.codemc.api.jenkins.JenkinsAPI; +import io.codemc.api.nexus.NexusAPI; import io.codemc.bot.MockCodeMCBot; import io.codemc.bot.MockJDA; import io.codemc.bot.commands.CmdCodeMC.Jenkins; import io.codemc.bot.commands.CmdCodeMC.Nexus; +import io.codemc.bot.commands.CmdCodeMC.Remove; +import io.codemc.bot.commands.CmdCodeMC.Validate; +import io.codemc.bot.utils.APIUtil; import io.codemc.bot.utils.CommandUtil; public class TestCmdCodeMC { @@ -71,4 +80,94 @@ public void testNexus() { MockJDA.assertSlashCommandEvent(listener, Map.of("user", "Inexistent"), CommandUtil.embedError("Failed to fetch Nexus Repository Info!")); } + @Test + @DisplayName("Test /codemc remove") + public void testRemove() { + Remove remove = (Remove) command.getChildren()[2]; + + assertEquals("remove", remove.getName()); + assertEquals(1, remove.getOptions().size()); + assertFalse(remove.allowedRoles.isEmpty()); + + TestCommandListener listener = new TestCommandListener(remove); + + JenkinsAPI.createJenkinsUser("TestRemove", "1234"); + NexusAPI.createNexus("TestRemove", "1234"); + + assertFalse(JenkinsAPI.getJenkinsUser("TestRemove").isEmpty()); + assertNotNull(NexusAPI.getNexusUser("TestRemove")); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestRemove"), CommandUtil.embedSuccess("Successfully removed TestRemove from the CodeMC Services!")); + + assertTrue(JenkinsAPI.getJenkinsUser("TestRemove").isEmpty()); + assertNull(NexusAPI.getNexusUser("TestRemove")); + } + + @Test + @DisplayName("Test /codemc validate") + public void testValidate() { + Validate validate = (Validate) command.getChildren()[3]; + + assertEquals("validate", validate.getName()); + assertEquals(1, validate.getOptions().size()); + assertFalse(validate.allowedRoles.isEmpty()); + + TestCommandListener listener = new TestCommandListener(validate); + + // Test One - Jenkins Only + + JenkinsAPI.createJenkinsUser("TestValidate_1", "1234"); + + assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_1").isEmpty()); + assertNull(NexusAPI.getNexusUser("TestValidate_1")); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestValidate_1"), CommandUtil.embedSuccess("Successfully validated 1 User(s)")); + + assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_1").isEmpty()); + assertNotNull(NexusAPI.getNexusUser("TestValidate_1")); + + MockCodeMCBot.INSTANCE.delete("TestValidate_1"); + + // Test One - Nexus Only + + NexusAPI.createNexus("TestValidate_2", "1234"); + + assertTrue(JenkinsAPI.getJenkinsUser("TestValidate_2").isEmpty()); + assertNotNull(NexusAPI.getNexusUser("TestValidate_2")); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestValidate_2"), CommandUtil.embedSuccess("Successfully validated 1 User(s)")); + + assertNotNull(JenkinsAPI.getJenkinsUser("TestValidate_2")); + assertNotNull(NexusAPI.getNexusUser("TestValidate_2")); + + MockCodeMCBot.INSTANCE.delete("TestValidate_2"); + + // Test All - Jenkins Only + + JenkinsAPI.createJenkinsUser("TestValidate_30", "1234"); + JenkinsAPI.createJenkinsUser("TestValidate_31", "1234"); + JenkinsAPI.createJenkinsUser("TestValidate_32", "1234"); + + assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_30").isEmpty()); + assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_31").isEmpty()); + assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_32").isEmpty()); + assertNull(NexusAPI.getNexusUser("TestValidate_30")); + assertNull(NexusAPI.getNexusUser("TestValidate_31")); + assertNull(NexusAPI.getNexusUser("TestValidate_32")); + + int size1 = JenkinsAPI.getAllJenkinsUsers().size(); + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedSuccess("Successfully validated " + size1 + " User(s)")); + + assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_30").isEmpty()); + assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_31").isEmpty()); + assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_32").isEmpty()); + assertNotNull(NexusAPI.getNexusUser("TestValidate_30")); + assertNotNull(NexusAPI.getNexusUser("TestValidate_31")); + assertNotNull(NexusAPI.getNexusUser("TestValidate_32")); + + MockCodeMCBot.INSTANCE.delete( "TestValidate_30"); + MockCodeMCBot.INSTANCE.delete("TestValidate_31"); + MockCodeMCBot.INSTANCE.delete("TestValidate_32"); + } + } From 2aa73c56c4c830970ec896799f34867021fc2b98 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 3 Dec 2024 20:00:03 +0000 Subject: [PATCH 28/51] Add Random Member IDs --- src/test/java/io/codemc/bot/MockJDA.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index 8956219..2db2ce5 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -32,6 +32,7 @@ import net.dv8tion.jda.api.utils.messages.MessageCreateData; import net.dv8tion.jda.api.utils.messages.MessageRequest; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -185,6 +186,13 @@ private static Guild mockGuild() { member.getRoles().add(role); return null; }); + when(guild.removeRoleFromMember(any(UserSnowflake.class), any(Role.class))).thenAnswer(inv -> { + Member member = inv.getArgument(0); + Role role = inv.getArgument(1); + + member.getRoles().remove(role); + return null; + }); when(guild.getRoleById(any(Long.class))).thenAnswer(inv -> { long id = inv.getArgument(0); return ROLES.stream().filter(role -> role.getIdLong() == id).findFirst().orElse(null); @@ -200,11 +208,16 @@ private static Guild mockGuild() { public static Member mockMember(String username) { Member member = JDAObjects.getMember(username, "0000"); + long id = new SecureRandom().nextLong(); when(member.getGuild()).thenReturn(GUILD); + when(member.getIdLong()).thenReturn(id); List roles = new ArrayList<>(); when(member.getRoles()).thenReturn(roles); + when(member.getUser().getEffectiveName()).thenReturn(username); + when(member.getUser().getIdLong()).thenReturn(id); + return member; } From 88a2a23ba965442c2bfcd6dde1ae0b08f306440a Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 3 Dec 2024 20:00:44 +0000 Subject: [PATCH 29/51] Add Tests for `/codemc link` and `/codemc unlink` --- .../io/codemc/bot/commands/CmdCodeMC.java | 13 ++++ .../io/codemc/bot/commands/TestCmdCodeMC.java | 72 ++++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java index f61e49d..1f6408b 100644 --- a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java +++ b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java @@ -390,6 +390,11 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g String username = event.getOption("username", null, OptionMapping::getAsString); Member target = event.getOption("discord", null, OptionMapping::getAsMember); + if (username == null || username.isEmpty()) { + CommandUtil.EmbedReply.from(hook).error("Invalid Jenkins User provided!").send(); + return; + } + if (JenkinsAPI.getJenkinsUser(username).isBlank()) { CommandUtil.EmbedReply.from(hook).error("The user does not have a Jenkins account!").send(); return; @@ -458,6 +463,14 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g .filter(user -> userTarget == null || user.equals(userTarget)) .findFirst() .orElse(null); + + if (username == null) { + if (userTarget == null) + CommandUtil.EmbedReply.from(hook).error("The user is not linked to any Jenkins/Nexus account!").send(); + else + CommandUtil.EmbedReply.from(hook).error("The user is not linked to the specified Jenkins/Nexus account!").send(); + return; + } if (DatabaseAPI.getUser(username) == null) { CommandUtil.EmbedReply.from(hook).error("The user is not linked to any Jenkins/Nexus account!").send(); diff --git a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java index b8cf2c5..9c1cc03 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java @@ -13,16 +13,19 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import io.codemc.api.database.DatabaseAPI; import io.codemc.api.jenkins.JenkinsAPI; import io.codemc.api.nexus.NexusAPI; import io.codemc.bot.MockCodeMCBot; import io.codemc.bot.MockJDA; import io.codemc.bot.commands.CmdCodeMC.Jenkins; +import io.codemc.bot.commands.CmdCodeMC.Link; import io.codemc.bot.commands.CmdCodeMC.Nexus; import io.codemc.bot.commands.CmdCodeMC.Remove; +import io.codemc.bot.commands.CmdCodeMC.Unlink; import io.codemc.bot.commands.CmdCodeMC.Validate; -import io.codemc.bot.utils.APIUtil; import io.codemc.bot.utils.CommandUtil; +import net.dv8tion.jda.api.entities.Member; public class TestCmdCodeMC { @@ -165,9 +168,74 @@ public void testValidate() { assertNotNull(NexusAPI.getNexusUser("TestValidate_31")); assertNotNull(NexusAPI.getNexusUser("TestValidate_32")); - MockCodeMCBot.INSTANCE.delete( "TestValidate_30"); + MockCodeMCBot.INSTANCE.delete("TestValidate_30"); MockCodeMCBot.INSTANCE.delete("TestValidate_31"); MockCodeMCBot.INSTANCE.delete("TestValidate_32"); } + @Test + @DisplayName("Test /codemc link") + public void testLink() { + Link link = (Link) command.getChildren()[4]; + + assertEquals("link", link.getName()); + assertEquals(2, link.getOptions().size()); + assertFalse(link.allowedRoles.isEmpty()); + + TestCommandListener listener = new TestCommandListener(link); + + Member m1 = MockJDA.mockMember("TestLink"); + MockJDA.GUILD.addRoleToMember(m1, MockJDA.AUTHOR); + + MockCodeMCBot.INSTANCE.create("TestLink", "Job"); + DatabaseAPI.removeUser("TestLink"); + + assertNull(DatabaseAPI.getUser("TestLink")); + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestLink", "discord", m1), CommandUtil.embedSuccess("Linked Discord User TestLink to Jenkins User TestLink!")); + assertNotNull(DatabaseAPI.getUser("TestLink")); + assertEquals(m1.getIdLong(), DatabaseAPI.getUser("TestLink").getDiscord()); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestLink", "discord", m1), CommandUtil.embedSuccess("Linked Discord User TestLink to Jenkins User TestLink!")); + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Jenkins User provided!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "Inexistent", "discord", m1), CommandUtil.embedError("The user does not have a Jenkins account!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestLink"), CommandUtil.embedError("Invalid Discord User provided!")); + + MockJDA.GUILD.removeRoleFromMember(m1, MockJDA.AUTHOR); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestLink", "discord", m1), CommandUtil.embedError("The user is not an Author!")); + + MockCodeMCBot.INSTANCE.delete("TestLink"); + DatabaseAPI.removeUser("TestLink"); + } + + @Test + @DisplayName("Test /codemc unlink") + public void testUnlink() { + Unlink unlink = (Unlink) command.getChildren()[5]; + + assertEquals("unlink", unlink.getName()); + assertEquals(2, unlink.getOptions().size()); + assertFalse(unlink.allowedRoles.isEmpty()); + + TestCommandListener listener = new TestCommandListener(unlink); + + Member m1 = MockJDA.mockMember("TestUnlink"); + MockJDA.GUILD.addRoleToMember(m1, MockJDA.AUTHOR); + + DatabaseAPI.removeUser("TestUnlink"); + MockCodeMCBot.INSTANCE.delete("TestUnlink"); + MockCodeMCBot.INSTANCE.create("TestUnlink", "Job"); + DatabaseAPI.addUser("TestUnlink", m1.getIdLong()); + + assertNotNull(DatabaseAPI.getUser("TestUnlink")); + MockJDA.assertSlashCommandEvent(listener, Map.of("discord", m1), CommandUtil.embedSuccess("Unlinked Discord User TestUnlink from their Jenkins/Nexus account!")); + assertNull(DatabaseAPI.getUser("TestUnlink")); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Discord User provided!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("discord", m1), CommandUtil.embedError("The user is not linked to any Jenkins/Nexus account!")); + + MockCodeMCBot.INSTANCE.delete("TestUnlink"); + DatabaseAPI.removeUser("TestUnlink"); + } + } From 93fb2373c75cbb3db1437023c7d6931f5a761abb Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 3 Dec 2024 22:36:22 +0000 Subject: [PATCH 30/51] Add Rest of Tests for `/codemc` --- .../io/codemc/bot/commands/CmdCodeMC.java | 16 +++- src/test/java/io/codemc/bot/MockJDA.java | 25 +++++- .../io/codemc/bot/commands/TestCmdCodeMC.java | 88 +++++++++++++++++++ 3 files changed, 123 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java index 1f6408b..955897b 100644 --- a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java +++ b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java @@ -532,7 +532,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g } if (JenkinsAPI.getJenkinsUser(username).isBlank()) { - CommandUtil.EmbedReply.from(hook).error("This user does not have a Jenkins account!").send(); + CommandUtil.EmbedReply.from(hook).error("You do not have a Jenkins account!").send(); return; } @@ -571,6 +571,11 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g String username = event.getOption("username", null, OptionMapping::getAsString); Member target = event.getOption("discord", null, OptionMapping::getAsMember); + if (username == null || username.isEmpty()) { + CommandUtil.EmbedReply.from(hook).error("Invalid Username provided!").send(); + return; + } + if (!JenkinsAPI.getJenkinsUser(username).isBlank()) { CommandUtil.EmbedReply.from(hook).error("A user with that username already exists.").send(); return; @@ -638,8 +643,13 @@ public void withModalReply(SlashCommandEvent event) {} public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild guild, Member member) { String username = event.getOption("username", null, OptionMapping::getAsString); - if (!JenkinsAPI.getJenkinsUser(username).isBlank()) { - CommandUtil.EmbedReply.from(hook).error("A user with that username already exists.").send(); + if (username == null || username.isEmpty()) { + CommandUtil.EmbedReply.from(hook).error("Invalid Username provided!").send(); + return; + } + + if (JenkinsAPI.getJenkinsUser(username).isBlank()) { + CommandUtil.EmbedReply.from(hook).error("The user does not exist!").send(); return; } diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index 2db2ce5..babcd65 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -24,6 +24,7 @@ import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.modals.Modal; import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.requests.restaction.MessageEditAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; @@ -98,6 +99,11 @@ public static InteractionHook mockInteractionHook(Interaction interaction) { messages.put(hook.getIdLong(), content); return mockReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel)); }); + when(hook.editOriginalFormat(anyString(), any())).thenAnswer(inv -> { + String content = String.format(inv.getArgument(0), (Object[]) inv.getArguments()[1]); + messages.put(hook.getIdLong(), content); + return mockReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel)); + }); when(hook.editOriginalEmbeds(any(MessageEmbed[].class))).thenAnswer(inv -> { Object obj = inv.getArgument(0); @@ -184,14 +190,14 @@ private static Guild mockGuild() { Role role = inv.getArgument(1); member.getRoles().add(role); - return null; + return mockAuditLog(); }); when(guild.removeRoleFromMember(any(UserSnowflake.class), any(Role.class))).thenAnswer(inv -> { Member member = inv.getArgument(0); Role role = inv.getArgument(1); member.getRoles().remove(role); - return null; + return mockAuditLog(); }); when(guild.getRoleById(any(Long.class))).thenAnswer(inv -> { long id = inv.getArgument(0); @@ -286,8 +292,11 @@ public static void assertEmbed(MessageEmbed embed, MessageEmbed expectedOutput, public static void assertSlashCommandEvent(TestCommandListener listener, Map options, MessageEmbed... outputs) { BotCommand command = listener.getCommand(); - SlashCommandEvent event = mockSlashCommandEvent(REQUEST_CHANNEL, command, options); + assertSlashCommandEvent(event, listener, outputs); + } + + public static void assertSlashCommandEvent(SlashCommandEvent event, TestCommandListener listener, MessageEmbed... outputs) { listener.onEvent(event); if (outputs != null) @@ -452,4 +461,14 @@ private static & RestAction> T mockReply(Class mockAuditLog() { + AuditableRestAction action = mock(AuditableRestAction.class); + doAnswer(inv -> null).when(action).queue(any()); + + when(action.reason(any())).thenReturn(action); + + return action; + } + } diff --git a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java index 9c1cc03..22f41ee 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java @@ -13,11 +13,16 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import com.jagrosh.jdautilities.command.SlashCommandEvent; + import io.codemc.api.database.DatabaseAPI; import io.codemc.api.jenkins.JenkinsAPI; import io.codemc.api.nexus.NexusAPI; import io.codemc.bot.MockCodeMCBot; import io.codemc.bot.MockJDA; +import io.codemc.bot.commands.CmdCodeMC.ChangePassword; +import io.codemc.bot.commands.CmdCodeMC.CreateUser; +import io.codemc.bot.commands.CmdCodeMC.DeleteUser; import io.codemc.bot.commands.CmdCodeMC.Jenkins; import io.codemc.bot.commands.CmdCodeMC.Link; import io.codemc.bot.commands.CmdCodeMC.Nexus; @@ -238,4 +243,87 @@ public void testUnlink() { DatabaseAPI.removeUser("TestUnlink"); } + @Test + @DisplayName("Test /codemc change-password") + public void testChangePassword() { + ChangePassword changePassword = (ChangePassword) command.getChildren()[6]; + + assertEquals("change-password", changePassword.getName()); + assertEquals(1, changePassword.getOptions().size()); + + SlashCommandEvent event = MockJDA.mockSlashCommandEvent(MockJDA.REQUEST_CHANNEL, changePassword, Map.of()); + TestCommandListener listener = new TestCommandListener(changePassword); + + JenkinsAPI.createJenkinsUser("User", "1234"); + NexusAPI.createNexus("User", "1234"); + DatabaseAPI.addUser("User", event.getMember().getIdLong()); + + MockJDA.assertSlashCommandEvent(event, listener, CommandUtil.embedSuccess("Successfully changed your password!")); + + JenkinsAPI.deleteUser("User"); + NexusAPI.deleteNexus("User"); + DatabaseAPI.removeUser("User"); + } + + @Test + @DisplayName("Test /codemc createuser") + public void testCreateUser() { + CreateUser createUser = (CreateUser) command.getChildren()[7]; + + assertEquals("createuser", createUser.getName()); + assertEquals(2, createUser.getOptions().size()); + assertFalse(createUser.allowedRoles.isEmpty()); + + TestCommandListener listener = new TestCommandListener(createUser); + + Member m1 = MockJDA.mockMember("TestCreateUser"); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestCreateUser", "discord", m1), CommandUtil.embedSuccess("Successfully created user TestCreateUser and linked it to " + m1.getUser().getEffectiveName() + "!")); + + Member m2 = MockJDA.mockMember("TestCreateUser2"); + MockCodeMCBot.INSTANCE.create("TestCreateUser2", "Job"); + DatabaseAPI.addUser("TestCreateUser2", m2.getIdLong()); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestCreateUser2", "discord", m2), CommandUtil.embedError("A user with that username already exists.")); + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Username provided!")); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestCreateUser3"), CommandUtil.embedError("Invalid Discord User provided!")); + + MockCodeMCBot.INSTANCE.delete("TestCreateUser"); + MockCodeMCBot.INSTANCE.delete("TestCreateUser2"); + DatabaseAPI.removeUser("TestCreateUser"); + DatabaseAPI.removeUser("TestCreateUser2"); + } + + @Test + @DisplayName("Test /codemc deluser") + public void testDelUser() { + DeleteUser delUser = (DeleteUser) command.getChildren()[8]; + + assertEquals("deluser", delUser.getName()); + assertEquals(1, delUser.getOptions().size()); + assertFalse(delUser.allowedRoles.isEmpty()); + + TestCommandListener listener = new TestCommandListener(delUser); + + Member m1 = MockJDA.mockMember("TestDelUser"); + + MockCodeMCBot.INSTANCE.create("TestDelUser", "Job"); + DatabaseAPI.addUser("TestDelUser", m1.getIdLong()); + + assertFalse(JenkinsAPI.getJenkinsUser("TestDelUser").isEmpty()); + assertNotNull(NexusAPI.getNexusUser("TestDelUser")); + assertNotNull(DatabaseAPI.getUser("TestDelUser")); + assertEquals(m1.getIdLong(), DatabaseAPI.getUser("TestDelUser").getDiscord()); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestDelUser"), CommandUtil.embedSuccess("Successfully deleted user TestDelUser!")); + + assertTrue(JenkinsAPI.getJenkinsUser("TestDelUser").isEmpty()); + assertNull(NexusAPI.getNexusUser("TestDelUser")); + assertNull(DatabaseAPI.getUser("TestDelUser")); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Username provided!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "Inexistent"), CommandUtil.embedError("The user does not exist!")); + } + } From 0f97c919f33c19e472ddfa7b7dd83467acb0a05c Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 3 Dec 2024 22:36:32 +0000 Subject: [PATCH 31/51] Remove Unused Imports --- src/main/java/io/codemc/bot/utils/ApplicationHandler.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/io/codemc/bot/utils/ApplicationHandler.java b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java index 71fd17f..273d4c4 100644 --- a/src/main/java/io/codemc/bot/utils/ApplicationHandler.java +++ b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java @@ -33,8 +33,6 @@ import org.slf4j.LoggerFactory; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class ApplicationHandler{ From d360680014f7979ba4e0ed471f1003cdba0c4448 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Thu, 5 Dec 2024 01:25:16 +0000 Subject: [PATCH 32/51] Create `CommandUtil#requestEmbed` --- .../java/io/codemc/bot/listeners/ModalListener.java | 11 +---------- src/main/java/io/codemc/bot/utils/CommandUtil.java | 12 ++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/codemc/bot/listeners/ModalListener.java b/src/main/java/io/codemc/bot/listeners/ModalListener.java index c64b3e7..525834a 100644 --- a/src/main/java/io/codemc/bot/listeners/ModalListener.java +++ b/src/main/java/io/codemc/bot/listeners/ModalListener.java @@ -38,8 +38,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.Instant; - public class ModalListener extends ListenerAdapter{ private final Logger logger = LoggerFactory.getLogger(ModalListener.class); @@ -103,14 +101,7 @@ public void onModalInteraction(@NotNull ModalInteractionEvent event){ String repoLink = MarkdownUtil.maskedLink(repo, repoLinkValue); String submitter = String.format("`%s` (%s)", event.getUser().getEffectiveName(), event.getUser().getAsMention()); - MessageEmbed embed = CommandUtil.getEmbed() - .addField("User/Organisation:", userLink, true) - .addField("Repository:", repoLink, true) - .addField("Submitted by:", submitter, true) - .addField("Description", description, false) - .setFooter(event.getUser().getId()) - .setTimestamp(Instant.now()) - .build(); + MessageEmbed embed = CommandUtil.requestEmbed(userLink, repoLink, submitter, description, event.getUser().getId()); requestChannel.sendMessageEmbeds(embed) .setActionRow( diff --git a/src/main/java/io/codemc/bot/utils/CommandUtil.java b/src/main/java/io/codemc/bot/utils/CommandUtil.java index c2a3f4e..4a9d7da 100644 --- a/src/main/java/io/codemc/bot/utils/CommandUtil.java +++ b/src/main/java/io/codemc/bot/utils/CommandUtil.java @@ -28,6 +28,7 @@ import net.dv8tion.jda.api.interactions.InteractionHook; import org.slf4j.LoggerFactory; +import java.time.Instant; import java.util.List; public class CommandUtil{ @@ -58,6 +59,17 @@ public static MessageEmbed embedError(String... lines){ public static MessageEmbed embedSuccess(String... lines){ return EmbedReply.empty().success(lines).build(); } + + public static MessageEmbed requestEmbed(String userLink, String repoLink, String submitter, String description, String id) { + return getEmbed() + .addField("User/Organisation:", userLink, true) + .addField("Repository:", repoLink, true) + .addField("Submitted by:", submitter, true) + .addField("Description", description, false) + .setFooter(id) + .setTimestamp(Instant.now()) + .build(); + } public static class EmbedReply { From 84183188ee2b6fedf19523e4096e2ac960da8ddc Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Thu, 5 Dec 2024 01:25:44 +0000 Subject: [PATCH 33/51] Add Database Cleanup --- src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java index 22f41ee..2b7bef3 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java @@ -324,6 +324,8 @@ public void testDelUser() { MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Username provided!")); MockJDA.assertSlashCommandEvent(listener, Map.of("username", "Inexistent"), CommandUtil.embedError("The user does not exist!")); + + DatabaseAPI.removeUser("TestDelUser"); } } From 18108644e34637481f9f4eb7a5165bb00bbc0c56 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Thu, 5 Dec 2024 01:25:52 +0000 Subject: [PATCH 34/51] Create TestApplicationHandler.java --- .../codemc/bot/utils/ApplicationHandler.java | 11 +- src/test/java/io/codemc/bot/MockJDA.java | 178 ++++++++++++++++-- .../bot/utils/TestApplicationHandler.java | 82 ++++++++ 3 files changed, 252 insertions(+), 19 deletions(-) create mode 100644 src/test/java/io/codemc/bot/utils/TestApplicationHandler.java diff --git a/src/main/java/io/codemc/bot/utils/ApplicationHandler.java b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java index 273d4c4..b4d7d7a 100644 --- a/src/main/java/io/codemc/bot/utils/ApplicationHandler.java +++ b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java @@ -29,6 +29,8 @@ import net.dv8tion.jda.api.requests.ErrorResponse; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import org.jetbrains.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,7 +65,7 @@ public static void handle(CodeMCBot bot, InteractionHook hook, Guild guild, long CommandUtil.EmbedReply.from(hook).error("Provided Message does not have any embeds.").send(); return; } - + MessageEmbed embed = embeds.get(0); if(embed.getFooter() == null || embed.getFields().isEmpty()){ CommandUtil.EmbedReply.from(hook).error("Embed does not have a Footer or any Embed Fields").send(); @@ -85,7 +87,7 @@ public static void handle(CodeMCBot bot, InteractionHook hook, Guild guild, long CommandUtil.EmbedReply.from(hook).error("Embed does not have a valid footer.").send(); return; } - + hook.editOriginalFormat( """ [2/5] Handling Join Request... @@ -167,7 +169,7 @@ public static void handle(CodeMCBot bot, InteractionHook hook, Guild guild, long boolean jenkinsSuccess = APIUtil.createJenkinsJob(hook, username, password, repoName, repoLink, true); boolean nexusSuccess = APIUtil.createNexus(hook, username, password); - if(!jenkinsSuccess || ! nexusSuccess) + if(!jenkinsSuccess || !nexusSuccess) return; if(member == null){ @@ -308,7 +310,8 @@ public static void handle(CodeMCBot bot, InteractionHook hook, Guild guild, long }); } - private static MessageCreateData getMessage(CodeMCBot bot, String userId, String userLink, String repoLink, String str, User reviewer, boolean accepted){ + @VisibleForTesting + static MessageCreateData getMessage(CodeMCBot bot, String userId, String userLink, String repoLink, String str, User reviewer, boolean accepted){ String msg = String.join("\n", bot.getConfigHandler().getStringList("messages", (accepted ? "accepted" : "denied"))); MessageEmbed embed = new EmbedBuilder() diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index babcd65..014e381 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -25,6 +25,7 @@ import net.dv8tion.jda.api.interactions.modals.Modal; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; +import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; import net.dv8tion.jda.api.requests.restaction.MessageEditAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; @@ -52,6 +53,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -63,9 +65,12 @@ public class MockJDA { private static final Map messages = new HashMap<>(); private static final Map embeds = new HashMap<>(); + private static final Map members = new HashMap<>(); + private static long CURRENT_ID = 0; public static final JDA JDA = JDAObjects.getJDA(); + public static final Member SELF = mockMember("Bot"); public static final Guild GUILD = mockGuild(); public static Modal CURRENT_MODAL = null; @@ -80,6 +85,14 @@ public class MockJDA { public static final Role AUTHOR = mockRole("Author", CONFIG.getLong("author_role"), 1); public static final List ROLES = List.of(ADMINISTRATOR, MAINTAINER, REVIEWER, AUTHOR); + public static String getMessage(long id) { + return messages.get(id); + } + + public static List getEmbeds(long id) { + return Arrays.asList(embeds.get(id)); + } + public static InteractionHook mockInteractionHook(Member user, MessageChannel channel, InteractionType type) { return mockInteractionHook(mockInteraction(user, channel, type)); } @@ -97,12 +110,12 @@ public static InteractionHook mockInteractionHook(Interaction interaction) { when(hook.editOriginal(anyString())).thenAnswer(inv -> { String content = inv.getArgument(0); messages.put(hook.getIdLong(), content); - return mockReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel)); + return mockWebhookReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel)); }); - when(hook.editOriginalFormat(anyString(), any())).thenAnswer(inv -> { - String content = String.format(inv.getArgument(0), (Object[]) inv.getArguments()[1]); + when(hook.editOriginalFormat(anyString(), any(Object[].class))).thenAnswer(inv -> { + String content = String.format(inv.getArgument(0), (Object[]) inv.getRawArguments()[1]); messages.put(hook.getIdLong(), content); - return mockReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel)); + return mockWebhookReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel)); }); when(hook.editOriginalEmbeds(any(MessageEmbed[].class))).thenAnswer(inv -> { @@ -112,7 +125,7 @@ public static InteractionHook mockInteractionHook(Interaction interaction) { else embeds.put(hook.getIdLong(), new MessageEmbed[] { (MessageEmbed) obj }); - return mockReply(WebhookMessageEditAction.class, hook, mockMessage(null, Arrays.asList(embeds.get(hook.getIdLong())), channel)); + return mockWebhookReply(WebhookMessageEditAction.class, hook, mockMessage(null, Arrays.asList(embeds.get(hook.getIdLong())), channel)); }); when(hook.sendMessageEmbeds(any(), any(MessageEmbed[].class))).thenAnswer(inv -> { @@ -129,7 +142,7 @@ public static InteractionHook mockInteractionHook(Interaction interaction) { embeds.add((MessageEmbed) obj); } - return mockReply(WebhookMessageCreateAction.class, hook, mockMessage(null, embeds, channel)); + return mockWebhookReply(WebhookMessageCreateAction.class, hook, mockMessage(null, embeds, channel)); }); return hook; @@ -140,6 +153,7 @@ public static Interaction mockInteraction(Member user, MessageChannel channel, I when(interaction.getJDA()).thenReturn(JDA); when(interaction.getChannel()).thenReturn(channel); when(interaction.getMember()).thenReturn(user); + when(interaction.getUser()).thenAnswer(inv -> user.getUser()); when(interaction.getGuild()).thenReturn(GUILD); when(interaction.getTypeRaw()).thenReturn(type.getKey()); when(interaction.getIdLong()).thenReturn(CURRENT_ID); @@ -149,19 +163,66 @@ public static Interaction mockInteraction(Member user, MessageChannel channel, I public static TextChannel mockChannel(String configName) { long id = CONFIG.getLong("channels", configName); - return (TextChannel) JDAObjects.getMessageChannel(configName.replace('_', '-'), id, Callback.single()); + TextChannel channel = (TextChannel) JDAObjects.getMessageChannel(configName.replace('_', '-'), id, Callback.single()); + + when(channel.getGuild()).thenReturn(GUILD); + when(channel.getJDA()).thenReturn(JDA); + + when(channel.retrieveMessageById(anyLong())).thenAnswer(inv -> { + long messageId = inv.getArgument(0); + String content = messages.get(messageId); + Message message = mockMessage( + content, Arrays.asList(embeds.getOrDefault(messageId, new MessageEmbed[0])), channel + ); + + return mockAction(message); + }); + when(channel.sendMessage(any(CharSequence.class))).thenAnswer(inv -> { + String content = inv.getArgument(0); + return mockReply(MessageCreateAction.class, mockMessage(content, channel)); + }); + when(channel.sendMessage(any(MessageCreateData.class))).thenAnswer(inv -> { + MessageCreateData data = inv.getArgument(0, MessageCreateData.class); + String content = data.getContent(); + List embeds = data.getEmbeds(); + + return mockReply(MessageCreateAction.class, mockMessage(content, embeds, channel)); + }); + when(channel.sendMessageFormat(anyString(), any(Object[].class))).thenAnswer(inv -> { + String content = String.format(inv.getArgument(0), (Object[]) inv.getRawArguments()[1]); + return mockReply(MessageCreateAction.class, mockMessage(content, channel)); + }); + when(channel.sendMessageEmbeds(any(), any(MessageEmbed[].class))).thenAnswer(inv -> { + MessageEmbed first = inv.getArgument(0); + + List embeds = new ArrayList<>(); + embeds.add(first); + if (inv.getArguments().length > 1) { + Object obj = inv.getArgument(1); + + if (obj instanceof MessageEmbed[] allEmbeds) + embeds.addAll(Arrays.asList(allEmbeds)); + else + embeds.add((MessageEmbed) obj); + } + + return mockReply(MessageCreateAction.class, mockMessage(null, embeds, channel)); + }); + + return channel; } public static Message mockMessage(String content, MessageChannel channel) { Message message = JDAObjects.getMessage(content, channel); messages.put(message.getIdLong(), content); + when(message.getContentRaw()).thenAnswer(inv -> messages.get(message.getIdLong())); when(message.getIdLong()).thenReturn(CURRENT_ID); when(message.getGuild()).thenReturn(GUILD); when(message.editMessage(anyString())).thenAnswer(inv -> { messages.put(message.getIdLong(), inv.getArgument(0)); - return mockReply(MessageEditAction.class, mockInteractionHook(message.getMember(), channel, InteractionType.COMMAND), message); + return mockWebhookReply(MessageEditAction.class, mockInteractionHook(message.getMember(), channel, InteractionType.COMMAND), message); }); return message; @@ -171,7 +232,13 @@ public static Message mockMessage(String content, List embeds, Mes Message message = mockMessage(content, channel); MockJDA.embeds.put(message.getIdLong(), embeds.toArray(new MessageEmbed[0])); - when(message.getEmbeds()).thenReturn(embeds); + when(message.getEmbeds()).thenAnswer(inv -> Arrays.asList(MockJDA.embeds.get(message.getIdLong()))); + when(message.getStartedThread()).thenReturn(null); + when(message.delete()).thenAnswer(inv -> { + messages.remove(message.getIdLong()); + MockJDA.embeds.remove(message.getIdLong()); + return mockAuditLog(); + }); return message; } @@ -184,6 +251,7 @@ private static Guild mockGuild() { when(guild.getJDA()).thenReturn(JDA); when(guild.getTextChannels()).thenReturn(CHANNELS); when(guild.getRoles()).thenReturn(ROLES); + when(guild.getSelfMember()).thenReturn(SELF); when(guild.addRoleToMember(any(UserSnowflake.class), any(Role.class))).thenAnswer(inv -> { Member member = inv.getArgument(0); @@ -199,14 +267,26 @@ private static Guild mockGuild() { member.getRoles().remove(role); return mockAuditLog(); }); - when(guild.getRoleById(any(Long.class))).thenAnswer(inv -> { + when(guild.getRoleById(anyLong())).thenAnswer(inv -> { long id = inv.getArgument(0); return ROLES.stream().filter(role -> role.getIdLong() == id).findFirst().orElse(null); }); - when(guild.getChannelById(any(), any(Long.class))).thenAnswer(inv -> { + when(guild.getChannelById(any(), anyLong())).thenAnswer(inv -> { long id = inv.getArgument(1); return CHANNELS.stream().filter(channel -> channel.getIdLong() == id).findFirst().orElse(null); }); + when(guild.getTextChannelById(anyLong())).thenAnswer(inv -> { + long id = inv.getArgument(0); + return CHANNELS.stream().filter(channel -> channel.getIdLong() == id).findFirst().orElse(null); + }); + when(guild.getMemberById(anyString())).thenAnswer(inv -> { + long id = Long.parseLong(inv.getArgument(0)); + return members.get(id); + }); + when(guild.getMemberById(anyLong())).thenAnswer(inv -> { + long id = inv.getArgument(0); + return members.get(id); + }); return guild; } @@ -215,14 +295,19 @@ public static Member mockMember(String username) { Member member = JDAObjects.getMember(username, "0000"); long id = new SecureRandom().nextLong(); + members.put(id, member); + + when(member.getJDA()).thenReturn(JDA); when(member.getGuild()).thenReturn(GUILD); + when(member.getId()).thenReturn(Long.toString(id)); when(member.getIdLong()).thenReturn(id); + when(member.getAsMention()).thenReturn("<@" + id + ">"); List roles = new ArrayList<>(); when(member.getRoles()).thenReturn(roles); - when(member.getUser().getEffectiveName()).thenReturn(username); when(member.getUser().getIdLong()).thenReturn(id); + when(member.getUser().getAsMention()).thenReturn("<@" + id + ">"); return member; } @@ -241,7 +326,7 @@ private static Role mockRole(String name, long id, int position) { return role; } - private static void assertEmbeds(long id, List expectedOutputs, boolean ignoreTimestamp) { + public static void assertEmbeds(long id, List expectedOutputs, boolean ignoreTimestamp) { MessageEmbed[] embeds = MockJDA.embeds.get(id); if (embeds == null && expectedOutputs.isEmpty()) return; @@ -321,6 +406,8 @@ public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, Bo GUILD.addRoleToMember(user, ADMINISTRATOR); when(event.getMember()).thenReturn(user); + when(event.getUser()).thenAnswer(inv -> user.getUser()); + when(event.getOptions()).thenAnswer(invocation -> options); when(event.getOption(anyString())).thenAnswer(invocation -> { if (options == null) return null; @@ -429,7 +516,7 @@ private static ModalCallbackAction mockModalReply(Member user, MessageChannel ch return action; } - private static & RestAction> T mockReply(Class clazz, InteractionHook hook, Message message) { + private static & RestAction> T mockWebhookReply(Class clazz, InteractionHook hook, Message message) { T action = mock(clazz); doAnswer(inv -> { @@ -461,10 +548,71 @@ private static & RestAction> T mockReply(Class & RestAction> T mockReply(Class clazz, Message message) { + T action = mock(clazz); + + doAnswer(inv -> { + Consumer messageConsumer = inv.getArgument(0); + messageConsumer.accept(message); + return null; + }).when(action).queue(any()); + + when(action.getEmbeds()).thenAnswer(inv -> message.getEmbeds()); + when(action.setEmbeds(anyCollection())).thenAnswer(inv -> { + Collection embed = inv.getArgument(0); + embeds.put(message.getIdLong(), embed.toArray(new MessageEmbed[0])); + return action; + }); + + return action; + } + + @SuppressWarnings("unchecked") + private static RestAction mockAction(T object) { + RestAction action = mock(RestAction.class); + + doAnswer(inv -> { + Consumer consumer = inv.getArgument(0); + consumer.accept(object); + return null; + }).when(action).queue(any()); + + doAnswer(inv -> { + Consumer consumer = inv.getArgument(0); + Consumer error = inv.getArgument(1); + + try { + consumer.accept(object); + } catch (Exception e) { + error.accept(e); + } + return null; + }).when(action).queue(any(), any()); + + return action; + } + @SuppressWarnings("unchecked") private static AuditableRestAction mockAuditLog() { AuditableRestAction action = mock(AuditableRestAction.class); - doAnswer(inv -> null).when(action).queue(any()); + + doAnswer(inv -> { + Consumer consumer = inv.getArgument(0); + consumer.accept(null); + return null; + }).when(action).queue(any()); + + doAnswer(inv -> { + Consumer consumer = inv.getArgument(0); + Consumer error = inv.getArgument(1); + + try { + consumer.accept(null); + } catch (Exception e) { + error.accept(e); + } + return null; + }).when(action).queue(any(), any()); when(action.reason(any())).thenReturn(action); diff --git a/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java b/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java new file mode 100644 index 0000000..f7d3e87 --- /dev/null +++ b/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java @@ -0,0 +1,82 @@ +package io.codemc.bot.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.api.database.DatabaseAPI; +import io.codemc.api.jenkins.JenkinsAPI; +import io.codemc.api.nexus.NexusAPI; +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.InteractionType; + +public class TestApplicationHandler { + + @Test + @DisplayName("Test ApplicationHandler#handle (Accepted)") + public void testHandleAccepted() { + String username = "TestApplicationHandlerAccepted"; + Member member = MockJDA.mockMember(username); + InteractionHook hook = MockJDA.mockInteractionHook(member, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); + Message message = MockJDA.mockMessage("", List.of(embed), MockJDA.REQUEST_CHANNEL); + + assertFalse(CommandUtil.hasRole(member, List.of(MockJDA.AUTHOR.getIdLong()))); + assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNull(NexusAPI.getNexusUser(username)); + assertNull(DatabaseAPI.getUser(username)); + + ApplicationHandler.handle( + MockCodeMCBot.INSTANCE, hook, MockJDA.GUILD, message.getIdLong(), null, true + ); + + assertTrue(CommandUtil.hasRole(member, List.of(MockJDA.AUTHOR.getIdLong()))); + assertFalse(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNotNull(NexusAPI.getNexusUser(username)); + assertNotNull(DatabaseAPI.getUser(username)); + assertEquals(member.getIdLong(), DatabaseAPI.getUser(username).getDiscord()); + + assertTrue(JenkinsAPI.deleteUser(username)); + assertTrue(NexusAPI.deleteNexus(username)); + assertEquals(1, DatabaseAPI.removeUser(username)); + } + + @Test + @DisplayName("Test ApplicationHandler#handle (Rejected)") + public void testHandleRejected() { + String username = "TestApplicationHandlerRejected"; + Member member = MockJDA.mockMember(username); + InteractionHook hook = MockJDA.mockInteractionHook(member, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); + Message message = MockJDA.mockMessage("", List.of(embed), MockJDA.REQUEST_CHANNEL); + + assertFalse(CommandUtil.hasRole(member, List.of(MockJDA.AUTHOR.getIdLong()))); + assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNull(NexusAPI.getNexusUser(username)); + assertNull(DatabaseAPI.getUser(username)); + + ApplicationHandler.handle( + MockCodeMCBot.INSTANCE, hook, MockJDA.GUILD, message.getIdLong(), "Denied", false + ); + + assertFalse(CommandUtil.hasRole(member, List.of(MockJDA.AUTHOR.getIdLong()))); + assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNull(NexusAPI.getNexusUser(username)); + assertNull(DatabaseAPI.getUser(username)); + } + +} From a7aef3f554e97174334fc75d48007f18b5869661 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Thu, 5 Dec 2024 16:44:46 +0000 Subject: [PATCH 35/51] Increase Coverage for `CmdCodeMC.java` --- .../io/codemc/bot/commands/TestCmdCodeMC.java | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java index 2b7bef3..48f6595 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java @@ -49,12 +49,21 @@ public static void cleanup() { MockCodeMCBot.INSTANCE.delete("CodeMC"); } + @Test + @DisplayName("Test /codemc") + public void testCodemc() { + assertEquals("codemc", command.getName()); + assertFalse(command.getHelp().isEmpty()); + assertTrue(command.getChildren().length > 1); + } + @Test @DisplayName("Test /codemc jenkins") public void testJenkins() { Jenkins jenkins = (Jenkins) command.getChildren()[0]; assertEquals("jenkins", jenkins.getName()); + assertFalse(jenkins.getHelp().isEmpty()); assertEquals(1, jenkins.getOptions().size()); TestCommandListener listener = new TestCommandListener(jenkins); @@ -76,6 +85,7 @@ public void testNexus() { Nexus nexus = (Nexus) command.getChildren()[1]; assertEquals("nexus", nexus.getName()); + assertFalse(nexus.getHelp().isEmpty()); assertEquals(1, nexus.getOptions().size()); TestCommandListener listener = new TestCommandListener(nexus); @@ -94,6 +104,7 @@ public void testRemove() { Remove remove = (Remove) command.getChildren()[2]; assertEquals("remove", remove.getName()); + assertFalse(remove.getHelp().isEmpty()); assertEquals(1, remove.getOptions().size()); assertFalse(remove.allowedRoles.isEmpty()); @@ -117,6 +128,7 @@ public void testValidate() { Validate validate = (Validate) command.getChildren()[3]; assertEquals("validate", validate.getName()); + assertFalse(validate.getHelp().isEmpty()); assertEquals(1, validate.getOptions().size()); assertFalse(validate.allowedRoles.isEmpty()); @@ -184,6 +196,7 @@ public void testLink() { Link link = (Link) command.getChildren()[4]; assertEquals("link", link.getName()); + assertFalse(link.getHelp().isEmpty()); assertEquals(2, link.getOptions().size()); assertFalse(link.allowedRoles.isEmpty()); @@ -219,6 +232,7 @@ public void testUnlink() { Unlink unlink = (Unlink) command.getChildren()[5]; assertEquals("unlink", unlink.getName()); + assertFalse(unlink.getHelp().isEmpty()); assertEquals(2, unlink.getOptions().size()); assertFalse(unlink.allowedRoles.isEmpty()); @@ -249,20 +263,21 @@ public void testChangePassword() { ChangePassword changePassword = (ChangePassword) command.getChildren()[6]; assertEquals("change-password", changePassword.getName()); + assertFalse(changePassword.getHelp().isEmpty()); assertEquals(1, changePassword.getOptions().size()); SlashCommandEvent event = MockJDA.mockSlashCommandEvent(MockJDA.REQUEST_CHANNEL, changePassword, Map.of()); TestCommandListener listener = new TestCommandListener(changePassword); - JenkinsAPI.createJenkinsUser("User", "1234"); - NexusAPI.createNexus("User", "1234"); - DatabaseAPI.addUser("User", event.getMember().getIdLong()); + JenkinsAPI.createJenkinsUser("Bot", "1234"); + NexusAPI.createNexus("Bot", "1234"); + DatabaseAPI.addUser("Bot", event.getMember().getIdLong()); MockJDA.assertSlashCommandEvent(event, listener, CommandUtil.embedSuccess("Successfully changed your password!")); - JenkinsAPI.deleteUser("User"); - NexusAPI.deleteNexus("User"); - DatabaseAPI.removeUser("User"); + JenkinsAPI.deleteUser("Bot"); + NexusAPI.deleteNexus("Bot"); + DatabaseAPI.removeUser("Bot"); } @Test @@ -271,6 +286,7 @@ public void testCreateUser() { CreateUser createUser = (CreateUser) command.getChildren()[7]; assertEquals("createuser", createUser.getName()); + assertFalse(createUser.getHelp().isEmpty()); assertEquals(2, createUser.getOptions().size()); assertFalse(createUser.allowedRoles.isEmpty()); @@ -301,6 +317,7 @@ public void testDelUser() { DeleteUser delUser = (DeleteUser) command.getChildren()[8]; assertEquals("deluser", delUser.getName()); + assertFalse(delUser.getHelp().isEmpty()); assertEquals(1, delUser.getOptions().size()); assertFalse(delUser.allowedRoles.isEmpty()); From c3f311b3e8e91caa30a9b51a8f95195e3f95af76 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Thu, 5 Dec 2024 16:48:41 +0000 Subject: [PATCH 36/51] Test Messages from ApplicationHandler --- .../codemc/bot/commands/CmdApplication.java | 8 +++- src/test/java/io/codemc/bot/MockJDA.java | 26 +++++++++-- .../bot/utils/TestApplicationHandler.java | 43 ++++++++++++++----- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/codemc/bot/commands/CmdApplication.java b/src/main/java/io/codemc/bot/commands/CmdApplication.java index f4263d1..8905d76 100644 --- a/src/main/java/io/codemc/bot/commands/CmdApplication.java +++ b/src/main/java/io/codemc/bot/commands/CmdApplication.java @@ -32,6 +32,8 @@ import java.util.List; +import org.jetbrains.annotations.VisibleForTesting; + public class CmdApplication extends BotCommand{ public CmdApplication(CodeMCBot bot){ @@ -54,7 +56,8 @@ public void withModalReply(SlashCommandEvent event){} @Override public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild guild, Member member){} - private static class Accept extends BotCommand{ + @VisibleForTesting + static class Accept extends BotCommand{ public Accept(CodeMCBot bot){ super(bot); @@ -99,7 +102,8 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g } } - private static class Deny extends BotCommand{ + @VisibleForTesting + static class Deny extends BotCommand{ public Deny(CodeMCBot bot){ super(bot); diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index 014e381..f1f36b8 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -66,6 +66,8 @@ public class MockJDA { private static final Map messages = new HashMap<>(); private static final Map embeds = new HashMap<>(); private static final Map members = new HashMap<>(); + private static final Map latestMessages = new HashMap<>(); + private static final Map latestEmbeds = new HashMap<>(); private static long CURRENT_ID = 0; @@ -93,6 +95,14 @@ public static List getEmbeds(long id) { return Arrays.asList(embeds.get(id)); } + public static String getLatestMessage(MessageChannel channel) { + return latestMessages.get(channel.getIdLong()); + } + + public static List getLatestEmbeds(MessageChannel channel) { + return Arrays.asList(latestEmbeds.get(channel.getIdLong())); + } + public static InteractionHook mockInteractionHook(Member user, MessageChannel channel, InteractionType type) { return mockInteractionHook(mockInteraction(user, channel, type)); } @@ -152,6 +162,8 @@ public static Interaction mockInteraction(Member user, MessageChannel channel, I Interaction interaction = mock(Interaction.class); when(interaction.getJDA()).thenReturn(JDA); when(interaction.getChannel()).thenReturn(channel); + when(interaction.getMessageChannel()).thenReturn(channel); + when(interaction.getMember()).thenReturn(user); when(interaction.getUser()).thenAnswer(inv -> user.getUser()); when(interaction.getGuild()).thenReturn(GUILD); @@ -215,6 +227,8 @@ public static TextChannel mockChannel(String configName) { public static Message mockMessage(String content, MessageChannel channel) { Message message = JDAObjects.getMessage(content, channel); messages.put(message.getIdLong(), content); + latestMessages.put(channel.getIdLong(), content); + latestEmbeds.remove(channel.getIdLong()); when(message.getContentRaw()).thenAnswer(inv -> messages.get(message.getIdLong())); when(message.getIdLong()).thenReturn(CURRENT_ID); @@ -231,6 +245,7 @@ public static Message mockMessage(String content, MessageChannel channel) { public static Message mockMessage(String content, List embeds, MessageChannel channel) { Message message = mockMessage(content, channel); MockJDA.embeds.put(message.getIdLong(), embeds.toArray(new MessageEmbed[0])); + latestEmbeds.put(channel.getIdLong(), embeds.toArray(new MessageEmbed[0])); when(message.getEmbeds()).thenAnswer(inv -> Arrays.asList(MockJDA.embeds.get(message.getIdLong()))); when(message.getStartedThread()).thenReturn(null); @@ -327,10 +342,12 @@ private static Role mockRole(String name, long id, int position) { } public static void assertEmbeds(long id, List expectedOutputs, boolean ignoreTimestamp) { - MessageEmbed[] embeds = MockJDA.embeds.get(id); - if (embeds == null && expectedOutputs.isEmpty()) return; - - assertEquals(expectedOutputs.size(), embeds.length, "Number of embeds"); + assertEmbeds(expectedOutputs, Arrays.asList(embeds.getOrDefault(id, new MessageEmbed[0])), ignoreTimestamp); + } + + + public static void assertEmbeds(List expectedOutputs, List embeds, boolean ignoreTimestamp) { + assertEquals(expectedOutputs.size(), embeds.size(), "Number of embeds"); int i = 0; for (MessageEmbed embed : embeds) { @@ -570,6 +587,7 @@ private static & RestAction> T mockReply(Class RestAction mockAction(T object) { RestAction action = mock(RestAction.class); + when(action.complete()).thenReturn(object); doAnswer(inv -> { Consumer consumer = inv.getArgument(0); diff --git a/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java b/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java index f7d3e87..e24203f 100644 --- a/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java +++ b/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java @@ -1,5 +1,12 @@ package io.codemc.bot.utils; +import static io.codemc.bot.MockJDA.ACCEPTED_CHANNEL; +import static io.codemc.bot.MockJDA.AUTHOR; +import static io.codemc.bot.MockJDA.GUILD; +import static io.codemc.bot.MockJDA.REJECTED_CHANNEL; +import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; +import static io.codemc.bot.MockJDA.SELF; +import static io.codemc.bot.MockJDA.assertEmbeds; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -21,6 +28,7 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.InteractionType; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; public class TestApplicationHandler { @@ -29,21 +37,29 @@ public class TestApplicationHandler { public void testHandleAccepted() { String username = "TestApplicationHandlerAccepted"; Member member = MockJDA.mockMember(username); - InteractionHook hook = MockJDA.mockInteractionHook(member, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + InteractionHook hook = MockJDA.mockInteractionHook(SELF, REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); - Message message = MockJDA.mockMessage("", List.of(embed), MockJDA.REQUEST_CHANNEL); + Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); - assertFalse(CommandUtil.hasRole(member, List.of(MockJDA.AUTHOR.getIdLong()))); + assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); assertNull(NexusAPI.getNexusUser(username)); assertNull(DatabaseAPI.getUser(username)); ApplicationHandler.handle( - MockCodeMCBot.INSTANCE, hook, MockJDA.GUILD, message.getIdLong(), null, true + MockCodeMCBot.INSTANCE, hook, GUILD, message.getIdLong(), null, true ); - assertTrue(CommandUtil.hasRole(member, List.of(MockJDA.AUTHOR.getIdLong()))); + String jenkinsUrl = MockCodeMCBot.INSTANCE.getConfigHandler().getString("jenkins", "url") + "/job/" + username + "/job/Job/"; + MessageCreateData expected = ApplicationHandler.getMessage(MockCodeMCBot.INSTANCE, member.getId(), "userLink", "repoLink", jenkinsUrl, SELF.getUser(), true); + String content = MockJDA.getLatestMessage(ACCEPTED_CHANNEL); + List embeds = MockJDA.getLatestEmbeds(ACCEPTED_CHANNEL); + + assertEquals(expected.getContent(), content); + assertEmbeds(expected.getEmbeds(), embeds, true); + + assertTrue(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); assertFalse(JenkinsAPI.getJenkinsUser(username).isEmpty()); assertNotNull(NexusAPI.getNexusUser(username)); assertNotNull(DatabaseAPI.getUser(username)); @@ -59,21 +75,28 @@ public void testHandleAccepted() { public void testHandleRejected() { String username = "TestApplicationHandlerRejected"; Member member = MockJDA.mockMember(username); - InteractionHook hook = MockJDA.mockInteractionHook(member, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + InteractionHook hook = MockJDA.mockInteractionHook(SELF, REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); - Message message = MockJDA.mockMessage("", List.of(embed), MockJDA.REQUEST_CHANNEL); + Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); - assertFalse(CommandUtil.hasRole(member, List.of(MockJDA.AUTHOR.getIdLong()))); + assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); assertNull(NexusAPI.getNexusUser(username)); assertNull(DatabaseAPI.getUser(username)); ApplicationHandler.handle( - MockCodeMCBot.INSTANCE, hook, MockJDA.GUILD, message.getIdLong(), "Denied", false + MockCodeMCBot.INSTANCE, hook, GUILD, message.getIdLong(), "Denied", false ); - assertFalse(CommandUtil.hasRole(member, List.of(MockJDA.AUTHOR.getIdLong()))); + MessageCreateData expected = ApplicationHandler.getMessage(MockCodeMCBot.INSTANCE, member.getId(), "userLink", "repoLink", "Denied", SELF.getUser(), false); + String content = MockJDA.getLatestMessage(REJECTED_CHANNEL); + List embeds = MockJDA.getLatestEmbeds(REJECTED_CHANNEL); + + assertEquals(expected.getContent(), content); + assertEmbeds(expected.getEmbeds(), embeds, true); + + assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); assertNull(NexusAPI.getNexusUser(username)); assertNull(DatabaseAPI.getUser(username)); From 7c59a2e3a11d2007c773ca6bcf4c84bd500f46d2 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Fri, 6 Dec 2024 02:10:24 +0000 Subject: [PATCH 37/51] Add Tests for `/application` --- src/test/java/io/codemc/bot/MockJDA.java | 16 +- .../bot/commands/TestCmdApplication.java | 161 ++++++++++++++++++ 2 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 src/test/java/io/codemc/bot/commands/TestCmdApplication.java diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index f1f36b8..a2fddda 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -69,7 +69,9 @@ public class MockJDA { private static final Map latestMessages = new HashMap<>(); private static final Map latestEmbeds = new HashMap<>(); - private static long CURRENT_ID = 0; + // Message ID controller - ensures unique IDs for each message + // Mostly needs to match Event ID for testing + public static long CURRENT_ID = 0; public static final JDA JDA = JDAObjects.getJDA(); public static final Member SELF = mockMember("Bot"); @@ -230,9 +232,12 @@ public static Message mockMessage(String content, MessageChannel channel) { latestMessages.put(channel.getIdLong(), content); latestEmbeds.remove(channel.getIdLong()); + long id = CURRENT_ID; when(message.getContentRaw()).thenAnswer(inv -> messages.get(message.getIdLong())); - when(message.getIdLong()).thenReturn(CURRENT_ID); + when(message.getIdLong()).thenReturn(id); + when(message.getId()).thenReturn(Long.toString(id)); when(message.getGuild()).thenReturn(GUILD); + when(message.getMember()).thenReturn(SELF); when(message.editMessage(anyString())).thenAnswer(inv -> { messages.put(message.getIdLong(), inv.getArgument(0)); @@ -392,19 +397,20 @@ public static void assertEmbed(MessageEmbed embed, MessageEmbed expectedOutput, Assertions.assertEquals(expectedOutput.getTimestamp(), embed.getTimestamp()); } - public static void assertSlashCommandEvent(TestCommandListener listener, Map options, MessageEmbed... outputs) { + public static long assertSlashCommandEvent(TestCommandListener listener, Map options, MessageEmbed... outputs) { BotCommand command = listener.getCommand(); SlashCommandEvent event = mockSlashCommandEvent(REQUEST_CHANNEL, command, options); - assertSlashCommandEvent(event, listener, outputs); + return assertSlashCommandEvent(event, listener, outputs); } - public static void assertSlashCommandEvent(SlashCommandEvent event, TestCommandListener listener, MessageEmbed... outputs) { + public static long assertSlashCommandEvent(SlashCommandEvent event, TestCommandListener listener, MessageEmbed... outputs) { listener.onEvent(event); if (outputs != null) assertEmbeds(event.getIdLong(), Arrays.asList(outputs), true); CURRENT_ID++; + return event.getIdLong(); } public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, BotCommand command, Map options) { diff --git a/src/test/java/io/codemc/bot/commands/TestCmdApplication.java b/src/test/java/io/codemc/bot/commands/TestCmdApplication.java new file mode 100644 index 0000000..d8ebac4 --- /dev/null +++ b/src/test/java/io/codemc/bot/commands/TestCmdApplication.java @@ -0,0 +1,161 @@ +package io.codemc.bot.commands; + +import static io.codemc.bot.MockJDA.AUTHOR; +import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.api.database.DatabaseAPI; +import io.codemc.api.jenkins.JenkinsAPI; +import io.codemc.api.nexus.NexusAPI; +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import io.codemc.bot.commands.CmdApplication.Accept; +import io.codemc.bot.commands.CmdApplication.Deny; +import io.codemc.bot.utils.CommandUtil; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; + +public class TestCmdApplication { + + private static CmdApplication command; + + @BeforeAll + public static void init() { + command = new CmdApplication(MockCodeMCBot.INSTANCE); + } + + @Test + @DisplayName("Test /application") + public void testApplication() { + assertEquals("application", command.getName()); + assertFalse(command.getHelp().isEmpty()); + assertEquals(0, command.getOptions().size()); + assertFalse(command.allowedRoles.isEmpty()); + assertTrue(command.getChildren().length > 0); + } + + @Test + @DisplayName("Test /application accept") + public void testAccept() { + Accept accept = (Accept) command.getChildren()[0]; + + assertEquals("accept", accept.getName()); + assertFalse(accept.getHelp().isEmpty()); + assertEquals(1, accept.getOptions().size()); + assertFalse(accept.allowedRoles.isEmpty()); + + TestCommandListener listener = new TestCommandListener(accept); + String username = "TestApplicationAccept"; + Member member = MockJDA.mockMember(username); + + MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); + Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); + MockJDA.CURRENT_ID++; + + JenkinsAPI.deleteUser(username); + NexusAPI.deleteNexus(username); + DatabaseAPI.removeUser(username); + + assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); + assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNull(NexusAPI.getNexusUser(username)); + assertNull(DatabaseAPI.getUser(username)); + + long id = MockJDA.assertSlashCommandEvent(listener, Map.of("id", message.getId())); + + String expected = String.format(""" + [5/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `accepted-requests` channel found! + - [<:like:935126958193405962>] Join Request removed! + - Thread archived! + - Request Message deleted! + - [<:like:935126958193405962>] Gave User Role! + - Found Author Role! + - Applied Author Role to User! + + **Successfully accepted Join Request of user %s!** + """, member.getId(), member.getUser().getEffectiveName()); + + assertEquals(expected, MockJDA.getMessage(id)); + + assertTrue(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); + assertFalse(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNotNull(NexusAPI.getNexusUser(username)); + assertNotNull(DatabaseAPI.getUser(username)); + assertEquals(member.getIdLong(), DatabaseAPI.getUser(username).getDiscord()); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Message ID was not present!")); + + assertTrue(JenkinsAPI.deleteUser(username)); + assertTrue(NexusAPI.deleteNexus(username)); + assertEquals(1, DatabaseAPI.removeUser(username)); + } + + @Test + @DisplayName("Test /application deny") + public void testDeny() { + Deny deny = (Deny) command.getChildren()[1]; + + assertEquals("deny", deny.getName()); + assertFalse(deny.getHelp().isEmpty()); + assertEquals(2, deny.getOptions().size()); + assertFalse(deny.allowedRoles.isEmpty()); + + TestCommandListener listener = new TestCommandListener(deny); + + String username = "TestApplicationDeny"; + Member member = MockJDA.mockMember(username); + + MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); + Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); + MockJDA.CURRENT_ID++; + + assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); + assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNull(NexusAPI.getNexusUser(username)); + assertNull(DatabaseAPI.getUser(username)); + + long id = MockJDA.assertSlashCommandEvent(listener, Map.of("id", message.getId(), "reason", "Denied")); + + String expected = String.format( """ + [<:like:935126958193405962>] Handling of Join Request complete! + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `rejected-requests` channel found! + - [<:like:935126958193405962>] Join Request removed! + - Thread archived! + - Request Message deleted! + - [<:like:935126958193405962>] Finished rejecting join request of %s! + """, member.getId(), member.getUser().getEffectiveName()); + + assertEquals(expected, MockJDA.getMessage(id)); + + assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); + assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNull(NexusAPI.getNexusUser(username)); + assertNull(DatabaseAPI.getUser(username)); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Message ID was not present!")); + } + +} From a58030ac14f5afa8d0527e0ab98ad0fd70a0f22e Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Fri, 6 Dec 2024 02:11:19 +0000 Subject: [PATCH 38/51] Add Test for `/reload`, Soft Test for `/disable` --- .../java/io/codemc/bot/MockCodeMCBot.java | 7 ++-- .../codemc/bot/commands/TestCmdDisable.java | 26 ++++++++++++++ .../io/codemc/bot/commands/TestCmdReload.java | 34 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/test/java/io/codemc/bot/commands/TestCmdDisable.java create mode 100644 src/test/java/io/codemc/bot/commands/TestCmdReload.java diff --git a/src/test/java/io/codemc/bot/MockCodeMCBot.java b/src/test/java/io/codemc/bot/MockCodeMCBot.java index 14939ae..5ade9d8 100644 --- a/src/test/java/io/codemc/bot/MockCodeMCBot.java +++ b/src/test/java/io/codemc/bot/MockCodeMCBot.java @@ -20,6 +20,11 @@ private MockCodeMCBot() { configHandler = new ConfigHandler(); configHandler.loadConfig(); + setTestConfig(); + start(); + } + + public void setTestConfig() { // Set Nexus Password try { File file = new File("/tmp/admin.password"); @@ -36,8 +41,6 @@ private MockCodeMCBot() { if (token != null && !token.isEmpty()) { configHandler.set(System.getenv("GITHUB_TOKEN"), "github"); } - - start(); } @Override diff --git a/src/test/java/io/codemc/bot/commands/TestCmdDisable.java b/src/test/java/io/codemc/bot/commands/TestCmdDisable.java new file mode 100644 index 0000000..6526e32 --- /dev/null +++ b/src/test/java/io/codemc/bot/commands/TestCmdDisable.java @@ -0,0 +1,26 @@ +package io.codemc.bot.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.bot.MockCodeMCBot; + +public class TestCmdDisable { + + @Test + @DisplayName("Test /disable") + public void testDisable() { + CmdDisable command = new CmdDisable(MockCodeMCBot.INSTANCE); + + assertEquals("disable", command.getName()); + assertFalse(command.getHelp().isEmpty()); + assertEquals(0, command.getOptions().size()); + assertFalse(command.allowedRoles.isEmpty()); + + // Command calls System.exit - cannot be tested + } + +} diff --git a/src/test/java/io/codemc/bot/commands/TestCmdReload.java b/src/test/java/io/codemc/bot/commands/TestCmdReload.java new file mode 100644 index 0000000..d56e5f7 --- /dev/null +++ b/src/test/java/io/codemc/bot/commands/TestCmdReload.java @@ -0,0 +1,34 @@ +package io.codemc.bot.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import io.codemc.bot.utils.CommandUtil; + +public class TestCmdReload { + + @Test + @DisplayName("Test /reload") + public void testReload() { + CmdReload command = new CmdReload(MockCodeMCBot.INSTANCE); + + assertEquals("reload", command.getName()); + assertFalse(command.getHelp().isEmpty()); + assertEquals(0, command.getOptions().size()); + assertFalse(command.allowedRoles.isEmpty()); + + TestCommandListener listener = new TestCommandListener(command); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedSuccess("Reload success!")); + + MockCodeMCBot.INSTANCE.setTestConfig(); + } + +} \ No newline at end of file From 187e59ba1d48fa2f7a30f14199e97f9cc250d7fc Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Fri, 6 Dec 2024 16:44:18 +0000 Subject: [PATCH 39/51] Improve APIUtil Coverage --- build.gradle | 4 ++ .../java/io/codemc/bot/utils/TestAPIUtil.java | 61 +++++++++++++++---- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 250890c..39e96b2 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,10 @@ dependencies { } tasks { + clean { + delete "logs" + } + test { useJUnitPlatform() diff --git a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java index 47b0240..fe99795 100644 --- a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java +++ b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java @@ -13,6 +13,8 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.List; + public class TestAPIUtil { @BeforeAll @@ -111,26 +113,61 @@ public void testJenkins() { assertNull(JenkinsAPI.getJobInfo(user2, j2)); assertTrue(JenkinsAPI.deleteUser(user2)); assertTrue(JenkinsAPI.getJenkinsUser(user2).isEmpty()); + + String user3 = "TestJenkins3"; + String j3 = "Job"; + Member u3 = MockJDA.mockMember(user3); + String p3 = APIUtil.newPassword(); + InteractionHook h3 = MockJDA.mockInteractionHook(u3, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + JenkinsAPI.createJenkinsUser(user3, p3, false); + APIUtil.createJenkinsJob(h3, user3, p3, j3, "https://github.com/CodeMC/Bot", false); + + MockJDA.assertEmbeds( + List.of(CommandUtil.embedError("Jenkins User for " + user3 + " already exists!")), + MockJDA.getEmbeds(h3.getIdLong()), + true + ); + + assertTrue(JenkinsAPI.deleteUser(user3)); } @Test @DisplayName("Test APIUtil#changePassword") public void testChangePassword() { - String username = "TestChangePassword"; - String job = "Job"; - Member user = MockJDA.mockMember(username); - InteractionHook hook = MockJDA.mockInteractionHook(user, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + String u1 = "TestChangePassword1"; + String j1 = "Job"; + Member user1 = MockJDA.mockMember(u1); + InteractionHook h1 = MockJDA.mockInteractionHook(user1, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + String old1 = APIUtil.newPassword(); + assertTrue(APIUtil.createNexus(h1, u1, old1)); + assertTrue(APIUtil.createJenkinsJob(h1, u1, old1, j1, "https://github.com/CodeMC/API", false)); + + String new1 = APIUtil.newPassword(); + assertTrue(APIUtil.changePassword(h1, u1, new1)); + + assertTrue(JenkinsAPI.deleteJob(u1, j1)); + assertTrue(JenkinsAPI.deleteUser(u1)); + assertTrue(NexusAPI.deleteNexus(u1)); + + String u2 = "TestChangePassword2"; + Member user2 = MockJDA.mockMember(u2); + InteractionHook h2 = MockJDA.mockInteractionHook(user2, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + + String old2 = APIUtil.newPassword(); + assertTrue(APIUtil.createNexus(h2, u2, old2)); - String oldPassword = APIUtil.newPassword(); - assertTrue(APIUtil.createNexus(hook, username, oldPassword)); - assertTrue(APIUtil.createJenkinsJob(hook, username, oldPassword, job, "https://github.com/CodeMC/API", false)); + String new2 = APIUtil.newPassword(); + APIUtil.changePassword(h2, u2, new2); - String newPassword = APIUtil.newPassword(); - assertTrue(APIUtil.changePassword(hook, username, newPassword)); + MockJDA.assertEmbeds( + List.of(CommandUtil.embedError("Failed to change Jenkins Password for " + u2 + "!")), + MockJDA.getEmbeds(h2.getIdLong()), + false + ); - assertTrue(JenkinsAPI.deleteJob(username, job)); - assertTrue(JenkinsAPI.deleteUser(username)); - assertTrue(NexusAPI.deleteNexus(username)); + assertTrue(NexusAPI.deleteNexus(u2)); } } From c3bb904198341a443a91e73b428c94269b214b2d Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sat, 7 Dec 2024 02:09:41 +0000 Subject: [PATCH 40/51] Rework Message & Event IDs --- src/test/java/io/codemc/bot/MockJDA.java | 155 ++++++++++++++---- .../bot/commands/TestCmdApplication.java | 12 +- 2 files changed, 125 insertions(+), 42 deletions(-) diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index a2fddda..6e19b17 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -18,10 +18,14 @@ import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; +import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.Interaction; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.InteractionType; import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.modals.Modal; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; @@ -62,6 +66,7 @@ public class MockJDA { private static final ConfigHandler CONFIG = MockCodeMCBot.INSTANCE.getConfigHandler(); + private static final SecureRandom RANDOM = new SecureRandom(); private static final Map messages = new HashMap<>(); private static final Map embeds = new HashMap<>(); @@ -69,10 +74,6 @@ public class MockJDA { private static final Map latestMessages = new HashMap<>(); private static final Map latestEmbeds = new HashMap<>(); - // Message ID controller - ensures unique IDs for each message - // Mostly needs to match Event ID for testing - public static long CURRENT_ID = 0; - public static final JDA JDA = JDAObjects.getJDA(); public static final Member SELF = mockMember("Bot"); public static final Guild GUILD = mockGuild(); @@ -106,14 +107,18 @@ public static List getLatestEmbeds(MessageChannel channel) { } public static InteractionHook mockInteractionHook(Member user, MessageChannel channel, InteractionType type) { - return mockInteractionHook(mockInteraction(user, channel, type)); + return mockInteractionHook(user, channel, type, -1); + } + + public static InteractionHook mockInteractionHook(Member user, MessageChannel channel, InteractionType type, long id) { + return mockInteractionHook(mockInteraction(user, channel, type, id)); } public static InteractionHook mockInteractionHook(Interaction interaction) { InteractionHook hook = mock(InteractionHook.class); when(hook.getJDA()).thenReturn(JDA); when(hook.getExpirationTimestamp()).thenReturn(0L); - when(hook.getIdLong()).thenReturn(CURRENT_ID); + when(hook.getIdLong()).thenAnswer(inv -> interaction.getIdLong()); when(hook.getInteraction()).thenReturn(interaction); @@ -122,12 +127,12 @@ public static InteractionHook mockInteractionHook(Interaction interaction) { when(hook.editOriginal(anyString())).thenAnswer(inv -> { String content = inv.getArgument(0); messages.put(hook.getIdLong(), content); - return mockWebhookReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel)); + return mockWebhookReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel, hook.getIdLong())); }); when(hook.editOriginalFormat(anyString(), any(Object[].class))).thenAnswer(inv -> { String content = String.format(inv.getArgument(0), (Object[]) inv.getRawArguments()[1]); messages.put(hook.getIdLong(), content); - return mockWebhookReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel)); + return mockWebhookReply(WebhookMessageEditAction.class, hook, mockMessage(content, channel, hook.getIdLong())); }); when(hook.editOriginalEmbeds(any(MessageEmbed[].class))).thenAnswer(inv -> { @@ -137,7 +142,7 @@ public static InteractionHook mockInteractionHook(Interaction interaction) { else embeds.put(hook.getIdLong(), new MessageEmbed[] { (MessageEmbed) obj }); - return mockWebhookReply(WebhookMessageEditAction.class, hook, mockMessage(null, Arrays.asList(embeds.get(hook.getIdLong())), channel)); + return mockWebhookReply(WebhookMessageEditAction.class, hook, mockMessage(null, Arrays.asList(embeds.get(hook.getIdLong())), channel, hook.getIdLong())); }); when(hook.sendMessageEmbeds(any(), any(MessageEmbed[].class))).thenAnswer(inv -> { @@ -154,14 +159,16 @@ public static InteractionHook mockInteractionHook(Interaction interaction) { embeds.add((MessageEmbed) obj); } - return mockWebhookReply(WebhookMessageCreateAction.class, hook, mockMessage(null, embeds, channel)); + return mockWebhookReply(WebhookMessageCreateAction.class, hook, mockMessage(null, embeds, channel, hook.getIdLong())); }); return hook; } - public static Interaction mockInteraction(Member user, MessageChannel channel, InteractionType type) { + public static Interaction mockInteraction(Member user, MessageChannel channel, InteractionType type, long id) { Interaction interaction = mock(Interaction.class); + long id0 = id == -1 ? RANDOM.nextLong() : id; + when(interaction.getJDA()).thenReturn(JDA); when(interaction.getChannel()).thenReturn(channel); when(interaction.getMessageChannel()).thenReturn(channel); @@ -170,15 +177,23 @@ public static Interaction mockInteraction(Member user, MessageChannel channel, I when(interaction.getUser()).thenAnswer(inv -> user.getUser()); when(interaction.getGuild()).thenReturn(GUILD); when(interaction.getTypeRaw()).thenReturn(type.getKey()); - when(interaction.getIdLong()).thenReturn(CURRENT_ID); + when(interaction.getIdLong()).thenReturn(id0); return interaction; } + private static MessageChannelUnion mockUnion(MessageChannel channel) { + MessageChannelUnion union = mock(MessageChannelUnion.class); + when(union.asTextChannel()).thenAnswer(inv -> (TextChannel) channel); + when(union.getJDA()).thenReturn(JDA); + + return union; + } + public static TextChannel mockChannel(String configName) { long id = CONFIG.getLong("channels", configName); TextChannel channel = (TextChannel) JDAObjects.getMessageChannel(configName.replace('_', '-'), id, Callback.single()); - + when(channel.getGuild()).thenReturn(GUILD); when(channel.getJDA()).thenReturn(JDA); @@ -186,7 +201,7 @@ public static TextChannel mockChannel(String configName) { long messageId = inv.getArgument(0); String content = messages.get(messageId); Message message = mockMessage( - content, Arrays.asList(embeds.getOrDefault(messageId, new MessageEmbed[0])), channel + content, Arrays.asList(embeds.getOrDefault(messageId, new MessageEmbed[0])), channel, messageId ); return mockAction(message); @@ -227,17 +242,28 @@ public static TextChannel mockChannel(String configName) { } public static Message mockMessage(String content, MessageChannel channel) { + return mockMessage(content, channel, RANDOM.nextLong()); + } + + public static Message mockMessage(String content, MessageChannel channel, long id) { Message message = JDAObjects.getMessage(content, channel); messages.put(message.getIdLong(), content); latestMessages.put(channel.getIdLong(), content); latestEmbeds.remove(channel.getIdLong()); - long id = CURRENT_ID; when(message.getContentRaw()).thenAnswer(inv -> messages.get(message.getIdLong())); when(message.getIdLong()).thenReturn(id); when(message.getId()).thenReturn(Long.toString(id)); when(message.getGuild()).thenReturn(GUILD); when(message.getMember()).thenReturn(SELF); + when(message.getChannel()).thenAnswer(inv -> mockUnion(channel)); + + when(message.getStartedThread()).thenReturn(null); + when(message.delete()).thenAnswer(inv -> { + messages.remove(message.getIdLong()); + MockJDA.embeds.remove(message.getIdLong()); + return mockAuditLog(); + }); when(message.editMessage(anyString())).thenAnswer(inv -> { messages.put(message.getIdLong(), inv.getArgument(0)); @@ -248,17 +274,15 @@ public static Message mockMessage(String content, MessageChannel channel) { } public static Message mockMessage(String content, List embeds, MessageChannel channel) { - Message message = mockMessage(content, channel); + return mockMessage(content, embeds, channel, RANDOM.nextLong()); + } + + public static Message mockMessage(String content, List embeds, MessageChannel channel, long id) { + Message message = mockMessage(content, channel, id); MockJDA.embeds.put(message.getIdLong(), embeds.toArray(new MessageEmbed[0])); latestEmbeds.put(channel.getIdLong(), embeds.toArray(new MessageEmbed[0])); when(message.getEmbeds()).thenAnswer(inv -> Arrays.asList(MockJDA.embeds.get(message.getIdLong()))); - when(message.getStartedThread()).thenReturn(null); - when(message.delete()).thenAnswer(inv -> { - messages.remove(message.getIdLong()); - MockJDA.embeds.remove(message.getIdLong()); - return mockAuditLog(); - }); return message; } @@ -314,7 +338,7 @@ private static Guild mockGuild() { public static Member mockMember(String username) { Member member = JDAObjects.getMember(username, "0000"); - long id = new SecureRandom().nextLong(); + long id = RANDOM.nextLong(); members.put(id, member); when(member.getJDA()).thenReturn(JDA); @@ -409,18 +433,19 @@ public static long assertSlashCommandEvent(SlashCommandEvent event, TestCommandL if (outputs != null) assertEmbeds(event.getIdLong(), Arrays.asList(outputs), true); - CURRENT_ID++; return event.getIdLong(); } public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, BotCommand command, Map options) { SlashCommandEvent event = mock(SlashCommandEvent.class); + long id = RANDOM.nextLong(); + when(event.getName()).thenAnswer(invocation -> command.getName()); when(event.getSubcommandName()).thenAnswer(invocation -> command.getName()); when(event.getSubcommandGroup()).thenAnswer(invocation -> command.getSubcommandGroup()); when(event.getChannel()).thenAnswer(invocation -> channel); when(event.getGuild()).thenAnswer(invocation -> GUILD); - when(event.getIdLong()).thenReturn(CURRENT_ID); + when(event.getIdLong()).thenReturn(id); Member user = mockMember("User"); GUILD.addRoleToMember(user, AUTHOR); @@ -462,23 +487,23 @@ public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, Bo }); when(event.reply(anyString())).thenAnswer(invocation -> - mockReply(mockMessage(invocation.getArgument(0), channel))); + mockReply(mockMessage(invocation.getArgument(0), channel, id))); when(event.reply(any(MessageCreateData.class))).thenAnswer(invocation -> - mockReply(mockMessage(invocation.getArgument(0, MessageCreateData.class).getContent(), channel))); + mockReply(mockMessage(invocation.getArgument(0, MessageCreateData.class).getContent(), channel, id))); when(event.replyEmbeds(anyList())).thenAnswer(invocation -> - mockReply(mockMessage(null, invocation.getArgument(0), channel))); + mockReply(mockMessage(null, invocation.getArgument(0), channel, id))); when(event.replyEmbeds(any(MessageEmbed.class), any(MessageEmbed[].class))).thenAnswer(invocation -> { List embeds = invocation.getArguments().length == 1 ? new ArrayList<>() : Arrays.asList(invocation.getArgument(1)); embeds.add(invocation.getArgument(0)); - return mockReply(mockMessage(null, embeds, channel)); + return mockReply(mockMessage(null, embeds, channel, id)); }); when(event.deferReply()).thenAnswer(invocation -> - mockReply(mockMessage(null, channel)) + mockReply(mockMessage(null, channel, id)) ); - when(event.deferReply(any(Boolean.class))).thenAnswer(invocation -> - mockReply(mockMessage(null, channel)) + when(event.deferReply(anyBoolean())).thenAnswer(invocation -> + mockReply(mockMessage(null, channel, id)) ); when(event.replyModal(any())).thenAnswer(inv -> { @@ -489,6 +514,70 @@ public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, Bo return event; } + public static long assertButtonInteractionEvent(ListenerAdapter listener, Message message, Button button, MessageEmbed... outputs) { + ButtonInteractionEvent event = mockButtonInteractionEvent(message, button); + return assertButtonInteractionEvent(listener, event, outputs); + } + + public static long assertButtonInteractionEvent(ListenerAdapter listener, ButtonInteractionEvent event, MessageEmbed... outputs) { + listener.onButtonInteraction(event); + + if (outputs != null) + assertEmbeds(event.getIdLong(), Arrays.asList(outputs), true); + + return event.getIdLong(); + } + + public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, Button button) { + ButtonInteractionEvent event = mock(ButtonInteractionEvent.class); + TextChannel channel = message.getChannel().asTextChannel(); + long id = RANDOM.nextLong(); + + when(event.getGuild()).thenReturn(GUILD); + when(event.getMessageChannel()).thenReturn(channel); + when(event.getButton()).thenReturn(button); + when(event.isFromGuild()).thenReturn(true); + + Member user = mockMember("User"); + GUILD.addRoleToMember(user, AUTHOR); + GUILD.addRoleToMember(user, REVIEWER); + GUILD.addRoleToMember(user, MAINTAINER); + GUILD.addRoleToMember(user, ADMINISTRATOR); + + when(event.getMember()).thenReturn(user); + when(event.getUser()).thenAnswer(inv -> user.getUser()); + when(event.getIdLong()).thenReturn(id); + + when(event.getMessage()).thenReturn(message); + when(event.getMessageId()).thenAnswer(inv -> message.getId()); + when(event.getMessageIdLong()).thenAnswer(inv -> message.getIdLong()); + + when(event.reply(anyString())).thenAnswer(invocation -> + mockReply(mockMessage(invocation.getArgument(0), channel, id))); + when(event.reply(any(MessageCreateData.class))).thenAnswer(invocation -> + mockReply(mockMessage(invocation.getArgument(0, MessageCreateData.class).getContent(), channel, id))); + when(event.replyEmbeds(anyList())).thenAnswer(invocation -> + mockReply(mockMessage(null, invocation.getArgument(0), channel, id))); + when(event.replyEmbeds(any(MessageEmbed.class), any(MessageEmbed[].class))).thenAnswer(invocation -> { + List embeds = invocation.getArguments().length == 1 ? new ArrayList<>() : Arrays.asList(invocation.getArgument(1)); + embeds.add(invocation.getArgument(0)); + return mockReply(mockMessage(null, embeds, channel, id)); + }); + when(event.replyModal(any(Modal.class))).thenAnswer(inv -> { + CURRENT_MODAL = inv.getArgument(0); + return mockModalReply(user, channel); + }); + + when(event.deferReply()).thenAnswer(inv -> { + return mockReply(mockMessage(null, channel, id)); + }); + when(event.deferReply(anyBoolean())).thenAnswer(inv -> { + return mockReply(mockMessage(null, channel, id)); + }); + + return event; + } + private static ReplyCallbackAction mockReply(Message message) { ReplyCallbackAction action = mock(ReplyCallbackAction.class); @@ -515,7 +604,7 @@ private static ReplyCallbackAction mockReply(Message message) { doAnswer(inv -> { Consumer hookConsumer = inv.getArgument(0); - hookConsumer.accept(mockInteractionHook(message.getMember(), message.getChannel(), InteractionType.COMMAND)); + hookConsumer.accept(mockInteractionHook(message.getMember(), message.getChannel(), InteractionType.COMMAND, message.getIdLong())); return null; }).when(action).queue(any()); diff --git a/src/test/java/io/codemc/bot/commands/TestCmdApplication.java b/src/test/java/io/codemc/bot/commands/TestCmdApplication.java index d8ebac4..bb51f80 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdApplication.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdApplication.java @@ -62,18 +62,13 @@ public void testAccept() { MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); - MockJDA.CURRENT_ID++; - - JenkinsAPI.deleteUser(username); - NexusAPI.deleteNexus(username); - DatabaseAPI.removeUser(username); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); assertNull(NexusAPI.getNexusUser(username)); assertNull(DatabaseAPI.getUser(username)); - long id = MockJDA.assertSlashCommandEvent(listener, Map.of("id", message.getId())); + long id = MockJDA.assertSlashCommandEvent(listener, Map.of("id", message.getId()), (MessageEmbed[]) null); String expected = String.format(""" [5/5] Handling Join Request... @@ -125,14 +120,13 @@ public void testDeny() { MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); - MockJDA.CURRENT_ID++; assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); assertNull(NexusAPI.getNexusUser(username)); assertNull(DatabaseAPI.getUser(username)); - long id = MockJDA.assertSlashCommandEvent(listener, Map.of("id", message.getId(), "reason", "Denied")); + long id = MockJDA.assertSlashCommandEvent(listener, Map.of("id", message.getId(), "reason", "Denied"), (MessageEmbed[]) null); String expected = String.format( """ [<:like:935126958193405962>] Handling of Join Request complete! @@ -147,7 +141,7 @@ public void testDeny() { - Request Message deleted! - [<:like:935126958193405962>] Finished rejecting join request of %s! """, member.getId(), member.getUser().getEffectiveName()); - + assertEquals(expected, MockJDA.getMessage(id)); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); From 0b013791cd3170af4dfa289beaa578054c913555 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sat, 7 Dec 2024 02:09:57 +0000 Subject: [PATCH 41/51] Create `TestButtonListener.java` --- .../codemc/bot/listeners/ButtonListener.java | 21 ++- .../bot/listeners/TestButtonListener.java | 136 ++++++++++++++++++ 2 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 src/test/java/io/codemc/bot/listeners/TestButtonListener.java diff --git a/src/main/java/io/codemc/bot/listeners/ButtonListener.java b/src/main/java/io/codemc/bot/listeners/ButtonListener.java index 4f20abd..f7bb33e 100644 --- a/src/main/java/io/codemc/bot/listeners/ButtonListener.java +++ b/src/main/java/io/codemc/bot/listeners/ButtonListener.java @@ -32,6 +32,7 @@ import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; import net.dv8tion.jda.api.interactions.modals.Modal; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,9 +54,11 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event){ CommandUtil.EmbedReply.from(event).error("Buttons only work on the CodeMC Server!").send(); return; } - - if(event.getButton().getId() == null){ - event.deferReply().queue(); + + String id = event.getButton().getId(); + if (id == null) { + CommandUtil.EmbedReply.from(event).error("Received Button Interaction with no ID!").send(); + logger.error("Received Button Interaction with no ID!"); return; } @@ -73,13 +76,6 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event){ CommandUtil.EmbedReply.from(event).error("Cannot get Member from Server!").send(); return; } - - String id = event.getButton().getId(); - if (id == null) { - CommandUtil.EmbedReply.from(event).error("Received Button Interaction with no ID!").send(); - logger.error("Received Button Interaction with no ID!"); - return; - } String[] values = id.split(":"); if(values.length < 4 || !values[0].equals("application")){ @@ -90,7 +86,7 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event){ if(!values[1].equals("accept") && !values[1].equals("deny")){ CommandUtil.EmbedReply.from(event).error( "Received unknown Button Application type.", - "Expected `accept` or `deny` but got " + values[1] + "." + "Expected `accept` or `deny` but got `" + values[1] + "`." ).send(); return; } @@ -128,7 +124,8 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event){ } } - private boolean lacksRole(List roleIds, List allowedRoleIds){ + @VisibleForTesting + boolean lacksRole(List roleIds, List allowedRoleIds){ if(roleIds.isEmpty()) return true; diff --git a/src/test/java/io/codemc/bot/listeners/TestButtonListener.java b/src/test/java/io/codemc/bot/listeners/TestButtonListener.java new file mode 100644 index 0000000..368161e --- /dev/null +++ b/src/test/java/io/codemc/bot/listeners/TestButtonListener.java @@ -0,0 +1,136 @@ +package io.codemc.bot.listeners; + +import static io.codemc.bot.MockJDA.AUTHOR; +import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.api.database.DatabaseAPI; +import io.codemc.api.jenkins.JenkinsAPI; +import io.codemc.api.nexus.NexusAPI; +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import io.codemc.bot.utils.CommandUtil; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.components.buttons.Button; + +public class TestButtonListener { + + private static ButtonListener listener; + + @BeforeAll + public static void init() { + listener = new ButtonListener(MockCodeMCBot.INSTANCE); + } + + @Test + @DisplayName("Test Application Accept") + public void testApplicationAccept() { + String username = "TestButtonListenerAccept"; + Member member = MockJDA.mockMember(username); + + MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); + Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); + + JenkinsAPI.deleteUser(username); + NexusAPI.deleteNexus(username); + DatabaseAPI.removeUser(username); + + assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); + assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNull(NexusAPI.getNexusUser(username)); + assertNull(DatabaseAPI.getUser(username)); + + MockJDA.assertButtonInteractionEvent(listener, message, Button.success("application:accept:" + username + ":Job", "Accept"), (MessageEmbed[]) null); + + assertTrue(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); + assertFalse(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNotNull(NexusAPI.getNexusUser(username)); + assertNotNull(DatabaseAPI.getUser(username)); + assertEquals(member.getIdLong(), DatabaseAPI.getUser(username).getDiscord()); + + assertTrue(JenkinsAPI.deleteUser(username)); + assertTrue(NexusAPI.deleteNexus(username)); + assertEquals(1, DatabaseAPI.removeUser(username)); + } + + @Test + @DisplayName("Test Application Deny") + public void testApplicationDeny() { + String username = "TestButtonListenerDeny"; + Member member = MockJDA.mockMember(username); + + MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); + Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); + + JenkinsAPI.deleteUser(username); + NexusAPI.deleteNexus(username); + DatabaseAPI.removeUser(username); + + assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); + assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNull(NexusAPI.getNexusUser(username)); + + MockJDA.assertButtonInteractionEvent(listener, message, Button.danger("application:deny:" + username + ":Job", "Deny"), (MessageEmbed[]) null); + + assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); + assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); + assertNull(NexusAPI.getNexusUser(username)); + } + + @Test + @DisplayName("Test ButtonListener Errors") + public void testButtonListenerErrors() { + Message m1 = MockJDA.mockMessage(null, REQUEST_CHANNEL); + ButtonInteractionEvent e1 = MockJDA.mockButtonInteractionEvent(m1, Button.primary("null", "null")); + when(e1.getGuild()).thenReturn(null); + MockJDA.assertButtonInteractionEvent(listener, e1, CommandUtil.embedError("Buttons only work on the CodeMC Server!")); + + Message m2 = MockJDA.mockMessage(null, REQUEST_CHANNEL); + Button b2 = mock(Button.class); + when(b2.getId()).thenReturn(null); + ButtonInteractionEvent e2 = MockJDA.mockButtonInteractionEvent(m2, b2); + MockJDA.assertButtonInteractionEvent(listener, e2, CommandUtil.embedError("Received Button Interaction with no ID!")); + + Message m3 = MockJDA.mockMessage(null, REQUEST_CHANNEL); + ButtonInteractionEvent e3 = MockJDA.mockButtonInteractionEvent(m3, Button.primary("null", "null")); + when(e3.getMember()).thenReturn(null); + MockJDA.assertButtonInteractionEvent(listener, e3, CommandUtil.embedError("Cannot get Member from Server!")); + + Message m4 = MockJDA.mockMessage(null, REQUEST_CHANNEL); + ButtonInteractionEvent e4 = MockJDA.mockButtonInteractionEvent(m4, Button.primary("null", "null")); + MockJDA.assertButtonInteractionEvent(listener, e4, CommandUtil.embedError("Received non-application button event!")); + + Message m5 = MockJDA.mockMessage(null, REQUEST_CHANNEL); + ButtonInteractionEvent e5 = MockJDA.mockButtonInteractionEvent(m5, Button.primary("application:null:null:null", "null")); + MockJDA.assertButtonInteractionEvent(listener, e5, CommandUtil.embedError("Received unknown Button Application type.", "Expected `accept` or `deny` but got `null`.")); + } + + @Test + @DisplayName("Test ButtonListener#lacksRole") + public void testLacksRole() { + assertTrue(listener.lacksRole(List.of(), List.of())); + + assertTrue(listener.lacksRole(List.of(1L, 2L, 3L), List.of(4L))); + assertTrue(listener.lacksRole(List.of(1L, 2L, 3L), List.of(4L, 5L))); + assertTrue(listener.lacksRole(List.of(2L, 3L, 4L), List.of(5L, 6L, 7L))); + + assertFalse(listener.lacksRole(List.of(1L, 2L, 3L), List.of(1L))); + assertFalse(listener.lacksRole(List.of(1L, 2L, 3L), List.of(1L, 2L))); + } + +} From 8a8f1df925e93b238fa121891062744a89e7b113 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sat, 7 Dec 2024 02:37:00 +0000 Subject: [PATCH 42/51] Prevent Negative IDs --- src/test/java/io/codemc/bot/MockJDA.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index 6e19b17..c95e94b 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -107,7 +107,7 @@ public static List getLatestEmbeds(MessageChannel channel) { } public static InteractionHook mockInteractionHook(Member user, MessageChannel channel, InteractionType type) { - return mockInteractionHook(user, channel, type, -1); + return mockInteractionHook(user, channel, type, RANDOM.nextLong(0, Long.MAX_VALUE)); } public static InteractionHook mockInteractionHook(Member user, MessageChannel channel, InteractionType type, long id) { @@ -167,7 +167,6 @@ public static InteractionHook mockInteractionHook(Interaction interaction) { public static Interaction mockInteraction(Member user, MessageChannel channel, InteractionType type, long id) { Interaction interaction = mock(Interaction.class); - long id0 = id == -1 ? RANDOM.nextLong() : id; when(interaction.getJDA()).thenReturn(JDA); when(interaction.getChannel()).thenReturn(channel); @@ -177,7 +176,7 @@ public static Interaction mockInteraction(Member user, MessageChannel channel, I when(interaction.getUser()).thenAnswer(inv -> user.getUser()); when(interaction.getGuild()).thenReturn(GUILD); when(interaction.getTypeRaw()).thenReturn(type.getKey()); - when(interaction.getIdLong()).thenReturn(id0); + when(interaction.getIdLong()).thenReturn(id); return interaction; } @@ -242,7 +241,7 @@ public static TextChannel mockChannel(String configName) { } public static Message mockMessage(String content, MessageChannel channel) { - return mockMessage(content, channel, RANDOM.nextLong()); + return mockMessage(content, channel, RANDOM.nextLong(0, Long.MAX_VALUE)); } public static Message mockMessage(String content, MessageChannel channel, long id) { @@ -274,7 +273,7 @@ public static Message mockMessage(String content, MessageChannel channel, long i } public static Message mockMessage(String content, List embeds, MessageChannel channel) { - return mockMessage(content, embeds, channel, RANDOM.nextLong()); + return mockMessage(content, embeds, channel, RANDOM.nextLong(0, Long.MAX_VALUE)); } public static Message mockMessage(String content, List embeds, MessageChannel channel, long id) { @@ -338,7 +337,7 @@ private static Guild mockGuild() { public static Member mockMember(String username) { Member member = JDAObjects.getMember(username, "0000"); - long id = RANDOM.nextLong(); + long id = RANDOM.nextLong(0, Long.MAX_VALUE); members.put(id, member); when(member.getJDA()).thenReturn(JDA); @@ -438,7 +437,7 @@ public static long assertSlashCommandEvent(SlashCommandEvent event, TestCommandL public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, BotCommand command, Map options) { SlashCommandEvent event = mock(SlashCommandEvent.class); - long id = RANDOM.nextLong(); + long id = RANDOM.nextLong(0, Long.MAX_VALUE); when(event.getName()).thenAnswer(invocation -> command.getName()); when(event.getSubcommandName()).thenAnswer(invocation -> command.getName()); @@ -531,7 +530,7 @@ public static long assertButtonInteractionEvent(ListenerAdapter listener, Button public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, Button button) { ButtonInteractionEvent event = mock(ButtonInteractionEvent.class); TextChannel channel = message.getChannel().asTextChannel(); - long id = RANDOM.nextLong(); + long id = RANDOM.nextLong(0, Long.MAX_VALUE); when(event.getGuild()).thenReturn(GUILD); when(event.getMessageChannel()).thenReturn(channel); From f6b8197b7e72c5cb2f41a8d50160d05595ec1a6d Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sat, 7 Dec 2024 04:07:27 +0000 Subject: [PATCH 43/51] Improve Exception Logging, Make Unit Test Compatible --- .../codemc/bot/listeners/ModalListener.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/codemc/bot/listeners/ModalListener.java b/src/main/java/io/codemc/bot/listeners/ModalListener.java index 525834a..218811b 100644 --- a/src/main/java/io/codemc/bot/listeners/ModalListener.java +++ b/src/main/java/io/codemc/bot/listeners/ModalListener.java @@ -32,7 +32,6 @@ import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.modals.ModalMapping; -import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.utils.MarkdownUtil; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -113,18 +112,20 @@ public void onModalInteraction(@NotNull ModalInteractionEvent event){ "[Request sent!](" + message.getJumpUrl() + ")") .send(); - RestAction.allOf( - message.createThreadChannel("Access Request - " + event.getUser().getName()), - message.addReaction(Emoji.fromCustom("like", 935126958193405962L, false)), - message.addReaction(Emoji.fromCustom("dislike", 935126958235344927L, false)) - ).queue(); + message.createThreadChannel("Access Request - " + event.getUser().getName()).queue(); + message.addReaction(Emoji.fromCustom("like", 935126958193405962L, false)).queue(); + message.addReaction(Emoji.fromCustom("dislike", 935126958235344927L, false)).queue(); logger.info("[Access Request] User {} requested access to the CI.", event.getUser().getEffectiveName()); }, - e -> CommandUtil.EmbedReply.from(hook).error( - "Error while submitting request!", - "Reported Error: " + e.getMessage() - ).send() + e -> { + CommandUtil.EmbedReply.from(hook).error( + "Error while submitting request!", + "Reported Error: " + e.getMessage() + ).send(); + + logger.error("Error while submitting request", e); + } ); }); From 73069dc229b01942a4c191f6ccaeeea386e91abd Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sat, 7 Dec 2024 04:07:35 +0000 Subject: [PATCH 44/51] Create TestModalListener.java --- src/test/java/io/codemc/bot/MockJDA.java | 128 +++++++++++++---- .../bot/listeners/TestModalListener.java | 132 ++++++++++++++++++ 2 files changed, 231 insertions(+), 29 deletions(-) create mode 100644 src/test/java/io/codemc/bot/listeners/TestModalListener.java diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index c95e94b..0968bb9 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -19,18 +19,23 @@ import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.interactions.Interaction; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.InteractionType; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.components.ItemComponent; import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.modals.Modal; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; import net.dv8tion.jda.api.requests.RestAction; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; import net.dv8tion.jda.api.requests.restaction.MessageEditAction; +import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; import net.dv8tion.jda.api.requests.restaction.interactions.ModalCallbackAction; @@ -46,6 +51,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.Consumer; import org.junit.jupiter.api.Assertions; @@ -79,10 +85,11 @@ public class MockJDA { public static final Guild GUILD = mockGuild(); public static Modal CURRENT_MODAL = null; + public static final TextChannel GENERAL = mockChannel("general"); // Not found, let it be -1 public static final TextChannel REQUEST_CHANNEL = mockChannel("request_access"); public static final TextChannel ACCEPTED_CHANNEL = mockChannel("accepted_requests"); public static final TextChannel REJECTED_CHANNEL = mockChannel("rejected_requests"); - public static final List CHANNELS = List.of(REQUEST_CHANNEL, ACCEPTED_CHANNEL, REJECTED_CHANNEL); + public static final List CHANNELS = List.of(GENERAL, REQUEST_CHANNEL, ACCEPTED_CHANNEL, REJECTED_CHANNEL); public static final Role ADMINISTRATOR = mockRole("Administrator", 405917902865170453L, 4); public static final Role MAINTAINER = mockRole("Maintainer", 659568973079379971L, 3); @@ -251,6 +258,7 @@ public static Message mockMessage(String content, MessageChannel channel, long i latestEmbeds.remove(channel.getIdLong()); when(message.getContentRaw()).thenAnswer(inv -> messages.get(message.getIdLong())); + when(message.getJumpUrl()).thenReturn(""); when(message.getIdLong()).thenReturn(id); when(message.getId()).thenReturn(Long.toString(id)); when(message.getGuild()).thenReturn(GUILD); @@ -268,6 +276,9 @@ public static Message mockMessage(String content, MessageChannel channel, long i messages.put(message.getIdLong(), inv.getArgument(0)); return mockWebhookReply(MessageEditAction.class, mockInteractionHook(message.getMember(), channel, InteractionType.COMMAND), message); }); + + when(message.createThreadChannel(anyString())).thenAnswer(inv -> mock(ThreadChannelAction.class)); + when(message.addReaction(any())).thenAnswer(inv -> mockAction(null)); return message; } @@ -422,7 +433,7 @@ public static void assertEmbed(MessageEmbed embed, MessageEmbed expectedOutput, public static long assertSlashCommandEvent(TestCommandListener listener, Map options, MessageEmbed... outputs) { BotCommand command = listener.getCommand(); - SlashCommandEvent event = mockSlashCommandEvent(REQUEST_CHANNEL, command, options); + SlashCommandEvent event = mockSlashCommandEvent(GENERAL, command, options); return assertSlashCommandEvent(event, listener, outputs); } @@ -485,27 +496,8 @@ public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, Bo return options.containsKey(key) ? options.get(key) : def; }); - when(event.reply(anyString())).thenAnswer(invocation -> - mockReply(mockMessage(invocation.getArgument(0), channel, id))); - when(event.reply(any(MessageCreateData.class))).thenAnswer(invocation -> - mockReply(mockMessage(invocation.getArgument(0, MessageCreateData.class).getContent(), channel, id))); - when(event.replyEmbeds(anyList())).thenAnswer(invocation -> - mockReply(mockMessage(null, invocation.getArgument(0), channel, id))); - when(event.replyEmbeds(any(MessageEmbed.class), any(MessageEmbed[].class))).thenAnswer(invocation -> { - List embeds = invocation.getArguments().length == 1 ? new ArrayList<>() : Arrays.asList(invocation.getArgument(1)); - embeds.add(invocation.getArgument(0)); - return mockReply(mockMessage(null, embeds, channel, id)); - }); - - when(event.deferReply()).thenAnswer(invocation -> - mockReply(mockMessage(null, channel, id)) - ); - - when(event.deferReply(anyBoolean())).thenAnswer(invocation -> - mockReply(mockMessage(null, channel, id)) - ); - - when(event.replyModal(any())).thenAnswer(inv -> { + mockReplyCallbacks(event, channel, id); + when(event.replyModal(any(Modal.class))).thenAnswer(inv -> { CURRENT_MODAL = inv.getArgument(0); return mockModalReply(user, channel); }); @@ -532,6 +524,7 @@ public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, TextChannel channel = message.getChannel().asTextChannel(); long id = RANDOM.nextLong(0, Long.MAX_VALUE); + when(event.isFromGuild()).thenReturn(true); when(event.getGuild()).thenReturn(GUILD); when(event.getMessageChannel()).thenReturn(channel); when(event.getButton()).thenReturn(button); @@ -551,6 +544,74 @@ public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, when(event.getMessageId()).thenAnswer(inv -> message.getId()); when(event.getMessageIdLong()).thenAnswer(inv -> message.getIdLong()); + mockReplyCallbacks(event, channel, id); + when(event.replyModal(any(Modal.class))).thenAnswer(inv -> { + CURRENT_MODAL = inv.getArgument(0); + return mockModalReply(user, channel); + }); + + return event; + } + + public static long assertModalInteractionEvent(ListenerAdapter listener, Modal modal, MessageChannel channel, Map options, MessageEmbed... outputs) { + ModalInteractionEvent event = mockModalInteractionEvent(modal, channel, options); + return assertModalInteractionEvent(listener, event, outputs); + } + + public static long assertModalInteractionEvent(ListenerAdapter listener, ModalInteractionEvent event, MessageEmbed... outputs) { + listener.onModalInteraction(event); + + if (outputs != null) + assertEmbeds(event.getIdLong(), Arrays.asList(outputs), true); + + return event.getIdLong(); + } + + public static ModalInteractionEvent mockModalInteractionEvent(Modal modal, MessageChannel channel, Map options) { + ModalInteractionEvent event = mock(ModalInteractionEvent.class); + long id = RANDOM.nextLong(0, Long.MAX_VALUE); + + when(event.getJDA()).thenReturn(JDA); + when(event.isFromGuild()).thenReturn(true); + when(event.getGuild()).thenReturn(GUILD); + when(event.getModalId()).thenAnswer(inv -> modal.getId()); + when(event.getChannel()).thenAnswer(inv -> mockUnion(channel)); + when(event.getMessageChannel()).thenReturn(channel); + + when(event.getValue(anyString())).thenAnswer(inv -> { + String key = inv.getArgument(0); + if (!options.containsKey(key)) return null; + + ModalMapping mapping = mock(ModalMapping.class); + when(mapping.getId()).thenReturn(key); + when(mapping.getAsString()).thenReturn(options.get(key)); + + return mapping; + }); + + Member user = mockMember("User"); + GUILD.addRoleToMember(user, AUTHOR); + GUILD.addRoleToMember(user, REVIEWER); + GUILD.addRoleToMember(user, MAINTAINER); + GUILD.addRoleToMember(user, ADMINISTRATOR); + + when(event.getMember()).thenReturn(user); + when(event.getUser()).thenAnswer(inv -> user.getUser()); + when(event.getIdLong()).thenReturn(id); + + mockReplyCallbacks(event, channel, id); + return event; + } + + public static Modal mockModal(String id, String title) { + Modal modal = mock(Modal.class); + when(modal.getId()).thenReturn(id); + when(modal.getTitle()).thenReturn(title); + + return modal; + } + + private static void mockReplyCallbacks(IReplyCallback event, MessageChannel channel, long id) { when(event.reply(anyString())).thenAnswer(invocation -> mockReply(mockMessage(invocation.getArgument(0), channel, id))); when(event.reply(any(MessageCreateData.class))).thenAnswer(invocation -> @@ -562,10 +623,6 @@ public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, embeds.add(invocation.getArgument(0)); return mockReply(mockMessage(null, embeds, channel, id)); }); - when(event.replyModal(any(Modal.class))).thenAnswer(inv -> { - CURRENT_MODAL = inv.getArgument(0); - return mockModalReply(user, channel); - }); when(event.deferReply()).thenAnswer(inv -> { return mockReply(mockMessage(null, channel, id)); @@ -573,8 +630,6 @@ public static ButtonInteractionEvent mockButtonInteractionEvent(Message message, when(event.deferReply(anyBoolean())).thenAnswer(inv -> { return mockReply(mockMessage(null, channel, id)); }); - - return event; } private static ReplyCallbackAction mockReply(Message message) { @@ -668,6 +723,18 @@ private static & RestAction> T mockReply(Class { + Consumer messageConsumer = inv.getArgument(0); + Consumer errorConsumer = inv.getArgument(1); + + try { + messageConsumer.accept(message); + } catch (Exception e) { + errorConsumer.accept(e); + } + return null; + }).when(action).queue(any(), any()); + when(action.getEmbeds()).thenAnswer(inv -> message.getEmbeds()); when(action.setEmbeds(anyCollection())).thenAnswer(inv -> { Collection embed = inv.getArgument(0); @@ -675,6 +742,9 @@ private static & RestAction> T mockReply(Class action); + when(action.setActionRow(anyCollection())).thenAnswer(inv -> action); + return action; } diff --git a/src/test/java/io/codemc/bot/listeners/TestModalListener.java b/src/test/java/io/codemc/bot/listeners/TestModalListener.java new file mode 100644 index 0000000..c3fedd9 --- /dev/null +++ b/src/test/java/io/codemc/bot/listeners/TestModalListener.java @@ -0,0 +1,132 @@ +package io.codemc.bot.listeners; + +import static io.codemc.bot.MockJDA.GENERAL; +import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import io.codemc.api.jenkins.JenkinsAPI; +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import io.codemc.bot.utils.CommandUtil; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.interactions.modals.Modal; + +public class TestModalListener { + + private static ModalListener listener; + + @BeforeAll + public static void init() { + listener = new ModalListener(MockCodeMCBot.INSTANCE); + } + + + @Test + @DisplayName("Test Submit") + public void testSubmit() { + String username = "TestModalListenerSubmit"; + Modal modal = MockJDA.mockModal("submit", "Submit"); + Map options = Map.of( + "user", username, + "repo", "Job", + "description", "description", + "repoLink", "repoLink" + ); + + MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, options, CommandUtil.embedSuccess("[Request sent!]()")); + MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, Map.of("user", username, "repo", "Job", "description", "description"), CommandUtil.embedSuccess("[Request sent!]()")); + + MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, Map.of(), CommandUtil.embedError("The provided user was invalid.")); + + JenkinsAPI.createJenkinsUser(username, "1234"); + MockJDA.assertModalInteractionEvent(listener, modal, GENERAL, options, CommandUtil.embedError("A Jenkins User named '" + username + "' already exists!")); + JenkinsAPI.deleteUser(username); + + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username, "description", "Description"), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username, "repo", "Job"), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username, "repo", "", "description", "Description"), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + MockJDA.assertModalInteractionEvent( + listener, modal, GENERAL, Map.of("user", username, "repo", "Job", "description", ""), + CommandUtil.embedError("The option User, Repo and/or Description was not set properly!") + ); + } + + @Test + @DisplayName("Test Deny Application") + public void testDenyApplication() { + String username = "TestModalApplicationDeny"; + Member member = MockJDA.mockMember(username); + + MessageEmbed embed = CommandUtil.requestEmbed("[" + username + "](userLink)", "[Job](repoLink)", member.getAsMention(), "description", member.getId()); + + Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); + Message message2 = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); + + Modal m1 = MockJDA.mockModal("deny_application:" + message.getId(), "Deny Application"); + Modal m2 = MockJDA.mockModal("deny_application:" + message2.getId(), "Deny Application"); + + long id1 = MockJDA.assertModalInteractionEvent(listener, m1, REQUEST_CHANNEL, Map.of(), (MessageEmbed[]) null); + long id2 = MockJDA.assertModalInteractionEvent(listener, m2, REQUEST_CHANNEL, Map.of("reason", "reason"), (MessageEmbed[]) null); + + String expected = String.format(""" + [<:like:935126958193405962>] Handling of Join Request complete! + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `rejected-requests` channel found! + - [<:like:935126958193405962>] Join Request removed! + - Thread archived! + - Request Message deleted! + - [<:like:935126958193405962>] Finished rejecting join request of %s! + """, member.getId(), member.getUser().getEffectiveName()); + + assertEquals(expected, MockJDA.getMessage(id1)); + assertEquals(expected, MockJDA.getMessage(id2)); + + Modal m3 = MockJDA.mockModal("deny_application", "Deny Application"); + MockJDA.assertModalInteractionEvent(listener, m3, REQUEST_CHANNEL, Map.of(), CommandUtil.embedError("Received invalid Deny Application modal!")); + + Modal m4 = MockJDA.mockModal("deny_application:abcd", "Deny Application"); + MockJDA.assertModalInteractionEvent(listener, m4, REQUEST_CHANNEL, Map.of(), CommandUtil.embedError("Received invalid message ID: abcd")); + } + + @Test + @DisplayName("Test ModalListener Errors") + public void testModalListenerErrors() { + Modal modal = MockJDA.mockModal("null", "null"); + + ModalInteractionEvent e1 = MockJDA.mockModalInteractionEvent(modal, REQUEST_CHANNEL, Map.of()); + when(e1.getGuild()).thenReturn(null); + MockJDA.assertModalInteractionEvent(listener, e1, CommandUtil.embedError("Unable to retrieve CodeMC Server!")); + + ModalInteractionEvent e2 = MockJDA.mockModalInteractionEvent(modal, REQUEST_CHANNEL, Map.of()); + MockJDA.assertModalInteractionEvent(listener, e2, CommandUtil.embedError("Received Modal with unknown ID `null`.")); + } + +} From 75411d0507ce9892119b8749b7542eb43582878d Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sun, 8 Dec 2024 17:01:15 +0000 Subject: [PATCH 45/51] Update to API v1.2.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 39e96b2..88bda46 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,7 @@ dependencies { implementation group: 'pw.chew', name: 'jda-chewtils-command', version: '2.0' implementation group: 'org.spongepowered', name: 'configurate-gson', version: '4.1.2' - implementation group: 'io.codemc.api', name: 'codemc-api', version: '1.1.1' + implementation group: 'io.codemc.api', name: 'codemc-api', version: '1.2.0' implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.9.0' implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-serialization-json', version: '1.7.3' implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '3.5.1' From 1f44a84bbc4651f297ad1f4189802c7fb792d265 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sun, 8 Dec 2024 17:02:07 +0000 Subject: [PATCH 46/51] Create `TestCmdMsg.java` --- .../java/io/codemc/bot/commands/CmdMsg.java | 10 ++- .../io/codemc/bot/commands/TestCmdMsg.java | 82 +++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/test/java/io/codemc/bot/commands/TestCmdMsg.java diff --git a/src/main/java/io/codemc/bot/commands/CmdMsg.java b/src/main/java/io/codemc/bot/commands/CmdMsg.java index e99e35c..eedd47a 100644 --- a/src/main/java/io/codemc/bot/commands/CmdMsg.java +++ b/src/main/java/io/codemc/bot/commands/CmdMsg.java @@ -39,6 +39,8 @@ import java.util.Arrays; +import org.jetbrains.annotations.VisibleForTesting; + public class CmdMsg extends BotCommand{ public CmdMsg(CodeMCBot bot){ @@ -61,7 +63,8 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g @Override public void withModalReply(SlashCommandEvent event){} - private static class Post extends BotCommand{ + @VisibleForTesting + static class Post extends BotCommand{ public Post(CodeMCBot bot){ super(bot); @@ -96,7 +99,7 @@ public void withModalReply(SlashCommandEvent event){ } TextInput input = TextInput.create("message", "Message", TextInputStyle.PARAGRAPH) - .setMaxLength(asEmbed ? MessageEmbed.DESCRIPTION_MAX_LENGTH : Message.MAX_CONTENT_LENGTH) + .setMaxLength(asEmbed ? TextInput.MAX_VALUE_LENGTH : Message.MAX_CONTENT_LENGTH) .setRequired(true) .build(); @@ -108,7 +111,8 @@ public void withModalReply(SlashCommandEvent event){ } } - private static class Edit extends BotCommand{ + @VisibleForTesting + static class Edit extends BotCommand{ public Edit(CodeMCBot bot){ super(bot); diff --git a/src/test/java/io/codemc/bot/commands/TestCmdMsg.java b/src/test/java/io/codemc/bot/commands/TestCmdMsg.java new file mode 100644 index 0000000..ee3fcb8 --- /dev/null +++ b/src/test/java/io/codemc/bot/commands/TestCmdMsg.java @@ -0,0 +1,82 @@ +package io.codemc.bot.commands; + +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import io.codemc.bot.utils.CommandUtil; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static io.codemc.bot.MockJDA.GENERAL; +import static org.junit.jupiter.api.Assertions.*; + +import io.codemc.bot.commands.CmdMsg.Post; +import io.codemc.bot.commands.CmdMsg.Edit; + +import java.util.Map; + +public class TestCmdMsg { + + private static CmdMsg command; + + @BeforeAll + public static void setup() { + command = new CmdMsg(MockCodeMCBot.INSTANCE); + } + + @Test + @DisplayName("Test /msg") + public void testMsg() { + assertEquals("msg", command.getName()); + assertFalse(command.getHelp().isEmpty()); + assertTrue(command.getChildren().length > 1); + } + + @Test + @DisplayName("Test /msg post") + public void testPost() { + Post post = new Post(MockCodeMCBot.INSTANCE); + + assertEquals("send", post.getName()); + assertFalse(post.getHelp().isEmpty()); + assertFalse(post.allowedRoles.isEmpty()); + assertTrue(post.hasModalReply); + assertEquals(2, post.getOptions().size()); + + TestCommandListener listener = new TestCommandListener(post); + + MockJDA.assertSlashCommandEvent(listener, Map.of("channel", GENERAL, "embed", true), (MessageEmbed[]) null); + MockJDA.assertSlashCommandEvent(listener, Map.of("channel", GENERAL, "embed", false), (MessageEmbed[]) null); + + assertNotNull(MockJDA.CURRENT_MODAL); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Received invalid Channel input.")); + } + + @Test + @DisplayName("Test /msg edit") + public void testEdit() { + Edit edit = new Edit(MockCodeMCBot.INSTANCE); + + assertEquals("edit", edit.getName()); + assertFalse(edit.getHelp().isEmpty()); + assertFalse(edit.allowedRoles.isEmpty()); + assertTrue(edit.hasModalReply); + assertEquals(3, edit.getOptions().size()); + + TestCommandListener listener = new TestCommandListener(edit); + Message message = MockJDA.mockMessage("", GENERAL); + + MockJDA.assertSlashCommandEvent(listener, Map.of("channel", GENERAL, "id", message.getIdLong(), "embed", true), (MessageEmbed[]) null); + MockJDA.assertSlashCommandEvent(listener, Map.of("channel", GENERAL, "id", message.getIdLong(), "embed", false), (MessageEmbed[]) null); + + assertNotNull(MockJDA.CURRENT_MODAL); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Received invalid Channel or Message ID.")); + MockJDA.assertSlashCommandEvent(listener, Map.of("channel", GENERAL), CommandUtil.embedError("Received invalid Channel or Message ID.")); + MockJDA.assertSlashCommandEvent(listener, Map.of("id", message.getIdLong()), CommandUtil.embedError("Received invalid Channel or Message ID.")); + } + +} From 26f13de3fa3f3ff83ebd73882cb8431f52779c7c Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sun, 8 Dec 2024 17:02:21 +0000 Subject: [PATCH 47/51] Create `TestBotCommand.java` --- .../codemc/bot/commands/TestBotCommand.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/test/java/io/codemc/bot/commands/TestBotCommand.java diff --git a/src/test/java/io/codemc/bot/commands/TestBotCommand.java b/src/test/java/io/codemc/bot/commands/TestBotCommand.java new file mode 100644 index 0000000..661c2aa --- /dev/null +++ b/src/test/java/io/codemc/bot/commands/TestBotCommand.java @@ -0,0 +1,67 @@ +package io.codemc.bot.commands; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.jagrosh.jdautilities.command.SlashCommandEvent; + +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; +import io.codemc.bot.utils.CommandUtil; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.interactions.InteractionHook; + +public class TestBotCommand { + + private static MockBotCommand command; + + private static final class MockBotCommand extends BotCommand { + + MockBotCommand() { + super(MockCodeMCBot.INSTANCE); + } + + @Override + public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild guild, Member member) {} + + @Override + public void withModalReply(SlashCommandEvent event) {} + + } + + @BeforeAll + public static void init() { + command = new MockBotCommand(); + } + + @Test + @DisplayName("Test BotCommand Errors") + public void testErrors() { + TestCommandListener listener = new TestCommandListener(command); + + SlashCommandEvent e1 = MockJDA.mockSlashCommandEvent(MockJDA.GENERAL, command, Map.of()); + when(e1.getGuild()).thenReturn(null); + MockJDA.assertSlashCommandEvent(e1, listener, CommandUtil.embedError("Command can only be executed in a Server!")); + + SlashCommandEvent e2 = MockJDA.mockSlashCommandEvent(MockJDA.GENERAL, command, Map.of()); + when(e2.getGuild()).thenAnswer(inv -> { + Guild g = mock(Guild.class); + when(g.getIdLong()).thenReturn(123L); + return g; + }); + MockJDA.assertSlashCommandEvent(e2, listener, CommandUtil.embedError("Unable to find CodeMC Server!")); + + SlashCommandEvent e3 = MockJDA.mockSlashCommandEvent(MockJDA.GENERAL, command, Map.of()); + when(e3.getMember()).thenReturn(null); + + MockJDA.assertSlashCommandEvent(e3, listener, CommandUtil.embedError("Unable to retrieve Member from Event!")); + } + +} From aa0eea8db5980770f8bc2ebbd39e77ee41bb650f Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sun, 8 Dec 2024 17:02:47 +0000 Subject: [PATCH 48/51] Update MockJDA.java --- src/test/java/io/codemc/bot/MockJDA.java | 66 ++++++++++++++++-------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index 0968bb9..a9f83f3 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -51,7 +51,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.BiFunction; import java.util.function.Consumer; import org.junit.jupiter.api.Assertions; @@ -65,10 +64,12 @@ import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@SuppressWarnings("unchecked") public class MockJDA { private static final ConfigHandler CONFIG = MockCodeMCBot.INSTANCE.getConfigHandler(); @@ -190,7 +191,7 @@ public static Interaction mockInteraction(Member user, MessageChannel channel, I private static MessageChannelUnion mockUnion(MessageChannel channel) { MessageChannelUnion union = mock(MessageChannelUnion.class); - when(union.asTextChannel()).thenAnswer(inv -> (TextChannel) channel); + when(union.asTextChannel()).thenAnswer(inv -> channel); when(union.getJDA()).thenReturn(JDA); return union; @@ -202,6 +203,7 @@ public static TextChannel mockChannel(String configName) { when(channel.getGuild()).thenReturn(GUILD); when(channel.getJDA()).thenReturn(JDA); + when(channel.canTalk()).thenReturn(true); when(channel.retrieveMessageById(anyLong())).thenAnswer(inv -> { long messageId = inv.getArgument(0); @@ -257,7 +259,7 @@ public static Message mockMessage(String content, MessageChannel channel, long i latestMessages.put(channel.getIdLong(), content); latestEmbeds.remove(channel.getIdLong()); - when(message.getContentRaw()).thenAnswer(inv -> messages.get(message.getIdLong())); + when(message.getContentRaw()).thenAnswer(inv -> messages.get(id)); when(message.getJumpUrl()).thenReturn(""); when(message.getIdLong()).thenReturn(id); when(message.getId()).thenReturn(Long.toString(id)); @@ -267,14 +269,28 @@ public static Message mockMessage(String content, MessageChannel channel, long i when(message.getStartedThread()).thenReturn(null); when(message.delete()).thenAnswer(inv -> { - messages.remove(message.getIdLong()); - MockJDA.embeds.remove(message.getIdLong()); + messages.remove(id); + MockJDA.embeds.remove(id); return mockAuditLog(); }); when(message.editMessage(anyString())).thenAnswer(inv -> { - messages.put(message.getIdLong(), inv.getArgument(0)); - return mockWebhookReply(MessageEditAction.class, mockInteractionHook(message.getMember(), channel, InteractionType.COMMAND), message); + messages.put(id, inv.getArgument(0)); + return mockReply(MessageEditAction.class, message); + }); + when(message.editMessageEmbeds(any(MessageEmbed[].class))).thenAnswer(inv -> { + Object obj = inv.getArgument(0); + if (obj instanceof MessageEmbed[] allEmbeds) + embeds.put(id, allEmbeds); + else + embeds.put(id, new MessageEmbed[] { (MessageEmbed) obj }); + + return mockReply(MessageEditAction.class, message); + }); + when(message.editMessageEmbeds(anyCollection())).thenAnswer(inv -> { + Collection allEmbeds = inv.getArgument(0); + embeds.put(id, allEmbeds.toArray(new MessageEmbed[0])); + return mockReply(MessageEditAction.class, message); }); when(message.createThreadChannel(anyString())).thenAnswer(inv -> mock(ThreadChannelAction.class)); @@ -333,6 +349,10 @@ private static Guild mockGuild() { long id = inv.getArgument(0); return CHANNELS.stream().filter(channel -> channel.getIdLong() == id).findFirst().orElse(null); }); + when(guild.getTextChannelById(anyString())).thenAnswer(inv -> { + String id = inv.getArgument(0); + return CHANNELS.stream().filter(channel -> channel.getId().equals(id)).findFirst().orElse(null); + }); when(guild.getMemberById(anyString())).thenAnswer(inv -> { long id = Long.parseLong(inv.getArgument(0)); return members.get(id); @@ -493,7 +513,15 @@ public static SlashCommandEvent mockSlashCommandEvent(MessageChannel channel, Bo String key = inv.getArgument(0); Object def = inv.getArgument(1); - return options.containsKey(key) ? options.get(key) : def; + return options.getOrDefault(key, def); + }); + when(event.getOption(anyString(), isA(Object.class), any())).thenAnswer(inv -> { + if (options == null) return null; + + String key = inv.getArgument(0); + Object def = inv.getArgument(1); + + return options.getOrDefault(key, def); }); mockReplyCallbacks(event, channel, id); @@ -624,12 +652,8 @@ private static void mockReplyCallbacks(IReplyCallback event, MessageChannel chan return mockReply(mockMessage(null, embeds, channel, id)); }); - when(event.deferReply()).thenAnswer(inv -> { - return mockReply(mockMessage(null, channel, id)); - }); - when(event.deferReply(anyBoolean())).thenAnswer(inv -> { - return mockReply(mockMessage(null, channel, id)); - }); + when(event.deferReply()).thenAnswer(inv -> mockReply(mockMessage(null, channel, id))); + when(event.deferReply(anyBoolean())).thenAnswer(inv -> mockReply(mockMessage(null, channel, id))); } private static ReplyCallbackAction mockReply(Message message) { @@ -638,9 +662,7 @@ private static ReplyCallbackAction mockReply(Message message) { messages.put(message.getIdLong(), message.getContentRaw()); embeds.put(message.getIdLong(), message.getEmbeds().toArray(new MessageEmbed[0])); - when(action.getEmbeds()).thenAnswer(inv -> { - return embeds.get(message.getIdLong()); - }); + when(action.getEmbeds()).thenAnswer(inv -> embeds.get(message.getIdLong())); when(action.setEmbeds(anyCollection())).thenAnswer(inv -> { Collection embed = inv.getArgument(0); @@ -691,9 +713,7 @@ private static & RestAction> T mockWebhookReply( return null; }).when(action).queue(any()); - when(action.getEmbeds()).thenAnswer(inv -> { - return embeds.get(message.getIdLong()); - }); + when(action.getEmbeds()).thenAnswer(inv -> embeds.get(message.getIdLong())); when(action.setEmbeds(anyCollection())).thenAnswer(inv -> { Collection embed = inv.getArgument(0); @@ -745,10 +765,13 @@ private static & RestAction> T mockReply(Class action); when(action.setActionRow(anyCollection())).thenAnswer(inv -> action); + if (action instanceof MessageEditAction edit) { + when(edit.setReplace(anyBoolean())).thenReturn(edit); + } + return action; } - @SuppressWarnings("unchecked") private static RestAction mockAction(T object) { RestAction action = mock(RestAction.class); when(action.complete()).thenReturn(object); @@ -774,7 +797,6 @@ private static RestAction mockAction(T object) { return action; } - @SuppressWarnings("unchecked") private static AuditableRestAction mockAuditLog() { AuditableRestAction action = mock(AuditableRestAction.class); From 39bb2a5a7e62c0a3864d32eca694d569e1e80eb7 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sun, 8 Dec 2024 17:03:09 +0000 Subject: [PATCH 49/51] Improve Coverage, Use `#exists` Functions from API --- .../io/codemc/bot/commands/CmdCodeMC.java | 17 +- .../codemc/bot/listeners/ModalListener.java | 22 ++- .../java/io/codemc/bot/utils/APIUtil.java | 2 +- .../java/io/codemc/bot/utils/CommandUtil.java | 4 +- .../java/io/codemc/bot/MockCodeMCBot.java | 4 +- .../bot/commands/TestCmdApplication.java | 19 +- .../io/codemc/bot/commands/TestCmdCodeMC.java | 168 ++++++++++++++---- .../bot/listeners/TestButtonListener.java | 16 +- .../bot/listeners/TestModalListener.java | 41 +++++ .../java/io/codemc/bot/utils/TestAPIUtil.java | 16 +- .../bot/utils/TestApplicationHandler.java | 63 ++++++- .../io/codemc/bot/utils/TestCommandUtil.java | 19 ++ 12 files changed, 305 insertions(+), 86 deletions(-) diff --git a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java index 955897b..92e09ed 100644 --- a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java +++ b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java @@ -236,7 +236,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g return; } - if (JenkinsAPI.getJenkinsUser(username).isBlank()) { + if (!JenkinsAPI.existsUser(username)) { CommandUtil.EmbedReply.from(hook).error("The user does not have a Jenkins account!").send(); return; } @@ -269,7 +269,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g .anyMatch(role -> role.getIdLong() == authorRole.getIdLong()); if (!hasAuthor) { - CommandUtil.EmbedReply.from(hook).error("The user is not an Author!").send(); + CommandUtil.EmbedReply.from(hook).error("User was deleted, but is not an Author!").send(); return; } @@ -395,7 +395,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g return; } - if (JenkinsAPI.getJenkinsUser(username).isBlank()) { + if (!JenkinsAPI.existsUser(username)) { CommandUtil.EmbedReply.from(hook).error("The user does not have a Jenkins account!").send(); return; } @@ -531,14 +531,17 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g return; } - if (JenkinsAPI.getJenkinsUser(username).isBlank()) { + if (!JenkinsAPI.existsUser(username)) { CommandUtil.EmbedReply.from(hook).error("You do not have a Jenkins account!").send(); return; } String password = APIUtil.newPassword(); boolean success = APIUtil.changePassword(hook, username, password); - if (!success) return; + if (!success) { + CommandUtil.EmbedReply.from(hook).error("Failed to regenerate your Nexus Credentials!").send(); + return; + } CommandUtil.EmbedReply.from(hook) .success("Successfully changed your password!") @@ -576,7 +579,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g return; } - if (!JenkinsAPI.getJenkinsUser(username).isBlank()) { + if (JenkinsAPI.existsUser(username)) { CommandUtil.EmbedReply.from(hook).error("A user with that username already exists.").send(); return; } @@ -648,7 +651,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g return; } - if (JenkinsAPI.getJenkinsUser(username).isBlank()) { + if (!JenkinsAPI.existsUser(username)) { CommandUtil.EmbedReply.from(hook).error("The user does not exist!").send(); return; } diff --git a/src/main/java/io/codemc/bot/listeners/ModalListener.java b/src/main/java/io/codemc/bot/listeners/ModalListener.java index 218811b..c2dbc96 100644 --- a/src/main/java/io/codemc/bot/listeners/ModalListener.java +++ b/src/main/java/io/codemc/bot/listeners/ModalListener.java @@ -69,7 +69,7 @@ public void onModalInteraction(@NotNull ModalInteractionEvent event){ return; } - if (!JenkinsAPI.getJenkinsUser(user).isEmpty()) { + if (JenkinsAPI.existsUser(user)) { CommandUtil.EmbedReply.from(hook) .error("A Jenkins User named '" + user + "' already exists!") .send(); @@ -201,16 +201,24 @@ public void onModalInteraction(@NotNull ModalInteractionEvent event){ if(asEmbed){ message.editMessageEmbeds(CommandUtil.getEmbed().setDescription(text).build()).setReplace(true).queue( m -> sendConfirmation(hook, m, true), - e -> CommandUtil.EmbedReply.from(hook) - .error("Unable to edit message. Reason: " + e.getMessage()) - .send() + e -> { + CommandUtil.EmbedReply.from(hook) + .error("Unable to edit message. Reason: " + e.getMessage()) + .send(); + + logger.error("Error while editing message", e); + } ); }else{ message.editMessage(text).setReplace(true).queue( m -> sendConfirmation(hook, m, true), - e -> CommandUtil.EmbedReply.from(hook) - .error("Unable to edit message. Reason: " + e.getMessage()) - .send() + e -> { + CommandUtil.EmbedReply.from(hook) + .error("Unable to edit message. Reason: " + e.getMessage()) + .send(); + + logger.error("Error while editing message", e); + } ); } } diff --git a/src/main/java/io/codemc/bot/utils/APIUtil.java b/src/main/java/io/codemc/bot/utils/APIUtil.java index 6c82bdf..446f4ab 100644 --- a/src/main/java/io/codemc/bot/utils/APIUtil.java +++ b/src/main/java/io/codemc/bot/utils/APIUtil.java @@ -44,7 +44,7 @@ public static boolean createNexus(InteractionHook hook, String username, String } public static boolean createJenkinsJob(InteractionHook hook, String username, String password, String project, String repoLink, boolean trigger) { - if (!JenkinsAPI.getJenkinsUser(username).isEmpty()) { + if (JenkinsAPI.existsUser(username)) { if (hook != null) CommandUtil.EmbedReply.from(hook) .error("Jenkins User for " + username + " already exists!") diff --git a/src/main/java/io/codemc/bot/utils/CommandUtil.java b/src/main/java/io/codemc/bot/utils/CommandUtil.java index 4a9d7da..1e97611 100644 --- a/src/main/java/io/codemc/bot/utils/CommandUtil.java +++ b/src/main/java/io/codemc/bot/utils/CommandUtil.java @@ -34,9 +34,7 @@ public class CommandUtil{ private static final Logger LOG = (Logger)LoggerFactory.getLogger(CommandUtil.class); - - public CommandUtil(){} - + public static EmbedBuilder getEmbed(){ return new EmbedBuilder().setColor(0x0172BA); } diff --git a/src/test/java/io/codemc/bot/MockCodeMCBot.java b/src/test/java/io/codemc/bot/MockCodeMCBot.java index 5ade9d8..b6afa62 100644 --- a/src/test/java/io/codemc/bot/MockCodeMCBot.java +++ b/src/test/java/io/codemc/bot/MockCodeMCBot.java @@ -31,6 +31,8 @@ public void setTestConfig() { if (file.exists()) { String password = Files.readString(file.toPath()); configHandler.set(password, "nexus", "password"); + } else { + logger.warn("Failed to read Nexus password from file: File does not exist"); } } catch (IOException e) { logger.error("Failed to read Nexus password from file", e); @@ -53,7 +55,7 @@ void start() { public void create(String username, String job) { String link = "https://github.com/" + username + "/" + job; - if (JenkinsAPI.getJenkinsUser(username).isEmpty()) { + if (!JenkinsAPI.existsUser(username)) { String password = APIUtil.newPassword(); APIUtil.createJenkinsJob(null, username, password, job, link, false); APIUtil.createNexus(null, username, password); diff --git a/src/test/java/io/codemc/bot/commands/TestCmdApplication.java b/src/test/java/io/codemc/bot/commands/TestCmdApplication.java index bb51f80..f590d8d 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdApplication.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdApplication.java @@ -64,8 +64,8 @@ public void testAccept() { Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNull(NexusAPI.getNexusUser(username)); + assertFalse(JenkinsAPI.existsUser(username)); + assertFalse(NexusAPI.exists(username)); assertNull(DatabaseAPI.getUser(username)); long id = MockJDA.assertSlashCommandEvent(listener, Map.of("id", message.getId()), (MessageEmbed[]) null); @@ -91,12 +91,13 @@ public void testAccept() { assertEquals(expected, MockJDA.getMessage(id)); assertTrue(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertFalse(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNotNull(NexusAPI.getNexusUser(username)); + assertTrue(JenkinsAPI.existsUser(username)); + assertTrue(NexusAPI.exists(username)); assertNotNull(DatabaseAPI.getUser(username)); assertEquals(member.getIdLong(), DatabaseAPI.getUser(username).getDiscord()); MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Message ID was not present!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("id", "abcd"), CommandUtil.embedError("Invalid message ID!")); assertTrue(JenkinsAPI.deleteUser(username)); assertTrue(NexusAPI.deleteNexus(username)); @@ -122,8 +123,8 @@ public void testDeny() { Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNull(NexusAPI.getNexusUser(username)); + assertFalse(JenkinsAPI.existsUser(username)); + assertFalse(NexusAPI.exists(username)); assertNull(DatabaseAPI.getUser(username)); long id = MockJDA.assertSlashCommandEvent(listener, Map.of("id", message.getId(), "reason", "Denied"), (MessageEmbed[]) null); @@ -145,11 +146,13 @@ public void testDeny() { assertEquals(expected, MockJDA.getMessage(id)); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNull(NexusAPI.getNexusUser(username)); + assertFalse(JenkinsAPI.existsUser(username)); + assertFalse(NexusAPI.exists(username)); assertNull(DatabaseAPI.getUser(username)); MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Message ID was not present!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("id", "abcd"), CommandUtil.embedError("Invalid message ID!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("id", "0"), CommandUtil.embedError("Message ID or Reason were not present!")); } } diff --git a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java index 48f6595..ed45a05 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java @@ -5,7 +5,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterAll; @@ -33,7 +35,7 @@ import net.dv8tion.jda.api.entities.Member; public class TestCmdCodeMC { - + private static CmdCodeMC command; @BeforeAll @@ -113,13 +115,66 @@ public void testRemove() { JenkinsAPI.createJenkinsUser("TestRemove", "1234"); NexusAPI.createNexus("TestRemove", "1234"); - assertFalse(JenkinsAPI.getJenkinsUser("TestRemove").isEmpty()); - assertNotNull(NexusAPI.getNexusUser("TestRemove")); + assertTrue(JenkinsAPI.existsUser("TestRemove")); + assertTrue(NexusAPI.exists("TestRemove")); MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestRemove"), CommandUtil.embedSuccess("Successfully removed TestRemove from the CodeMC Services!")); - assertTrue(JenkinsAPI.getJenkinsUser("TestRemove").isEmpty()); - assertNull(NexusAPI.getNexusUser("TestRemove")); + assertFalse(JenkinsAPI.existsUser("TestRemove")); + assertFalse(NexusAPI.exists("TestRemove")); + + MockCodeMCBot.INSTANCE.delete("TestRemove2"); + DatabaseAPI.removeUser("TestRemove2"); + Member m1 = MockJDA.mockMember("TestRemove2"); + MockJDA.GUILD.addRoleToMember(m1, MockJDA.AUTHOR); + + JenkinsAPI.createJenkinsUser("TestRemove2", "5678"); + NexusAPI.createNexus("TestRemove2", "5678"); + DatabaseAPI.addUser("TestRemove2", m1.getIdLong()); + + assertTrue(JenkinsAPI.existsUser("TestRemove2")); + assertTrue(NexusAPI.exists("TestRemove2")); + assertNotNull(DatabaseAPI.getUser("TestRemove2")); + assertEquals(m1.getIdLong(), DatabaseAPI.getUser("TestRemove2").getDiscord()); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestRemove2"), CommandUtil.embedSuccess("Revoked Author Status from TestRemove2!")); + + assertFalse(JenkinsAPI.existsUser("TestRemove2")); + assertFalse(NexusAPI.exists("TestRemove2")); + assertNull(DatabaseAPI.getUser("TestRemove2")); + + MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Jenkins User provided!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "Inexistent"), CommandUtil.embedError("The user does not have a Jenkins account!")); + + Member m2 = MockJDA.mockMember("TestRemove3"); + + JenkinsAPI.createJenkinsUser("TestRemove3", "1234"); + NexusAPI.createNexus("TestRemove3", "1234"); + DatabaseAPI.addUser("TestRemove3", m2.getIdLong()); + + assertTrue(JenkinsAPI.existsUser("TestRemove3")); + assertTrue(NexusAPI.exists("TestRemove3")); + assertNotNull(DatabaseAPI.getUser("TestRemove3")); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestRemove3"), CommandUtil.embedError("User was deleted, but is not an Author!")); + + assertFalse(JenkinsAPI.existsUser("TestRemove3")); + assertFalse(NexusAPI.exists("TestRemove3")); + assertNull(DatabaseAPI.getUser("TestRemove3")); + + JenkinsAPI.createJenkinsUser("TestRemove4", "1234"); + NexusAPI.createNexus("TestRemove4", "1234"); + DatabaseAPI.addUser("TestRemove4", -10L); + + assertTrue(JenkinsAPI.existsUser("TestRemove4")); + assertTrue(NexusAPI.exists("TestRemove4")); + assertNotNull(DatabaseAPI.getUser("TestRemove4")); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestRemove4"), CommandUtil.embedSuccess("Successfully removed TestRemove4 from the CodeMC Services!")); + + assertFalse(JenkinsAPI.existsUser("TestRemove4")); + assertFalse(NexusAPI.exists("TestRemove4")); + assertNull(DatabaseAPI.getUser("TestRemove4")); } @Test @@ -138,13 +193,13 @@ public void testValidate() { JenkinsAPI.createJenkinsUser("TestValidate_1", "1234"); - assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_1").isEmpty()); - assertNull(NexusAPI.getNexusUser("TestValidate_1")); + assertTrue(JenkinsAPI.existsUser("TestValidate_1")); + assertFalse(NexusAPI.exists("TestValidate_1")); MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestValidate_1"), CommandUtil.embedSuccess("Successfully validated 1 User(s)")); - assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_1").isEmpty()); - assertNotNull(NexusAPI.getNexusUser("TestValidate_1")); + assertTrue(JenkinsAPI.existsUser("TestValidate_1")); + assertTrue(NexusAPI.exists("TestValidate_1")); MockCodeMCBot.INSTANCE.delete("TestValidate_1"); @@ -152,13 +207,13 @@ public void testValidate() { NexusAPI.createNexus("TestValidate_2", "1234"); - assertTrue(JenkinsAPI.getJenkinsUser("TestValidate_2").isEmpty()); - assertNotNull(NexusAPI.getNexusUser("TestValidate_2")); + assertFalse(JenkinsAPI.existsUser("TestValidate_2")); + assertTrue(NexusAPI.exists("TestValidate_2")); MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestValidate_2"), CommandUtil.embedSuccess("Successfully validated 1 User(s)")); - assertNotNull(JenkinsAPI.getJenkinsUser("TestValidate_2")); - assertNotNull(NexusAPI.getNexusUser("TestValidate_2")); + assertTrue(JenkinsAPI.existsUser("TestValidate_2")); + assertTrue(NexusAPI.exists("TestValidate_2")); MockCodeMCBot.INSTANCE.delete("TestValidate_2"); @@ -168,22 +223,22 @@ public void testValidate() { JenkinsAPI.createJenkinsUser("TestValidate_31", "1234"); JenkinsAPI.createJenkinsUser("TestValidate_32", "1234"); - assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_30").isEmpty()); - assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_31").isEmpty()); - assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_32").isEmpty()); - assertNull(NexusAPI.getNexusUser("TestValidate_30")); - assertNull(NexusAPI.getNexusUser("TestValidate_31")); - assertNull(NexusAPI.getNexusUser("TestValidate_32")); + assertTrue(JenkinsAPI.existsUser("TestValidate_30")); + assertTrue(JenkinsAPI.existsUser("TestValidate_31")); + assertTrue(JenkinsAPI.existsUser("TestValidate_32")); + assertFalse(NexusAPI.exists("TestValidate_30")); + assertFalse(NexusAPI.exists("TestValidate_31")); + assertFalse(NexusAPI.exists("TestValidate_32")); int size1 = JenkinsAPI.getAllJenkinsUsers().size(); MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedSuccess("Successfully validated " + size1 + " User(s)")); - assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_30").isEmpty()); - assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_31").isEmpty()); - assertFalse(JenkinsAPI.getJenkinsUser("TestValidate_32").isEmpty()); - assertNotNull(NexusAPI.getNexusUser("TestValidate_30")); - assertNotNull(NexusAPI.getNexusUser("TestValidate_31")); - assertNotNull(NexusAPI.getNexusUser("TestValidate_32")); + assertTrue(JenkinsAPI.existsUser("TestValidate_30")); + assertTrue(JenkinsAPI.existsUser("TestValidate_31")); + assertTrue(JenkinsAPI.existsUser("TestValidate_32")); + assertTrue(NexusAPI.exists("TestValidate_30")); + assertTrue(NexusAPI.exists("TestValidate_31")); + assertTrue(NexusAPI.exists("TestValidate_32")); MockCodeMCBot.INSTANCE.delete("TestValidate_30"); MockCodeMCBot.INSTANCE.delete("TestValidate_31"); @@ -206,13 +261,24 @@ public void testLink() { MockJDA.GUILD.addRoleToMember(m1, MockJDA.AUTHOR); MockCodeMCBot.INSTANCE.create("TestLink", "Job"); - DatabaseAPI.removeUser("TestLink"); assertNull(DatabaseAPI.getUser("TestLink")); MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestLink", "discord", m1), CommandUtil.embedSuccess("Linked Discord User TestLink to Jenkins User TestLink!")); assertNotNull(DatabaseAPI.getUser("TestLink")); assertEquals(m1.getIdLong(), DatabaseAPI.getUser("TestLink").getDiscord()); + Member m2 = MockJDA.mockMember("TestLink2"); + MockJDA.GUILD.addRoleToMember(m2, MockJDA.AUTHOR); + + MockCodeMCBot.INSTANCE.create("TestLink2", "Job"); + MockCodeMCBot.INSTANCE.create("TestLink3", "Job"); + + assertNull(DatabaseAPI.getUser("TestLink2")); + assertNull(DatabaseAPI.getUser("TestLink3")); + + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestLink2", "discord", m2), CommandUtil.embedSuccess("Linked Discord User TestLink2 to Jenkins User TestLink2!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestLink3", "discord", m2), CommandUtil.embedSuccess("Linked Discord User TestLink2 to Jenkins User TestLink3!")); + MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestLink", "discord", m1), CommandUtil.embedSuccess("Linked Discord User TestLink to Jenkins User TestLink!")); MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Jenkins User provided!")); MockJDA.assertSlashCommandEvent(listener, Map.of("username", "Inexistent", "discord", m1), CommandUtil.embedError("The user does not have a Jenkins account!")); @@ -223,7 +289,11 @@ public void testLink() { MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestLink", "discord", m1), CommandUtil.embedError("The user is not an Author!")); MockCodeMCBot.INSTANCE.delete("TestLink"); + MockCodeMCBot.INSTANCE.delete("TestLink2"); + MockCodeMCBot.INSTANCE.delete("TestLink3"); DatabaseAPI.removeUser("TestLink"); + DatabaseAPI.removeUser("TestLink2"); + DatabaseAPI.removeUser("TestLink3"); } @Test @@ -241,8 +311,6 @@ public void testUnlink() { Member m1 = MockJDA.mockMember("TestUnlink"); MockJDA.GUILD.addRoleToMember(m1, MockJDA.AUTHOR); - DatabaseAPI.removeUser("TestUnlink"); - MockCodeMCBot.INSTANCE.delete("TestUnlink"); MockCodeMCBot.INSTANCE.create("TestUnlink", "Job"); DatabaseAPI.addUser("TestUnlink", m1.getIdLong()); @@ -255,6 +323,27 @@ public void testUnlink() { MockCodeMCBot.INSTANCE.delete("TestUnlink"); DatabaseAPI.removeUser("TestUnlink"); + + Member m2 = MockJDA.mockMember("TestUnlink2"); + MockJDA.GUILD.addRoleToMember(m2, MockJDA.AUTHOR); + + DatabaseAPI.removeUser("TestUnlink2"); + DatabaseAPI.removeUser("TestUnlink3"); + MockCodeMCBot.INSTANCE.create("TestUnlink2", "Job"); + MockCodeMCBot.INSTANCE.create("TestUnlink3", "Job"); + DatabaseAPI.addUser("TestUnlink2", m2.getIdLong()); + DatabaseAPI.addUser("TestUnlink3", m2.getIdLong()); + + assertNotNull(DatabaseAPI.getUser("TestUnlink2")); + assertNotNull(DatabaseAPI.getUser("TestUnlink3")); + MockJDA.assertSlashCommandEvent(listener, Map.of("discord", m2, "username", "TestUnlink3"),CommandUtil.embedSuccess("Unlinked Discord User TestUnlink2 from their Jenkins/Nexus account!")); + assertNotNull(DatabaseAPI.getUser("TestUnlink2")); + assertNull(DatabaseAPI.getUser("TestUnlink3")); + + MockCodeMCBot.INSTANCE.delete("TestUnlink2"); + MockCodeMCBot.INSTANCE.delete("TestUnlink3"); + DatabaseAPI.removeUser("TestUnlink2"); + DatabaseAPI.removeUser("TestUnlink3"); } @Test @@ -275,9 +364,18 @@ public void testChangePassword() { MockJDA.assertSlashCommandEvent(event, listener, CommandUtil.embedSuccess("Successfully changed your password!")); - JenkinsAPI.deleteUser("Bot"); - NexusAPI.deleteNexus("Bot"); - DatabaseAPI.removeUser("Bot"); + assertTrue(JenkinsAPI.deleteUser("Bot")); + assertTrue(NexusAPI.deleteNexus("Bot")); + + MockJDA.assertSlashCommandEvent(event, listener, CommandUtil.embedError("You do not have a Jenkins account!")); + + assertEquals(1, DatabaseAPI.removeUser("Bot")); + + MockJDA.assertSlashCommandEvent(event, listener, CommandUtil.embedError("You are not linked to any Jenkins/Nexus accounts!")); + + when(event.getMember().getRoles()).thenReturn(List.of()); + + MockJDA.assertSlashCommandEvent(event, listener, CommandUtil.embedError("Only Authors can regenerate their credentials.")); } @Test @@ -328,15 +426,15 @@ public void testDelUser() { MockCodeMCBot.INSTANCE.create("TestDelUser", "Job"); DatabaseAPI.addUser("TestDelUser", m1.getIdLong()); - assertFalse(JenkinsAPI.getJenkinsUser("TestDelUser").isEmpty()); - assertNotNull(NexusAPI.getNexusUser("TestDelUser")); + assertTrue(JenkinsAPI.existsUser("TestDelUser")); + assertTrue(NexusAPI.exists("TestDelUser")); assertNotNull(DatabaseAPI.getUser("TestDelUser")); assertEquals(m1.getIdLong(), DatabaseAPI.getUser("TestDelUser").getDiscord()); MockJDA.assertSlashCommandEvent(listener, Map.of("username", "TestDelUser"), CommandUtil.embedSuccess("Successfully deleted user TestDelUser!")); - assertTrue(JenkinsAPI.getJenkinsUser("TestDelUser").isEmpty()); - assertNull(NexusAPI.getNexusUser("TestDelUser")); + assertFalse(JenkinsAPI.existsUser("TestDelUser")); + assertFalse(NexusAPI.exists("TestDelUser")); assertNull(DatabaseAPI.getUser("TestDelUser")); MockJDA.assertSlashCommandEvent(listener, Map.of(), CommandUtil.embedError("Invalid Username provided!")); diff --git a/src/test/java/io/codemc/bot/listeners/TestButtonListener.java b/src/test/java/io/codemc/bot/listeners/TestButtonListener.java index 368161e..ea5e115 100644 --- a/src/test/java/io/codemc/bot/listeners/TestButtonListener.java +++ b/src/test/java/io/codemc/bot/listeners/TestButtonListener.java @@ -51,15 +51,15 @@ public void testApplicationAccept() { DatabaseAPI.removeUser(username); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNull(NexusAPI.getNexusUser(username)); + assertFalse(JenkinsAPI.existsUser(username)); + assertFalse(NexusAPI.exists(username)); assertNull(DatabaseAPI.getUser(username)); MockJDA.assertButtonInteractionEvent(listener, message, Button.success("application:accept:" + username + ":Job", "Accept"), (MessageEmbed[]) null); assertTrue(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertFalse(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNotNull(NexusAPI.getNexusUser(username)); + assertTrue(JenkinsAPI.existsUser(username)); + assertTrue(NexusAPI.exists(username)); assertNotNull(DatabaseAPI.getUser(username)); assertEquals(member.getIdLong(), DatabaseAPI.getUser(username).getDiscord()); @@ -82,14 +82,14 @@ public void testApplicationDeny() { DatabaseAPI.removeUser(username); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNull(NexusAPI.getNexusUser(username)); + assertFalse(JenkinsAPI.existsUser(username)); + assertFalse(NexusAPI.exists(username)); MockJDA.assertButtonInteractionEvent(listener, message, Button.danger("application:deny:" + username + ":Job", "Deny"), (MessageEmbed[]) null); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNull(NexusAPI.getNexusUser(username)); + assertFalse(JenkinsAPI.existsUser(username)); + assertFalse(NexusAPI.exists(username)); } @Test diff --git a/src/test/java/io/codemc/bot/listeners/TestModalListener.java b/src/test/java/io/codemc/bot/listeners/TestModalListener.java index c3fedd9..0639323 100644 --- a/src/test/java/io/codemc/bot/listeners/TestModalListener.java +++ b/src/test/java/io/codemc/bot/listeners/TestModalListener.java @@ -116,6 +116,47 @@ public void testDenyApplication() { MockJDA.assertModalInteractionEvent(listener, m4, REQUEST_CHANNEL, Map.of(), CommandUtil.embedError("Received invalid message ID: abcd")); } + @Test + @DisplayName("Test Message") + public void testMessage() { + String channelId = REQUEST_CHANNEL.getId(); + + // Test Post + Modal m1 = MockJDA.mockModal("message:post:" + channelId + ":true", "Message"); + MockJDA.assertModalInteractionEvent(listener, m1, GENERAL, Map.of("message", "test"), CommandUtil.embedSuccess("[Message sent!]()")); + + Modal m2 = MockJDA.mockModal("message:post:" + channelId + ":false", "Message"); + MockJDA.assertModalInteractionEvent(listener, m2, GENERAL, Map.of("message", "test"), CommandUtil.embedSuccess("[Message sent!]()")); + + // Test Edit + Message msg1 = MockJDA.mockMessage("Message", GENERAL); + Modal m3 = MockJDA.mockModal("message:edit:" + channelId + ":true:" + msg1.getId(), "Message"); + MockJDA.assertModalInteractionEvent(listener, m3, GENERAL, Map.of("message", "test"), CommandUtil.embedSuccess("[Message edited!]()")); + + Message msg2 = MockJDA.mockMessage("Message", GENERAL); + Modal m4 = MockJDA.mockModal("message:edit:" + channelId + ":false:" + msg2.getId(), "Message"); + MockJDA.assertModalInteractionEvent(listener, m4, GENERAL, Map.of("message", "test"), CommandUtil.embedSuccess("[Message edited!]()")); + + // Test Errors + Modal m5 = MockJDA.mockModal("message", "Message"); + MockJDA.assertModalInteractionEvent(listener, m5, GENERAL, Map.of(), CommandUtil.embedError(("Invalid Modal data. Expected `4+` arguments but received `1`!"))); + + Modal m6 = MockJDA.mockModal("message:post:abcd:true", "Message"); + MockJDA.assertModalInteractionEvent(listener, m6, GENERAL, Map.of(), CommandUtil.embedError("Received invalid Text Channel.")); + + Modal m7 = MockJDA.mockModal("message:post:" + channelId + ":true", "Message"); + MockJDA.assertModalInteractionEvent(listener, m7, GENERAL, Map.of(), CommandUtil.embedError("Received invalid Message to sent/edit.")); + + Modal m8 = MockJDA.mockModal("message:edit:" + channelId + ":true", "Message"); + MockJDA.assertModalInteractionEvent(listener, m8, GENERAL, Map.of("message", "test"), CommandUtil.embedError("Received invalid Modal data. Expected `>4` but got `=4`")); + + Modal m9 = MockJDA.mockModal("message:edit:" + channelId + ":true:abcd", "Message"); + MockJDA.assertModalInteractionEvent(listener, m9, GENERAL, Map.of("message", "test"), CommandUtil.embedError("Received invalid message ID `abcd`.")); + + Modal m10 = MockJDA.mockModal("message:unknown:" + channelId + ":true", "Message"); + MockJDA.assertModalInteractionEvent(listener, m10, GENERAL, Map.of("message", "test"), CommandUtil.embedError("Received Unknown Message type: `unknown`.")); + } + @Test @DisplayName("Test ModalListener Errors") public void testModalListenerErrors() { diff --git a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java index fe99795..3300a53 100644 --- a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java +++ b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java @@ -60,11 +60,11 @@ public void testNexus() { InteractionHook h1 = MockJDA.mockInteractionHook(u1, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); assertTrue(APIUtil.createNexus(h1, user1, p1)); - assertNotNull(NexusAPI.getNexusUser(user1)); + assertTrue(NexusAPI.exists(user1)); assertNotNull(NexusAPI.getNexusRepository(user1)); assertTrue(NexusAPI.deleteNexus(user1)); - assertNull(NexusAPI.getNexusUser(user1)); + assertFalse(NexusAPI.exists(user1)); assertNull(NexusAPI.getNexusRepository(user1)); String user2 = "TestNexus2"; @@ -73,11 +73,11 @@ public void testNexus() { InteractionHook h2 = MockJDA.mockInteractionHook(u2, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); assertTrue(APIUtil.createNexus(h2, user2, p2)); - assertNotNull(NexusAPI.getNexusUser(user2)); + assertTrue(NexusAPI.exists(user2)); assertNotNull(NexusAPI.getNexusRepository(user2)); assertTrue(NexusAPI.deleteNexus(user2)); - assertNull(NexusAPI.getNexusUser(user2)); + assertFalse(NexusAPI.exists(user2)); assertNull(NexusAPI.getNexusRepository(user2)); } @@ -91,13 +91,13 @@ public void testJenkins() { InteractionHook h1 = MockJDA.mockInteractionHook(u1, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); assertTrue(APIUtil.createJenkinsJob(h1, user1, p1, j1, "https://github.com/gmitch215/SocketMC", false)); - assertFalse(JenkinsAPI.getJenkinsUser(user1).isEmpty()); + assertTrue(JenkinsAPI.existsUser(user1)); assertNotNull(JenkinsAPI.getJobInfo(user1, j1)); assertTrue(JenkinsAPI.deleteJob(user1, j1)); assertNull(JenkinsAPI.getJobInfo(user1, j1)); assertTrue(JenkinsAPI.deleteUser(user1)); - assertTrue(JenkinsAPI.getJenkinsUser(user1).isEmpty()); + assertFalse(JenkinsAPI.existsUser(user1)); String user2 = "TestJenkins2"; String j2 = "Job"; @@ -106,13 +106,13 @@ public void testJenkins() { InteractionHook h2 = MockJDA.mockInteractionHook(u2, MockJDA.REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); assertTrue(APIUtil.createJenkinsJob(h2, user2, p2, j2, "https://github.com/CodeMC/Bot", false)); - assertFalse(JenkinsAPI.getJenkinsUser(user2).isEmpty()); + assertTrue(JenkinsAPI.existsUser(user2)); assertNotNull(JenkinsAPI.getJobInfo(user2, j2)); assertTrue(JenkinsAPI.deleteJob(user2, j2)); assertNull(JenkinsAPI.getJobInfo(user2, j2)); assertTrue(JenkinsAPI.deleteUser(user2)); - assertTrue(JenkinsAPI.getJenkinsUser(user2).isEmpty()); + assertFalse(JenkinsAPI.existsUser(user2)); String user3 = "TestJenkins3"; String j3 = "Job"; diff --git a/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java b/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java index e24203f..beeeef8 100644 --- a/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java +++ b/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java @@ -43,8 +43,8 @@ public void testHandleAccepted() { Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNull(NexusAPI.getNexusUser(username)); + assertFalse(JenkinsAPI.existsUser(username)); + assertFalse(NexusAPI.exists(username)); assertNull(DatabaseAPI.getUser(username)); ApplicationHandler.handle( @@ -60,8 +60,8 @@ public void testHandleAccepted() { assertEmbeds(expected.getEmbeds(), embeds, true); assertTrue(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertFalse(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNotNull(NexusAPI.getNexusUser(username)); + assertTrue(JenkinsAPI.existsUser(username)); + assertTrue(NexusAPI.exists(username)); assertNotNull(DatabaseAPI.getUser(username)); assertEquals(member.getIdLong(), DatabaseAPI.getUser(username).getDiscord()); @@ -81,8 +81,8 @@ public void testHandleRejected() { Message message = MockJDA.mockMessage("", List.of(embed), REQUEST_CHANNEL); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNull(NexusAPI.getNexusUser(username)); + assertFalse(JenkinsAPI.existsUser(username)); + assertFalse(NexusAPI.exists(username)); assertNull(DatabaseAPI.getUser(username)); ApplicationHandler.handle( @@ -97,9 +97,56 @@ public void testHandleRejected() { assertEmbeds(expected.getEmbeds(), embeds, true); assertFalse(CommandUtil.hasRole(member, List.of(AUTHOR.getIdLong()))); - assertTrue(JenkinsAPI.getJenkinsUser(username).isEmpty()); - assertNull(NexusAPI.getNexusUser(username)); + assertFalse(JenkinsAPI.existsUser(username)); + assertFalse(NexusAPI.exists(username)); assertNull(DatabaseAPI.getUser(username)); } + @Test + @DisplayName("Test ApplicationHandler#handle (Errors)") + public void testHandleErrors() { + InteractionHook h1 = MockJDA.mockInteractionHook(SELF, REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + Message m1 = MockJDA.mockMessage("", List.of(), REQUEST_CHANNEL); + + ApplicationHandler.handle( + MockCodeMCBot.INSTANCE, h1, GUILD, m1.getIdLong(), null, true + ); + assertEmbeds( + List.of(CommandUtil.embedError("Provided Message does not have any embeds.")), MockJDA.getEmbeds(h1.getIdLong()), true + ); + + InteractionHook h2 = MockJDA.mockInteractionHook(SELF, REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + MessageEmbed e2 = CommandUtil.getEmbed().build(); + Message m2 = MockJDA.mockMessage("", List.of(e2), REQUEST_CHANNEL); + + ApplicationHandler.handle( + MockCodeMCBot.INSTANCE, h2, GUILD, m2.getIdLong(), null, true + ); + assertEmbeds( + List.of(CommandUtil.embedError("Embed does not have a Footer or any Embed Fields")), MockJDA.getEmbeds(h2.getIdLong()), true + ); + + InteractionHook h3 = MockJDA.mockInteractionHook(SELF, REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + MessageEmbed e3 = CommandUtil.getEmbed().setFooter(" ").addField("null", "null", true).build(); + Message m3 = MockJDA.mockMessage("", List.of(e3), REQUEST_CHANNEL); + + ApplicationHandler.handle( + MockCodeMCBot.INSTANCE, h3, GUILD, m3.getIdLong(), null, true + ); + assertEmbeds( + List.of(CommandUtil.embedError("Embed does not have a valid footer.")), MockJDA.getEmbeds(h3.getIdLong()), true + ); + + InteractionHook h4 = MockJDA.mockInteractionHook(SELF, REQUEST_CHANNEL, InteractionType.MODAL_SUBMIT); + MessageEmbed e4 = CommandUtil.getEmbed().setFooter("id").addField("null", "null", true).build(); + Message m4 = MockJDA.mockMessage("", List.of(e4), REQUEST_CHANNEL); + + ApplicationHandler.handle( + MockCodeMCBot.INSTANCE, h4, GUILD, m4.getIdLong(), null, true + ); + assertEmbeds( + List.of(CommandUtil.embedError("Embed does not have all valid Fields.")), MockJDA.getEmbeds(h4.getIdLong()), true + ); + } + } diff --git a/src/test/java/io/codemc/bot/utils/TestCommandUtil.java b/src/test/java/io/codemc/bot/utils/TestCommandUtil.java index f5c7626..f041eaf 100644 --- a/src/test/java/io/codemc/bot/utils/TestCommandUtil.java +++ b/src/test/java/io/codemc/bot/utils/TestCommandUtil.java @@ -12,6 +12,8 @@ import io.codemc.bot.MockJDA; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.interactions.InteractionType; public class TestCommandUtil { @@ -55,4 +57,21 @@ public void testEmbedReply() { MockJDA.assertEmbed(m2, CommandUtil.embedError("Error!"), true); } + @Test + @DisplayName("Test CommandUtil.EmbedReply#send") + public void testEmbedSend() { + Member member = MockJDA.mockMember("gmitch215"); + InteractionHook h1 = MockJDA.mockInteractionHook(member, MockJDA.REQUEST_CHANNEL, InteractionType.COMMAND); + CommandUtil.EmbedReply r1 = CommandUtil.EmbedReply.from(h1); + r1.success("Success!").send(); + MockJDA.assertEmbeds( + List.of(CommandUtil.embedSuccess("Success!")), + MockJDA.getEmbeds(h1.getIdLong()), + true + ); + + CommandUtil.EmbedReply r2 = CommandUtil.EmbedReply.empty(); + r2.error("Error!").send(); + } + } From 9e66f7b6f5883fc00bfb7d02b10998ad77b03d09 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Sun, 8 Dec 2024 17:03:19 +0000 Subject: [PATCH 50/51] Loosen Coverage Requirements --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c26785..a10e4dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,7 +92,7 @@ jobs: coverage_report_name: Code Coverage github_token: ${{ secrets.GITHUB_TOKEN }} skip_check_run: false - minimum_coverage: 85 + minimum_coverage: 80 fail_below_threshold: false publish_only_summary: false @@ -120,5 +120,5 @@ jobs: paths: build/jacoco.xml token: ${{ secrets.GITHUB_TOKEN }} pass-emoji: ✅ - min-coverage-overall: 85 - min-coverage-changed-files: 90 + min-coverage-overall: 80 + min-coverage-changed-files: 85 From 55da8c082c0857c9304c2f50de6df98464f381ab Mon Sep 17 00:00:00 2001 From: Gregory Mitchell <54124162+gmitch215@users.noreply.github.com> Date: Sun, 8 Dec 2024 11:07:50 -0600 Subject: [PATCH 51/51] Code Cleanup --- src/main/java/io/codemc/bot/CodeMCBot.java | 1 - .../io/codemc/bot/commands/CmdCodeMC.java | 4 +- .../java/io/codemc/bot/commands/CmdMsg.java | 1 - .../codemc/bot/utils/ApplicationHandler.java | 2 +- .../java/io/codemc/bot/MockCodeMCBot.java | 2 +- src/test/java/io/codemc/bot/MockJDA.java | 42 ++++--------------- .../codemc/bot/commands/TestBotCommand.java | 18 ++++---- .../bot/commands/TestCmdApplication.java | 25 +++++------ .../io/codemc/bot/commands/TestCmdCodeMC.java | 36 +++++----------- .../codemc/bot/commands/TestCmdDisable.java | 7 ++-- .../io/codemc/bot/commands/TestCmdMsg.java | 9 ++-- .../io/codemc/bot/commands/TestCmdReload.java | 15 ++++--- .../io/codemc/bot/commands/TestCmdSubmit.java | 9 ++-- .../bot/commands/TestCommandListener.java | 1 - .../codemc/bot/config/TestConfigHandler.java | 7 ++-- .../bot/listeners/TestButtonListener.java | 27 +++++------- .../bot/listeners/TestModalListener.java | 23 +++++----- .../java/io/codemc/bot/utils/TestAPIUtil.java | 4 +- .../bot/utils/TestApplicationHandler.java | 25 ++++------- 19 files changed, 91 insertions(+), 167 deletions(-) diff --git a/src/main/java/io/codemc/bot/CodeMCBot.java b/src/main/java/io/codemc/bot/CodeMCBot.java index 44e000a..d4891e1 100644 --- a/src/main/java/io/codemc/bot/CodeMCBot.java +++ b/src/main/java/io/codemc/bot/CodeMCBot.java @@ -116,7 +116,6 @@ public final void validateConfig() { if(configHandler.getLong("server") == -1L){ logger.warn("Unable to retrieve Server ID. This value is required!"); System.exit(1); - return; } } diff --git a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java index 92e09ed..61b366c 100644 --- a/src/main/java/io/codemc/bot/commands/CmdCodeMC.java +++ b/src/main/java/io/codemc/bot/commands/CmdCodeMC.java @@ -619,7 +619,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g APIUtil.createNexus(hook, username, password); CommandUtil.EmbedReply.from(hook).success("Successfully created user " + username + " and linked it to " + target.getUser().getEffectiveName() + "!").send(); - LOGGER.info("Created user '" + username + "' in the Jenkins/Nexus services."); + LOGGER.info("Created user '{}' in the Jenkins/Nexus services.", username); } } @@ -661,7 +661,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g NexusAPI.deleteNexus(username); CommandUtil.EmbedReply.from(hook).success("Successfully deleted user " + username + "!").send(); - LOGGER.info("Deleted user '" + username + "' from the Jenkins/Nexus services."); + LOGGER.info("Deleted user '{}' from the Jenkins/Nexus services.", username); } } } diff --git a/src/main/java/io/codemc/bot/commands/CmdMsg.java b/src/main/java/io/codemc/bot/commands/CmdMsg.java index eedd47a..6878ce5 100644 --- a/src/main/java/io/codemc/bot/commands/CmdMsg.java +++ b/src/main/java/io/codemc/bot/commands/CmdMsg.java @@ -25,7 +25,6 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.interactions.InteractionHook; diff --git a/src/main/java/io/codemc/bot/utils/ApplicationHandler.java b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java index b4d7d7a..1c1c707 100644 --- a/src/main/java/io/codemc/bot/utils/ApplicationHandler.java +++ b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java @@ -114,7 +114,7 @@ public static void handle(CodeMCBot bot, InteractionHook hook, Guild guild, long String user = userField.getValue(); String repo = repoField.getValue(); - String username = user.substring(1, user.indexOf("]"));; + String username = user.substring(1, user.indexOf("]")); String userLink = user.substring(user.indexOf("(") + 1, user.length() - 1); String repoName = repo.substring(1, repo.indexOf("]")); String repoLink = repo.substring(repo.indexOf("(") + 1, repo.length() - 1); diff --git a/src/test/java/io/codemc/bot/MockCodeMCBot.java b/src/test/java/io/codemc/bot/MockCodeMCBot.java index b6afa62..c1ad86e 100644 --- a/src/test/java/io/codemc/bot/MockCodeMCBot.java +++ b/src/test/java/io/codemc/bot/MockCodeMCBot.java @@ -13,7 +13,7 @@ public class MockCodeMCBot extends CodeMCBot { - public static MockCodeMCBot INSTANCE = new MockCodeMCBot(); + public static final MockCodeMCBot INSTANCE = new MockCodeMCBot(); private MockCodeMCBot() { logger = LoggerFactory.getLogger(MockCodeMCBot.class); diff --git a/src/test/java/io/codemc/bot/MockJDA.java b/src/test/java/io/codemc/bot/MockJDA.java index a9f83f3..c9fab2a 100644 --- a/src/test/java/io/codemc/bot/MockJDA.java +++ b/src/test/java/io/codemc/bot/MockJDA.java @@ -1,19 +1,13 @@ package io.codemc.bot; +import com.jagrosh.jdautilities.command.SlashCommandEvent; import dev.coly.jdat.JDAObjects; import dev.coly.util.Callback; import io.codemc.bot.commands.BotCommand; import io.codemc.bot.commands.TestCommandListener; import io.codemc.bot.config.ConfigHandler; import net.dv8tion.jda.api.JDA; -import net.dv8tion.jda.api.entities.Guild; -import net.dv8tion.jda.api.entities.IMentionable; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.entities.UserSnowflake; +import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.Message.Attachment; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; @@ -32,42 +26,20 @@ import net.dv8tion.jda.api.interactions.modals.Modal; import net.dv8tion.jda.api.interactions.modals.ModalMapping; import net.dv8tion.jda.api.requests.RestAction; -import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; -import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; -import net.dv8tion.jda.api.requests.restaction.MessageEditAction; -import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction; -import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; -import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; +import net.dv8tion.jda.api.requests.restaction.*; import net.dv8tion.jda.api.requests.restaction.interactions.ModalCallbackAction; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import net.dv8tion.jda.api.utils.messages.MessageRequest; +import org.junit.jupiter.api.Assertions; import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.Consumer; -import org.junit.jupiter.api.Assertions; - -import com.jagrosh.jdautilities.command.SlashCommandEvent; - import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyCollection; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; @SuppressWarnings("unchecked") public class MockJDA { diff --git a/src/test/java/io/codemc/bot/commands/TestBotCommand.java b/src/test/java/io/codemc/bot/commands/TestBotCommand.java index 661c2aa..133ce1a 100644 --- a/src/test/java/io/codemc/bot/commands/TestBotCommand.java +++ b/src/test/java/io/codemc/bot/commands/TestBotCommand.java @@ -1,22 +1,20 @@ package io.codemc.bot.commands; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Map; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - import com.jagrosh.jdautilities.command.SlashCommandEvent; - import io.codemc.bot.MockCodeMCBot; import io.codemc.bot.MockJDA; import io.codemc.bot.utils.CommandUtil; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.interactions.InteractionHook; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TestBotCommand { diff --git a/src/test/java/io/codemc/bot/commands/TestCmdApplication.java b/src/test/java/io/codemc/bot/commands/TestCmdApplication.java index f590d8d..1b15bf3 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdApplication.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdApplication.java @@ -1,20 +1,5 @@ package io.codemc.bot.commands; -import static io.codemc.bot.MockJDA.AUTHOR; -import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - import io.codemc.api.database.DatabaseAPI; import io.codemc.api.jenkins.JenkinsAPI; import io.codemc.api.nexus.NexusAPI; @@ -26,6 +11,16 @@ import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static io.codemc.bot.MockJDA.AUTHOR; +import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; +import static org.junit.jupiter.api.Assertions.*; public class TestCmdApplication { diff --git a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java index ed45a05..78a087b 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdCodeMC.java @@ -1,38 +1,24 @@ package io.codemc.bot.commands; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - import com.jagrosh.jdautilities.command.SlashCommandEvent; - import io.codemc.api.database.DatabaseAPI; import io.codemc.api.jenkins.JenkinsAPI; import io.codemc.api.nexus.NexusAPI; import io.codemc.bot.MockCodeMCBot; import io.codemc.bot.MockJDA; -import io.codemc.bot.commands.CmdCodeMC.ChangePassword; -import io.codemc.bot.commands.CmdCodeMC.CreateUser; -import io.codemc.bot.commands.CmdCodeMC.DeleteUser; -import io.codemc.bot.commands.CmdCodeMC.Jenkins; -import io.codemc.bot.commands.CmdCodeMC.Link; -import io.codemc.bot.commands.CmdCodeMC.Nexus; -import io.codemc.bot.commands.CmdCodeMC.Remove; -import io.codemc.bot.commands.CmdCodeMC.Unlink; -import io.codemc.bot.commands.CmdCodeMC.Validate; +import io.codemc.bot.commands.CmdCodeMC.*; import io.codemc.bot.utils.CommandUtil; import net.dv8tion.jda.api.entities.Member; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; public class TestCmdCodeMC { diff --git a/src/test/java/io/codemc/bot/commands/TestCmdDisable.java b/src/test/java/io/codemc/bot/commands/TestCmdDisable.java index 6526e32..7c02c3d 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdDisable.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdDisable.java @@ -1,12 +1,11 @@ package io.codemc.bot.commands; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - +import io.codemc.bot.MockCodeMCBot; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import io.codemc.bot.MockCodeMCBot; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; public class TestCmdDisable { diff --git a/src/test/java/io/codemc/bot/commands/TestCmdMsg.java b/src/test/java/io/codemc/bot/commands/TestCmdMsg.java index ee3fcb8..ee3a4ef 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdMsg.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdMsg.java @@ -2,6 +2,8 @@ import io.codemc.bot.MockCodeMCBot; import io.codemc.bot.MockJDA; +import io.codemc.bot.commands.CmdMsg.Edit; +import io.codemc.bot.commands.CmdMsg.Post; import io.codemc.bot.utils.CommandUtil; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; @@ -9,14 +11,11 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import java.util.Map; + import static io.codemc.bot.MockJDA.GENERAL; import static org.junit.jupiter.api.Assertions.*; -import io.codemc.bot.commands.CmdMsg.Post; -import io.codemc.bot.commands.CmdMsg.Edit; - -import java.util.Map; - public class TestCmdMsg { private static CmdMsg command; diff --git a/src/test/java/io/codemc/bot/commands/TestCmdReload.java b/src/test/java/io/codemc/bot/commands/TestCmdReload.java index d56e5f7..211b6a4 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdReload.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdReload.java @@ -1,16 +1,15 @@ package io.codemc.bot.commands; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - -import java.util.Map; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - import io.codemc.bot.MockCodeMCBot; import io.codemc.bot.MockJDA; import io.codemc.bot.utils.CommandUtil; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; public class TestCmdReload { diff --git a/src/test/java/io/codemc/bot/commands/TestCmdSubmit.java b/src/test/java/io/codemc/bot/commands/TestCmdSubmit.java index 6235e30..1a290b5 100644 --- a/src/test/java/io/codemc/bot/commands/TestCmdSubmit.java +++ b/src/test/java/io/codemc/bot/commands/TestCmdSubmit.java @@ -1,15 +1,12 @@ package io.codemc.bot.commands; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - +import io.codemc.bot.MockCodeMCBot; +import io.codemc.bot.MockJDA; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import io.codemc.bot.MockCodeMCBot; -import io.codemc.bot.MockJDA; +import static org.junit.jupiter.api.Assertions.*; public class TestCmdSubmit { diff --git a/src/test/java/io/codemc/bot/commands/TestCommandListener.java b/src/test/java/io/codemc/bot/commands/TestCommandListener.java index f771b61..191107b 100644 --- a/src/test/java/io/codemc/bot/commands/TestCommandListener.java +++ b/src/test/java/io/codemc/bot/commands/TestCommandListener.java @@ -2,7 +2,6 @@ import com.jagrosh.jdautilities.command.SlashCommand; import com.jagrosh.jdautilities.command.SlashCommandEvent; - import net.dv8tion.jda.api.events.GenericEvent; import net.dv8tion.jda.api.hooks.EventListener; diff --git a/src/test/java/io/codemc/bot/config/TestConfigHandler.java b/src/test/java/io/codemc/bot/config/TestConfigHandler.java index 3858c1e..e71775c 100644 --- a/src/test/java/io/codemc/bot/config/TestConfigHandler.java +++ b/src/test/java/io/codemc/bot/config/TestConfigHandler.java @@ -1,12 +1,11 @@ package io.codemc.bot.config; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - +import io.codemc.bot.MockCodeMCBot; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import io.codemc.bot.MockCodeMCBot; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; public class TestConfigHandler { diff --git a/src/test/java/io/codemc/bot/listeners/TestButtonListener.java b/src/test/java/io/codemc/bot/listeners/TestButtonListener.java index ea5e115..a0d30c5 100644 --- a/src/test/java/io/codemc/bot/listeners/TestButtonListener.java +++ b/src/test/java/io/codemc/bot/listeners/TestButtonListener.java @@ -1,21 +1,5 @@ package io.codemc.bot.listeners; -import static io.codemc.bot.MockJDA.AUTHOR; -import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.List; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - import io.codemc.api.database.DatabaseAPI; import io.codemc.api.jenkins.JenkinsAPI; import io.codemc.api.nexus.NexusAPI; @@ -27,6 +11,17 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.interactions.components.buttons.Button; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static io.codemc.bot.MockJDA.AUTHOR; +import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TestButtonListener { diff --git a/src/test/java/io/codemc/bot/listeners/TestModalListener.java b/src/test/java/io/codemc/bot/listeners/TestModalListener.java index 0639323..ac133f7 100644 --- a/src/test/java/io/codemc/bot/listeners/TestModalListener.java +++ b/src/test/java/io/codemc/bot/listeners/TestModalListener.java @@ -1,17 +1,5 @@ package io.codemc.bot.listeners; -import static io.codemc.bot.MockJDA.GENERAL; -import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - import io.codemc.api.jenkins.JenkinsAPI; import io.codemc.bot.MockCodeMCBot; import io.codemc.bot.MockJDA; @@ -21,6 +9,17 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.interactions.modals.Modal; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static io.codemc.bot.MockJDA.GENERAL; +import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; public class TestModalListener { diff --git a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java index 3300a53..ae1a0dd 100644 --- a/src/test/java/io/codemc/bot/utils/TestAPIUtil.java +++ b/src/test/java/io/codemc/bot/utils/TestAPIUtil.java @@ -11,10 +11,10 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + public class TestAPIUtil { @BeforeAll diff --git a/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java b/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java index beeeef8..8931908 100644 --- a/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java +++ b/src/test/java/io/codemc/bot/utils/TestApplicationHandler.java @@ -1,23 +1,5 @@ package io.codemc.bot.utils; -import static io.codemc.bot.MockJDA.ACCEPTED_CHANNEL; -import static io.codemc.bot.MockJDA.AUTHOR; -import static io.codemc.bot.MockJDA.GUILD; -import static io.codemc.bot.MockJDA.REJECTED_CHANNEL; -import static io.codemc.bot.MockJDA.REQUEST_CHANNEL; -import static io.codemc.bot.MockJDA.SELF; -import static io.codemc.bot.MockJDA.assertEmbeds; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - import io.codemc.api.database.DatabaseAPI; import io.codemc.api.jenkins.JenkinsAPI; import io.codemc.api.nexus.NexusAPI; @@ -29,6 +11,13 @@ import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.InteractionType; import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static io.codemc.bot.MockJDA.*; +import static org.junit.jupiter.api.Assertions.*; public class TestApplicationHandler {