Add more unit tests (#3555)

This commit is contained in:
lucko 2023-01-05 21:32:50 +00:00 committed by GitHub
parent 4d726f4e8b
commit 3ecdeacaf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2641 additions and 243 deletions

View File

@ -9,8 +9,13 @@ test {
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
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.mockito:mockito-core:4.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:4.11.0'
testImplementation 'com.h2database:h2:2.1.214'
api project(':api')
api 'org.checkerframework:checker-qual:3.12.0'

View File

@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* A simple pagination utility
@ -93,6 +94,19 @@ public class Paginated<T> {
public String toString() {
return this.position + ": " + this.value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Entry)) return false;
Entry<?> entry = (Entry<?>) o;
return this.position == entry.position && Objects.equals(this.value, entry.value);
}
@Override
public int hashCode() {
return Objects.hash(this.position, this.value);
}
}
}

View File

@ -0,0 +1,252 @@
/*
* 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.calculator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import me.lucko.luckperms.common.cacheddata.CacheMetadata;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.calculator.processor.AbstractOverrideWildcardProcessor;
import me.lucko.luckperms.common.calculator.processor.DirectProcessor;
import me.lucko.luckperms.common.calculator.processor.PermissionProcessor;
import me.lucko.luckperms.common.calculator.processor.RegexProcessor;
import me.lucko.luckperms.common.calculator.processor.SpongeWildcardProcessor;
import me.lucko.luckperms.common.calculator.processor.WildcardProcessor;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.query.QueryOptionsImpl;
import me.lucko.luckperms.common.treeview.PermissionRegistry;
import me.lucko.luckperms.common.verbose.VerboseCheckTarget;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import net.luckperms.api.node.Node;
import net.luckperms.api.util.Tristate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Map;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class)
public class PermissionCalculatorTest {
private static final CacheMetadata MOCK_METADATA = new CacheMetadata(
HolderType.GROUP,
VerboseCheckTarget.of(VerboseCheckTarget.GROUP_TYPE, "test"),
QueryOptionsImpl.DEFAULT_CONTEXTUAL
);
private static final Map<String, Node> EXAMPLE_PERMISSIONS = ImmutableMap.<String, Boolean>builder()
// direct
.put("test.node1", true)
.put("test.node2", false)
// wildcard
.put("one.two.three.four", true)
.put("one.two.three.*", false)
.put("one.two.three", true)
.put("one.two.*", false)
.put("one.two", true)
.put("one.*", false)
.put("one", true)
.put("*", false)
// regex
.put("r=hello\\d+", true)
.put("R=rege(x(es)?|xps?)[1-5]", false)
// override
.put("overridetest.*", true)
.build().entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> NodeBuilders.determineMostApplicable(e.getKey()).value(e.getValue()).build()
));
@Mock private LuckPermsPlugin plugin;
@BeforeEach
public void setupMocks() {
lenient().when(this.plugin.getVerboseHandler()).thenReturn(mock(VerboseHandler.class));
lenient().when(this.plugin.getPermissionRegistry()).thenReturn(mock(PermissionRegistry.class));
}
private PermissionCalculator createCalculator(PermissionProcessor... processors) {
return new PermissionCalculator(this.plugin, MOCK_METADATA, ImmutableList.copyOf(processors));
}
@ParameterizedTest
@CsvSource({
"test, UNDEFINED",
"test.node1, TRUE",
"test.node2, FALSE"
})
public void testDirect(String node, Tristate expected) {
PermissionCalculator calculator = createCalculator(new DirectProcessor());
calculator.setSourcePermissions(EXAMPLE_PERMISSIONS);
TristateResult result = calculator.checkPermission(node, CheckOrigin.INTERNAL);
assertEquals(expected, result.result());
assertNull(result.overriddenResult());
if (expected != Tristate.UNDEFINED) {
assertNotNull(result.node());
assertSame(DirectProcessor.class, result.processorClass());
} else {
assertNull(result.node());
assertNull(result.processorClass());
}
}
@ParameterizedTest
@CsvSource({
"one.two.three.four, true, direct",
"one.two.three.test, false, wildcard",
"one.two.three.*, false, direct",
"one.two.three, true, direct",
"one.two.test, false, wildcard",
"one.two.*, false, direct",
"one.two, true, direct",
"one.test, false, wildcard",
"one.*, false, direct",
"one, true, direct",
"test, false, wildcard",
"*, false, direct",
})
public void testWildcard(String node, boolean expected, String type) {
PermissionCalculator calculator = createCalculator(new DirectProcessor(), new WildcardProcessor());
calculator.setSourcePermissions(EXAMPLE_PERMISSIONS);
TristateResult result = calculator.checkPermission(node, CheckOrigin.INTERNAL);
assertEquals(Tristate.of(expected), result.result());
assertNull(result.overriddenResult());
assertNotNull(result.node());
if (type.equals("direct")) {
assertSame(DirectProcessor.class, result.processorClass());
} else if (type.equals("wildcard")) {
assertSame(WildcardProcessor.class, result.processorClass());
} else {
throw new AssertionError();
}
}
@ParameterizedTest
@CsvSource({
"one, true, direct",
"one.test, true, wildcard",
"one.two, true, direct",
"one.two.test, true, wildcard",
})
public void testSpongeWildcard(String node, boolean expected, String type) {
PermissionCalculator calculator = createCalculator(new DirectProcessor(), new SpongeWildcardProcessor());
calculator.setSourcePermissions(EXAMPLE_PERMISSIONS);
TristateResult result = calculator.checkPermission(node, CheckOrigin.INTERNAL);
assertEquals(Tristate.of(expected), result.result());
assertNull(result.overriddenResult());
assertNotNull(result.node());
if (type.equals("direct")) {
assertSame(DirectProcessor.class, result.processorClass());
} else if (type.equals("wildcard")) {
assertSame(SpongeWildcardProcessor.class, result.processorClass());
} else {
throw new AssertionError();
}
}
@ParameterizedTest
@CsvSource({
"hello, UNDEFINED",
"hello1, TRUE",
"hello123, TRUE",
"helloo, UNDEFINED",
"regex1, FALSE",
"regexes2, FALSE",
"regexp3, FALSE",
"regexps4, FALSE",
})
public void testRegex(String node, Tristate expected) {
PermissionCalculator calculator = createCalculator(new DirectProcessor(), new RegexProcessor());
calculator.setSourcePermissions(EXAMPLE_PERMISSIONS);
TristateResult result = calculator.checkPermission(node, CheckOrigin.INTERNAL);
assertEquals(expected, result.result());
assertNull(result.overriddenResult());
if (expected != Tristate.UNDEFINED) {
assertNotNull(result.node());
assertSame(RegexProcessor.class, result.processorClass());
} else {
assertNull(result.node());
assertNull(result.processorClass());
}
}
@Test
public void testOverrideWildcard() {
AbstractOverrideWildcardProcessor overrideProcessor = new AbstractOverrideWildcardProcessor(true) {
@Override
protected TristateResult hasPermission(String permission) {
if (permission.equals("overridetest.test")) {
return new TristateResult.Factory(AbstractOverrideWildcardProcessor.class)
.result(Tristate.FALSE);
}
return TristateResult.UNDEFINED;
}
};
PermissionCalculator calculator = createCalculator(new DirectProcessor(), new WildcardProcessor(), overrideProcessor);
calculator.setSourcePermissions(EXAMPLE_PERMISSIONS);
TristateResult result = calculator.checkPermission("overridetest.test", CheckOrigin.INTERNAL);
assertEquals(Tristate.FALSE, result.result());
assertSame(AbstractOverrideWildcardProcessor.class, result.processorClass());
TristateResult overriddenResult = result.overriddenResult();
assertNotNull(overriddenResult);
assertSame(WildcardProcessor.class, overriddenResult.processorClass());
}
}

View File

@ -25,7 +25,6 @@
package me.lucko.luckperms.common.context;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import net.luckperms.api.context.Context;
@ -33,19 +32,20 @@ import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ImmutableContextSet;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ContextSetTest {
public class ImmutableContextSetTest {
@Test
public void testImmutableBuilder() {
List<Consumer<ImmutableContextSet.Builder>> tests = ImmutableList.of(
private static Stream<Consumer<ImmutableContextSet.Builder>> testBuilder() {
return Stream.of(
builder -> {
builder.add("test", "a");
builder.add("test", "b");
@ -73,29 +73,31 @@ public class ContextSetTest {
builder.add("test", "c");
}
);
}
for (Consumer<ImmutableContextSet.Builder> action : tests) {
ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl();
action.accept(builder);
ImmutableContextSet set = builder.build();
@ParameterizedTest
@MethodSource
public void testBuilder(Consumer<ImmutableContextSet.Builder> action) {
ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl();
action.accept(builder);
ImmutableContextSet set = builder.build();
ImmutableSet<Context> expected = ImmutableSet.of(
new ContextImpl("test", "a"),
new ContextImpl("test", "b"),
new ContextImpl("test", "c")
);
ImmutableSet<Context> expected = ImmutableSet.of(
new ContextImpl("test", "a"),
new ContextImpl("test", "b"),
new ContextImpl("test", "c")
);
assertEquals(expected, set.toSet());
assertEquals(3, set.size());
assertEquals(expected, set.toSet());
assertEquals(3, set.size());
assertTrue(set.contains("test", "a"));
assertTrue(set.contains("test", "b"));
assertTrue(set.contains("test", "c"));
}
assertTrue(set.contains("test", "a"));
assertTrue(set.contains("test", "b"));
assertTrue(set.contains("test", "c"));
}
@Test
public void testImmutableContains() {
public void testContains() {
ImmutableContextSet set = new ImmutableContextSetImpl.BuilderImpl()
.add("test", "a")
.add("test", "a")
@ -111,8 +113,18 @@ public class ContextSetTest {
assertFalse(set.containsKey("aaa"));
}
@Test
public void testImmutableContainsAll() {
private static Stream<Consumer<ImmutableContextSet.Builder>> testContainsAllTrue() {
return Stream.of(
builder -> builder.add("aaa", "a").add("bbb", "a"),
builder -> builder.add("aaa", "b").add("bbb", "a"),
builder -> builder.add("aaa", "c").add("bbb", "a"),
builder -> builder.add("aaa", "c").add("bbb", "b")
);
}
@ParameterizedTest
@MethodSource
public void testContainsAllTrue(Consumer<ImmutableContextSet.Builder> setup) {
ImmutableContextSetImpl set = (ImmutableContextSetImpl) new ImmutableContextSetImpl.BuilderImpl()
.add("aaa", "a")
.add("aaa", "b")
@ -121,38 +133,43 @@ public class ContextSetTest {
.add("bbb", "b")
.build();
List<Consumer<ImmutableContextSet.Builder>> trueTests = ImmutableList.of(
builder -> builder.add("aaa", "a").add("bbb", "a"),
builder -> builder.add("aaa", "b").add("bbb", "a"),
builder -> builder.add("aaa", "c").add("bbb", "a"),
builder -> builder.add("aaa", "c").add("bbb", "b")
ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl();
setup.accept(builder);
assertTrue(set.otherContainsAll(
builder.build(),
ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY)
);
}
for (Consumer<ImmutableContextSet.Builder> test : trueTests) {
ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl();
test.accept(builder);
assertTrue(set.otherContainsAll(
builder.build(),
ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY)
);
}
List<Consumer<ImmutableContextSet.Builder>> falseTests = ImmutableList.of(
private static Stream<Consumer<ImmutableContextSet.Builder>> testContainsAllFalse() {
return Stream.of(
builder -> builder.add("aaa", "a").add("bbb", "z"),
builder -> builder.add("aaa", "b").add("bbb", "z"),
builder -> builder.add("aaa", "b"),
builder -> builder.add("aaa", "c"),
builder -> {}
);
}
for (Consumer<ImmutableContextSet.Builder> test : falseTests) {
ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl();
test.accept(builder);
assertFalse(set.otherContainsAll(
builder.build(),
ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY)
);
}
@ParameterizedTest
@MethodSource
public void testContainsAllFalse(Consumer<ImmutableContextSet.Builder> setup) {
ImmutableContextSetImpl set = (ImmutableContextSetImpl) new ImmutableContextSetImpl.BuilderImpl()
.add("aaa", "a")
.add("aaa", "b")
.add("aaa", "c")
.add("bbb", "a")
.add("bbb", "b")
.build();
ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl();
setup.accept(builder);
assertFalse(set.otherContainsAll(
builder.build(),
ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY)
);
}
}

View File

@ -25,54 +25,23 @@
package me.lucko.luckperms.common.dependencies;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import java.util.Base64;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class DependencyChecksumTest {
@Test
@ParameterizedTest
@EnumSource
@Tag("dependency_checksum")
public void check() {
Dependency[] dependencies = Dependency.values();
DependencyRepository[] repos = DependencyRepository.values();
ExecutorService pool = Executors.newCachedThreadPool();
AtomicBoolean failed = new AtomicBoolean(false);
for (Dependency dependency : dependencies) {
for (DependencyRepository repo : repos) {
pool.submit(() -> {
try {
byte[] hash = Dependency.createDigest().digest(repo.downloadRaw(dependency));
if (!dependency.checksumMatches(hash)) {
System.out.println("NO MATCH - " + repo.name() + " - " + dependency.name() + ": " + Base64.getEncoder().encodeToString(hash));
failed.set(true);
} else {
System.out.println("OK - " + repo.name() + " - " + dependency.name());
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
pool.shutdown();
try {
pool.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (failed.get()) {
Assertions.fail("Some dependency checksums did not match");
public void checksumMatches(Dependency dependency) throws DependencyDownloadException {
for (DependencyRepository repo : DependencyRepository.values()) {
byte[] hash = Dependency.createDigest().digest(repo.downloadRaw(dependency));
assertTrue(dependency.checksumMatches(hash), "Dependency " + dependency.name() + " has hash " + Base64.getEncoder().encodeToString(hash));
}
}

View File

@ -0,0 +1,96 @@
/*
* 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.event;
import me.lucko.luckperms.common.event.gen.GeneratedEventClass;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.player.PlayerDataSaveEvent;
import net.luckperms.api.event.sync.PreSyncEvent;
import net.luckperms.api.model.PlayerSaveResult;
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.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class)
public class EventGeneratorTest {
@Mock public LuckPerms luckPermsApi;
@Test
public void testGenerateAll() {
GeneratedEventClass.preGenerate();
}
@Test
public void testSimple() throws Throwable {
UUID randomUniqueId = UUID.randomUUID();
String randomUsername = "random";
PlayerSaveResult mockResult = mock(PlayerSaveResult.class);
GeneratedEventClass eventClass = GeneratedEventClass.generate(PlayerDataSaveEvent.class);
LuckPermsEvent rawEvent = eventClass.newInstance(this.luckPermsApi, randomUniqueId, randomUsername, mockResult);
assertTrue(rawEvent instanceof PlayerDataSaveEvent);
PlayerDataSaveEvent event = (PlayerDataSaveEvent) rawEvent;
assertEquals(PlayerDataSaveEvent.class, event.getEventType());
assertSame(randomUniqueId, event.getUniqueId());
assertSame(randomUsername, event.getUsername());
assertSame(mockResult, event.getResult());
assertSame(this.luckPermsApi, event.getLuckPerms());
}
@Test
public void testDefaultMethods() throws Throwable {
AtomicBoolean state = new AtomicBoolean(false);
GeneratedEventClass eventClass = GeneratedEventClass.generate(PreSyncEvent.class);
PreSyncEvent event = (PreSyncEvent) eventClass.newInstance(this.luckPermsApi, state);
assertFalse(event.isCancelled());
assertTrue(event.isNotCancelled());
assertEquals("PreSyncEvent{cancellationState=false}", event.toString());
assertFalse(event.setCancelled(true));
assertTrue(event.isCancelled());
assertFalse(event.isNotCancelled());
assertEquals("PreSyncEvent{cancellationState=true}", event.toString());
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.model;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.common.model.manager.group.StandardGroupManager;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
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.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class GroupManagerTest {
@Mock private LuckPermsPlugin plugin;
@Test
public void testSanitizeIdentifier() {
StandardGroupManager manager = new StandardGroupManager(this.plugin) {
@Override
public Group apply(String name) {
Group group = mock(Group.class);
when(group.getName()).thenReturn(name);
return group;
}
};
Group group = manager.getOrMake("DEFAULT");
assertEquals("default", group.getName());
assertEquals(ImmutableSet.of("default"), manager.getAll().keySet());
}
@Test
public void testGetByDisplayName() {
StandardGroupManager manager = new StandardGroupManager(this.plugin) {
@Override
public Group apply(String name) {
return mock(Group.class);
}
};
Group defaultGroup = manager.getOrMake("default");
when(defaultGroup.getDisplayName()).thenReturn(Optional.of("member"));
assertSame(defaultGroup, manager.getByDisplayName("default"));
assertSame(defaultGroup, manager.getByDisplayName("Default"));
assertSame(defaultGroup, manager.getByDisplayName("member"));
assertSame(defaultGroup, manager.getByDisplayName("Member"));
assertNull(manager.getByDisplayName("test"));
Group memberGroup = manager.getOrMake("member");
assertSame(defaultGroup, manager.getByDisplayName("default"));
assertSame(defaultGroup, manager.getByDisplayName("Default"));
assertSame(memberGroup, manager.getByDisplayName("member"));
assertSame(memberGroup, manager.getByDisplayName("Member"));
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.model;
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.graph.TraversalAlgorithm;
import me.lucko.luckperms.common.inheritance.InheritanceGraphFactory;
import me.lucko.luckperms.common.model.manager.group.GroupManager;
import me.lucko.luckperms.common.model.manager.group.StandardGroupManager;
import me.lucko.luckperms.common.node.types.Inheritance;
import me.lucko.luckperms.common.node.types.Weight;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.query.QueryOptionsImpl;
import net.luckperms.api.context.ContextSatisfyMode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class InheritanceTest {
@Mock private LuckPermsPlugin plugin;
@Mock private LuckPermsConfiguration configuration;
private StandardGroupManager groupManager;
@BeforeEach
public void setupMocks() {
this.groupManager = new StandardGroupManager(this.plugin);
//noinspection unchecked,rawtypes
lenient().when(this.plugin.getGroupManager()).thenReturn((GroupManager) this.groupManager);
lenient().when(this.plugin.getInheritanceGraphFactory()).thenReturn(new InheritanceGraphFactory(this.plugin));
lenient().when(this.plugin.getConfiguration()).thenReturn(this.configuration);
lenient().when(this.plugin.getEventDispatcher()).thenReturn(mock(EventDispatcher.class));
lenient().when(this.configuration.get(ConfigKeys.CONTEXT_SATISFY_MODE)).thenReturn(ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY);
lenient().when(this.configuration.get(ConfigKeys.GROUP_WEIGHTS)).thenReturn(Collections.emptyMap());
}
/*
* Given the following inheritance setup:
* (value in brackets is the group weight)
*
* test user
*
* owner (13)
* admin (12)
* mod (11)
* helper (10)
* member (0)
* vip+ (6)
* vip (5)
* member (0)
*
* This test checks if the resolved inheritance order is correct :)
*/
@ParameterizedTest(name = "[{index}] {0}, {1}")
@CsvSource({
"DEPTH_FIRST_PRE_ORDER, false, 'owner -> admin -> mod -> helper -> member -> vip+ -> vip'",
"BREADTH_FIRST, false, 'owner -> vip+ -> admin -> vip -> mod -> member -> helper'",
"DEPTH_FIRST_POST_ORDER, false, 'member -> helper -> mod -> admin -> owner -> vip -> vip+'",
"DEPTH_FIRST_PRE_ORDER, true, 'owner -> admin -> mod -> helper -> vip+ -> vip -> member'",
"BREADTH_FIRST, true, 'owner -> admin -> mod -> helper -> vip+ -> vip -> member'",
"DEPTH_FIRST_POST_ORDER, true, 'owner -> admin -> mod -> helper -> vip+ -> vip -> member'"
})
public void testInheritanceTree(TraversalAlgorithm traversalAlgorithm, boolean postTraversalSort, String expected) {
when(this.configuration.get(ConfigKeys.INHERITANCE_TRAVERSAL_ALGORITHM)).thenReturn(traversalAlgorithm);
when(this.configuration.get(ConfigKeys.POST_TRAVERSAL_INHERITANCE_SORT)).thenReturn(postTraversalSort);
Group member = this.groupManager.getOrMake("member");
Group helper = createGroup("helper", 10, member);
Group mod = createGroup("mod", 11, helper);
Group admin = createGroup("admin", 12, mod);
Group owner = createGroup("owner", 13, admin);
Group vip = createGroup("vip", 5, member);
Group vipPlus = createGroup("vip+", 6, vip);
PermissionHolder testHolder = this.groupManager.getOrMake("test");
testHolder.normalData().add(Inheritance.builder().group(owner.getName()).build());
testHolder.normalData().add(Inheritance.builder().group(vipPlus.getName()).build());
List<String> groups = testHolder.resolveInheritanceTree(QueryOptionsImpl.DEFAULT_CONTEXTUAL)
.stream().map(Group::getName).collect(Collectors.toList());
List<String> expectedList = Arrays.stream(expected.split(" -> ")).collect(Collectors.toList());
assertEquals(expectedList, groups);
}
private Group createGroup(String name, int weight, Group parent) {
Group group = this.groupManager.getOrMake(name);
group.normalData().add(Inheritance.builder().group(parent.getName()).build());
group.normalData().add(Weight.builder().weight(weight).build());
return group;
}
}

