mirror of
https://github.com/LuckPerms/LuckPerms.git
synced 2025-01-14 12:11:42 +01:00
Add integration tests for storage and messaging
This commit is contained in:
parent
6523e708a1
commit
4068c71d5a
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -32,13 +32,15 @@ jobs:
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
- name: Run build and tests with Gradle wrapper
|
||||
run: ./gradlew test build
|
||||
run: ./gradlew test build -PdockerTests
|
||||
|
||||
- name: Publish test report
|
||||
uses: mikepenz/action-junit-report@v3
|
||||
if: success() || failure()
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
annotate_notice: true
|
||||
detailed_summary: true
|
||||
|
||||
- name: Upload all artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
|
@ -36,6 +36,12 @@ cd LuckPerms/
|
||||
|
||||
You can find the output jars in the `loader/build/libs` or `build/libs` directories.
|
||||
|
||||
## Tests
|
||||
There are some automated tests which run during each build.
|
||||
|
||||
* Unit tests are defined in [`common/src/test`](https://github.com/LuckPerms/LuckPerms/tree/master/common/src/test)
|
||||
* Integration tests are defined in [`standalone/src/test`](https://github.com/LuckPerms/LuckPerms/tree/master/standalone/src/test).
|
||||
|
||||
## Contributing
|
||||
#### Pull Requests
|
||||
If you make any changes or improvements to the plugin which you think would be beneficial to others, please consider making a pull request to merge your changes back into the upstream project. (especially if your changes are bug fixes!)
|
||||
|
@ -706,6 +706,11 @@ public final class ConfigKeys {
|
||||
*/
|
||||
public static final ConfigKey<String> RABBITMQ_PASSWORD = notReloadable(stringKey("rabbitmq.password", "guest"));
|
||||
|
||||
/**
|
||||
* If the editor key should be generated lazily (only when needed)
|
||||
*/
|
||||
public static final ConfigKey<Boolean> EDITOR_LAZILY_GENERATE_KEY = booleanKey("editor-lazily-generate-key", false);
|
||||
|
||||
/**
|
||||
* The URL of the bytebin instance used to upload data
|
||||
*/
|
||||
|
@ -37,6 +37,7 @@ import com.rabbitmq.client.DeliverCallback;
|
||||
import com.rabbitmq.client.Delivery;
|
||||
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
import me.lucko.luckperms.common.plugin.scheduler.SchedulerTask;
|
||||
|
||||
import net.luckperms.api.messenger.IncomingMessageConsumer;
|
||||
import net.luckperms.api.messenger.Messenger;
|
||||
@ -44,6 +45,8 @@ import net.luckperms.api.messenger.message.OutgoingMessage;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* An implementation of {@link Messenger} using RabbitMQ.
|
||||
*/
|
||||
@ -62,6 +65,7 @@ public class RabbitMQMessenger implements Messenger {
|
||||
private Connection connection;
|
||||
private Channel channel;
|
||||
private Subscription sub;
|
||||
private SchedulerTask checkConnectionTask;
|
||||
|
||||
public RabbitMQMessenger(LuckPermsPlugin plugin, IncomingMessageConsumer consumer) {
|
||||
this.plugin = plugin;
|
||||
@ -81,7 +85,8 @@ public class RabbitMQMessenger implements Messenger {
|
||||
this.connectionFactory.setPassword(password);
|
||||
|
||||
this.sub = new Subscription();
|
||||
this.plugin.getBootstrap().getScheduler().executeAsync(this.sub);
|
||||
checkAndReopenConnection(true);
|
||||
this.checkConnectionTask = this.plugin.getBootstrap().getScheduler().asyncRepeating(() -> checkAndReopenConnection(false), 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -100,7 +105,7 @@ public class RabbitMQMessenger implements Messenger {
|
||||
try {
|
||||
this.channel.close();
|
||||
this.connection.close();
|
||||
this.sub.isClosed = true;
|
||||
this.checkConnectionTask.cancel();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -159,30 +164,7 @@ public class RabbitMQMessenger implements Messenger {
|
||||
}
|
||||
}
|
||||
|
||||
private class Subscription implements Runnable, DeliverCallback {
|
||||
private boolean isClosed = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean firstStartup = true;
|
||||
while (!Thread.interrupted() && !this.isClosed) {
|
||||
try {
|
||||
if (!checkAndReopenConnection(firstStartup)) {
|
||||
// Sleep for 5 seconds to prevent massive spam in console
|
||||
Thread.sleep(5000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check connection life every every 30 seconds
|
||||
Thread.sleep(30_000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
firstStartup = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Subscription implements DeliverCallback {
|
||||
@Override
|
||||
public void handle(String consumerTag, Delivery message) {
|
||||
try {
|
||||
|
@ -25,6 +25,10 @@
|
||||
|
||||
package me.lucko.luckperms.common.webeditor.store;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
|
||||
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
import me.lucko.luckperms.common.webeditor.socket.CryptographyUtils;
|
||||
|
||||
@ -35,17 +39,29 @@ import java.util.concurrent.CompletableFuture;
|
||||
* Contains a store of known web editor sessions and provides a lookup function for
|
||||
* trusted editor public keys.
|
||||
*/
|
||||
@SuppressWarnings("Guava")
|
||||
public class WebEditorStore {
|
||||
private final WebEditorSessionMap sessions;
|
||||
private final WebEditorSocketMap sockets;
|
||||
private final WebEditorKeystore keystore;
|
||||
private final CompletableFuture<KeyPair> keyPair;
|
||||
private final Supplier<CompletableFuture<KeyPair>> keyPair;
|
||||
|
||||
public WebEditorStore(LuckPermsPlugin plugin) {
|
||||
this.sessions = new WebEditorSessionMap();
|
||||
this.sockets = new WebEditorSocketMap();
|
||||
this.keystore = new WebEditorKeystore(plugin.getBootstrap().getConfigDirectory().resolve("editor-keystore.json"));
|
||||
this.keyPair = CompletableFuture.supplyAsync(CryptographyUtils::generateKeyPair, plugin.getBootstrap().getScheduler().async());
|
||||
|
||||
Supplier<CompletableFuture<KeyPair>> keyPair = () -> CompletableFuture.supplyAsync(
|
||||
CryptographyUtils::generateKeyPair,
|
||||
plugin.getBootstrap().getScheduler().async()
|
||||
);
|
||||
|
||||
if (plugin.getConfiguration().get(ConfigKeys.EDITOR_LAZILY_GENERATE_KEY)) {
|
||||
this.keyPair = Suppliers.memoize(keyPair);
|
||||
} else {
|
||||
CompletableFuture<KeyPair> future = keyPair.get();
|
||||
this.keyPair = () -> future;
|
||||
}
|
||||
}
|
||||
|
||||
public WebEditorSessionMap sessions() {
|
||||
@ -61,10 +77,10 @@ public class WebEditorStore {
|
||||
}
|
||||
|
||||
public KeyPair keyPair() {
|
||||
if (!this.keyPair.isDone()) {
|
||||
if (!this.keyPair.get().isDone()) {
|
||||
throw new IllegalStateException("Web editor keypair has not been generated yet! Has the server just started?");
|
||||
}
|
||||
return this.keyPair.join();
|
||||
return this.keyPair.get().join();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -130,6 +130,10 @@ public class LuckPermsApplication implements AutoCloseable {
|
||||
this.healthReporter = healthReporter;
|
||||
}
|
||||
|
||||
public LuckPerms getApi() {
|
||||
return this.luckPermsApi;
|
||||
}
|
||||
|
||||
public CommandExecutor getCommandExecutor() {
|
||||
return this.commandExecutor;
|
||||
}
|
||||
|
@ -6,7 +6,11 @@ sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
|
||||
test {
|
||||
useJUnitPlatform {}
|
||||
useJUnitPlatform {
|
||||
if (!project.hasProperty('dockerTests')) {
|
||||
excludeTags 'docker'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -19,9 +23,18 @@ dependencies {
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.1'
|
||||
testImplementation "org.testcontainers:junit-jupiter:1.17.6"
|
||||
testImplementation 'org.mockito:mockito-core:4.11.0'
|
||||
testImplementation 'org.mockito:mockito-junit-jupiter:4.11.0'
|
||||
|
||||
testImplementation 'com.h2database:h2:2.1.214'
|
||||
testImplementation 'mysql:mysql-connector-java:8.0.23'
|
||||
testImplementation 'org.mariadb.jdbc:mariadb-java-client:2.7.2'
|
||||
testImplementation 'org.postgresql:postgresql:42.2.19'
|
||||
testImplementation 'org.mongodb:mongodb-driver-sync:4.5.0'
|
||||
testImplementation 'me.lucko.configurate:configurate-toml:3.7'
|
||||
testImplementation 'org.spongepowered:configurate-hocon:3.7.2'
|
||||
|
||||
testImplementation project(':standalone:app')
|
||||
testImplementation project(':common:loader-utils')
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ public class LPStandaloneBootstrap implements LuckPermsBootstrap, LoaderBootstra
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
LPStandaloneBootstrap(LuckPermsApplication loader, ClassPathAppender classPathAppender) {
|
||||
protected LPStandaloneBootstrap(LuckPermsApplication loader, ClassPathAppender classPathAppender) {
|
||||
this.loader = loader;
|
||||
|
||||
this.logger = new Log4jPluginLogger(LuckPermsApplication.LOGGER);
|
||||
@ -82,7 +82,7 @@ public class LPStandaloneBootstrap implements LuckPermsBootstrap, LoaderBootstra
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
LPStandalonePlugin createTestPlugin() {
|
||||
protected LPStandalonePlugin createTestPlugin() {
|
||||
return new LPStandalonePlugin(this);
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,9 @@ package me.lucko.luckperms.standalone;
|
||||
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||
import me.lucko.luckperms.common.model.Group;
|
||||
import me.lucko.luckperms.common.node.types.Permission;
|
||||
import me.lucko.luckperms.standalone.app.LuckPermsApplication;
|
||||
import me.lucko.luckperms.standalone.app.integration.CommandExecutor;
|
||||
import me.lucko.luckperms.standalone.app.integration.HealthReporter;
|
||||
import me.lucko.luckperms.standalone.utils.TestPluginProvider;
|
||||
|
||||
import net.luckperms.api.model.data.DataType;
|
||||
import net.luckperms.api.node.NodeEqualityPredicate;
|
||||
@ -49,13 +49,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
/**
|
||||
* A set of 'integration tests' for the standalone LuckPerms app.
|
||||
*/
|
||||
public class StandaloneIntegrationTests {
|
||||
|
||||
private @TempDir Path tempDir;
|
||||
public class IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testLoadEnableDisable() {
|
||||
useTestPlugin((app, bootstrap, plugin) -> {
|
||||
public void testLoadEnableDisable(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
|
||||
HealthReporter.Health health = app.getHealthReporter().poll();
|
||||
assertNotNull(health);
|
||||
assertTrue(health.isUp());
|
||||
@ -63,8 +61,8 @@ public class StandaloneIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunCommand() {
|
||||
useTestPlugin((app, bootstrap, plugin) -> {
|
||||
public void testRunCommand(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
|
||||
CommandExecutor commandExecutor = app.getCommandExecutor();
|
||||
commandExecutor.execute("group default permission set test").join();
|
||||
|
||||
@ -75,15 +73,15 @@ public class StandaloneIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReloadConfig() throws IOException {
|
||||
useTestPlugin((app, bootstrap, plugin) -> {
|
||||
public void testReloadConfig(@TempDir Path tempDir) throws IOException {
|
||||
TestPluginProvider.use(tempDir, (app, bootstrap, plugin) -> {
|
||||
String server = plugin.getConfiguration().get(ConfigKeys.SERVER);
|
||||
assertEquals("global", server);
|
||||
|
||||
Integer syncTime = plugin.getConfiguration().get(ConfigKeys.SYNC_TIME);
|
||||
assertEquals(-1, syncTime);
|
||||
|
||||
Path config = this.tempDir.resolve("config.yml");
|
||||
Path config = tempDir.resolve("config.yml");
|
||||
assertTrue(Files.exists(config));
|
||||
|
||||
String configString = Files.readString(config)
|
||||
@ -101,22 +99,4 @@ public class StandaloneIntegrationTests {
|
||||
});
|
||||
}
|
||||
|
||||
private <E extends Throwable> void useTestPlugin(TestPluginConsumer<E> consumer) throws E {
|
||||
LuckPermsApplication app = new LuckPermsApplication(() -> {});
|
||||
LPStandaloneTestBootstrap bootstrap = new LPStandaloneTestBootstrap(app, this.tempDir);
|
||||
|
||||
bootstrap.onLoad();
|
||||
bootstrap.onEnable();
|
||||
|
||||
try {
|
||||
consumer.accept(app, bootstrap, bootstrap.getPlugin());
|
||||
} finally {
|
||||
bootstrap.onDisable();
|
||||
}
|
||||
}
|
||||
|
||||
interface TestPluginConsumer<E extends Throwable> {
|
||||
void accept(LuckPermsApplication app, LPStandaloneTestBootstrap bootstrap, LPStandalonePlugin plugin) throws E;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package me.lucko.luckperms.standalone;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import me.lucko.luckperms.common.messaging.InternalMessagingService;
|
||||
import me.lucko.luckperms.standalone.app.integration.HealthReporter;
|
||||
import me.lucko.luckperms.standalone.utils.TestPluginProvider;
|
||||
|
||||
import net.luckperms.api.event.sync.PreNetworkSyncEvent;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Testcontainers
|
||||
@Tag("docker")
|
||||
public class MessagingIntegrationTest {
|
||||
|
||||
private static void testMessaging(Map<String, String> config, Path tempDirA, Path tempDirB) throws InterruptedException {
|
||||
try (TestPluginProvider.Plugin pluginA = TestPluginProvider.create(tempDirA, config);
|
||||
TestPluginProvider.Plugin pluginB = TestPluginProvider.create(tempDirB, config)) {
|
||||
|
||||
// check the plugins are healthy
|
||||
HealthReporter.Health healthA = pluginA.app().getHealthReporter().poll();
|
||||
assertNotNull(healthA);
|
||||
assertTrue(healthA.isUp());
|
||||
|
||||
HealthReporter.Health healthB = pluginB.app().getHealthReporter().poll();
|
||||
assertNotNull(healthB);
|
||||
assertTrue(healthB.isUp());
|
||||
|
||||
InternalMessagingService messagingServiceA = pluginA.plugin().getMessagingService().orElse(null);
|
||||
InternalMessagingService messagingServiceB = pluginB.plugin().getMessagingService().orElse(null);
|
||||
assertNotNull(messagingServiceA);
|
||||
assertNotNull(messagingServiceB);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
pluginB.app().getApi().getEventBus().subscribe(PreNetworkSyncEvent.class, e -> latch.countDown());
|
||||
|
||||
// send a message from plugin A to plugin B and wait for the message to be received
|
||||
messagingServiceA.pushUpdate();
|
||||
assertTrue(latch.await(30, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Sql {
|
||||
|
||||
@Container
|
||||
private final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("mysql:8"))
|
||||
.withEnv("MYSQL_DATABASE", "minecraft")
|
||||
.withEnv("MYSQL_ROOT_PASSWORD", "passw0rd")
|
||||
.withExposedPorts(3306);
|
||||
|
||||
@Test
|
||||
public void testMySql(@TempDir Path tempDirA, @TempDir Path tempDirB) throws InterruptedException {
|
||||
assertTrue(this.container.isRunning());
|
||||
|
||||
String host = this.container.getHost();
|
||||
Integer port = this.container.getFirstMappedPort();
|
||||
|
||||
Map<String, String> config = ImmutableMap.<String, String>builder()
|
||||
.put("storage-method", "mysql")
|
||||
.put("data.address", host + ":" + port)
|
||||
.put("data.database", "minecraft")
|
||||
.put("data.username", "root")
|
||||
.put("data.password", "passw0rd")
|
||||
.build();
|
||||
|
||||
testMessaging(config, tempDirA, tempDirB);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Redis {
|
||||
|
||||
@Container
|
||||
private final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("redis"))
|
||||
.withExposedPorts(6379);
|
||||
|
||||
@Test
|
||||
public void testRedis(@TempDir Path tempDirA, @TempDir Path tempDirB) throws InterruptedException {
|
||||
assertTrue(this.container.isRunning());
|
||||
|
||||
String host = this.container.getHost();
|
||||
Integer port = this.container.getFirstMappedPort();
|
||||
|
||||
Map<String, String> config = ImmutableMap.<String, String>builder()
|
||||
.put("messaging-service", "redis")
|
||||
.put("redis.enabled", "true")
|
||||
.put("redis.address", host + ":" + port)
|
||||
.build();
|
||||
|
||||
testMessaging(config, tempDirA, tempDirB);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class RabbitMq {
|
||||
|
||||
@Container
|
||||
private final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("rabbitmq"))
|
||||
.withExposedPorts(5672);
|
||||
|
||||
@Test
|
||||
public void testRabbitMq(@TempDir Path tempDirA, @TempDir Path tempDirB) throws InterruptedException {
|
||||
assertTrue(this.container.isRunning());
|
||||
|
||||
String host = this.container.getHost();
|
||||
Integer port = this.container.getFirstMappedPort();
|
||||
|
||||
Map<String, String> config = ImmutableMap.<String, String>builder()
|
||||
.put("messaging-service", "rabbitmq")
|
||||
.put("rabbitmq.enabled", "true")
|
||||
.put("rabbitmq.address", host + ":" + port)
|
||||
.build();
|
||||
|
||||
testMessaging(config, tempDirA, tempDirB);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Nats {
|
||||
|
||||
@Container
|
||||
private final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("nats"))
|
||||
.withExposedPorts(4222);
|
||||
|
||||
@Test
|
||||
public void testNats(@TempDir Path tempDirA, @TempDir Path tempDirB) throws InterruptedException {
|
||||
assertTrue(this.container.isRunning());
|
||||
|
||||
String host = this.container.getHost();
|
||||
Integer port = this.container.getFirstMappedPort();
|
||||
|
||||
Map<String, String> config = ImmutableMap.<String, String>builder()
|
||||
.put("messaging-service", "nats")
|
||||
.put("nats.enabled", "true")
|
||||
.put("nats.address", host + ":" + port)
|
||||
.build();
|
||||
|
||||
testMessaging(config, tempDirA, tempDirB);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package me.lucko.luckperms.standalone;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import me.lucko.luckperms.common.model.Group;
|
||||
import me.lucko.luckperms.common.node.types.Inheritance;
|
||||
import me.lucko.luckperms.common.node.types.Permission;
|
||||
import me.lucko.luckperms.standalone.app.LuckPermsApplication;
|
||||
import me.lucko.luckperms.standalone.app.integration.HealthReporter;
|
||||
import me.lucko.luckperms.standalone.utils.TestPluginBootstrap;
|
||||
import me.lucko.luckperms.standalone.utils.TestPluginBootstrap.TestPlugin;
|
||||
import me.lucko.luckperms.standalone.utils.TestPluginProvider;
|
||||
|
||||
import net.luckperms.api.event.cause.CreationCause;
|
||||
import net.luckperms.api.model.data.DataType;
|
||||
import net.luckperms.api.node.Node;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Testcontainers
|
||||
@Tag("docker")
|
||||
public class StorageIntegrationTest {
|
||||
|
||||
private static final Node TEST_PERMISSION = Permission.builder()
|
||||
.permission("test")
|
||||
.value(false)
|
||||
.expiry(1, TimeUnit.HOURS)
|
||||
.withContext("server", "foo")
|
||||
.withContext("world", "bar")
|
||||
.withContext("test", "test")
|
||||
.build();
|
||||
|
||||
private static final Node TEST_GROUP = Inheritance.builder()
|
||||
.group("default")
|
||||
.value(false)
|
||||
.expiry(1, TimeUnit.HOURS)
|
||||
.withContext("server", "foo")
|
||||
.withContext("world", "bar")
|
||||
.withContext("test", "test")
|
||||
.build();
|
||||
|
||||
|
||||
private static void testStorage(LuckPermsApplication app, TestPluginBootstrap bootstrap, TestPlugin plugin) {
|
||||
// check the plugin is healthy
|
||||
HealthReporter.Health health = app.getHealthReporter().poll();
|
||||
assertNotNull(health);
|
||||
assertTrue(health.isUp());
|
||||
|
||||
// try to create / save a group
|
||||
Group group = plugin.getStorage().createAndLoadGroup("test", CreationCause.INTERNAL).join();
|
||||
group.setNode(DataType.NORMAL, TEST_PERMISSION, true);
|
||||
group.setNode(DataType.NORMAL, TEST_GROUP, true);
|
||||
plugin.getStorage().saveGroup(group).join();
|
||||
|
||||
plugin.getStorage().loadAllGroups().join();
|
||||
|
||||
Group testGroup = plugin.getGroupManager().getIfLoaded("test");
|
||||
assertNotNull(testGroup);
|
||||
|
||||
assertEquals(ImmutableSet.of(TEST_PERMISSION, TEST_GROUP), testGroup.normalData().asSet());
|
||||
}
|
||||
|
||||
@Nested
|
||||
class MySql {
|
||||
|
||||
@Container
|
||||
private final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("mysql:8"))
|
||||
.withEnv("MYSQL_DATABASE", "minecraft")
|
||||
.withEnv("MYSQL_ROOT_PASSWORD", "passw0rd")
|
||||
.withExposedPorts(3306);
|
||||
|
||||
@Test
|
||||
public void testMySql(@TempDir Path tempDir) {
|
||||
assertTrue(this.container.isRunning());
|
||||
|
||||
String host = this.container.getHost();
|
||||
Integer port = this.container.getFirstMappedPort();
|
||||
|
||||
Map<String, String> config = ImmutableMap.<String, String>builder()
|
||||
.put("storage-method", "mysql")
|
||||
.put("data.address", host + ":" + port)
|
||||
.put("data.database", "minecraft")
|
||||
.put("data.username", "root")
|
||||
.put("data.password", "passw0rd")
|
||||
.build();
|
||||
|
||||
TestPluginProvider.use(tempDir, config, StorageIntegrationTest::testStorage);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class MariaDb {
|
||||
|
||||
@Container
|
||||
private final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("mariadb"))
|
||||
.withEnv("MARIADB_USER", "minecraft")
|
||||
.withEnv("MARIADB_PASSWORD", "passw0rd")
|
||||
.withEnv("MARIADB_ROOT_PASSWORD", "rootpassw0rd")
|
||||
.withEnv("MARIADB_DATABASE", "minecraft")
|
||||
.withExposedPorts(3306);
|
||||
|
||||
@Test
|
||||
public void testMariaDb(@TempDir Path tempDir) {
|
||||
assertTrue(this.container.isRunning());
|
||||
|
||||
String host = this.container.getHost();
|
||||
Integer port = this.container.getFirstMappedPort();
|
||||
|
||||
Map<String, String> config = ImmutableMap.<String, String>builder()
|
||||
.put("storage-method", "mariadb")
|
||||
.put("data.address", host + ":" + port)
|
||||
.put("data.database", "minecraft")
|
||||
.put("data.username", "minecraft")
|
||||
.put("data.password", "passw0rd")
|
||||
.build();
|
||||
|
||||
TestPluginProvider.use(tempDir, config, StorageIntegrationTest::testStorage);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Postgres {
|
||||
|
||||
@Container
|
||||
private final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("postgres"))
|
||||
.withEnv("POSTGRES_PASSWORD", "passw0rd")
|
||||
.withExposedPorts(5432);
|
||||
|
||||
@Test
|
||||
public void testPostgres(@TempDir Path tempDir) {
|
||||
assertTrue(this.container.isRunning());
|
||||
|
||||
String host = this.container.getHost();
|
||||
Integer port = this.container.getFirstMappedPort();
|
||||
|
||||
Map<String, String> config = ImmutableMap.<String, String>builder()
|
||||
.put("storage-method", "postgresql")
|
||||
.put("data.address", host + ":" + port)
|
||||
.put("data.database", "postgres")
|
||||
.put("data.username", "postgres")
|
||||
.put("data.password", "passw0rd")
|
||||
.build();
|
||||
|
||||
TestPluginProvider.use(tempDir, config, StorageIntegrationTest::testStorage);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class MongoDb {
|
||||
|
||||
@Container
|
||||
private final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("mongo"))
|
||||
.withExposedPorts(27017);
|
||||
|
||||
@Test
|
||||
public void testMongo(@TempDir Path tempDir) {
|
||||
assertTrue(this.container.isRunning());
|
||||
|
||||
String host = this.container.getHost();
|
||||
Integer port = this.container.getFirstMappedPort();
|
||||
|
||||
Map<String, String> config = ImmutableMap.<String, String>builder()
|
||||
.put("storage-method", "mongodb")
|
||||
.put("data.address", host + ":" + port)
|
||||
.put("data.database", "minecraft")
|
||||
.put("data.username", "")
|
||||
.put("data.password", "")
|
||||
.build();
|
||||
|
||||
TestPluginProvider.use(tempDir, config, StorageIntegrationTest::testStorage);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FlatFile {
|
||||
|
||||
@Test
|
||||
public void testYaml(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, ImmutableMap.of("storage-method", "yaml"), StorageIntegrationTest::testStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJson(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, ImmutableMap.of("storage-method", "json"), StorageIntegrationTest::testStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHocon(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, ImmutableMap.of("storage-method", "hocon"), StorageIntegrationTest::testStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToml(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, ImmutableMap.of("storage-method", "toml"), StorageIntegrationTest::testStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testYamlCombined(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, ImmutableMap.of("storage-method", "yaml-combined"), StorageIntegrationTest::testStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonCombined(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, ImmutableMap.of("storage-method", "json-combined"), StorageIntegrationTest::testStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHoconCombined(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, ImmutableMap.of("storage-method", "hocon-combined"), StorageIntegrationTest::testStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTomlCombined(@TempDir Path tempDir) {
|
||||
TestPluginProvider.use(tempDir, ImmutableMap.of("storage-method", "toml-combined"), StorageIntegrationTest::testStorage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -23,12 +23,14 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package me.lucko.luckperms.standalone;
|
||||
package me.lucko.luckperms.standalone.utils;
|
||||
|
||||
import me.lucko.luckperms.common.dependencies.Dependency;
|
||||
import me.lucko.luckperms.common.dependencies.DependencyManager;
|
||||
import me.lucko.luckperms.common.plugin.classpath.ClassPathAppender;
|
||||
import me.lucko.luckperms.common.storage.StorageType;
|
||||
import me.lucko.luckperms.standalone.LPStandaloneBootstrap;
|
||||
import me.lucko.luckperms.standalone.LPStandalonePlugin;
|
||||
import me.lucko.luckperms.standalone.app.LuckPermsApplication;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@ -45,18 +47,18 @@ import java.util.Set;
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
public final class LPStandaloneTestBootstrap extends LPStandaloneBootstrap {
|
||||
public final class TestPluginBootstrap extends LPStandaloneBootstrap {
|
||||
private static final ClassPathAppender NOOP_APPENDER = file -> {};
|
||||
|
||||
private final Path dataDirectory;
|
||||
private LPStandaloneTestPlugin plugin;
|
||||
private TestPlugin plugin;
|
||||
|
||||
LPStandaloneTestBootstrap(LuckPermsApplication app, Path dataDirectory) {
|
||||
TestPluginBootstrap(LuckPermsApplication app, Path dataDirectory) {
|
||||
super(app, NOOP_APPENDER);
|
||||
this.dataDirectory = dataDirectory;
|
||||
}
|
||||
|
||||
public LPStandaloneTestPlugin getPlugin() {
|
||||
public TestPlugin getPlugin() {
|
||||
return this.plugin;
|
||||
}
|
||||
|
||||
@ -66,14 +68,13 @@ public final class LPStandaloneTestBootstrap extends LPStandaloneBootstrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
LPStandalonePlugin createTestPlugin() {
|
||||
System.setProperty("luckperms.auto-install-translations", "false");
|
||||
this.plugin = new LPStandaloneTestPlugin(this);
|
||||
protected LPStandalonePlugin createTestPlugin() {
|
||||
this.plugin = new TestPlugin(this);
|
||||
return this.plugin;
|
||||
}
|
||||
|
||||
static final class LPStandaloneTestPlugin extends LPStandalonePlugin {
|
||||
LPStandaloneTestPlugin(LPStandaloneBootstrap bootstrap) {
|
||||
public static final class TestPlugin extends LPStandalonePlugin {
|
||||
TestPlugin(LPStandaloneBootstrap bootstrap) {
|
||||
super(bootstrap);
|
||||
}
|
||||
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package me.lucko.luckperms.standalone.utils;
|
||||
|
||||
import me.lucko.luckperms.standalone.app.LuckPermsApplication;
|
||||
import me.lucko.luckperms.standalone.utils.TestPluginBootstrap.TestPlugin;
|
||||
|
||||
import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class TestPluginProvider {
|
||||
private TestPluginProvider() {}
|
||||
|
||||
/**
|
||||
* Creates a test LuckPerms plugin instance, loads/enables it, and returns it.
|
||||
*
|
||||
* @param tempDir the temporary directory to run the plugin in
|
||||
* @param config the config to set
|
||||
* @return the plugin
|
||||
*/
|
||||
public static Plugin create(Path tempDir, Map<String, String> config) {
|
||||
Map<String, String> props = new HashMap<>(config);
|
||||
props.putIfAbsent("auto-install-translations", "false");
|
||||
props.putIfAbsent("editor-lazily-generate-key", "true");
|
||||
|
||||
props.forEach((k, v) -> System.setProperty("luckperms." + k, v));
|
||||
|
||||
LuckPermsApplication app = new LuckPermsApplication(() -> {});
|
||||
TestPluginBootstrap bootstrap = new TestPluginBootstrap(app, tempDir);
|
||||
|
||||
bootstrap.onLoad();
|
||||
bootstrap.onEnable();
|
||||
|
||||
props.keySet().forEach((k) -> System.clearProperty("luckperms." + k));
|
||||
|
||||
return new Plugin(app, bootstrap, bootstrap.getPlugin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test LuckPerms plugin instance, loads/enables it, and returns it.
|
||||
*
|
||||
* @param tempDir the temporary directory to run the plugin in
|
||||
* @return the plugin
|
||||
*/
|
||||
public static Plugin create(Path tempDir) {
|
||||
return create(tempDir, ImmutableMap.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test LuckPerms plugin instance, loads/enables it, runs the consumer, then disables it.
|
||||
*
|
||||
* @param tempDir the temporary directory to run the plugin in
|
||||
* @param config the config to set
|
||||
* @param consumer the consumer
|
||||
* @param <E> the exception class thrown by the consumer
|
||||
* @throws E exception
|
||||
*/
|
||||
public static <E extends Throwable> void use(Path tempDir, Map<String, String> config, Consumer<E> consumer) throws E {
|
||||
try (Plugin plugin = create(tempDir, config)) {
|
||||
consumer.accept(plugin.app, plugin.bootstrap, plugin.plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test LuckPerms plugin instance, loads/enables it, runs the consumer, then disables it.
|
||||
*
|
||||
* @param tempDir the temporary directory to run the plugin in
|
||||
* @param consumer the consumer
|
||||
* @param <E> the exception class thrown by the consumer
|
||||
* @throws E exception
|
||||
*/
|
||||
public static <E extends Throwable> void use(Path tempDir, Consumer<E> consumer) throws E {
|
||||
use(tempDir, ImmutableMap.of(), consumer);
|
||||
}
|
||||
|
||||
public interface Consumer<E extends Throwable> {
|
||||
void accept(LuckPermsApplication app, TestPluginBootstrap bootstrap, TestPlugin plugin) throws E;
|
||||
}
|
||||
|
||||
public record Plugin(LuckPermsApplication app, TestPluginBootstrap bootstrap, TestPlugin plugin) implements AutoCloseable {
|
||||
@Override
|
||||
public void close() {
|
||||
this.bootstrap.onDisable();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user