Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: E2E Tests

on:
pull_request:
push:
branches:
- master
workflow_dispatch: # Allow manual triggering

jobs:
e2e:
runs-on: ubuntu-latest
name: E2E - ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
include:
- platform: Bukkit
task: testBukkit
build_task: ":BanManagerBukkit:shadowJar"
compose_dir: platforms/bukkit

steps:
- uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: 21
distribution: "temurin"

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: ${{ github.ref != 'refs/heads/master' }}

- uses: actions/cache@v4
with:
path: |
.gradle/loom-cache
key: ${{ runner.os }}-loom-${{ hashFiles('**/libs.versions.*', '**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-loom-

# Docker Buildx for better caching
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# Cache Docker layers for test runner image
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-docker-e2e-${{ hashFiles('e2e/Dockerfile.tests', 'e2e/tests/package*.json') }}
restore-keys: |
${{ runner.os }}-docker-e2e-

# Pre-build the test runner image with cache
- name: Build test runner image
uses: docker/build-push-action@v5
with:
context: e2e
file: e2e/Dockerfile.tests
push: false
load: true
tags: e2e-tests:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

# Move cache to avoid ever-growing cache
- name: Move Docker cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache

- name: Build plugin JARs
run: ./gradlew ${{ matrix.build_task }} --build-cache

- name: Run E2E tests
run: ./gradlew :BanManagerE2E:${{ matrix.task }}
timeout-minutes: 15

- name: Collect logs on failure
if: failure()
working-directory: e2e/${{ matrix.compose_dir }}
run: |
docker compose logs > ../../e2e-logs-${{ matrix.platform }}.txt 2>&1 || true

- name: Upload logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-logs-${{ matrix.platform }}
path: e2e-logs-${{ matrix.platform }}.txt
retention-days: 7

- name: Cleanup
if: always()
working-directory: e2e/${{ matrix.compose_dir }}
run: docker compose down -v || true
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,11 @@ target
/dependency-reduced-pom.xml
*-private.sh
*.txt

# E2E Tests (Node.js)
e2e/tests/node_modules/
e2e/tests/dist/
e2e/tests/*.log
e2e/tests/coverage/

dependency-reduced-pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class BMBukkitPlugin extends JavaPlugin {
@Getter
private BanManagerPlugin plugin;

private ChatListener chatListener;

private String[] configs = new String[]{
"config.yml",
"console.yml",
Expand Down Expand Up @@ -125,17 +127,9 @@ public void setupListeners() {
registerEvent(new CommandListener(plugin));
registerEvent(new HookListener(plugin));

registerChatListener();

String chatPriority = plugin.getConfig().getChatPriority();
if(!chatPriority.equals("NONE")) {
ChatListener chatListener = new ChatListener(plugin);
// Set custom priority
getServer().getPluginManager().registerEvent(AsyncPlayerChatEvent.class, chatListener, EventPriority.valueOf(chatPriority),
(listener, event) -> {
((ChatListener) listener).onPlayerChat((AsyncPlayerChatEvent) event);
((ChatListener) listener).onIpChat((AsyncPlayerChatEvent) event);
}, this);
}
registerEvent(new ReloadListener(this));

if (plugin.getConfig().isDisplayNotificationsEnabled()) {
registerEvent(new BanListener(plugin));
Expand All @@ -153,6 +147,32 @@ public void setupListeners() {
}
}

private void unregisterChatListener() {
if (chatListener != null) {
AsyncPlayerChatEvent.getHandlerList().unregister(chatListener);
chatListener = null;
plugin.getLogger().info("Chat listener unregistered");
}
}

public void registerChatListener() {
unregisterChatListener();

String chatPriority = plugin.getConfig().getChatPriority();
if (!chatPriority.equals("NONE")) {
chatListener = new ChatListener(plugin);
// Set custom priority
getServer().getPluginManager().registerEvent(AsyncPlayerChatEvent.class, chatListener, EventPriority.valueOf(chatPriority),
(listener, event) -> {
((ChatListener) listener).onPlayerChat((AsyncPlayerChatEvent) event);
((ChatListener) listener).onIpChat((AsyncPlayerChatEvent) event);
}, this);
plugin.getLogger().info("Chat listener registered with priority: " + chatPriority);
} else {
plugin.getLogger().info("Chat listener disabled (priority: NONE)");
}
}

private void registerEvent(Listener listener) {
getServer().getPluginManager().registerEvents(listener, this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package me.confuser.banmanager.bukkit.listeners;

import lombok.RequiredArgsConstructor;
import me.confuser.banmanager.bukkit.BMBukkitPlugin;
import me.confuser.banmanager.bukkit.api.events.PluginReloadedEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;

@RequiredArgsConstructor
public class ReloadListener implements Listener {
private final BMBukkitPlugin bukkitPlugin;

@EventHandler(priority = EventPriority.MONITOR)
public void onReload(final PluginReloadedEvent event) {
bukkitPlugin.registerChatListener();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class BMBungeePlugin extends Plugin {
@Getter
private BungeeConfig bungeeConfig;

private ChatListener chatListener;

private String[] configs = new String[]{
"config.yml",
"bungeecord.yml",
Expand Down Expand Up @@ -150,9 +152,9 @@ public void setupListeners() {
registerEvent(new LeaveListener(plugin));
registerEvent(new HookListener(plugin));

if (!plugin.getConfig().getChatPriority().equals("NONE")) {
registerEvent(new ChatListener(plugin));
}
registerChatListener();

registerEvent(new ReloadListener(this));

if (plugin.getConfig().isDisplayNotificationsEnabled()) {
registerEvent(new BanListener(plugin));
Expand All @@ -165,6 +167,22 @@ public void setupListeners() {
}
}

private void unregisterChatListener() {
if (chatListener != null) {
getProxy().getPluginManager().unregisterListener(chatListener);
chatListener = null;
}
}

public void registerChatListener() {
unregisterChatListener();

if (!plugin.getConfig().getChatPriority().equals("NONE")) {
chatListener = new ChatListener(plugin);
registerEvent(chatListener);
}
}

private void registerEvent(Listener listener) {
getProxy().getPluginManager().registerListener(this, listener);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package me.confuser.banmanager.bungee.listeners;

import lombok.RequiredArgsConstructor;
import me.confuser.banmanager.bungee.BMBungeePlugin;
import me.confuser.banmanager.bungee.api.events.PluginReloadedEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;

@RequiredArgsConstructor
public class ReloadListener implements Listener {
private final BMBungeePlugin bungeePlugin;

@EventHandler(priority = EventPriority.HIGHEST)
public void onReload(final PluginReloadedEvent event) {
bungeePlugin.registerChatListener();
}
}
6 changes: 6 additions & 0 deletions e2e/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
tests/node_modules
*.log
.git
platforms/
*.sh
13 changes: 13 additions & 0 deletions e2e/Dockerfile.tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM node:22-alpine

WORKDIR /tests

# Install dependencies first for better caching
COPY tests/package*.json ./
RUN npm ci

# Copy test source files
COPY tests/ ./

# Run tests
CMD ["npm", "test"]
99 changes: 99 additions & 0 deletions e2e/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
plugins {
base
}

description = "E2E tests for BanManager using Docker and Mineflayer"

// Helper function to create platform test tasks
fun createPlatformTestTask(
taskName: String,
platformDir: String,
pluginTask: String,
description: String
) {
tasks.register<Exec>(taskName) {
group = "verification"
this.description = description

dependsOn(pluginTask)

workingDir = file("platforms/$platformDir")
commandLine(
"docker", "compose", "up",
"--build",
"--abort-on-container-exit",
"--exit-code-from", "tests"
)

doLast {
exec {
workingDir = file("platforms/$platformDir")
commandLine("docker", "compose", "down", "-v")
isIgnoreExitValue = true
}
}
}
}

// Bukkit/Paper E2E tests
createPlatformTestTask(
"testBukkit",
"bukkit",
":BanManagerBukkit:shadowJar",
"Run Bukkit E2E tests in Docker"
)

tasks.register("testAll") {
group = "verification"
description = "Run E2E tests for all working platforms (currently Bukkit only)"

dependsOn("testBukkit")
}

// Backward compatibility - "test" now runs Bukkit tests
tasks.register("test") {
group = "verification"
description = "Run Bukkit E2E tests (alias for testBukkit)"
dependsOn("testBukkit")
}

// Helper tasks for debugging
tasks.register<Exec>("startBukkit") {
group = "verification"
description = "Start the Bukkit test server without running tests (for debugging)"

dependsOn(":BanManagerBukkit:shadowJar")

workingDir = file("platforms/bukkit")
commandLine("docker", "compose", "up", "-d", "mariadb", "paper")
}

tasks.register<Exec>("stopBukkit") {
group = "verification"
description = "Stop the Bukkit test server"

workingDir = file("platforms/bukkit")
commandLine("docker", "compose", "down", "-v")
isIgnoreExitValue = true
}

tasks.register<Exec>("logsBukkit") {
group = "verification"
description = "Show Bukkit server logs"

workingDir = file("platforms/bukkit")
commandLine("docker", "compose", "logs", "-f", "paper")
}

tasks.named("clean") {
doLast {
// Clean up all platform Docker resources
listOf("bukkit").forEach { platform ->
exec {
workingDir = file("platforms/$platform")
commandLine("docker", "compose", "down", "-v", "--rmi", "local")
isIgnoreExitValue = true
}
}
}
}
Loading