View File

@ -0,0 +1,331 @@
/*
* 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.model;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.common.context.ImmutableContextSetImpl;
import me.lucko.luckperms.common.model.nodemap.NodeMapMutable;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import me.lucko.luckperms.common.query.QueryOptionsBuilderImpl;
import me.lucko.luckperms.common.util.Difference;
import net.luckperms.api.context.ContextSatisfyMode;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata;
import net.luckperms.api.node.types.InheritanceNode;
import net.luckperms.api.query.Flag;
import net.luckperms.api.query.QueryMode;
import net.luckperms.api.query.QueryOptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.Duration;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class NodeMapTest {
private static final PermissionHolderIdentifier ORIGIN = new PermissionHolderIdentifier(HolderType.GROUP, "test");
@Mock private PermissionHolder mockHolder;
@BeforeEach
public void setupMocks() {
when(this.mockHolder.getIdentifier()).thenReturn(ORIGIN);
}
private static Node makeNode(String key) {
return NodeBuilders.determineMostApplicable(key).build();
}
@Test
public void testSimpleAddAndRemove() {
NodeMapMutable map = new NodeMapMutable(this.mockHolder, DataType.NORMAL);
assertEquals(0, map.size());
Node node = makeNode("test");
Difference<Node> r1 = map.add(node);
assertEquals(ImmutableSet.of(node), r1.getAdded());
assertEquals(ImmutableSet.of(), r1.getRemoved());
assertEquals(1, map.size());
Difference<Node> r2 = map.remove(node);
assertEquals(ImmutableSet.of(), r2.getAdded());
assertEquals(ImmutableSet.of(node), r2.getRemoved());
assertEquals(0, map.size());
}
@Test
public void testInheritanceOrigin() {
NodeMapMutable map = new NodeMapMutable(this.mockHolder, DataType.NORMAL);
Node node = makeNode("test");
map.add(node);
List<Node> nodes = map.asList();
assertEquals(ImmutableList.of(node), map.asList());
InheritanceOriginMetadata origin = nodes.get(0).metadata(InheritanceOriginMetadata.KEY);
assertEquals(ORIGIN, origin.getOrigin());
assertEquals(DataType.NORMAL, origin.getDataType());
}
@ParameterizedTest
@CsvSource({
"test, true, false",
"test, false, true",
"group.test, true, false",
"group.test, false, true"
})
public void testRemoveMatchingButNotSameValue(String nodeKey, boolean firstValue, boolean secondValue) {
NodeMapMutable map = new NodeMapMutable(this.mockHolder, DataType.NORMAL);
Node first = makeNode(nodeKey).toBuilder().value(firstValue).build();
Node second = makeNode(nodeKey).toBuilder().value(secondValue).build();
map.add(first);
Difference<Node> diff = map.add(second);
assertEquals(ImmutableSet.of(first), diff.getRemoved());
assertEquals(ImmutableSet.of(second), diff.getAdded());
assertEquals(ImmutableList.of(second), map.asList());
if (second.getType() == NodeType.INHERITANCE && second.getValue()) {
assertEquals(ImmutableList.of(second), map.inheritanceAsList());
} else {
assertEquals(ImmutableList.of(), map.inheritanceAsList());
}
}
@ParameterizedTest
@CsvSource({
"test, 1, 5",
"test, 5, 1",
"group.test, 1, 5",
"group.test, 5, 1"
})
public void testRemoveMatchingButNotSameExpiry(String nodeKey, int firstDuration, int secondDuration) {
NodeMapMutable map = new NodeMapMutable(this.mockHolder, DataType.NORMAL);
Node first = makeNode(nodeKey).toBuilder()
.expiry(firstDuration == 0 ? null : Duration.ofDays(firstDuration))
.build();
Node second = makeNode(nodeKey).toBuilder()
.expiry(secondDuration == 0 ? null : Duration.ofDays(secondDuration))
.build();
map.add(first);
Difference<Node> diff = map.add(second);
assertEquals(ImmutableSet.of(first), diff.getRemoved());
assertEquals(ImmutableSet.of(second), diff.getAdded());
assertEquals(ImmutableList.of(second), map.asList());
if (second.getType() == NodeType.INHERITANCE) {
assertEquals(ImmutableList.of(second), map.inheritanceAsList());
} else {
assertEquals(ImmutableList.of(), map.inheritanceAsList());
}
}
@Test
public void testRemove() {
NodeMapMutable map = new NodeMapMutable(this.mockHolder, DataType.NORMAL);
map.add(makeNode("test1"));
map.add(makeNode("test2").toBuilder().value(false).build());
map.add(makeNode("test3").toBuilder().expiry(1, TimeUnit.HOURS).build());
map.add(makeNode("test4").toBuilder().withContext("hello", "world").build());
assertEquals(4, map.size());
map.remove(makeNode("test1").toBuilder().withContext("hello", "world").build());
map.remove(makeNode("test3"));
map.remove(makeNode("test4"));
map.remove(makeNode("test4").toBuilder().withContext("hello", "world").withContext("aaa", "bbb").build());
map.remove(makeNode("test5"));
assertEquals(4, map.size());
map.remove(makeNode("test1"));
map.remove(makeNode("test2").toBuilder().value(true).build());
map.remove(makeNode("test3").toBuilder().expiry(2, TimeUnit.HOURS).build());
map.remove(makeNode("test4").toBuilder().withContext("hello", "world").build());
assertEquals(0, map.size());
}
@Test
public void testRemoveExact() {
NodeMapMutable map = new NodeMapMutable(this.mockHolder, DataType.NORMAL);
map.add(makeNode("test1"));
map.add(makeNode("test2").toBuilder().value(false).build());
map.add(makeNode("test3").toBuilder().expiry(1, TimeUnit.HOURS).build());
map.add(makeNode("test4").toBuilder().withContext("hello", "world").build());
assertEquals(4, map.size());
map.removeExact(makeNode("test1").toBuilder().withContext("hello", "world").build());
map.removeExact(makeNode("test3"));
map.removeExact(makeNode("test4"));
map.removeExact(makeNode("test4").toBuilder().withContext("hello", "world").withContext("aaa", "bbb").build());
map.removeExact(makeNode("test5"));
assertEquals(4, map.size());
map.removeExact(makeNode("test2").toBuilder().value(true).build());
map.removeExact(makeNode("test3").toBuilder().expiry(2, TimeUnit.HOURS).build());
map.removeExact(makeNode("test4").toBuilder().withContext("hello", "world").withContext("aaa", "bbb").build());
assertEquals(4, map.size());
map.removeExact(makeNode("test1"));
map.removeExact(makeNode("test2").toBuilder().value(false).build());
map.removeExact(makeNode("test3").toBuilder().expiry(1, TimeUnit.HOURS).build());
map.removeExact(makeNode("test4").toBuilder().withContext("hello", "world").build());
assertEquals(0, map.size());
}
@ParameterizedTest(name = "s={0} w={1} is={2} iw={3}")
@CsvSource({
"true, true, true, true, 8, 4",
"true, true, false, false, 8, 1",
"false, true, true, true, 6, 4",
"true, false, true, true, 6, 4",
"false, false, true, true, 5, 4",
"false, true, false, true, 4, 2",
"true, false, true, false, 4, 2",
"false, false, false, false, 2, 1"
})
public void testFlagsFiltering(boolean includeServer, boolean includeWorld, boolean inheritanceIncludeServer, boolean inheritanceIncludeWorld, int expected, int expectedInheritance) {
NodeMapMutable map = new NodeMapMutable(this.mockHolder, DataType.NORMAL) {
@Override
protected ContextSatisfyMode defaultSatisfyMode() {
return ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY;
}
};
map.add(makeNode("test1"));
map.add(makeNode("test2").toBuilder().withContext("server", "test").build());
map.add(makeNode("test3").toBuilder().withContext("world", "test").build());
map.add(makeNode("test4").toBuilder().withContext("server", "test").withContext("world", "test").build());
map.add(makeNode("group.test1"));
map.add(makeNode("group.test2").toBuilder().withContext("server", "test").build());
map.add(makeNode("group.test3").toBuilder().withContext("world", "test").build());
map.add(makeNode("group.test4").toBuilder().withContext("server", "test").withContext("world", "test").build());
Set<Flag> flags = EnumSet.noneOf(Flag.class);
if (includeServer) flags.add(Flag.INCLUDE_NODES_WITHOUT_SERVER_CONTEXT);
if (includeWorld) flags.add(Flag.INCLUDE_NODES_WITHOUT_WORLD_CONTEXT);
if (inheritanceIncludeServer) flags.add(Flag.APPLY_INHERITANCE_NODES_WITHOUT_SERVER_CONTEXT);
if (inheritanceIncludeWorld) flags.add(Flag.APPLY_INHERITANCE_NODES_WITHOUT_WORLD_CONTEXT);
QueryOptions options = new QueryOptionsBuilderImpl(QueryMode.NON_CONTEXTUAL)
.flags(flags)
.build();
Set<Node> output = new HashSet<>();
map.copyTo(output, options);
assertEquals(expected, output.size());
output.clear();
map.forEach(options, output::add);
assertEquals(expected, output.size());
Set<InheritanceNode> inheritanceOutput = new HashSet<>();
map.copyInheritanceNodesTo(inheritanceOutput, options);
assertEquals(expectedInheritance, inheritanceOutput.size());
}
@ParameterizedTest
@CsvSource({
"'', 2, 1",
"server=test, 4, 2",
"world=test, 4, 2",
"server=test|world=test, 8, 4",
"server=test|world=test|test=test, 8, 4",
})
public void testContextFiltering(String context, int expected, int expectedInheritance) {
NodeMapMutable map = new NodeMapMutable(this.mockHolder, DataType.NORMAL) {
@Override
protected ContextSatisfyMode defaultSatisfyMode() {
return ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY;
}
};
map.add(makeNode("test1"));
map.add(makeNode("test2").toBuilder().withContext("server", "test").build());
map.add(makeNode("test3").toBuilder().withContext("world", "test").build());
map.add(makeNode("test4").toBuilder().withContext("server", "test").withContext("world", "test").build());
map.add(makeNode("group.test1"));
map.add(makeNode("group.test2").toBuilder().withContext("server", "test").build());
map.add(makeNode("group.test3").toBuilder().withContext("world", "test").build());
map.add(makeNode("group.test4").toBuilder().withContext("server", "test").withContext("world", "test").build());
ImmutableContextSet.Builder builder = new ImmutableContextSetImpl.BuilderImpl();
if (!context.isEmpty()) {
Splitter.on('|').withKeyValueSeparator('=').split(context).forEach(builder::add);
}
QueryOptions options = new QueryOptionsBuilderImpl(QueryMode.CONTEXTUAL)
.context(builder.build())
.build();
Set<Node> output = new HashSet<>();
map.copyTo(output, options);
assertEquals(expected, output.size());
output.clear();
map.forEach(options, output::add);
assertEquals(expected, output.size());
Set<InheritanceNode> inheritanceOutput = new HashSet<>();
map.copyInheritanceNodesTo(inheritanceOutput, options);
assertEquals(expectedInheritance, inheritanceOutput.size());
}
}

View File

@ -0,0 +1,135 @@
/*
* 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.model;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.event.EventDispatcher;
import me.lucko.luckperms.common.node.types.Permission;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.model.data.DataMutateResult;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.model.data.TemporaryNodeMergeStrategy;
import net.luckperms.api.node.types.PermissionNode;
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.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
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;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class PermissionHolderTest {
@Mock private LuckPermsPlugin plugin;
@BeforeEach
public void setupMocks() {
when(this.plugin.getEventDispatcher()).thenReturn(mock(EventDispatcher.class));
}
@Test
public void testTemporaryMergeNone() {
PermissionHolder holder = new Group("test", this.plugin);
PermissionNode node1 = Permission.builder().permission("test").expiry(1, TimeUnit.HOURS).build();
PermissionNode node2 = Permission.builder().permission("test").expiry(2, TimeUnit.HOURS).build();
TemporaryNodeMergeStrategy strategy = TemporaryNodeMergeStrategy.NONE;
DataMutateResult.WithMergedNode r1 = holder.setNode(DataType.NORMAL, node1, strategy);
assertEquals(DataMutateResult.SUCCESS, r1.getResult());
assertEquals(ImmutableList.of(node1), holder.normalData().asList());
DataMutateResult.WithMergedNode r2 = holder.setNode(DataType.NORMAL, node2, strategy);
assertEquals(DataMutateResult.FAIL_ALREADY_HAS, r2.getResult());
assertEquals(ImmutableList.of(node1), holder.normalData().asList());
}
@Test
public void testTemporaryMergeReplaceIfLonger() {
PermissionHolder holder = new Group("test", this.plugin);
PermissionNode node1 = Permission.builder().permission("test").expiry(1, TimeUnit.HOURS).build();
PermissionNode node2 = Permission.builder().permission("test").expiry(2, TimeUnit.HOURS).build();
TemporaryNodeMergeStrategy strategy = TemporaryNodeMergeStrategy.REPLACE_EXISTING_IF_DURATION_LONGER;
DataMutateResult.WithMergedNode r1 = holder.setNode(DataType.NORMAL, node1, strategy);
assertEquals(DataMutateResult.SUCCESS, r1.getResult());
assertEquals(ImmutableList.of(node1), holder.normalData().asList());
DataMutateResult.WithMergedNode r2 = holder.setNode(DataType.NORMAL, node2, strategy);
assertEquals(DataMutateResult.SUCCESS, r2.getResult());
assertEquals(ImmutableList.of(node2), holder.normalData().asList());
assertEquals(node2, r2.getMergedNode());
DataMutateResult.WithMergedNode r3 = holder.setNode(DataType.NORMAL, node1, strategy);
assertEquals(DataMutateResult.FAIL_ALREADY_HAS, r3.getResult());
assertEquals(ImmutableList.of(node2), holder.normalData().asList());
}
@Test
public void testTemporaryMergeAddDurations() {
PermissionHolder holder = new Group("test", this.plugin);
PermissionNode node1 = Permission.builder().permission("test").expiry(2, TimeUnit.HOURS).build();
PermissionNode node2 = Permission.builder().permission("test").expiry(1, TimeUnit.HOURS).build();
TemporaryNodeMergeStrategy strategy = TemporaryNodeMergeStrategy.ADD_NEW_DURATION_TO_EXISTING;
DataMutateResult.WithMergedNode r1 = holder.setNode(DataType.NORMAL, node1, strategy);
assertEquals(DataMutateResult.SUCCESS, r1.getResult());
assertEquals(ImmutableList.of(node1), holder.normalData().asList());
DataMutateResult.WithMergedNode r2 = holder.setNode(DataType.NORMAL, node2, strategy);
assertEquals(DataMutateResult.SUCCESS, r2.getResult());
Instant originalExpiry = node1.getExpiry();
assertNotNull(originalExpiry);
Instant newExpiry = r2.getMergedNode().getExpiry();
assertNotNull(newExpiry);
Instant expectedExpiry = node1.getExpiry().plus(1, ChronoUnit.HOURS);
// uses wall-clock time, so allow for the tests to run slowly
assertTrue(Duration.between(newExpiry, expectedExpiry).abs().getSeconds() < 5);
}
}

View File

@ -23,48 +23,31 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.node.utils;
package me.lucko.luckperms.common.model;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.common.model.manager.track.StandardTrackManager;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ShorthandParserTest {
@ExtendWith(MockitoExtension.class)
public class TrackManagerTest {
private static void test(String shorthand, String... expected) {
assertEquals(ImmutableSet.copyOf(expected), ShorthandParser.expandShorthand(shorthand));
}
@Mock private LuckPermsPlugin plugin;
@Test
void testNumericRange() {
test("{2-4}", "2", "3", "4");
}
@Test
void testCharacterRange() {
test("{a-d}", "a", "b", "c", "d");
test("{A-D}", "A", "B", "C", "D");
}
@Test
void testList() {
test("{aa,bb,cc}", "aa", "bb", "cc");
test("{aa|bb|cc}", "aa", "bb", "cc");
test("{aa,bb|cc}", "aa", "bb", "cc");
}
@Test
void testGroups() {
test("he{y|llo} {1-2}", "hey 1", "hey 2", "hello 1", "hello 2");
test("my.permission.{test,hi}", "my.permission.test", "my.permission.hi");
test("my.permission.{a-c}", "my.permission.a", "my.permission.b", "my.permission.c");
// use ( ) instead
test("he(y|llo) (1-2)", "hey 1", "hey 2", "hello 1", "hello 2");
test("my.permission.(test,hi)", "my.permission.test", "my.permission.hi");
test("my.permission.(a-c)", "my.permission.a", "my.permission.b", "my.permission.c");
public void testSanitizeIdentifier() {
StandardTrackManager manager = new StandardTrackManager(this.plugin);
Track track = manager.getOrMake("TEST");
assertEquals("test", track.getName());
assertEquals(ImmutableSet.of("test"), manager.getAll().keySet());
}
}

View File

@ -0,0 +1,115 @@
/*
* 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.model;
import com.google.common.collect.ImmutableList;
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.manager.user.StandardUserManager;
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 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.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class)
public class UserManagerTest {
@Mock private LuckPermsPlugin plugin;
@Mock private LuckPermsBootstrap bootstrap;
@Mock private LuckPermsConfiguration configuration;
@BeforeEach
public void setupMocks() {
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");
}
@Test
public void testGiveDefaultIfNeeded() {
StandardUserManager manager = new StandardUserManager(this.plugin);
User user = manager.getOrMake(UUID.randomUUID());
assertEquals(ImmutableList.of(), user.normalData().asList());
boolean changed = manager.giveDefaultIfNeeded(user);
assertTrue(changed);
Inheritance defaultNode = Inheritance.builder("default").build();
assertEquals(ImmutableList.of(defaultNode), user.normalData().asList());
changed = manager.giveDefaultIfNeeded(user);
assertFalse(changed);
}
@Test
public void testIsNonDefaultUser() {
StandardUserManager manager = new StandardUserManager(this.plugin);
User user = manager.getOrMake(UUID.randomUUID());
manager.giveDefaultIfNeeded(user);
assertFalse(manager.isNonDefaultUser(user));
user.normalData().add(Permission.builder().permission("test").build());
assertTrue(manager.isNonDefaultUser(user));
}
@Test
public void testIsDefaultNode() {
StandardUserManager manager = new StandardUserManager(this.plugin);
assertTrue(manager.isDefaultNode(Inheritance.builder().group("default").build()));
assertTrue(manager.isDefaultNode(Inheritance.builder().group("Default").build()));
assertFalse(manager.isDefaultNode(Inheritance.builder().group("default").value(false).build()));
assertFalse(manager.isDefaultNode(Inheritance.builder().group("default").withContext("server", "test").build()));
assertFalse(manager.isDefaultNode(Inheritance.builder().group("default").expiry(1, TimeUnit.DAYS).build()));
assertFalse(manager.isDefaultNode(Inheritance.builder().group("test").build()));
assertFalse(manager.isDefaultNode(Permission.builder().permission("hello").build()));
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.node;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import me.lucko.luckperms.common.node.types.Permission;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeBuilder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class NodeBuildersTest {
@ParameterizedTest
@CsvSource({
"luckperms.user.info, Permission$Builder, PERMISSION",
"group.default, Inheritance$Builder, INHERITANCE",
"meta.key.value, Meta$Builder, META",
"prefix.100.hello, Prefix$Builder, PREFIX",
"suffix.100.hello, Suffix$Builder, SUFFIX",
"displayname.hello, DisplayName$Builder, DISPLAY_NAME",
"weight.10, Weight$Builder, WEIGHT",
"r=hello, RegexPermission$Builder, REGEX_PERMISSION",
"R=hello, RegexPermission$Builder, REGEX_PERMISSION"
})
public void testDetermineMostApplicableType(String key, String expectedBuilderClass, String expectedType) {
NodeBuilder<?, ?> builder = NodeBuilders.determineMostApplicable(key);
assertTrue(builder.getClass().getName().endsWith(expectedBuilderClass));
Node node = builder.build();
assertEquals(expectedType, node.getType().name());
}
@Test
public void testNonSpecificNodeBuild() {
Permission.Builder builder = Permission.builder().permission("group.default");
assertThrows(IllegalArgumentException.class, builder::build);
}
}

View File

@ -0,0 +1,253 @@
/*
* 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.node;
import me.lucko.luckperms.common.node.types.DisplayName;
import me.lucko.luckperms.common.node.types.Inheritance;
import me.lucko.luckperms.common.node.types.Meta;
import me.lucko.luckperms.common.node.types.Prefix;
import me.lucko.luckperms.common.node.types.RegexPermission;
import me.lucko.luckperms.common.node.types.Suffix;
import me.lucko.luckperms.common.node.types.Weight;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class NodeParseTest {
@ParameterizedTest
@CsvSource({
"group.test, test",
"group.TEST, test",
"group.hello world, hello world"
})
public void testInheritance(String key, String expectedGroupName) {
Inheritance.Builder builder = Inheritance.parse(key);
assertNotNull(builder);
Inheritance node = builder.build();
assertEquals(expectedGroupName, node.getGroupName());
}
@ParameterizedTest
@ValueSource(strings = {
"",
"aaaa"
})
public void testInheritanceFail(String key) {
Inheritance.Builder builder = Inheritance.parse(key);
assertNull(builder);
}
@ParameterizedTest
@CsvSource({
"displayname.test, test",
"displayname.TEST, TEST",
"displayname.hello world, hello world"
})
public void testDisplayName(String key, String expectedDisplayName) {
DisplayName.Builder builder = DisplayName.parse(key);
assertNotNull(builder);
DisplayName node = builder.build();
assertEquals(expectedDisplayName, node.getDisplayName());
}
@ParameterizedTest
@ValueSource(strings = {
"",
"aaaa"
})
public void testDisplayNameFail(String key) {
DisplayName.Builder builder = DisplayName.parse(key);
assertNull(builder);
}
@ParameterizedTest
@CsvSource({
"weight.100, 100",
"weight.-100, -100",
"weight.0, 0"
})
public void testWeight(String key, int expectedWeight) {
Weight.Builder builder = Weight.parse(key);
assertNotNull(builder);
Weight node = builder.build();
assertEquals(expectedWeight, node.getWeight());
}
@ParameterizedTest
@ValueSource(strings = {
"",
"aaaa",
"weight.",
"weight.hello"
})
public void testWeightFail(String key) {
Weight.Builder builder = Weight.parse(key);
assertNull(builder);
}
@ParameterizedTest
@CsvSource({
"prefix.100.hello, 100, hello",
"prefix.-100.hello, -100, hello",
"prefix.0.hello, 0, hello",
"prefix.100.hello world, 100, hello world",
"prefix.100.HELLO world &123, 100, HELLO world &123",
"prefix.100., 100, ''"
})
public void testPrefix(String key, int expectedPriority, String expectedValue) {
Prefix.Builder builder = Prefix.parse(key);
assertNotNull(builder);
Prefix node = builder.build();
assertEquals(expectedPriority, node.getPriority());
assertEquals(expectedValue, node.getMetaValue());
}
@ParameterizedTest
@ValueSource(strings = {
"",
"aaaa",
"prefix.",
"prefix.hello",
"prefix.100",
"prefix.hello.hello"
})
public void testPrefixFail(String key) {
Prefix.Builder builder = Prefix.parse(key);
assertNull(builder);
}
@ParameterizedTest
@CsvSource({
"suffix.100.hello, 100, hello",
"suffix.-100.hello, -100, hello",
"suffix.0.hello, 0, hello",
"suffix.100.hello world, 100, hello world",
"suffix.100.HELLO world &123, 100, HELLO world &123",
"suffix.100., 100, ''"
})
public void testSuffix(String key, int expectedPriority, String expectedValue) {
Suffix.Builder builder = Suffix.parse(key);
assertNotNull(builder);
Suffix node = builder.build();
assertEquals(expectedPriority, node.getPriority());
assertEquals(expectedValue, node.getMetaValue());
}
@ParameterizedTest
@ValueSource(strings = {
"",
"aaaa",
"suffix.",
"suffix.hello",
"suffix.100",
"suffix.hello.hello"
})
public void testSuffixFail(String key) {
Suffix.Builder builder = Suffix.parse(key);
assertNull(builder);
}
@ParameterizedTest
@CsvSource({
"meta.k.v, k, v",
"meta.hello.world, hello, world",
"meta.hello., hello, ''"
})
public void testMeta(String key, String expectedKey, String expectedValue) {
Meta.Builder builder = Meta.parse(key);
assertNotNull(builder);
Meta node = builder.build();
assertEquals(expectedKey, node.getMetaKey());
assertEquals(expectedValue, node.getMetaValue());
}
@ParameterizedTest
@ValueSource(strings = {
"",
"aaaa",
"meta.",
"meta.hello",
})
public void testMetaFail(String key) {
Meta.Builder builder = Meta.parse(key);
assertNull(builder);
}
@ParameterizedTest
@ValueSource(strings = {
"meta.."
})
public void testMetaFailThrows(String key) {
assertThrows(IllegalArgumentException.class, () -> Meta.parse(key));
}
@ParameterizedTest
@CsvSource({
"r=hello, hello",
"R=hello, hello",
"r=.*&^12 3[], .*&^12 3[]"
})
public void testRegexPermission(String key, String expectedPattern) {
RegexPermission.Builder builder = RegexPermission.parse(key);
assertNotNull(builder);
RegexPermission node = builder.build();
assertEquals(expectedPattern, node.getPatternString());
}
@ParameterizedTest
@ValueSource(strings = {
"",
"aaaa"
})
public void testRegexPermissionFail(String key) {
RegexPermission.Builder builder = RegexPermission.parse(key);
assertNull(builder);
}
@ParameterizedTest
@ValueSource(strings = {
"r=",
"R="
})
public void testRegexPermissionFailThrows(String key) {
assertThrows(IllegalArgumentException.class, () -> RegexPermission.parse(key));
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.node;
import me.lucko.luckperms.common.context.ImmutableContextSetImpl;
import me.lucko.luckperms.common.node.types.Permission;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.metadata.NodeMetadataKey;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class NodeTest {
@Test
public void testBasic() {
Node node = Permission.builder()
.permission("hello.world")
.build();
assertEquals("hello.world", node.getKey());
assertTrue(node.getValue());
assertNull(node.getExpiry());
assertNull(node.getExpiryDuration());
assertFalse(node.hasExpired());
assertFalse(node.hasExpiry());
}
@Test
public void testExpiry() {
Instant instant = Instant.now()
.plusSeconds(60)
.truncatedTo(ChronoUnit.SECONDS);
Node node = Permission.builder()
.permission("hello.world")
.expiry(instant)
.build();
assertEquals("hello.world", node.getKey());
assertTrue(node.getValue());
assertEquals(instant, node.getExpiry());
assertNotNull(node.getExpiryDuration());
assertFalse(node.hasExpired());
assertTrue(node.hasExpiry());
}
@Test
public void testContext() {
Node node = Permission.builder()
.permission("hello.world")
.withContext("hello", "123")
.withContext("world", "456")
.build();
ImmutableContextSet contexts = node.getContexts();
ImmutableContextSet expected = new ImmutableContextSetImpl.BuilderImpl()
.add("hello", "123")
.add("world", "456")
.build();
assertEquals(expected, contexts);
}
@Test
public void testMetadata() {
NodeMetadataKey<UUID> key = NodeMetadataKey.of("test2", UUID.class);
UUID randomUniqueId = UUID.randomUUID();
Node node = Permission.builder()
.permission("hello.world")
.withMetadata(key, randomUniqueId)
.build();
assertEquals(Optional.of(randomUniqueId), node.getMetadata(key));
assertEquals(randomUniqueId, node.metadata(key));
}
@Test
public void testMetadataFails() {
NodeMetadataKey<String> key1 = NodeMetadataKey.of("test1", String.class);
NodeMetadataKey<UUID> key2 = NodeMetadataKey.of("test2", UUID.class);
NodeMetadataKey<UUID> key3 = NodeMetadataKey.of("test2", UUID.class);
NodeMetadataKey<String> key4 = NodeMetadataKey.of("test2", String.class);
UUID randomUniqueId = UUID.randomUUID();
Node node = Permission.builder()
.permission("hello.world")
.withMetadata(key2, randomUniqueId)
.build();
assertEquals(Optional.of(randomUniqueId), node.getMetadata(key2));
assertEquals(Optional.of(randomUniqueId), node.getMetadata(key3));
assertEquals(randomUniqueId, node.metadata(key2));
assertEquals(randomUniqueId, node.metadata(key3));
assertEquals(Optional.empty(), node.getMetadata(key1));
assertEquals(Optional.empty(), node.getMetadata(key4));
assertThrows(IllegalStateException.class, () -> node.metadata(key1));
assertThrows(IllegalStateException.class, () -> node.metadata(key4));
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.node;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.common.node.utils.ShorthandParser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
public class ShorthandParserTest {
private static Stream<Arguments> testParse() {
return Stream.of(
// numeric range
Arguments.of("{2-4}", new String[]{"2", "3", "4"}),
// character range
Arguments.of("{a-d}", new String[]{"a", "b", "c", "d"}),
Arguments.of("{A-D}", new String[]{"A", "B", "C", "D"}),
// list
Arguments.of("{aa,bb,cc}", new String[]{"aa", "bb", "cc"}),
Arguments.of("{aa|bb|cc}", new String[]{"aa", "bb", "cc"}),
Arguments.of("{aa,bb|cc}", new String[]{"aa", "bb", "cc"}),
// groups
Arguments.of("he{y|llo} {1-2}", new String[]{"hey 1", "hey 2", "hello 1", "hello 2"}),
Arguments.of("my.permission.{test,hi}", new String[]{"my.permission.test", "my.permission.hi"}),
Arguments.of("my.permission.{a-c}", new String[]{"my.permission.a", "my.permission.b", "my.permission.c"}),
// groups - using () instead
Arguments.of("he(y|llo) (1-2)", new String[]{"hey 1", "hey 2", "hello 1", "hello 2"}),
Arguments.of("my.permission.(test,hi)", new String[]{"my.permission.test", "my.permission.hi"}),
Arguments.of("my.permission.(a-c)", new String[]{"my.permission.a", "my.permission.b", "my.permission.c"})
);
}
@ParameterizedTest
@MethodSource
public void testParse(String shorthand, String[] expected) {
Assertions.assertEquals(ImmutableSet.copyOf(expected), ShorthandParser.expandShorthand(shorthand));
}
}

View File

@ -0,0 +1,246 @@
/*
* 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.ImmutableSet;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.event.EventDispatcher;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.manager.group.GroupManager;
import me.lucko.luckperms.common.model.manager.group.StandardGroupManager;
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.storage.implementation.sql.SqlStorage;
import me.lucko.luckperms.common.storage.implementation.sql.connection.ConnectionFactory;
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.node.Node;
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.DriverManager;
import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static org.junit.jupiter.api.Assertions.assertEquals;
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 class SqlStorageTest {
@Mock private LuckPermsPlugin plugin;
@Mock private LuckPermsBootstrap bootstrap;
private SqlStorage storage;
@BeforeEach
public void setupMocksAndDatabase() throws Exception {
lenient().when(this.plugin.getBootstrap()).thenReturn(this.bootstrap);
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 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());
}
private static class TestH2ConnectionFactory implements ConnectionFactory {
private final NonClosableConnection connection;
TestH2ConnectionFactory() throws SQLException {
this.connection = new NonClosableConnection(
DriverManager.getConnection("jdbc:h2:mem:test")
);
}
@Override
public Connection getConnection() {
return this.connection;
}
@Override
public String getImplementationName() {
return "H2";
}
@Override
public void init(LuckPermsPlugin plugin) {
}
@Override
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`')
.replace("LIKE", "ILIKE")
.replace("value", "`value`")
.replace("``value``", "`value`");
}
@Override
public void shutdown() throws Exception {
this.connection.shutdown();
}
}
}

View File

@ -0,0 +1,86 @@
/*
* 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.util;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.common.util.Difference.ChangeType;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class DifferenceTest {
@Test
public void testSimple() {
Difference<String> diff = new Difference<>();
assertTrue(diff.isEmpty());
diff.recordChange(ChangeType.ADD, "test1");
diff.recordChange(ChangeType.REMOVE, "test2");
diff.recordChange(ChangeType.ADD, "test3");
assertEquals(ImmutableSet.of("test1", "test3"), diff.getAdded());
assertEquals(ImmutableSet.of("test2"), diff.getRemoved());
assertFalse(diff.isEmpty());
assertEquals(3, diff.getChanges().size());
}
@Test
public void testOverride() {
Difference<String> diff = new Difference<>();
assertTrue(diff.isEmpty());
diff.recordChange(ChangeType.ADD, "test1");
diff.recordChange(ChangeType.REMOVE, "test1");
assertTrue(diff.isEmpty());
}
@Test
public void testMerge() {
Difference<String> diff = new Difference<>();
diff.recordChange(ChangeType.ADD, "test1");
Difference<String> diff2 = new Difference<>();
diff2.recordChange(ChangeType.REMOVE, "test1");
assertEquals(1, diff.getChanges().size());
assertEquals(1, diff2.getChanges().size());
Difference<String> returnedDiff = diff.mergeFrom(diff2);
assertSame(diff, returnedDiff);
assertEquals(0, diff.getChanges().size());
assertEquals(1, diff2.getChanges().size());
}
}

View File

@ -0,0 +1,116 @@
/*
* 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.util;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.util.UTF8ResourceBundleControl;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class DurationFormatterTest {
private static TranslatableComponentRenderer<Locale> renderer;
@BeforeAll
public static void setupRenderer() {
TranslationRegistry registry = TranslationRegistry.create(Key.key("luckperms", "test"));
ResourceBundle bundle = ResourceBundle.getBundle("luckperms", Locale.ENGLISH, UTF8ResourceBundleControl.get());
registry.registerAll(Locale.ENGLISH, bundle, false);
renderer = TranslatableComponentRenderer.usingTranslationSource(registry);
}
private static String render(Component component) {
return PlainTextComponentSerializer.plainText().serialize(renderer.render(component, Locale.ENGLISH));
}
private static Stream<Arguments> testSimple() {
Duration years = ChronoUnit.YEARS.getDuration();
Duration months = ChronoUnit.MONTHS.getDuration();
return Stream.of(
Arguments.of("1 year", years.multipliedBy(1)),
Arguments.of("2 years", years.multipliedBy(2)),
Arguments.of("3 years", years.multipliedBy(3)),
Arguments.of("15 years", years.multipliedBy(15)),
Arguments.of("1 month", months.multipliedBy(1)),
Arguments.of("2 months", months.multipliedBy(2)),
Arguments.of("1 week", Duration.ofDays(7)),
Arguments.of("2 weeks", Duration.ofDays(7 * 2)),
Arguments.of("1 day", Duration.ofDays(1)),
Arguments.of("2 days", Duration.ofDays(2)),
Arguments.of("1 hour", Duration.ofHours(1)),
Arguments.of("2 hours", Duration.ofHours(2)),
Arguments.of("15 hours", Duration.ofHours(15)),
Arguments.of("1 minute", Duration.ofMinutes(1)),
Arguments.of("2 minutes", Duration.ofMinutes(2)),
Arguments.of("15 minutes", Duration.ofMinutes(15)),
Arguments.of("1 second", Duration.ofSeconds(1)),
Arguments.of("2 seconds", Duration.ofSeconds(2)),
Arguments.of("15 seconds", Duration.ofSeconds(15)),
Arguments.of("0 seconds", Duration.ZERO)
);
}
@ParameterizedTest
@MethodSource
public void testSimple(String expected, Duration input) {
assertEquals(expected, render(DurationFormatter.LONG.format(input)));
}
@Test
public void testFormats() {
Duration duration = ChronoUnit.YEARS.getDuration().multipliedBy(5)
.plus(ChronoUnit.MONTHS.getDuration().multipliedBy(4))
.plus(ChronoUnit.WEEKS.getDuration().multipliedBy(3))
.plusDays(2)
.plusHours(1)
.plusMinutes(6)
.plusSeconds(7);
assertEquals("5y 4mo 3w 2d 1h 6m 7s", render(DurationFormatter.CONCISE.format(duration)));
assertEquals("5y 4mo 3w", render(DurationFormatter.CONCISE_LOW_ACCURACY.format(duration)));
assertEquals("5 years 4 months 3 weeks 2 days 1 hour 6 minutes 7 seconds", render(DurationFormatter.LONG.format(duration)));
}
}

View File

@ -25,82 +25,100 @@
package me.lucko.luckperms.common.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class DurationParserTest {
private static void test(Duration expected, String input) {
private static Stream<Arguments> testSimple() {
Duration years = ChronoUnit.YEARS.getDuration();
Duration months = ChronoUnit.MONTHS.getDuration();
return Stream.of(
Arguments.of("2y", years.multipliedBy(2)),
Arguments.of("3year", years.multipliedBy(3)),
Arguments.of("4years", years.multipliedBy(4)),
Arguments.of("2 y", years.multipliedBy(2)),
Arguments.of("3 year", years.multipliedBy(3)),
Arguments.of("4 years", years.multipliedBy(4)),
Arguments.of("2mo", months.multipliedBy(2)),
Arguments.of("3month", months.multipliedBy(3)),
Arguments.of("4months", months.multipliedBy(4)),
Arguments.of("2 mo", months.multipliedBy(2)),
Arguments.of("3 month", months.multipliedBy(3)),
Arguments.of("4 months", months.multipliedBy(4)),
Arguments.of("2w", Duration.ofDays(7 * 2)),
Arguments.of("3week", Duration.ofDays(7 * 3)),
Arguments.of("4weeks", Duration.ofDays(7 * 4)),
Arguments.of("2 w", Duration.ofDays(7 * 2)),
Arguments.of("3 week", Duration.ofDays(7 * 3)),
Arguments.of("4 weeks", Duration.ofDays(7 * 4)),
Arguments.of("2d", Duration.ofDays(2)),
Arguments.of("3day", Duration.ofDays(3)),
Arguments.of("4days", Duration.ofDays(4)),
Arguments.of("2 d", Duration.ofDays(2)),
Arguments.of("3 day", Duration.ofDays(3)),
Arguments.of("4 days", Duration.ofDays(4)),
Arguments.of("2h", Duration.ofHours(2)),
Arguments.of("3hour", Duration.ofHours(3)),
Arguments.of("4hours", Duration.ofHours(4)),
Arguments.of("2 h", Duration.ofHours(2)),
Arguments.of("3 hour", Duration.ofHours(3)),
Arguments.of("4 hours", Duration.ofHours(4)),
Arguments.of("2m", Duration.ofMinutes(2)),
Arguments.of("3min", Duration.ofMinutes(3)),
Arguments.of("4mins", Duration.ofMinutes(4)),
Arguments.of("5minute", Duration.ofMinutes(5)),
Arguments.of("6minutes", Duration.ofMinutes(6)),
Arguments.of("2 m", Duration.ofMinutes(2)),
Arguments.of("3 min", Duration.ofMinutes(3)),
Arguments.of("4 mins", Duration.ofMinutes(4)),
Arguments.of("5 minute", Duration.ofMinutes(5)),
Arguments.of("6 minutes", Duration.ofMinutes(6)),
Arguments.of("2s", Duration.ofSeconds(2)),
Arguments.of("3sec", Duration.ofSeconds(3)),
Arguments.of("4secs", Duration.ofSeconds(4)),
Arguments.of("5second", Duration.ofSeconds(5)),
Arguments.of("6seconds", Duration.ofSeconds(6)),
Arguments.of("2 s", Duration.ofSeconds(2)),
Arguments.of("3 sec", Duration.ofSeconds(3)),
Arguments.of("4 secs", Duration.ofSeconds(4)),
Arguments.of("5 second", Duration.ofSeconds(5)),
Arguments.of("6 seconds", Duration.ofSeconds(6))
);
}
@ParameterizedTest
@MethodSource
public void testSimple(String input, Duration expected) {
assertEquals(expected, DurationParser.parseDuration(input));
}
@Test
void testSimple() {
test(ChronoUnit.YEARS.getDuration().multipliedBy(2), "2y");
test(ChronoUnit.YEARS.getDuration().multipliedBy(3), "3year");
test(ChronoUnit.YEARS.getDuration().multipliedBy(4), "4years");
test(ChronoUnit.YEARS.getDuration().multipliedBy(2), "2 y");
test(ChronoUnit.YEARS.getDuration().multipliedBy(3), "3 year");
test(ChronoUnit.YEARS.getDuration().multipliedBy(4), "4 years");
test(ChronoUnit.MONTHS.getDuration().multipliedBy(2), "2mo");
test(ChronoUnit.MONTHS.getDuration().multipliedBy(3), "3month");
test(ChronoUnit.MONTHS.getDuration().multipliedBy(4), "4months");
test(ChronoUnit.MONTHS.getDuration().multipliedBy(2), "2 mo");
test(ChronoUnit.MONTHS.getDuration().multipliedBy(3), "3 month");
test(ChronoUnit.MONTHS.getDuration().multipliedBy(4), "4 months");
test(Duration.ofDays(7 * 2), "2w");
test(Duration.ofDays(7 * 3), "3week");
test(Duration.ofDays(7 * 4), "4weeks");
test(Duration.ofDays(7 * 2), "2 w");
test(Duration.ofDays(7 * 3), "3 week");
test(Duration.ofDays(7 * 4), "4 weeks");
test(Duration.ofDays(2), "2d");
test(Duration.ofDays(3), "3day");
test(Duration.ofDays(4), "4days");
test(Duration.ofDays(2), "2 d");
test(Duration.ofDays(3), "3 day");
test(Duration.ofDays(4), "4 days");
test(Duration.ofHours(2), "2h");
test(Duration.ofHours(3), "3hour");
test(Duration.ofHours(4), "4hours");
test(Duration.ofHours(2), "2 h");
test(Duration.ofHours(3), "3 hour");
test(Duration.ofHours(4), "4 hours");
test(Duration.ofMinutes(2), "2m");
test(Duration.ofMinutes(3), "3min");
test(Duration.ofMinutes(4), "4mins");
test(Duration.ofMinutes(5), "5minute");
test(Duration.ofMinutes(6), "6minutes");
test(Duration.ofMinutes(2), "2 m");
test(Duration.ofMinutes(3), "3 min");
test(Duration.ofMinutes(4), "4 mins");
test(Duration.ofMinutes(5), "5 minute");
test(Duration.ofMinutes(6), "6 minutes");
test(Duration.ofSeconds(2), "2s");
test(Duration.ofSeconds(3), "3sec");
test(Duration.ofSeconds(4), "4secs");
test(Duration.ofSeconds(5), "5second");
test(Duration.ofSeconds(6), "6seconds");
test(Duration.ofSeconds(2), "2 s");
test(Duration.ofSeconds(3), "3 sec");
test(Duration.ofSeconds(4), "4 secs");
test(Duration.ofSeconds(5), "5 second");
test(Duration.ofSeconds(6), "6 seconds");
}
@Test
void testCombined() {
@ParameterizedTest
@ValueSource(strings = {
"5y 4mo 3w 2d 1h 6m 7s",
"5y4mo3w2d1h6m7s",
"5 years 4 months 3 weeks 2 days 1 hour 6 minutes 7 seconds",
"5y, 4mo, 3w, 2d, 1h, 6m, 7s",
"5y,4mo,3w,2d,1h,6m,7s",
"5 years, 4 months, 3 weeks, 2 days, 1 hour, 6 minutes, 7 seconds"
})
public void testCombined(String input) {
Duration expected = ChronoUnit.YEARS.getDuration().multipliedBy(5)
.plus(ChronoUnit.MONTHS.getDuration().multipliedBy(4))
.plus(ChronoUnit.WEEKS.getDuration().multipliedBy(3))
@ -109,20 +127,17 @@ public class DurationParserTest {
.plusMinutes(6)
.plusSeconds(7);
test(expected, "5y 4mo 3w 2d 1h 6m 7s");
test(expected, "5y4mo3w2d1h6m7s");
test(expected, "5 years 4 months 3 weeks 2 days 1 hour 6 minutes 7 seconds");
test(expected, "5y, 4mo, 3w, 2d, 1h, 6m, 7s");
test(expected, "5y,4mo,3w,2d,1h,6m,7s");
test(expected, "5 years, 4 months, 3 weeks, 2 days, 1 hour, 6 minutes, 7 seconds");
assertEquals(expected, DurationParser.parseDuration(input));
}
@Test
void testFail() {
assertThrows(IllegalArgumentException.class, () -> DurationParser.parseDuration("definitely not a duration"));
assertThrows(IllegalArgumentException.class, () -> DurationParser.parseDuration("still 1 not a duration"));
assertThrows(IllegalArgumentException.class, () -> DurationParser.parseDuration("still 1s not a duration"));
@ParameterizedTest
@ValueSource(strings = {
"definitely not a duration",
"still 1 not a duration",
"still 1s not a duration"
})
public void testFail(String input) {
assertThrows(IllegalArgumentException.class, () -> DurationParser.parseDuration(input));
}
}

View File

@ -36,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class EnumNamerTest {
@Test
void testSimple() {
public void testSimple() {
EnumNamer<TestEnum> namer = new EnumNamer<>(
TestEnum.class,
ImmutableMap.of(TestEnum.THING, "hi"),

View File

@ -36,12 +36,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class IteratorsTest {
@Test
void testDivideEmpty() {
public void testDivideEmpty() {
assertEquals(ImmutableList.of(), Iterators.divideIterable(ImmutableList.of(), 2));
}
@Test
void testDivideSimple() {
public void testDivideSimple() {
List<List<String>> expected = ImmutableList.of(
ImmutableList.of("one", "two"),
ImmutableList.of("three", "four"),
@ -57,7 +57,7 @@ public class IteratorsTest {
}
@Test
void testDivideBoundary() {
public void testDivideBoundary() {
List<List<String>> expected = ImmutableList.of(
ImmutableList.of("one", "two"),
ImmutableList.of("three", "four")

View File

@ -27,44 +27,79 @@ package me.lucko.luckperms.common.util;
import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class PaginatedTest {
@Test
void testSimple() {
Paginated<String> paginated = new Paginated<>(ImmutableList.of("one", "two", "three", "four", "five"));
assertEquals(3, paginated.getMaxPages(2));
assertEquals(1, paginated.getMaxPages(5));
assertEquals(1, paginated.getMaxPages(6));
private static final Paginated<String> EXAMPLE_PAGE = new Paginated<>(ImmutableList.of("one", "two", "three", "four", "five"));
List<Paginated.Entry<String>> page1 = paginated.getPage(1, 2);
assertEquals(2, page1.size());
assertEquals("one", page1.get(0).value());
assertEquals(1, page1.get(0).position());
assertEquals("two", page1.get(1).value());
assertEquals(2, page1.get(1).position());
@ParameterizedTest
@CsvSource({
"3, 2",
"1, 5",
"1, 6"
})
public void testMaxPages(int expected, int entriesPerPage) {
assertEquals(expected, EXAMPLE_PAGE.getMaxPages(entriesPerPage));
}
List<Paginated.Entry<String>> page2 = paginated.getPage(2, 2);
assertEquals(2, page2.size());
assertEquals("three", page2.get(0).value());
assertEquals(3, page2.get(0).position());
assertEquals("four", page2.get(1).value());
assertEquals(4, page2.get(1).position());
@ParameterizedTest
@CsvSource({
"1, 2",
"2, 2",
"3, 1"
})
public void testPageSize(int pageNo, int expectedSize) {
List<Paginated.Entry<String>> page = EXAMPLE_PAGE.getPage(pageNo, 2);
assertEquals(expectedSize, page.size());
}
List<Paginated.Entry<String>> page3 = paginated.getPage(3, 2);
assertEquals(1, page3.size());
assertEquals("five", page3.get(0).value());
assertEquals(5, page3.get(0).position());
private static Stream<Arguments> testPageContent() {
return Stream.of(
Arguments.of(1, ImmutableList.of(
new Paginated.Entry<>(1, "one"),
new Paginated.Entry<>(2, "two")
)),
Arguments.of(2, ImmutableList.of(
new Paginated.Entry<>(3, "three"),
new Paginated.Entry<>(4, "four")
)),
Arguments.of(3, ImmutableList.of(
new Paginated.Entry<>(5, "five")
))
);
}
assertThrows(IllegalStateException.class, () -> paginated.getPage(4, 2));
assertThrows(IllegalArgumentException.class, () -> paginated.getPage(0, 2));
assertThrows(IllegalArgumentException.class, () -> paginated.getPage(-1, 2));
@ParameterizedTest
@MethodSource
public void testPageContent(int pageNo, List<Paginated.Entry<String>> expectedContent) {
assertEquals(expectedContent, EXAMPLE_PAGE.getPage(pageNo, 2));
}
@ParameterizedTest
@CsvSource({
"4, 2",
})
public void testFailState(int pageNo, int pageSize) {
assertThrows(IllegalStateException.class, () -> EXAMPLE_PAGE.getPage(pageNo, pageSize));
}
@ParameterizedTest
@CsvSource({
"0, 2",
"-1, 2"
})
public void testFailArgument(int pageNo, int pageSize) {
assertThrows(IllegalArgumentException.class, () -> EXAMPLE_PAGE.getPage(pageNo, pageSize));
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.util;
import me.lucko.luckperms.common.event.EventDispatcher;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.AdditionalAnswers.returnsSecondArg;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class UniqueIdTypeTest {
@Mock private LuckPermsPlugin plugin;
@Mock private EventDispatcher dispatcher;
@BeforeEach
public void setupMocks() {
when(this.plugin.getEventDispatcher()).thenReturn(this.dispatcher);
when(this.dispatcher.dispatchUniqueIdDetermineType(any(), anyString())).then(returnsSecondArg());
}
@ParameterizedTest
@CsvSource({
"797a99ba-c040-4f04-8cfc-6b01a4890d2f, authenticated",
"5d41402a-bc4b-3a76-b971-9d911017c592, unauthenticated",
"cfa4fcb1-a786-23fc-b956-33914c7bb373, npc",
"00000000-0000-0000-0000-000000000000, unknown"
})
public void testParse(UUID uuid, String expectedType) {
UniqueIdType uniqueIdType = UniqueIdType.determineType(uuid, this.plugin);
assertEquals(expectedType, uniqueIdType.getType());
}
}

View File

@ -26,28 +26,35 @@
package me.lucko.luckperms.common.verbose;
import me.lucko.luckperms.common.verbose.expression.BooleanExpressionCompiler;
import me.lucko.luckperms.common.verbose.expression.BooleanExpressionCompiler.VariableEvaluator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BooleanExpressionTest {
private static void test(String expression, boolean expected) {
assertEquals(
expected,
BooleanExpressionCompiler.compile(expression).eval(var -> var.equals("true")),
expression + " is not " + expected
);
private static final VariableEvaluator STRING_EVAL = var -> var.equals("true");
@ParameterizedTest
@ValueSource(strings = {
"false & false | true",
"true | false & false",
"(true & ((true | false) & !(true & false)))"
})
public void testEvaluatesTrue(String expression) {
assertTrue(BooleanExpressionCompiler.compile(expression).eval(STRING_EVAL));
}
@Test
void testBrackets() {
test("false & false | true", true);
test("false & (false | true)", false);
test("true | false & false", true);
test("(true | false) & false", false);
test("(true & ((true | false) & !(true & false)))", true);
@ParameterizedTest
@ValueSource(strings = {
"false & (false | true)",
"(true | false) & false"
})
public void testEvaluatesFalse(String expression) {
assertFalse(BooleanExpressionCompiler.compile(expression).eval(STRING_EVAL));
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.verbose;
import me.lucko.luckperms.common.cacheddata.result.StringResult;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.query.QueryOptionsImpl;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import me.lucko.luckperms.common.verbose.event.MetaCheckEvent;
import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import net.luckperms.api.util.Tristate;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class VerboseFilterTest {
@Test
public void testAcceptAll() {
VerboseFilter filter = VerboseFilter.acceptAll();
assertTrue(filter.isBlank());
}
@ParameterizedTest
@CsvSource({
"luckperms, true",
"luckperms.user, true",
"luckperms.group, false",
"Player1, true",
"Player2, false",
"luckperms & Player1, true",
"permission & luckperms & Player1, true",
"permission & luckperms & Player1 & true, true",
"luckperms & Player2, false",
"luckperms | test, true"
})
public void testPermissionEvent(String expression, boolean expected) throws InvalidFilterException {
VerboseFilter filter = VerboseFilter.compile(expression);
assertFalse(filter.isBlank());
PermissionCheckEvent relevantEvent = new PermissionCheckEvent(
CheckOrigin.INTERNAL,
VerboseCheckTarget.of(VerboseCheckTarget.USER_TYPE, "Player1"),
QueryOptionsImpl.DEFAULT_CONTEXTUAL,
System.currentTimeMillis(),
new Throwable(),
"test",
"luckperms.user.parent.info",
TristateResult.forMonitoredResult(Tristate.TRUE)
);
PermissionCheckEvent nonRelevantEvent = new PermissionCheckEvent(
CheckOrigin.INTERNAL,
VerboseCheckTarget.of(VerboseCheckTarget.USER_TYPE, "aaaaaaa"),
QueryOptionsImpl.DEFAULT_CONTEXTUAL,
System.currentTimeMillis(),
new Throwable(),
"test",
"aaaaaaaaa",
TristateResult.forMonitoredResult(Tristate.FALSE)
);
assertEquals(expected, filter.evaluate(relevantEvent));
assertFalse(filter.evaluate(nonRelevantEvent));
}
@ParameterizedTest
@CsvSource({
"nametags, true",
"nametags.nametag, true",
"nametags.other, false",
"Player1, true",
"Player2, false",
"nametags & Player1, true",
"meta & nametags & Player1, true",
"meta & nametags & Player1 & admin, true",
"nametags & Player2, false",
"nametags | test, true"
})
public void testMetaEvent(String expression, boolean expected) throws InvalidFilterException {
VerboseFilter filter = VerboseFilter.compile(expression);
assertFalse(filter.isBlank());
MetaCheckEvent relevantEvent = new MetaCheckEvent(
CheckOrigin.INTERNAL,
VerboseCheckTarget.of(VerboseCheckTarget.USER_TYPE, "Player1"),
QueryOptionsImpl.DEFAULT_CONTEXTUAL,
System.currentTimeMillis(),
new Throwable(),
"test",
"nametags.nametag",
StringResult.of("ADMIN")
);
MetaCheckEvent nonRelevantEvent = new MetaCheckEvent(
CheckOrigin.INTERNAL,
VerboseCheckTarget.of(VerboseCheckTarget.USER_TYPE, "aaaaaaa"),
QueryOptionsImpl.DEFAULT_CONTEXTUAL,
System.currentTimeMillis(),
new Throwable(),
"test",
"aaaaaaaaa",
StringResult.of("aaaaaa")
);
assertEquals(expected, filter.evaluate(relevantEvent));
assertFalse(filter.evaluate(nonRelevantEvent));
}
}

View File

@ -0,0 +1 @@
mock-maker-inline