mirror of
https://github.com/lucko/LuckPerms.git
synced 2025-08-22 14:12:48 +02:00
Implement paginated logs for all storage backends
This commit is contained in:
@@ -4,7 +4,11 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform {}
|
useJUnitPlatform {
|
||||||
|
if (!project.hasProperty('dockerTests')) {
|
||||||
|
excludeTags 'docker'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jacocoTestReport {
|
jacocoTestReport {
|
||||||
@@ -15,9 +19,14 @@ dependencies {
|
|||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
|
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-engine:5.9.1'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.1'
|
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.1'
|
||||||
|
testImplementation "org.testcontainers:junit-jupiter:1.19.8"
|
||||||
testImplementation 'org.mockito:mockito-core:5.11.0'
|
testImplementation 'org.mockito:mockito-core:5.11.0'
|
||||||
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
|
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
|
||||||
testImplementation 'com.h2database:h2:2.1.214'
|
testImplementation 'com.h2database:h2:2.1.214'
|
||||||
|
testImplementation 'org.mongodb:mongodb-driver-legacy:4.5.0'
|
||||||
|
testImplementation 'org.spongepowered:configurate-yaml:3.7.2'
|
||||||
|
testImplementation 'org.spongepowered:configurate-hocon:3.7.2'
|
||||||
|
testImplementation 'me.lucko.configurate:configurate-toml:3.7'
|
||||||
|
|
||||||
api project(':api')
|
api project(':api')
|
||||||
api 'org.checkerframework:checker-qual:3.12.0'
|
api 'org.checkerframework:checker-qual:3.12.0'
|
||||||
@@ -87,7 +96,7 @@ dependencies {
|
|||||||
compileOnly 'redis.clients:jedis:4.4.3'
|
compileOnly 'redis.clients:jedis:4.4.3'
|
||||||
compileOnly 'io.nats:jnats:2.16.4'
|
compileOnly 'io.nats:jnats:2.16.4'
|
||||||
compileOnly 'com.rabbitmq:amqp-client:5.12.0'
|
compileOnly 'com.rabbitmq:amqp-client:5.12.0'
|
||||||
api 'org.mongodb:mongodb-driver-legacy:4.5.0'
|
compileOnly 'org.mongodb:mongodb-driver-legacy:4.5.0'
|
||||||
compileOnly 'org.postgresql:postgresql:42.6.0'
|
compileOnly 'org.postgresql:postgresql:42.6.0'
|
||||||
compileOnly 'org.yaml:snakeyaml:1.28'
|
compileOnly 'org.yaml:snakeyaml:1.28'
|
||||||
}
|
}
|
||||||
|
@@ -42,6 +42,10 @@ public class Log {
|
|||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Log of(List<LoggedAction> content) {
|
||||||
|
return content.isEmpty() ? EMPTY : new Log(content);
|
||||||
|
}
|
||||||
|
|
||||||
public static Log empty() {
|
public static Log empty() {
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,10 @@ public class LogPage {
|
|||||||
return new LogPage.Builder();
|
return new LogPage.Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LogPage of(List<LoggedAction> content) {
|
||||||
|
return content.isEmpty() ? EMPTY : new LogPage(content);
|
||||||
|
}
|
||||||
|
|
||||||
public static LogPage empty() {
|
public static LogPage empty() {
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
@@ -62,7 +62,7 @@ public final class ActionFilterMongoBuilder extends FilterMongoBuilder<Action> {
|
|||||||
if (value instanceof String | value instanceof UUID) {
|
if (value instanceof String | value instanceof UUID) {
|
||||||
return value;
|
return value;
|
||||||
} else if (value instanceof Action.Target.Type) {
|
} else if (value instanceof Action.Target.Type) {
|
||||||
return LoggedAction.getTypeString((Action.Target.Type) value);
|
return ((Action.Target.Type) value).name();
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Don't know how to map value with type: " + value.getClass().getName());
|
throw new IllegalArgumentException("Don't know how to map value with type: " + value.getClass().getName());
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,10 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.common.filter;
|
package me.lucko.luckperms.common.filter;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class PageParameters {
|
public class PageParameters {
|
||||||
|
|
||||||
private final int pageSize;
|
private final int pageSize;
|
||||||
@@ -43,4 +47,18 @@ public class PageParameters {
|
|||||||
return this.pageNumber;
|
return this.pageNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> List<T> paginate(List<T> input) {
|
||||||
|
int fromIndex = this.pageSize * (this.pageNumber - 1);
|
||||||
|
if (fromIndex >= input.size()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
int toIndex = Math.min(fromIndex + this.pageSize, input.size());
|
||||||
|
return input.subList(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Stream<T> paginate(Stream<T> input) {
|
||||||
|
return input.skip((long) this.pageSize * (this.pageNumber - 1)).limit(this.pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -76,7 +76,7 @@ public class ConstraintMongoBuilder {
|
|||||||
public static <R> FindIterable<R> page(PageParameters pageParameters, FindIterable<R> iterable) {
|
public static <R> FindIterable<R> page(PageParameters pageParameters, FindIterable<R> iterable) {
|
||||||
int pageSize = pageParameters.pageSize();
|
int pageSize = pageParameters.pageSize();
|
||||||
int pageNumber = pageParameters.pageNumber();
|
int pageNumber = pageParameters.pageNumber();
|
||||||
return iterable.limit(pageNumber).skip((pageNumber - 1) * pageSize);
|
return iterable.limit(pageSize).skip((pageNumber - 1) * pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -25,12 +25,10 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.common.filter.mongo;
|
package me.lucko.luckperms.common.filter.mongo;
|
||||||
|
|
||||||
import com.mongodb.client.FindIterable;
|
|
||||||
import com.mongodb.client.model.Filters;
|
import com.mongodb.client.model.Filters;
|
||||||
import me.lucko.luckperms.common.filter.Filter;
|
import me.lucko.luckperms.common.filter.Filter;
|
||||||
import me.lucko.luckperms.common.filter.FilterField;
|
import me.lucko.luckperms.common.filter.FilterField;
|
||||||
import me.lucko.luckperms.common.filter.FilterList;
|
import me.lucko.luckperms.common.filter.FilterList;
|
||||||
import me.lucko.luckperms.common.filter.PageParameters;
|
|
||||||
import org.bson.conversions.Bson;
|
import org.bson.conversions.Bson;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -64,10 +62,4 @@ public abstract class FilterMongoBuilder<T> extends ConstraintMongoBuilder {
|
|||||||
return make(filters.operator(), filters);
|
return make(filters.operator(), filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <R> FindIterable<R> page(PageParameters pageParameters, FindIterable<R> iterable) {
|
|
||||||
int pageSize = pageParameters.pageSize();
|
|
||||||
int pageNumber = pageParameters.pageNumber();
|
|
||||||
return iterable.limit(pageNumber).skip((pageNumber - 1) * pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -177,7 +177,7 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LogPage getLogPage(FilterList<Action> filters, PageParameters page) throws Exception {
|
public LogPage getLogPage(FilterList<Action> filters, PageParameters page) throws Exception {
|
||||||
throw new UnsupportedOperationException();
|
return this.actionLogger.getLogPage(filters, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -25,12 +25,14 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.common.storage.implementation.file;
|
package me.lucko.luckperms.common.storage.implementation.file;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import me.lucko.luckperms.common.actionlog.ActionJsonSerializer;
|
import me.lucko.luckperms.common.actionlog.ActionJsonSerializer;
|
||||||
import me.lucko.luckperms.common.actionlog.Log;
|
import me.lucko.luckperms.common.actionlog.Log;
|
||||||
import me.lucko.luckperms.common.actionlog.LogPage;
|
import me.lucko.luckperms.common.actionlog.LogPage;
|
||||||
|
import me.lucko.luckperms.common.actionlog.LoggedAction;
|
||||||
import me.lucko.luckperms.common.cache.BufferedRequest;
|
import me.lucko.luckperms.common.cache.BufferedRequest;
|
||||||
import me.lucko.luckperms.common.filter.FilterList;
|
import me.lucko.luckperms.common.filter.FilterList;
|
||||||
import me.lucko.luckperms.common.filter.PageParameters;
|
import me.lucko.luckperms.common.filter.PageParameters;
|
||||||
@@ -45,11 +47,14 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class FileActionLogger {
|
public class FileActionLogger {
|
||||||
|
|
||||||
@@ -133,36 +138,37 @@ public class FileActionLogger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Log getLog() throws IOException {
|
private Stream<LoggedAction> getRawLog() throws IOException {
|
||||||
// if there is log content waiting to be written, flush immediately before trying to read
|
// if there is log content waiting to be written, flush immediately before trying to read
|
||||||
if (this.saveBuffer.isEnqueued()) {
|
if (this.saveBuffer.isEnqueued()) {
|
||||||
this.saveBuffer.requestDirectly();
|
this.saveBuffer.requestDirectly();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Files.exists(this.contentFile)) {
|
if (!Files.exists(this.contentFile)) {
|
||||||
return Log.empty();
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Builder log = Log.builder();
|
Stream.Builder<LoggedAction> builder = Stream.builder();
|
||||||
|
|
||||||
try (BufferedReader reader = Files.newBufferedReader(this.contentFile, StandardCharsets.UTF_8)) {
|
try (BufferedReader reader = Files.newBufferedReader(this.contentFile, StandardCharsets.UTF_8)) {
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
try {
|
try {
|
||||||
JsonElement parsed = GsonProvider.parser().parse(line);
|
JsonElement parsed = GsonProvider.parser().parse(line);
|
||||||
log.add(ActionJsonSerializer.deserialize(parsed));
|
builder.add(ActionJsonSerializer.deserialize(parsed));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
return log.build();
|
public Log getLog() throws IOException {
|
||||||
|
return Log.of(getRawLog().collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogPage getLogPage(FilterList<Action> filters, PageParameters page) throws IOException {
|
public LogPage getLogPage(FilterList<Action> filters, PageParameters page) throws IOException {
|
||||||
|
return LogPage.of(page.paginate(getRawLog().filter(filters::evaluate).sorted(Comparator.reverseOrder())).collect(Collectors.toList()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class SaveBuffer extends BufferedRequest<Void> {
|
private final class SaveBuffer extends BufferedRequest<Void> {
|
||||||
|
@@ -31,11 +31,13 @@ import com.mongodb.MongoClientOptions;
|
|||||||
import com.mongodb.MongoClientURI;
|
import com.mongodb.MongoClientURI;
|
||||||
import com.mongodb.MongoCredential;
|
import com.mongodb.MongoCredential;
|
||||||
import com.mongodb.ServerAddress;
|
import com.mongodb.ServerAddress;
|
||||||
|
import com.mongodb.client.MongoClients;
|
||||||
import com.mongodb.client.MongoCollection;
|
import com.mongodb.client.MongoCollection;
|
||||||
import com.mongodb.client.MongoCursor;
|
import com.mongodb.client.MongoCursor;
|
||||||
import com.mongodb.client.MongoDatabase;
|
import com.mongodb.client.MongoDatabase;
|
||||||
import com.mongodb.client.model.Filters;
|
import com.mongodb.client.model.Filters;
|
||||||
import com.mongodb.client.model.ReplaceOptions;
|
import com.mongodb.client.model.ReplaceOptions;
|
||||||
|
import com.mongodb.client.model.Sorts;
|
||||||
import me.lucko.luckperms.common.actionlog.Log;
|
import me.lucko.luckperms.common.actionlog.Log;
|
||||||
import me.lucko.luckperms.common.actionlog.LogPage;
|
import me.lucko.luckperms.common.actionlog.LogPage;
|
||||||
import me.lucko.luckperms.common.actionlog.LoggedAction;
|
import me.lucko.luckperms.common.actionlog.LoggedAction;
|
||||||
@@ -70,6 +72,7 @@ import net.luckperms.api.node.Node;
|
|||||||
import net.luckperms.api.node.NodeBuilder;
|
import net.luckperms.api.node.NodeBuilder;
|
||||||
import org.bson.Document;
|
import org.bson.Document;
|
||||||
import org.bson.UuidRepresentation;
|
import org.bson.UuidRepresentation;
|
||||||
|
import org.bson.conversions.Bson;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -193,7 +196,7 @@ public class MongoStorage implements StorageImplementation {
|
|||||||
public LogPage getLogPage(FilterList<Action> filters, PageParameters page) throws Exception {
|
public LogPage getLogPage(FilterList<Action> filters, PageParameters page) throws Exception {
|
||||||
LogPage.Builder log = LogPage.builder();
|
LogPage.Builder log = LogPage.builder();
|
||||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "action");
|
MongoCollection<Document> c = this.database.getCollection(this.prefix + "action");
|
||||||
try (MongoCursor<Document> cursor = FilterMongoBuilder.page(page, c.find(ActionFilterMongoBuilder.INSTANCE.make(filters))).iterator()) {
|
try (MongoCursor<Document> cursor = ConstraintMongoBuilder.page(page, c.find(ActionFilterMongoBuilder.INSTANCE.make(filters)).sort(Sorts.descending("timestamp"))).iterator()) {
|
||||||
while (cursor.hasNext()) {
|
while (cursor.hasNext()) {
|
||||||
log.add(actionFromDoc(cursor.next()));
|
log.add(actionFromDoc(cursor.next()));
|
||||||
}
|
}
|
||||||
|
@@ -268,7 +268,7 @@ public class SqlStorage implements StorageImplementation {
|
|||||||
ActionFilterSqlBuilder sqlBuilder = new ActionFilterSqlBuilder();
|
ActionFilterSqlBuilder sqlBuilder = new ActionFilterSqlBuilder();
|
||||||
sqlBuilder.builder().append(ACTION_SELECT_ALL);
|
sqlBuilder.builder().append(ACTION_SELECT_ALL);
|
||||||
sqlBuilder.visit(filter);
|
sqlBuilder.visit(filter);
|
||||||
sqlBuilder.builder().append(" ORDER BY id DESC");
|
sqlBuilder.builder().append(" ORDER BY time DESC");
|
||||||
sqlBuilder.visit(page);
|
sqlBuilder.visit(page);
|
||||||
|
|
||||||
try (PreparedStatement ps = sqlBuilder.builder().build(c, this.statementProcessor)) {
|
try (PreparedStatement ps = sqlBuilder.builder().build(c, this.statementProcessor)) {
|
||||||
|
@@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.common.storage.misc;
|
package me.lucko.luckperms.common.storage.misc;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -54,6 +56,10 @@ public class StorageCredentials {
|
|||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StorageCredentials(String address, String database, String username, String password) {
|
||||||
|
this(address, database, username, password, 10, 10, 1800000, 0, 5000, ImmutableMap.of());
|
||||||
|
}
|
||||||
|
|
||||||
public String getAddress() {
|
public String getAddress() {
|
||||||
return Objects.requireNonNull(this.address, "address");
|
return Objects.requireNonNull(this.address, "address");
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,355 @@
|
|||||||
|
/*
|
||||||
|
* 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.common.storage;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
|
||||||
|
import me.lucko.luckperms.common.actionlog.Log;
|
||||||
|
import me.lucko.luckperms.common.actionlog.LogPage;
|
||||||
|
import me.lucko.luckperms.common.actionlog.LoggedAction;
|
||||||
|
import me.lucko.luckperms.common.filter.PageParameters;
|
||||||
|
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||||
|
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
|
||||||
|
import me.lucko.luckperms.common.event.EventDispatcher;
|
||||||
|
import me.lucko.luckperms.common.model.Group;
|
||||||
|
import me.lucko.luckperms.common.model.PrimaryGroupHolder;
|
||||||
|
import me.lucko.luckperms.common.model.User;
|
||||||
|
import me.lucko.luckperms.common.model.manager.group.GroupManager;
|
||||||
|
import me.lucko.luckperms.common.model.manager.group.StandardGroupManager;
|
||||||
|
import me.lucko.luckperms.common.model.manager.user.StandardUserManager;
|
||||||
|
import me.lucko.luckperms.common.model.manager.user.UserManager;
|
||||||
|
import me.lucko.luckperms.common.node.types.Inheritance;
|
||||||
|
import me.lucko.luckperms.common.node.types.Permission;
|
||||||
|
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||||
|
import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap;
|
||||||
|
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.StorageImplementation;
|
||||||
|
import net.luckperms.api.actionlog.Action;
|
||||||
|
import net.luckperms.api.model.PlayerSaveResult;
|
||||||
|
import net.luckperms.api.model.PlayerSaveResult.Outcome;
|
||||||
|
import net.luckperms.api.model.data.DataType;
|
||||||
|
import net.luckperms.api.node.Node;
|
||||||
|
import net.luckperms.api.node.types.InheritanceNode;
|
||||||
|
import net.luckperms.api.node.types.PermissionNode;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
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.assertNotSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.AdditionalAnswers.answer;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public abstract class AbstractStorageTest {
|
||||||
|
|
||||||
|
@Mock protected LuckPermsPlugin plugin;
|
||||||
|
@Mock protected LuckPermsBootstrap bootstrap;
|
||||||
|
@Mock protected LuckPermsConfiguration configuration;
|
||||||
|
|
||||||
|
private StorageImplementation storage;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public final void setupMocksAndStorage() throws Exception {
|
||||||
|
lenient().when(this.plugin.getBootstrap()).thenReturn(this.bootstrap);
|
||||||
|
lenient().when(this.plugin.getConfiguration()).thenReturn(this.configuration);
|
||||||
|
lenient().when(this.plugin.getEventDispatcher()).thenReturn(mock(EventDispatcher.class));
|
||||||
|
lenient().when(this.bootstrap.getScheduler()).thenReturn(mock(SchedulerAdapter.class));
|
||||||
|
lenient().when(this.configuration.get(ConfigKeys.PRIMARY_GROUP_CALCULATION)).thenReturn(PrimaryGroupHolder.AllParentsByWeight::new);
|
||||||
|
lenient().when(this.configuration.get(ConfigKeys.PRIMARY_GROUP_CALCULATION_METHOD)).thenReturn("parents-by-weight");
|
||||||
|
lenient().when(this.bootstrap.getResourceStream(anyString()))
|
||||||
|
.then(answer((String path) -> AbstractStorageTest.class.getClassLoader().getResourceAsStream(path)));
|
||||||
|
lenient().when(this.plugin.getEventDispatcher()).thenReturn(mock(EventDispatcher.class));
|
||||||
|
|
||||||
|
this.storage = makeStorage(this.plugin);
|
||||||
|
this.storage.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception;
|
||||||
|
|
||||||
|
protected void cleanupResources() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public final void shutdownStorage() {
|
||||||
|
this.storage.shutdown();
|
||||||
|
cleanupResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionLog() throws Exception {
|
||||||
|
LoggedAction action = LoggedAction.build()
|
||||||
|
.source(UUID.randomUUID())
|
||||||
|
.sourceName("Test Source")
|
||||||
|
.targetType(Action.Target.Type.USER)
|
||||||
|
.target(UUID.randomUUID())
|
||||||
|
.targetName("Test Target")
|
||||||
|
.description("hello 123 hello 123")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.storage.logAction(action);
|
||||||
|
|
||||||
|
Log log = this.storage.getLog();
|
||||||
|
assertEquals(1, log.getContent().size());
|
||||||
|
assertEquals(action, log.getContent().first());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionLogPage() throws Exception {
|
||||||
|
UUID sourceUuid = UUID.randomUUID();
|
||||||
|
UUID targetUuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
Instant baseTime = Instant.now();
|
||||||
|
|
||||||
|
Function<Integer, LoggedAction> mockAction = i -> LoggedAction.build()
|
||||||
|
.source(i % 2 == 0 ? sourceUuid : UUID.randomUUID())
|
||||||
|
.sourceName("Test Source")
|
||||||
|
.targetType(Action.Target.Type.USER)
|
||||||
|
.target(targetUuid)
|
||||||
|
.targetName("Test Target")
|
||||||
|
.description("hello " + i)
|
||||||
|
.timestamp(baseTime.plusSeconds(i))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
this.storage.logAction(mockAction.apply(-i));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
this.storage.logAction(mockAction.apply(i));
|
||||||
|
}
|
||||||
|
for (int i = 100; i < 200; i++) {
|
||||||
|
this.storage.logAction(mockAction.apply(-i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
this.storage.logAction(LoggedAction.build()
|
||||||
|
.source(UUID.randomUUID())
|
||||||
|
.sourceName("Test Source")
|
||||||
|
.targetType(Action.Target.Type.GROUP)
|
||||||
|
.targetName(i % 2 == 0 ? "test_group" : "dummy")
|
||||||
|
.description("group test " + i)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
this.storage.logAction(LoggedAction.build()
|
||||||
|
.source(UUID.randomUUID())
|
||||||
|
.sourceName("Test Source")
|
||||||
|
.targetType(Action.Target.Type.TRACK)
|
||||||
|
.targetName(i % 2 == 0 ? "test_track" : "dummy")
|
||||||
|
.description("track test " + i)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
LogPage page = this.storage.getLogPage(ActionFilters.source(sourceUuid), new PageParameters(5, 1));
|
||||||
|
assertEquals(ImmutableList.of(
|
||||||
|
mockAction.apply(98),
|
||||||
|
mockAction.apply(96),
|
||||||
|
mockAction.apply(94),
|
||||||
|
mockAction.apply(92),
|
||||||
|
mockAction.apply(90)
|
||||||
|
), page.getContent());
|
||||||
|
|
||||||
|
|
||||||
|
page = this.storage.getLogPage(ActionFilters.source(sourceUuid), new PageParameters(5, 3));
|
||||||
|
assertEquals(ImmutableList.of(
|
||||||
|
mockAction.apply(78),
|
||||||
|
mockAction.apply(76),
|
||||||
|
mockAction.apply(74),
|
||||||
|
mockAction.apply(72),
|
||||||
|
mockAction.apply(70)
|
||||||
|
), page.getContent());
|
||||||
|
|
||||||
|
page = this.storage.getLogPage(ActionFilters.source(sourceUuid), new PageParameters(500, 1));
|
||||||
|
assertEquals(150, page.getContent().size());
|
||||||
|
|
||||||
|
page = this.storage.getLogPage(ActionFilters.all(), new PageParameters(500, 1));
|
||||||
|
assertEquals(320, page.getContent().size());
|
||||||
|
|
||||||
|
page = this.storage.getLogPage(ActionFilters.user(targetUuid), new PageParameters(500, 1));
|
||||||
|
assertEquals(300, page.getContent().size());
|
||||||
|
|
||||||
|
page = this.storage.getLogPage(ActionFilters.group("test_group"), new PageParameters(10, 1));
|
||||||
|
assertEquals(5, page.getContent().size());
|
||||||
|
|
||||||
|
page = this.storage.getLogPage(ActionFilters.track("test_track"), new PageParameters(10, 1));
|
||||||
|
assertEquals(5, page.getContent().size());
|
||||||
|
|
||||||
|
page = this.storage.getLogPage(ActionFilters.search("hello"), new PageParameters(500, 1));
|
||||||
|
assertEquals(300, page.getContent().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSavePlayerData() throws Exception {
|
||||||
|
UUID uniqueId = UUID.randomUUID();
|
||||||
|
|
||||||
|
// clean insert
|
||||||
|
PlayerSaveResult r1 = this.storage.savePlayerData(uniqueId, "Player1");
|
||||||
|
assertEquals(ImmutableSet.of(Outcome.CLEAN_INSERT), r1.getOutcomes());
|
||||||
|
assertNull(r1.getOtherUniqueIds());
|
||||||
|
assertNull(r1.getPreviousUsername());
|
||||||
|
|
||||||
|
// no change expected
|
||||||
|
PlayerSaveResult r2 = this.storage.savePlayerData(uniqueId, "Player1");
|
||||||
|
assertEquals(ImmutableSet.of(Outcome.NO_CHANGE), r2.getOutcomes());
|
||||||
|
assertNull(r2.getOtherUniqueIds());
|
||||||
|
assertNull(r2.getPreviousUsername());
|
||||||
|
|
||||||
|
// changed username
|
||||||
|
PlayerSaveResult r3 = this.storage.savePlayerData(uniqueId, "Player2");
|
||||||
|
assertEquals(ImmutableSet.of(Outcome.USERNAME_UPDATED), r3.getOutcomes());
|
||||||
|
assertNull(r3.getOtherUniqueIds());
|
||||||
|
assertTrue("Player1".equalsIgnoreCase(r3.getPreviousUsername()));
|
||||||
|
|
||||||
|
// changed uuid
|
||||||
|
UUID newUniqueId = UUID.randomUUID();
|
||||||
|
PlayerSaveResult r4 = this.storage.savePlayerData(newUniqueId, "Player2");
|
||||||
|
assertEquals(ImmutableSet.of(Outcome.CLEAN_INSERT, Outcome.OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME), r4.getOutcomes());
|
||||||
|
assertNotNull(r4.getOtherUniqueIds());
|
||||||
|
assertEquals(ImmutableSet.of(uniqueId), r4.getOtherUniqueIds());
|
||||||
|
assertNull(r2.getPreviousUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPlayerUniqueIdAndName() throws Exception {
|
||||||
|
UUID uniqueId = UUID.randomUUID();
|
||||||
|
String username = "Player1";
|
||||||
|
|
||||||
|
this.storage.savePlayerData(uniqueId, username);
|
||||||
|
|
||||||
|
assertEquals(uniqueId, this.storage.getPlayerUniqueId("Player1"));
|
||||||
|
assertTrue(username.equalsIgnoreCase(this.storage.getPlayerName(uniqueId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPlayerUniqueIdAndNameNull() throws Exception {
|
||||||
|
assertNull(this.storage.getPlayerUniqueId("Player1"));
|
||||||
|
assertNull(this.storage.getPlayerName(UUID.randomUUID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveAndLoadGroup() throws Exception {
|
||||||
|
StandardGroupManager groupManager = new StandardGroupManager(this.plugin);
|
||||||
|
|
||||||
|
//noinspection unchecked,rawtypes
|
||||||
|
lenient().when(this.plugin.getGroupManager()).thenReturn((GroupManager) groupManager);
|
||||||
|
|
||||||
|
Group group = this.storage.createAndLoadGroup("test");
|
||||||
|
|
||||||
|
group.normalData().add(Permission.builder()
|
||||||
|
.permission("test.1")
|
||||||
|
.withContext("server", "test")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
group.normalData().add(Permission.builder()
|
||||||
|
.permission("test.2")
|
||||||
|
.withContext("world", "test")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
group.normalData().add(Permission.builder()
|
||||||
|
.permission("test.3")
|
||||||
|
.expiry(1, TimeUnit.HOURS)
|
||||||
|
.withContext("server", "test")
|
||||||
|
.withContext("world", "test")
|
||||||
|
.withContext("hello", "test")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<Node> nodes = group.normalData().asSet();
|
||||||
|
assertEquals(3, nodes.size());
|
||||||
|
|
||||||
|
this.storage.saveGroup(group);
|
||||||
|
groupManager.unload("test");
|
||||||
|
|
||||||
|
Group loaded = this.storage.loadGroup("test").orElse(null);
|
||||||
|
assertNotNull(loaded);
|
||||||
|
assertNotSame(group, loaded);
|
||||||
|
assertEquals(nodes, loaded.normalData().asSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveAndDeleteUser() throws Exception {
|
||||||
|
StandardUserManager userManager = new StandardUserManager(this.plugin);
|
||||||
|
|
||||||
|
//noinspection unchecked,rawtypes
|
||||||
|
when(this.plugin.getUserManager()).thenReturn((UserManager) userManager);
|
||||||
|
|
||||||
|
UUID exampleUniqueId = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5");
|
||||||
|
String exampleUsername = "Notch";
|
||||||
|
PermissionNode examplePermission = Permission.builder()
|
||||||
|
.permission("test.1")
|
||||||
|
.withContext("server", "test")
|
||||||
|
.build();
|
||||||
|
InheritanceNode defaultGroupNode = Inheritance.builder(GroupManager.DEFAULT_GROUP_NAME).build();
|
||||||
|
|
||||||
|
// create a default user, assert that is doesn't appear in unique users list
|
||||||
|
this.storage.savePlayerData(exampleUniqueId, exampleUsername);
|
||||||
|
assertFalse(this.storage.getUniqueUsers().contains(exampleUniqueId));
|
||||||
|
|
||||||
|
// give the user a node, assert that it does appear in unique users list
|
||||||
|
User user = this.storage.loadUser(exampleUniqueId, exampleUsername);
|
||||||
|
user.setNode(DataType.NORMAL, examplePermission, true);
|
||||||
|
this.storage.saveUser(user);
|
||||||
|
assertTrue(this.storage.getUniqueUsers().contains(exampleUniqueId));
|
||||||
|
|
||||||
|
// clear all nodes (reset to default) and assert that it does not appear in unique users list
|
||||||
|
user.clearNodes(DataType.NORMAL, null, true);
|
||||||
|
this.storage.saveUser(user);
|
||||||
|
assertFalse(this.storage.getUniqueUsers().contains(exampleUniqueId));
|
||||||
|
assertEquals(ImmutableSet.of(defaultGroupNode), user.normalData().asSet());
|
||||||
|
|
||||||
|
// give it a node again, assert that it shows as a unique user
|
||||||
|
user.setNode(DataType.NORMAL, examplePermission, true);
|
||||||
|
this.storage.saveUser(user);
|
||||||
|
assertTrue(this.storage.getUniqueUsers().contains(exampleUniqueId));
|
||||||
|
assertEquals(ImmutableSet.of(defaultGroupNode, examplePermission), user.normalData().asSet());
|
||||||
|
|
||||||
|
// reload user data from the db and assert that it is unchanged
|
||||||
|
user = this.storage.loadUser(exampleUniqueId, exampleUsername);
|
||||||
|
assertEquals(ImmutableSet.of(defaultGroupNode, examplePermission), user.normalData().asSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,116 @@
|
|||||||
|
package me.lucko.luckperms.common.storage;
|
||||||
|
|
||||||
|
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.StorageImplementation;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.file.CombinedConfigurateStorage;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.file.SeparatedConfigurateStorage;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.file.loader.HoconLoader;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.file.loader.JsonLoader;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.file.loader.TomlLoader;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.file.loader.YamlLoader;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
|
||||||
|
public class ConfigurateStorageTest {
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SeparatedYaml extends AbstractStorageTest {
|
||||||
|
@TempDir
|
||||||
|
private Path directory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
lenient().when(this.bootstrap.getDataDirectory()).thenReturn(this.directory);
|
||||||
|
return new SeparatedConfigurateStorage(plugin, "YAML", new YamlLoader(), ".yml", "yaml-storage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SeparatedJson extends AbstractStorageTest {
|
||||||
|
@TempDir
|
||||||
|
private Path directory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
lenient().when(this.bootstrap.getDataDirectory()).thenReturn(this.directory);
|
||||||
|
return new SeparatedConfigurateStorage(plugin, "JSON", new JsonLoader(), ".json", "json-storage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SeparatedHocon extends AbstractStorageTest {
|
||||||
|
@TempDir
|
||||||
|
private Path directory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
lenient().when(this.bootstrap.getDataDirectory()).thenReturn(this.directory);
|
||||||
|
return new SeparatedConfigurateStorage(plugin, "HOCON", new HoconLoader(), ".conf", "hocon-storage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class SeparatedToml extends AbstractStorageTest {
|
||||||
|
@TempDir
|
||||||
|
private Path directory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
lenient().when(this.bootstrap.getDataDirectory()).thenReturn(this.directory);
|
||||||
|
return new SeparatedConfigurateStorage(plugin, "TOML", new TomlLoader(), ".toml", "toml-storage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CombinedYaml extends AbstractStorageTest {
|
||||||
|
@TempDir
|
||||||
|
private Path directory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
lenient().when(this.bootstrap.getDataDirectory()).thenReturn(this.directory);
|
||||||
|
return new CombinedConfigurateStorage(plugin, "YAML", new YamlLoader(), ".yml", "yaml-storage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CombinedJson extends AbstractStorageTest {
|
||||||
|
@TempDir
|
||||||
|
private Path directory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
lenient().when(this.bootstrap.getDataDirectory()).thenReturn(this.directory);
|
||||||
|
return new CombinedConfigurateStorage(plugin, "JSON", new JsonLoader(), ".json", "json-storage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CombinedHocon extends AbstractStorageTest {
|
||||||
|
@TempDir
|
||||||
|
private Path directory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
lenient().when(this.bootstrap.getDataDirectory()).thenReturn(this.directory);
|
||||||
|
return new CombinedConfigurateStorage(plugin, "HOCON", new HoconLoader(), ".conf", "hocon-storage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CombinedToml extends AbstractStorageTest {
|
||||||
|
@TempDir
|
||||||
|
private Path directory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
lenient().when(this.bootstrap.getDataDirectory()).thenReturn(this.directory);
|
||||||
|
return new CombinedConfigurateStorage(plugin, "TOML", new TomlLoader(), ".toml", "toml-storage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
package me.lucko.luckperms.common.storage;
|
||||||
|
|
||||||
|
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.StorageImplementation;
|
||||||
|
import me.lucko.luckperms.common.storage.implementation.mongodb.MongoStorage;
|
||||||
|
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
|
||||||
|
//@Tag("docker")
|
||||||
|
public class MongoStorageTest extends AbstractStorageTest {
|
||||||
|
|
||||||
|
private final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("mongo"))
|
||||||
|
.withExposedPorts(27017);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
this.container.start();
|
||||||
|
String host = this.container.getHost();
|
||||||
|
Integer port = this.container.getFirstMappedPort();
|
||||||
|
|
||||||
|
StorageCredentials credentials = new StorageCredentials(
|
||||||
|
host + ":" + port,
|
||||||
|
"minecraft",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
return new MongoStorage(plugin, credentials, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanupResources() {
|
||||||
|
this.container.stop();
|
||||||
|
}
|
||||||
|
}
|
@@ -25,316 +25,22 @@
|
|||||||
|
|
||||||
package me.lucko.luckperms.common.storage;
|
package me.lucko.luckperms.common.storage;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
|
|
||||||
import me.lucko.luckperms.common.actionlog.Log;
|
|
||||||
import me.lucko.luckperms.common.actionlog.LogPage;
|
|
||||||
import me.lucko.luckperms.common.actionlog.LoggedAction;
|
|
||||||
import me.lucko.luckperms.common.filter.PageParameters;
|
|
||||||
import me.lucko.luckperms.common.config.ConfigKeys;
|
|
||||||
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
|
|
||||||
import me.lucko.luckperms.common.event.EventDispatcher;
|
|
||||||
import me.lucko.luckperms.common.model.Group;
|
|
||||||
import me.lucko.luckperms.common.model.PrimaryGroupHolder;
|
|
||||||
import me.lucko.luckperms.common.model.User;
|
|
||||||
import me.lucko.luckperms.common.model.manager.group.GroupManager;
|
|
||||||
import me.lucko.luckperms.common.model.manager.group.StandardGroupManager;
|
|
||||||
import me.lucko.luckperms.common.model.manager.user.StandardUserManager;
|
|
||||||
import me.lucko.luckperms.common.model.manager.user.UserManager;
|
|
||||||
import me.lucko.luckperms.common.node.types.Inheritance;
|
|
||||||
import me.lucko.luckperms.common.node.types.Permission;
|
|
||||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||||
import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap;
|
import me.lucko.luckperms.common.storage.implementation.StorageImplementation;
|
||||||
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
|
|
||||||
import me.lucko.luckperms.common.storage.implementation.sql.SqlStorage;
|
import me.lucko.luckperms.common.storage.implementation.sql.SqlStorage;
|
||||||
import me.lucko.luckperms.common.storage.implementation.sql.connection.ConnectionFactory;
|
import me.lucko.luckperms.common.storage.implementation.sql.connection.ConnectionFactory;
|
||||||
import me.lucko.luckperms.common.storage.implementation.sql.connection.file.NonClosableConnection;
|
import me.lucko.luckperms.common.storage.implementation.sql.connection.file.NonClosableConnection;
|
||||||
import net.luckperms.api.actionlog.Action;
|
|
||||||
import net.luckperms.api.model.PlayerSaveResult;
|
|
||||||
import net.luckperms.api.model.PlayerSaveResult.Outcome;
|
|
||||||
import net.luckperms.api.model.data.DataType;
|
|
||||||
import net.luckperms.api.node.Node;
|
|
||||||
import net.luckperms.api.node.types.InheritanceNode;
|
|
||||||
import net.luckperms.api.node.types.PermissionNode;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
public class SqlStorageTest extends AbstractStorageTest {
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.mockito.AdditionalAnswers.answer;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.Mockito.lenient;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@Override
|
||||||
public class SqlStorageTest {
|
protected StorageImplementation makeStorage(LuckPermsPlugin plugin) throws Exception {
|
||||||
|
return new SqlStorage(plugin, new TestH2ConnectionFactory(), "luckperms_");
|
||||||
@Mock private LuckPermsPlugin plugin;
|
|
||||||
@Mock private LuckPermsBootstrap bootstrap;
|
|
||||||
@Mock private LuckPermsConfiguration configuration;
|
|
||||||
|
|
||||||
private SqlStorage storage;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setupMocksAndDatabase() throws Exception {
|
|
||||||
lenient().when(this.plugin.getBootstrap()).thenReturn(this.bootstrap);
|
|
||||||
lenient().when(this.plugin.getConfiguration()).thenReturn(this.configuration);
|
|
||||||
lenient().when(this.plugin.getEventDispatcher()).thenReturn(mock(EventDispatcher.class));
|
|
||||||
lenient().when(this.bootstrap.getScheduler()).thenReturn(mock(SchedulerAdapter.class));
|
|
||||||
lenient().when(this.configuration.get(ConfigKeys.PRIMARY_GROUP_CALCULATION)).thenReturn(PrimaryGroupHolder.AllParentsByWeight::new);
|
|
||||||
lenient().when(this.configuration.get(ConfigKeys.PRIMARY_GROUP_CALCULATION_METHOD)).thenReturn("parents-by-weight");
|
|
||||||
lenient().when(this.bootstrap.getResourceStream(anyString()))
|
|
||||||
.then(answer((String path) -> SqlStorageTest.class.getClassLoader().getResourceAsStream(path)));
|
|
||||||
lenient().when(this.plugin.getEventDispatcher()).thenReturn(mock(EventDispatcher.class));
|
|
||||||
|
|
||||||
this.storage = new SqlStorage(this.plugin, new TestH2ConnectionFactory(), "luckperms_");
|
|
||||||
this.storage.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void shutdownDatabase() {
|
|
||||||
this.storage.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testActionLog() throws Exception {
|
|
||||||
LoggedAction action = LoggedAction.build()
|
|
||||||
.source(UUID.randomUUID())
|
|
||||||
.sourceName("Test Source")
|
|
||||||
.targetType(Action.Target.Type.USER)
|
|
||||||
.target(UUID.randomUUID())
|
|
||||||
.targetName("Test Target")
|
|
||||||
.description("hello 123 hello 123")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
this.storage.logAction(action);
|
|
||||||
|
|
||||||
Log log = this.storage.getLog();
|
|
||||||
assertEquals(1, log.getContent().size());
|
|
||||||
assertEquals(action, log.getContent().first());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testActionLogPage() throws Exception {
|
|
||||||
UUID sourceUuid = UUID.randomUUID();
|
|
||||||
UUID targetUuid = UUID.randomUUID();
|
|
||||||
|
|
||||||
Function<Integer, LoggedAction> mockAction = i -> LoggedAction.build()
|
|
||||||
.source(i % 2 == 0 ? sourceUuid : UUID.randomUUID())
|
|
||||||
.sourceName("Test Source")
|
|
||||||
.targetType(Action.Target.Type.USER)
|
|
||||||
.target(targetUuid)
|
|
||||||
.targetName("Test Target")
|
|
||||||
.description("hello " + i)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
this.storage.logAction(mockAction.apply(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
this.storage.logAction(LoggedAction.build()
|
|
||||||
.source(UUID.randomUUID())
|
|
||||||
.sourceName("Test Source")
|
|
||||||
.targetType(Action.Target.Type.GROUP)
|
|
||||||
.targetName(i % 2 == 0 ? "test_group" : "dummy")
|
|
||||||
.description("group test " + i)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
this.storage.logAction(LoggedAction.build()
|
|
||||||
.source(UUID.randomUUID())
|
|
||||||
.sourceName("Test Source")
|
|
||||||
.targetType(Action.Target.Type.TRACK)
|
|
||||||
.targetName(i % 2 == 0 ? "test_track" : "dummy")
|
|
||||||
.description("track test " + i)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
LogPage page = this.storage.getLogPage(ActionFilters.source(sourceUuid), new PageParameters(5, 1));
|
|
||||||
assertEquals(ImmutableList.of(
|
|
||||||
mockAction.apply(98),
|
|
||||||
mockAction.apply(96),
|
|
||||||
mockAction.apply(94),
|
|
||||||
mockAction.apply(92),
|
|
||||||
mockAction.apply(90)
|
|
||||||
), page.getContent());
|
|
||||||
|
|
||||||
page = this.storage.getLogPage(ActionFilters.source(sourceUuid), new PageParameters(5, 3));
|
|
||||||
assertEquals(ImmutableList.of(
|
|
||||||
mockAction.apply(78),
|
|
||||||
mockAction.apply(76),
|
|
||||||
mockAction.apply(74),
|
|
||||||
mockAction.apply(72),
|
|
||||||
mockAction.apply(70)
|
|
||||||
), page.getContent());
|
|
||||||
|
|
||||||
page = this.storage.getLogPage(ActionFilters.source(sourceUuid), new PageParameters(200, 1));
|
|
||||||
assertEquals(50, page.getContent().size());
|
|
||||||
|
|
||||||
page = this.storage.getLogPage(ActionFilters.all(), new PageParameters(200, 1));
|
|
||||||
assertEquals(120, page.getContent().size());
|
|
||||||
|
|
||||||
page = this.storage.getLogPage(ActionFilters.user(targetUuid), new PageParameters(200, 1));
|
|
||||||
assertEquals(100, page.getContent().size());
|
|
||||||
|
|
||||||
page = this.storage.getLogPage(ActionFilters.group("test_group"), new PageParameters(10, 1));
|
|
||||||
assertEquals(5, page.getContent().size());
|
|
||||||
|
|
||||||
page = this.storage.getLogPage(ActionFilters.track("test_track"), new PageParameters(10, 1));
|
|
||||||
assertEquals(5, page.getContent().size());
|
|
||||||
|
|
||||||
page = this.storage.getLogPage(ActionFilters.search("hello"), new PageParameters(200, 1));
|
|
||||||
assertEquals(100, page.getContent().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSavePlayerData() throws Exception {
|
|
||||||
UUID uniqueId = UUID.randomUUID();
|
|
||||||
|
|
||||||
// clean insert
|
|
||||||
PlayerSaveResult r1 = this.storage.savePlayerData(uniqueId, "Player1");
|
|
||||||
assertEquals(ImmutableSet.of(Outcome.CLEAN_INSERT), r1.getOutcomes());
|
|
||||||
assertNull(r1.getOtherUniqueIds());
|
|
||||||
assertNull(r1.getPreviousUsername());
|
|
||||||
|
|
||||||
// no change expected
|
|
||||||
PlayerSaveResult r2 = this.storage.savePlayerData(uniqueId, "Player1");
|
|
||||||
assertEquals(ImmutableSet.of(Outcome.NO_CHANGE), r2.getOutcomes());
|
|
||||||
assertNull(r2.getOtherUniqueIds());
|
|
||||||
assertNull(r2.getPreviousUsername());
|
|
||||||
|
|
||||||
// changed username
|
|
||||||
PlayerSaveResult r3 = this.storage.savePlayerData(uniqueId, "Player2");
|
|
||||||
assertEquals(ImmutableSet.of(Outcome.USERNAME_UPDATED), r3.getOutcomes());
|
|
||||||
assertNull(r3.getOtherUniqueIds());
|
|
||||||
assertTrue("Player1".equalsIgnoreCase(r3.getPreviousUsername()));
|
|
||||||
|
|
||||||
// changed uuid
|
|
||||||
UUID newUniqueId = UUID.randomUUID();
|
|
||||||
PlayerSaveResult r4 = this.storage.savePlayerData(newUniqueId, "Player2");
|
|
||||||
assertEquals(ImmutableSet.of(Outcome.CLEAN_INSERT, Outcome.OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME), r4.getOutcomes());
|
|
||||||
assertNotNull(r4.getOtherUniqueIds());
|
|
||||||
assertEquals(ImmutableSet.of(uniqueId), r4.getOtherUniqueIds());
|
|
||||||
assertNull(r2.getPreviousUsername());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetPlayerUniqueIdAndName() throws Exception {
|
|
||||||
UUID uniqueId = UUID.randomUUID();
|
|
||||||
String username = "Player1";
|
|
||||||
|
|
||||||
this.storage.savePlayerData(uniqueId, username);
|
|
||||||
|
|
||||||
assertEquals(uniqueId, this.storage.getPlayerUniqueId("Player1"));
|
|
||||||
assertTrue(username.equalsIgnoreCase(this.storage.getPlayerName(uniqueId)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetPlayerUniqueIdAndNameNull() throws Exception {
|
|
||||||
assertNull(this.storage.getPlayerUniqueId("Player1"));
|
|
||||||
assertNull(this.storage.getPlayerName(UUID.randomUUID()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSaveAndLoadGroup() throws Exception {
|
|
||||||
StandardGroupManager groupManager = new StandardGroupManager(this.plugin);
|
|
||||||
|
|
||||||
//noinspection unchecked,rawtypes
|
|
||||||
lenient().when(this.plugin.getGroupManager()).thenReturn((GroupManager) groupManager);
|
|
||||||
|
|
||||||
Group group = this.storage.createAndLoadGroup("test");
|
|
||||||
|
|
||||||
group.normalData().add(Permission.builder()
|
|
||||||
.permission("test.1")
|
|
||||||
.withContext("server", "test")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
group.normalData().add(Permission.builder()
|
|
||||||
.permission("test.2")
|
|
||||||
.withContext("world", "test")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
group.normalData().add(Permission.builder()
|
|
||||||
.permission("test.3")
|
|
||||||
.expiry(1, TimeUnit.HOURS)
|
|
||||||
.withContext("server", "test")
|
|
||||||
.withContext("world", "test")
|
|
||||||
.withContext("hello", "test")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
|
|
||||||
Set<Node> nodes = group.normalData().asSet();
|
|
||||||
assertEquals(3, nodes.size());
|
|
||||||
|
|
||||||
this.storage.saveGroup(group);
|
|
||||||
groupManager.unload("test");
|
|
||||||
|
|
||||||
Group loaded = this.storage.loadGroup("test").orElse(null);
|
|
||||||
assertNotNull(loaded);
|
|
||||||
assertNotSame(group, loaded);
|
|
||||||
assertEquals(nodes, loaded.normalData().asSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSaveAndDeleteUser() throws SQLException {
|
|
||||||
StandardUserManager userManager = new StandardUserManager(this.plugin);
|
|
||||||
|
|
||||||
//noinspection unchecked,rawtypes
|
|
||||||
when(this.plugin.getUserManager()).thenReturn((UserManager) userManager);
|
|
||||||
|
|
||||||
UUID exampleUniqueId = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5");
|
|
||||||
String exampleUsername = "Notch";
|
|
||||||
PermissionNode examplePermission = Permission.builder()
|
|
||||||
.permission("test.1")
|
|
||||||
.withContext("server", "test")
|
|
||||||
.build();
|
|
||||||
InheritanceNode defaultGroupNode = Inheritance.builder(GroupManager.DEFAULT_GROUP_NAME).build();
|
|
||||||
|
|
||||||
// create a default user, assert that is doesn't appear in unique users list
|
|
||||||
this.storage.savePlayerData(exampleUniqueId, exampleUsername);
|
|
||||||
assertFalse(this.storage.getUniqueUsers().contains(exampleUniqueId));
|
|
||||||
|
|
||||||
// give the user a node, assert that it does appear in unique users list
|
|
||||||
User user = this.storage.loadUser(exampleUniqueId, exampleUsername);
|
|
||||||
user.setNode(DataType.NORMAL, examplePermission, true);
|
|
||||||
this.storage.saveUser(user);
|
|
||||||
assertTrue(this.storage.getUniqueUsers().contains(exampleUniqueId));
|
|
||||||
|
|
||||||
// clear all nodes (reset to default) and assert that it does not appear in unique users list
|
|
||||||
user.clearNodes(DataType.NORMAL, null, true);
|
|
||||||
this.storage.saveUser(user);
|
|
||||||
assertFalse(this.storage.getUniqueUsers().contains(exampleUniqueId));
|
|
||||||
assertEquals(ImmutableSet.of(defaultGroupNode), user.normalData().asSet());
|
|
||||||
|
|
||||||
// give it a node again, assert that it shows as a unique user
|
|
||||||
user.setNode(DataType.NORMAL, examplePermission, true);
|
|
||||||
this.storage.saveUser(user);
|
|
||||||
assertTrue(this.storage.getUniqueUsers().contains(exampleUniqueId));
|
|
||||||
assertEquals(ImmutableSet.of(defaultGroupNode, examplePermission), user.normalData().asSet());
|
|
||||||
|
|
||||||
// reload user data from the db and assert that it is unchanged
|
|
||||||
user = this.storage.loadUser(exampleUniqueId, exampleUsername);
|
|
||||||
assertEquals(ImmutableSet.of(defaultGroupNode, examplePermission), user.normalData().asSet());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TestH2ConnectionFactory implements ConnectionFactory {
|
private static class TestH2ConnectionFactory implements ConnectionFactory {
|
||||||
@@ -379,5 +85,4 @@ public class SqlStorageTest {
|
|||||||
this.connection.shutdown();
|
this.connection.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user