diff --git a/build.gradle b/build.gradle index a1a3468..650ecc1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java' +apply plugin: 'groovy' apply plugin: 'eclipse' sourceCompatibility = '1.6' @@ -55,5 +56,10 @@ compile 'org.lwjgl.lwjgl:lwjgl_util:2.9.0' compile 'org.yaml:snakeyaml:1.13' - testCompile 'junit:junit:4.5' + testCompile "org.codehaus.groovy:groovy-all:2.3.0" + testCompile "org.spockframework:spock-core:1.0-groovy-2.0-SNAPSHOT" + testCompile 'junit:junit:4.5' + + testRuntime "cglib:cglib-nodep:2.2.2" + testRuntime "org.objenesis:objenesis:1.2" } diff --git a/src/main/java/org/ultramine/permission/GroupPermission.java b/src/main/java/org/ultramine/permission/GroupPermission.java new file mode 100644 index 0000000..988e4a6 --- /dev/null +++ b/src/main/java/org/ultramine/permission/GroupPermission.java @@ -0,0 +1,173 @@ +package org.ultramine.permission; + +import java.util.*; + +/** + * Created by Евгений on 08.05.2014. + */ +public class GroupPermission extends MetaHolder implements IChangeablePermission, IDirtyListener +{ + private String key; + private boolean dirty; + + private PermissionResolver resolver = new PermissionResolver(); + private List listeners = new ArrayList(); + private Set permissions = new HashSet(); + private Map effectiveMeta = new HashMap(); + + public GroupPermission(String key) + { + super(); + this.key = key.toLowerCase(); + this.dirty = false; + } + + public GroupPermission(String key, Map meta) + { + super(meta); + this.key = key.toLowerCase(); + this.dirty = true; + } + + @Override + public String getKey() + { + return key; + } + + @Override + public String getName() + { + if (innerMeta.containsKey("name")) + return (String) innerMeta.get("name"); + else + return key; + } + + @Override + public String getDescription() + { + if (innerMeta.containsKey("description")) + return (String) innerMeta.get("description"); + else + return ""; + } + + @Override + public PermissionResolver getResolver() + { + if (isDirty()) + calculate(); + + return resolver; + } + + @Override + public Map getEffectiveMeta() + { + if (isDirty()) + calculate(); + + return effectiveMeta; + } + + public void addPermission(IPermission permission) + { + permissions.add(permission); + makeDirty(); + } + + public void removePermission(IPermission permission) + { + permissions.remove(permission); + makeDirty(); + } + + @Override + public boolean isDirty() + { + return dirty; + } + + @Override + public void subscribe(IDirtyListener listener) + { + listeners.add(listener); + } + + @Override + public void unsubscribe(IDirtyListener listener) + { + listeners.remove(listener); + } + + @Override + public void makeDirty() + { + if (isDirty()) + return; + + dirty = true; + for (IDirtyListener listener : listeners) + listener.makeDirty(); + } + + @Override + public void setValue(String key, Object value) + { + super.setValue(key, value); + makeDirty(); + } + + @Override + public void removeValue(String key) + { + super.removeValue(key); + makeDirty(); + } + + public void calculate() + { + if (!isDirty()) + return; + + resolver.clear(); + for (IPermission permission : permissions) + resolver.merge(permission.getResolver(), permission.getPriority()); + + effectiveMeta.clear(); + Map priorities = new HashMap(); + + for (IPermission permission : permissions) + { + Map meta = permission.getEffectiveMeta(); + + for (String key : meta.keySet()) + if (!priorities.containsKey(key) || permission.getPriority() > priorities.get(key)) + { + effectiveMeta.put(key, meta.get(key)); + priorities.put(key, permission.getPriority()); + } + } + + for (String key : innerMeta.keySet()) + effectiveMeta.put(key, innerMeta.get(key)); + + this.dirty = false; + } + + @Override + public int hashCode() + { + return getKey().hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof IPermission) + return getKey().equals(((IPermission)obj).getKey()); + + return super.equals(obj); + } +} diff --git a/src/main/java/org/ultramine/permission/IChangeablePermission.java b/src/main/java/org/ultramine/permission/IChangeablePermission.java new file mode 100644 index 0000000..a79f0da --- /dev/null +++ b/src/main/java/org/ultramine/permission/IChangeablePermission.java @@ -0,0 +1,13 @@ +package org.ultramine.permission; + +import java.util.Map; + +/** + * Created by Евгений on 07.05.2014. + */ +public interface IChangeablePermission extends IPermission +{ + public boolean isDirty(); + public void subscribe(IDirtyListener listener); + public void unsubscribe(IDirtyListener listener); +} diff --git a/src/main/java/org/ultramine/permission/IDirtyListener.java b/src/main/java/org/ultramine/permission/IDirtyListener.java new file mode 100644 index 0000000..4aff430 --- /dev/null +++ b/src/main/java/org/ultramine/permission/IDirtyListener.java @@ -0,0 +1,9 @@ +package org.ultramine.permission; + +/** + * Created by Евгений on 07.05.2014. + */ +public interface IDirtyListener +{ + public void makeDirty(); +} diff --git a/src/main/java/org/ultramine/permission/IPermission.java b/src/main/java/org/ultramine/permission/IPermission.java new file mode 100644 index 0000000..7b3e197 --- /dev/null +++ b/src/main/java/org/ultramine/permission/IPermission.java @@ -0,0 +1,17 @@ +package org.ultramine.permission; + +import java.util.Map; + +/** + * Created by Евгений on 02.05.2014. + */ +public interface IPermission +{ + public String getKey(); + public String getName(); + public String getDescription(); + public int getPriority(); + + public PermissionResolver getResolver(); + public Map getEffectiveMeta(); +} \ No newline at end of file diff --git a/src/main/java/org/ultramine/permission/MetaHolder.java b/src/main/java/org/ultramine/permission/MetaHolder.java new file mode 100644 index 0000000..93bfb80 --- /dev/null +++ b/src/main/java/org/ultramine/permission/MetaHolder.java @@ -0,0 +1,57 @@ +package org.ultramine.permission; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by Евгений on 08.05.2014. + */ +public abstract class MetaHolder +{ + protected Map innerMeta; + + public MetaHolder() + { + innerMeta = new HashMap(); + } + + public MetaHolder(Map meta) + { + innerMeta = meta; + } + + public void setValue(String key, Object value) + { + innerMeta.put(key, value); + } + + public void removeValue(String key) + { + innerMeta.remove(key); + } + + public String getString(String key) + { + Map meta = getEffectiveMeta(); + if (meta.containsKey(key)) + return (String)meta.get(key); + else + return ""; + } + + public int getInt(String key) + { + Map meta = getEffectiveMeta(); + if (meta.containsKey(key)) + return (Integer)meta.get(key); + else + return 0; + } + + public int getPriority() + { + return getInt("priority"); + } + + public abstract Map getEffectiveMeta(); +} diff --git a/src/main/java/org/ultramine/permission/NegativePermission.java b/src/main/java/org/ultramine/permission/NegativePermission.java new file mode 100644 index 0000000..24871fd --- /dev/null +++ b/src/main/java/org/ultramine/permission/NegativePermission.java @@ -0,0 +1,103 @@ +package org.ultramine.permission; + +import java.util.Map; + +/** + * Created by Евгений on 07.05.2014. + */ +public class NegativePermission implements IChangeablePermission +{ + private IPermission permission; + private boolean isChangeable; + private PermissionResolver resolver; + + public NegativePermission(IPermission permission) + { + this.permission = permission; + this.resolver = PermissionResolver.createInverted(permission.getResolver()); + this.isChangeable = false; + } + + public NegativePermission(IChangeablePermission permission) + { + this.permission = permission; + this.resolver = PermissionResolver.createInverted(permission.getResolver()); + this.isChangeable = true; + } + + @Override + public String getKey() + { + return "^" + permission.getKey(); + } + + @Override + public String getName() + { + return "NOT: " + permission.getName(); + } + + @Override + public String getDescription() + { + if (!permission.getDescription().isEmpty()) + return "NOT: " + permission.getDescription(); + else + return ""; + } + + @Override + public int getPriority() + { + return permission.getPriority(); + } + + @Override + public PermissionResolver getResolver() + { + if (isDirty()) + resolver = PermissionResolver.createInverted(permission.getResolver()); + return resolver; + } + + @Override + public Map getEffectiveMeta() + { + return permission.getEffectiveMeta(); + } + + @Override + public boolean isDirty() + { + return isChangeable && ((IChangeablePermission) permission).isDirty(); + } + + @Override + public void subscribe(IDirtyListener listener) + { + if (isChangeable) + ((IChangeablePermission) permission).subscribe(listener); + } + + @Override + public void unsubscribe(IDirtyListener listener) + { + if (isChangeable) + ((IChangeablePermission) permission).unsubscribe(listener); + } + + @Override + public int hashCode() + { + return getKey().hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof IPermission) + return getKey().equals(((IPermission)obj).getKey()); + + return super.equals(obj); + } +} diff --git a/src/main/java/org/ultramine/permission/Permission.java b/src/main/java/org/ultramine/permission/Permission.java new file mode 100644 index 0000000..f1b6b99 --- /dev/null +++ b/src/main/java/org/ultramine/permission/Permission.java @@ -0,0 +1,79 @@ +package org.ultramine.permission; + +import java.util.Collections; +import java.util.Map; + +/** + * Created by Евгений on 07.05.2014. + */ +public class Permission implements IPermission +{ + private String key; + private String name; + private String description; + private PermissionResolver resolver; + + public Permission(String key, String name, String description) + { + this.key = key.toLowerCase(); + this.name = name; + this.description = description; + this.resolver = PermissionResolver.createForKey(key, getPriority()); + } + + public Permission(String key) + { + this(key, key, ""); + } + + @Override + public String getKey() + { + return key; + } + + @Override + public String getName() + { + return name; + } + + @Override + public String getDescription() + { + return description; + } + + @Override + public int getPriority() + { + return Integer.MAX_VALUE; + } + + @Override + public PermissionResolver getResolver() + { + return resolver; + } + + @Override + public Map getEffectiveMeta() + { + return Collections.emptyMap(); + } + + @Override + public int hashCode() + { + return getKey().hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof IPermission) + return getKey().equals(((IPermission)obj).getKey()); + + return super.equals(obj); + } +} diff --git a/src/main/java/org/ultramine/permission/PermissionRepository.java b/src/main/java/org/ultramine/permission/PermissionRepository.java new file mode 100644 index 0000000..23021ba --- /dev/null +++ b/src/main/java/org/ultramine/permission/PermissionRepository.java @@ -0,0 +1,157 @@ +package org.ultramine.permission; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Created by Евгений on 08.05.2014. + */ +public class PermissionRepository +{ + private Map permissions = new HashMap(); + private Map proxyPermissions = new HashMap(); + + public ProxyPermission getPermission(String key) + { + key = key.toLowerCase(); + if (!proxyPermissions.containsKey(key)) + proxyPermissions.put(key, new ProxyPermission(key)); + + return proxyPermissions.get(key); + } + + public void registerPermission(IPermission permission) + { + if (permissions.containsKey(permission.getKey())) + throw new IllegalArgumentException("Permission already registered"); + + ProxyPermission proxy = getPermission(permission.getKey()); + proxy.linkPermission(permission); + permissions.put(permission.getKey(), permission); + } + + public class ProxyPermission implements IChangeablePermission + { + private String key; + private IPermission wrappedPermission; + private boolean changeable; + private List listeners = new ArrayList(); + + private ProxyPermission(String key) + { + this.key = key; + this.wrappedPermission = new Permission(key); + this.changeable = false; + } + + @Override + public String getKey() + { + return key; + } + + public IPermission getWrappedPermission() + { + return wrappedPermission; + } + + public boolean isChangeable() + { + return changeable; + } + + @Override + public String getName() + { + return wrappedPermission.getName(); + } + + @Override + public String getDescription() + { + return wrappedPermission.getDescription(); + } + + @Override + public int getPriority() + { + return wrappedPermission.getPriority(); + } + + @Override + public PermissionResolver getResolver() + { + return wrappedPermission.getResolver(); + } + + @Override + public Map getEffectiveMeta() + { + return wrappedPermission.getEffectiveMeta(); + } + + @Override + public boolean isDirty() + { + return changeable && ((IChangeablePermission)wrappedPermission).isDirty(); + } + + @Override + public void subscribe(IDirtyListener listener) + { + if (changeable) + ((IChangeablePermission)wrappedPermission).subscribe(listener); + else + listeners.add(listener); + } + + @Override + public void unsubscribe(IDirtyListener listener) + { + if (changeable) + ((IChangeablePermission)wrappedPermission).unsubscribe(listener); + else + listeners.remove(listener); + } + + private void linkPermission(IPermission permission) + { + wrappedPermission = permission; + for (IDirtyListener listener : listeners) + listener.makeDirty(); + listeners.clear(); + } + + private void linkPermission(IChangeablePermission permission) + { + wrappedPermission = permission; + for (IDirtyListener listener : listeners) + { + permission.subscribe(listener); + listener.makeDirty(); + } + listeners.clear(); + this.changeable = true; + } + + @Override + public int hashCode() + { + return wrappedPermission.hashCode(); + } + + @Override + public boolean equals(Object obj) + { + return wrappedPermission.equals(obj); + } + + @Override + public String toString() + { + return wrappedPermission.toString(); + } + } +} diff --git a/src/main/java/org/ultramine/permission/PermissionResolver.java b/src/main/java/org/ultramine/permission/PermissionResolver.java new file mode 100644 index 0000000..a24d583 --- /dev/null +++ b/src/main/java/org/ultramine/permission/PermissionResolver.java @@ -0,0 +1,79 @@ +package org.ultramine.permission; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Created by Евгений on 07.05.2014. + */ +public class PermissionResolver +{ + private Map permissions; + private Map priorities; + + public static PermissionResolver createInverted(PermissionResolver anotherResolver) + { + PermissionResolver resolver = new PermissionResolver(); + for (Map.Entry entry : anotherResolver.permissions.entrySet()) + { + resolver.permissions.put(entry.getKey(), !entry.getValue()); + resolver.priorities.put(entry.getKey(), anotherResolver.priorities.get(entry.getKey())); + } + return resolver; + } + + public static PermissionResolver createForKey(String key, int priority) + { + PermissionResolver resolver = new PermissionResolver(); + resolver.permissions.put(key, true); + resolver.priorities.put(key, priority); + return resolver; + } + + public PermissionResolver() + { + permissions = new HashMap(); + priorities = new HashMap(); + } + + public boolean has(String key) + { + if (key == null) + return false; + + key = key.toLowerCase(); + if (permissions.containsKey(key)) + return permissions.get(key); + + int index = key.lastIndexOf('.'); + while (index >= 0) + { + key = key.substring(0, index); + String wildcard = key + ".*"; + if (permissions.containsKey(wildcard)) + return permissions.get(wildcard); + + index = key.lastIndexOf('.'); + } + if (permissions.containsKey("*")) + return permissions.get("*"); + + return false; + } + + public void clear() + { + permissions.clear(); + } + + public void merge(PermissionResolver anotherResolver, int priority) + { + for (String key : anotherResolver.permissions.keySet()) + if (!priorities.containsKey(key) || priorities.get(key) < priority) + { + permissions.put(key, anotherResolver.permissions.get(key)); + priorities.put(key, priority); + } + } +} diff --git a/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy b/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy new file mode 100644 index 0000000..fe948ed --- /dev/null +++ b/src/test/groovy/org/ultramine/permission/GroupPermissionTest.groovy @@ -0,0 +1,53 @@ +package org.ultramine.permission + +import spock.lang.Specification + +/** + * Created by Евгений on 08.05.2014. + */ +class GroupPermissionTest extends Specification { + + def "Test calculation"() { + setup: + def resolver = Mock(PermissionResolver) + def perm1 = Mock(IPermission) { + getKey() >> "p.1" + getResolver() >> resolver + getEffectiveMeta() >> [test1: "1", test2: "1", test3: "1"] + getPriority() >> 1 + } + def perm2 = Mock(IPermission) { + getKey() >> "p.2" + getResolver() >> resolver + getEffectiveMeta() >> [test2: "2"] + getPriority() >> 2 + } + + def group = new GroupPermission("group.test", [test1: "0"]) + group.resolver = resolver + group.addPermission(perm1) + group.addPermission(perm2) + + when: "Calculate meta and permissions" + group.calculate() + + then: "Permissions are calculated" + 1 * resolver.clear() + 1 * resolver.merge(resolver, 1) + 1 * resolver.merge(resolver, 2) + 0 * resolver._ + + and: "Meta is correct" + def meta = group.getEffectiveMeta() + meta.test1 == "0" + meta.test2 == "2" + meta.test3 == "1" + + when: "Calculate one more time" + group.calculate() + + then: "Nothing happens" + 0 * resolver._ + + } +} diff --git a/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy b/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy new file mode 100644 index 0000000..2648528 --- /dev/null +++ b/src/test/groovy/org/ultramine/permission/NegativePermissionTest.groovy @@ -0,0 +1,136 @@ +package org.ultramine.permission + +import spock.lang.Specification + +/** + * Created by Евгений on 08.05.2014. + */ +class NegativePermissionTest extends Specification { + + def "Test wrap IPermission"() { + setup: + IPermission permission = Mock(IPermission) { + getKey() >> "test.key" + getName() >> "Test Name" + getDescription() >> "Test Description" + getPriority() >> 100 + getResolver() >> PermissionResolver.createForKey("test.key", 1) + getEffectiveMeta() >> [test: "data"] + } + + when: "Create new NegativePermission" + def perm = new NegativePermission(permission) + + then: "PermissionResolver was inverted" + perm.getKey() == "^test.key" + perm.getName() == "NOT: Test Name" + perm.getDescription() == "NOT: Test Description" + perm.getPriority() == 100 + perm.getEffectiveMeta() == [test: "data"] + !perm.getResolver().has("test.key") + } + + def "Test isDirty IPermission"() { + setup: + IPermission permission = Mock(IPermission) { + getResolver() >> PermissionResolver.createForKey("test.key", 1) + isDirty() >> true + } + + when: "Create new NegativePermission" + def perm = new NegativePermission(permission) + + then: "It is not dirty" + !perm.isDirty() + 0 * permission.isDirty() + } + + def "Test subscribe/unsubscribe IPermission"() { + setup: + IPermission permission = Mock(IPermission) { + getResolver() >> PermissionResolver.createForKey("test.key", 1) + } + + def listener = Mock(IDirtyListener) + def perm = new NegativePermission(permission) + + when: "Try to subscribe/unsubscribe listener" + perm.subscribe(listener) + perm.unsubscribe(listener) + + then: "No interaction were done" + 0 * permission._ + 0 * listener._ + } + + def "Test wrap IChangeablePermission"() { + setup: + IPermission permission = Mock(IChangeablePermission) { + getKey() >> "test.key" + getName() >> "Test Name" + getDescription() >> "Test Description" + getPriority() >> 100 + getResolver() >> PermissionResolver.createForKey("test.key", 1) + getEffectiveMeta() >> [test: "data"] + } + + when: "Create new NegativePermission" + def perm = new NegativePermission(permission) + + then: "PermissionResolver was inverted" + perm.getKey() == "^test.key" + perm.getName() == "NOT: Test Name" + perm.getDescription() == "NOT: Test Description" + perm.getPriority() == 100 + perm.getEffectiveMeta() == [test: "data"] + !perm.getResolver().has("test.key") + } + + def "Test isDirty IChangeablePermission"() { + setup: + IPermission permission = Mock(IChangeablePermission) { + getResolver() >> PermissionResolver.createForKey("test.key", 1) + } + def perm = new NegativePermission(permission) + + when: "Wrapped permission dirty changes" + permission.isDirty() >>> [true, false] + + then: "Negative permission dirty is changing too" + perm.isDirty() + !perm.isDirty() + } + + def "Test subscribe/unsubscribe IChangeablePermission"() { + setup: + IPermission permission = Mock(IChangeablePermission) { + getResolver() >> PermissionResolver.createForKey("test.key", 1) + } + + def listener = Mock(IDirtyListener) + def perm = new NegativePermission(permission) + + when: "Try to subscribe/unsubscribe listener" + perm.subscribe(listener) + perm.unsubscribe(listener) + + then: "Permission received interactions" + 1 * permission.subscribe(listener) + 1 * permission.unsubscribe(listener) + 0 * listener._ + } + + def "Test blank description"() { + setup: "Permission with blank description" + IPermission permission = Mock(IChangeablePermission) { + getResolver() >> PermissionResolver.createForKey("test.key", 1) + getDescription() >> "" + } + + when: "Create new NegativePermission" + def perm = new NegativePermission(permission) + + then: "Description is blank" + perm.getDescription() == "" + } +} diff --git a/src/test/groovy/org/ultramine/permission/PermissionResolverTest.groovy b/src/test/groovy/org/ultramine/permission/PermissionResolverTest.groovy new file mode 100644 index 0000000..c08765a --- /dev/null +++ b/src/test/groovy/org/ultramine/permission/PermissionResolverTest.groovy @@ -0,0 +1,143 @@ +package org.ultramine.permission + +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Created by Евгений on 08.05.2014. + */ +class PermissionResolverTest extends Specification { + + def setupSpec() { + PermissionResolver.metaClass.addEntry = { String key, Boolean value, Integer prio -> + delegate.permissions.put(key, value) + delegate.priorities.put(key, prio) + } + } + + @Unroll + def "Test createForKey: #key"() { + when: "Creating resolver for key" + def resolver = PermissionResolver.createForKey(key, 0) + + then: "Resolver has this key" + resolver.has(key) + + where: + key << ["test.key", "super.test.*", "^group.admin"] + } + + def "Test createInverted"() { + setup: + def resolver = new PermissionResolver() + resolver.addEntry("p.true", true, 0) + resolver.addEntry("p.false", false, 0) + + when: "Create inverted resolver" + def inverted = PermissionResolver.createInverted(resolver) + + then: "Permission are inverted" + inverted.has("p.false") + !inverted.has("p.true") + + and: "New permissions are not created" + !inverted.has("group.admin") + } + + def "Test wildcard"() { + setup: "Resolver with wildcard permission" + def resolver = new PermissionResolver() + resolver.addEntry("test.perm.*", true, 0) + + expect: "Other permissions are not affected" + !resolver.has("group.admin") + !resolver.has("group.admin.super") + + and: "Parent nodes are not affected" + !resolver.has("test") + !resolver.has("test.perm") + + and: "Child nodes are affected" + resolver.has("test.perm.1") + resolver.has("test.perm.2.3") + } + + def "Test single permission override wildcard"() { + setup: "Resolver with wildcard and permission" + def resolver = new PermissionResolver() + resolver.addEntry("test.perm.*", true, 1) + resolver.addEntry("test.perm.super", false, 0) + + expect: "Wildcard has lower priority" + !resolver.has("test.perm.super") + resolver.has("test.perm.super2") + + when: "Invert resolver" + resolver = PermissionResolver.createInverted(resolver) + + then: "Same effect" + resolver.has("test.perm.super") + !resolver.has("test.perm.super2") + } + + def "Test clear"() { + setup: + def resolver = new PermissionResolver() + resolver.addEntry("test.perm", true, 0) + + when: "Clear resolver's data" + resolver.clear() + + then: "It has no permissions" + !resolver.has("test.perm") + } + + def "Test merge"() { + setup: "First resolver" + def resolver1 = new PermissionResolver() + resolver1.addEntry("test.perm", true, 1) + resolver1.addEntry("test.perm.1", true, 1) + resolver1.addEntry("test.perm.2", false, 1) + + and: "Second resolver" + def resolver2 = new PermissionResolver() + resolver2.addEntry("test.perm", false, 0) + resolver2.addEntry("test.perm.1", false, 2) + resolver2.addEntry("test.perm.3", true, 2) + + when: "Merging first then second" + def result = new PermissionResolver() + result.merge(resolver1, 1) + result.merge(resolver2, 2) + + then: + !result.has("test.perm") + !result.has("test.perm.1") + !result.has("test.perm.2") + result.has("test.perm.3") + !result.has("group.admin") + + when: "Merge second then first" + result = new PermissionResolver() + result.merge(resolver2, 2) + result.merge(resolver1, 1) + + then: "Same effect" + !result.has("test.perm") + !result.has("test.perm.1") + !result.has("test.perm.2") + result.has("test.perm.3") + !result.has("group.admin") + + when: "Merge first to second" + resolver2.merge(resolver1, 1) + + then: + resolver2.has("test.perm") + !resolver2.has("test.perm.1") + !resolver2.has("test.perm.2") + resolver2.has("test.perm.3") + !resolver2.has("group.admin") + + } +}