diff --git a/AGENTS.md b/AGENTS.md index 32fa5e16fa..66c829d305 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,8 @@ mvn -B -f AdvancedCore/pom.xml test mvn -B -f AdvancedCore/pom.xml package ``` +All test classes and test-only helpers must live under `com.bencodez.advancedcore.tests` or its subpackages, with matching directories under `AdvancedCore/src/test/java`. Keep tests out of production packages. Preserve production visibility when relocating tests; use narrowly scoped test-only reflective helpers when package-private access is required. Preserve test discovery and the package-phase artifact tests. + Confirm the current workflow and POM before relying on these commands. Do not use deployment, install, or developer-copy profiles for routine validation. Verify that the package invocation produced a fresh JAR, record discovered test counts, and run `git diff --check`. ## Architecture and compatibility boundaries diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/AdvancedCoreUuidTabCompletionRefreshTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/AdvancedCoreUuidTabCompletionRefreshTest.java similarity index 80% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/AdvancedCoreUuidTabCompletionRefreshTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/AdvancedCoreUuidTabCompletionRefreshTest.java index 34832542d5..50e75c1344 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/AdvancedCoreUuidTabCompletionRefreshTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/AdvancedCoreUuidTabCompletionRefreshTest.java @@ -1,4 +1,9 @@ -package com.bencodez.advancedcore; +package com.bencodez.advancedcore.tests; + +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; + +import com.bencodez.advancedcore.AdvancedCoreConfigOptions; +import com.bencodez.advancedcore.AdvancedCorePlugin; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -46,15 +51,18 @@ void storageEnumerationIsDeferredAndTheLatestRefreshWins() { return null; }).when(scheduler).runTask(any(), any()); - AdvancedCorePlugin.UuidTabCompletionRefresh refresh = new AdvancedCorePlugin.UuidTabCompletionRefresh(); + Class refreshType = ReflectiveAccess.type(AdvancedCorePlugin.class.getName() + "$UuidTabCompletionRefresh"); + Object refresh = ReflectiveAccess.construct(refreshType, new Class[0]); try (MockedStatic bukkit = org.mockito.Mockito.mockStatic(Bukkit.class)) { bukkit.when(Bukkit::getOnlinePlayers).thenReturn(List.of()); - refresh.request(plugin, handle); + ReflectiveAccess.invoke(refreshType, refresh, "request", + new Class[] { AdvancedCorePlugin.class, TabCompleteHandle.class }, plugin, handle); verify(users, never()).getAllUUIDs(); assertEquals(1, workers.size(), "request must enqueue, not enumerate on the caller thread"); - refresh.request(plugin, handle); + ReflectiveAccess.invoke(refreshType, refresh, "request", + new Class[] { AdvancedCorePlugin.class, TabCompleteHandle.class }, plugin, handle); assertEquals(1, workers.size(), "overlapping requests must be coalesced"); workers.remove(0).run(); diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/command/PlayerCommandHandlerTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/command/PlayerCommandHandlerTest.java similarity index 99% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/command/PlayerCommandHandlerTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/command/PlayerCommandHandlerTest.java index a9b02802a1..1079818002 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/command/PlayerCommandHandlerTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/command/PlayerCommandHandlerTest.java @@ -1,4 +1,6 @@ -package com.bencodez.advancedcore.api.command; +package com.bencodez.advancedcore.tests.api.command; + +import com.bencodez.advancedcore.api.command.PlayerCommandHandler; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptEngineConcurrencyTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptEngineConcurrencyTest.java similarity index 79% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptEngineConcurrencyTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptEngineConcurrencyTest.java index 20c75a0c0c..e5aa80ad69 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptEngineConcurrencyTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptEngineConcurrencyTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.api.javascript; +package com.bencodez.advancedcore.tests.api.javascript; + +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; + +import com.bencodez.advancedcore.api.javascript.JavascriptEngine; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -33,11 +37,11 @@ void sharedEngineBindingsAndEvaluationAreAtomic() throws Exception { try { Future first = executor.submit(() -> { start.await(); - return JavascriptEngine.evaluateWithBindings(engine, "value", Map.of("value", "first")); + return evaluate(engine, Map.of("value", "first")); }); Future second = executor.submit(() -> { start.await(); - return JavascriptEngine.evaluateWithBindings(engine, "value", Map.of("value", "second")); + return evaluate(engine, Map.of("value", "second")); }); start.countDown(); @@ -49,6 +53,11 @@ void sharedEngineBindingsAndEvaluationAreAtomic() throws Exception { } } + private static Object evaluate(javax.script.ScriptEngine engine, Map bindings) { + return ReflectiveAccess.invoke(JavascriptEngine.class, null, "evaluateWithBindings", + new Class[] { javax.script.ScriptEngine.class, String.class, Map.class }, engine, "value", bindings); + } + private static final class LockCheckingScriptEngine extends AbstractScriptEngine { private final AtomicBoolean allEvaluationsHeldLock = new AtomicBoolean(true); diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptPlaceholderBinderTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptPlaceholderBinderTest.java similarity index 75% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptPlaceholderBinderTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptPlaceholderBinderTest.java index b22af5a01e..6c6812c2a8 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptPlaceholderBinderTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptPlaceholderBinderTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.api.javascript; +package com.bencodez.advancedcore.tests.api.javascript; + +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; + +import com.bencodez.advancedcore.api.javascript.JavascriptPlaceholderBinder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -10,13 +14,20 @@ import org.junit.jupiter.api.Test; class JavascriptPlaceholderBinderTest { + private static String bind(String expression, java.util.function.Function resolver, + java.util.function.BiConsumer bindings) { + return ReflectiveAccess.invoke(JavascriptPlaceholderBinder.class, null, "bind", + new Class[] { String.class, java.util.function.Function.class, java.util.function.BiConsumer.class }, + expression, resolver, bindings); + } + @Test void barePlaceholderIsBoundAsData() { HashMap bindings = new HashMap<>(); String injection = "Bukkit.dispatchCommand(Console, 'op attacker')"; - String prepared = JavascriptPlaceholderBinder.bind("%value% == true", ignored -> injection, bindings::put); + String prepared = bind("%value% == true", ignored -> injection, bindings::put); assertEquals("__advancedCorePlaceholder0 == true", prepared); assertEquals(injection, bindings.get("__advancedCorePlaceholder0")); @@ -27,7 +38,7 @@ void barePlaceholderIsBoundAsData() { void numericAndBooleanValuesKeepExpressionTypes() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("%count% > 1 && %allowed% == true", + String prepared = bind("%count% > 1 && %allowed% == true", token -> token.equals("%count%") ? "2.5" : "true", bindings::put); assertEquals("__advancedCorePlaceholder0 > 1 && __advancedCorePlaceholder1 == true", prepared); @@ -39,7 +50,7 @@ void numericAndBooleanValuesKeepExpressionTypes() { void ordinaryCompactModuloExpressionIsNotTreatedAsAPlaceholder() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("10%3%2", token -> token, bindings::put); + String prepared = bind("10%3%2", token -> token, bindings::put); assertEquals("10%3%2", prepared); assertTrue(bindings.isEmpty()); @@ -49,7 +60,7 @@ void ordinaryCompactModuloExpressionIsNotTreatedAsAPlaceholder() { void unresolvedPercentTokenRemainsUntouched() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("'%unknown%'", token -> token, bindings::put); + String prepared = bind("'%unknown%'", token -> token, bindings::put); assertEquals("'%unknown%'", prepared); assertTrue(bindings.isEmpty()); @@ -59,7 +70,7 @@ void unresolvedPercentTokenRemainsUntouched() { void compactModuloCanAppearBesideAResolvedPlaceholder() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("10%3%2 + %value%", + String prepared = bind("10%3%2 + %value%", token -> token.equals("%value%") ? "4" : token, bindings::put); assertEquals("10%3%2 + __advancedCorePlaceholder0", prepared); @@ -70,7 +81,7 @@ void compactModuloCanAppearBesideAResolvedPlaceholder() { void moduloCandidateCannotHideAnOverlappingResolvedPlaceholder() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("10%3 == 1 && %value% == true", + String prepared = bind("10%3 == 1 && %value% == true", token -> token.equals("%value%") ? "true" : token, bindings::put); assertEquals("10%3 == 1 && __advancedCorePlaceholder0 == true", prepared); @@ -81,7 +92,7 @@ void moduloCandidateCannotHideAnOverlappingResolvedPlaceholder() { void placeholderApiStyleTokenMayContainWhitespace() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("%exp_argument with spaces% == true", + String prepared = bind("%exp_argument with spaces% == true", ignored -> "true", bindings::put); assertEquals("__advancedCorePlaceholder0 == true", prepared); @@ -92,7 +103,7 @@ void placeholderApiStyleTokenMayContainWhitespace() { void quotedAndEmbeddedPlaceholdersStayStrings() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("'%code%' === '001' && 'Hello %name%!' === 'Hello Ben!'", + String prepared = bind("'%code%' === '001' && 'Hello %name%!' === 'Hello Ben!'", token -> token.equals("%code%") ? "001" : "Ben", bindings::put); assertEquals("'001' === '001' && 'Hello Ben!' === 'Hello Ben!'", prepared); @@ -104,7 +115,7 @@ void stringInjectionIsEscaped() { HashMap bindings = new HashMap<>(); String injection = "'; Bukkit.dispatchCommand(Console, 'op attacker'); '"; - String prepared = JavascriptPlaceholderBinder.bind("'%name%' == 'safe'", ignored -> injection, bindings::put); + String prepared = bind("'%name%' == 'safe'", ignored -> injection, bindings::put); assertEquals("'\\'; Bukkit.dispatchCommand(Console, \\'op attacker\\'); \\'' == 'safe'", prepared); assertTrue(bindings.isEmpty()); @@ -113,7 +124,7 @@ void stringInjectionIsEscaped() { @Test void templateTextAndExpressionUseDifferentContexts() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("`Hello %name% ${%count% + 1}`", + String prepared = bind("`Hello %name% ${%count% + 1}`", token -> token.equals("%name%") ? "${attack()}`" : "2", bindings::put); assertEquals("`Hello \\${attack()}\\` ${__advancedCorePlaceholder0 + 1}`", prepared); @@ -124,7 +135,7 @@ void templateTextAndExpressionUseDifferentContexts() { void oddBackslashBeforeTemplatePlaceholderCannotReactivateInterpolation() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("`\\%value%`", ignored -> "${attack()}", bindings::put); + String prepared = bind("`\\%value%`", ignored -> "${attack()}", bindings::put); assertEquals("`\\${attack()}`", prepared); assertTrue(bindings.isEmpty()); @@ -134,7 +145,7 @@ void oddBackslashBeforeTemplatePlaceholderCannotReactivateInterpolation() { void evenBackslashesBeforeTemplatePlaceholderRemainLiteralAndSafe() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("`\\\\%value%`", ignored -> "${attack()}", bindings::put); + String prepared = bind("`\\\\%value%`", ignored -> "${attack()}", bindings::put); assertEquals("`\\\\\\${attack()}`", prepared); assertTrue(bindings.isEmpty()); @@ -145,7 +156,7 @@ void oddBackslashBeforeStringPlaceholderCannotReactivateQuote() { HashMap bindings = new HashMap<>(); String injection = "'; attack(); '"; - String prepared = JavascriptPlaceholderBinder.bind("'\\%value%'", ignored -> injection, bindings::put); + String prepared = bind("'\\%value%'", ignored -> injection, bindings::put); assertEquals("'\\'; attack(); \\''", prepared); assertTrue(bindings.isEmpty()); @@ -155,7 +166,7 @@ void oddBackslashBeforeStringPlaceholderCannotReactivateQuote() { void regexPlaceholderIsEscapedAsLiteralPatternText() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("/^%name%$/i.test(value)", + String prepared = bind("/^%name%$/i.test(value)", ignored -> "Ben.*", bindings::put); assertEquals("/^Ben\\.\\*$/i.test(value)", prepared); @@ -166,7 +177,7 @@ void regexPlaceholderIsEscapedAsLiteralPatternText() { void oddBackslashBeforeRegexPlaceholderCannotReactivateDelimiter() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("/\\%value%/.test(input)", + String prepared = bind("/\\%value%/.test(input)", ignored -> "/; attack() //", bindings::put); assertEquals("/\\/; attack\\(\\) \\/\\//.test(input)", prepared); @@ -177,7 +188,7 @@ void oddBackslashBeforeRegexPlaceholderCannotReactivateDelimiter() { void placeholdersInCommentsAreNotEvaluatedOrRewritten() { HashMap bindings = new HashMap<>(); - String prepared = JavascriptPlaceholderBinder.bind("obj?.name; /* %name% */ true", + String prepared = bind("obj?.name; /* %name% */ true", ignored -> "danger()", bindings::put); assertEquals("obj?.name; /* %name% */ true", prepared); @@ -189,7 +200,7 @@ void invalidPlaceholderBearingJavascriptFailsClosed() { HashMap bindings = new HashMap<>(); assertThrows(IllegalArgumentException.class, - () -> JavascriptPlaceholderBinder.bind("if ( %name%", ignored -> "Ben", bindings::put)); + () -> bind("if ( %name%", ignored -> "Ben", bindings::put)); assertTrue(bindings.isEmpty()); } } diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptSecurityFindingRegressionTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptSecurityFindingRegressionTest.java similarity index 98% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptSecurityFindingRegressionTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptSecurityFindingRegressionTest.java index b30e2178a7..f82e7e6188 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptSecurityFindingRegressionTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptSecurityFindingRegressionTest.java @@ -1,4 +1,4 @@ -package com.bencodez.advancedcore.api.javascript; +package com.bencodez.advancedcore.tests.api.javascript; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyString; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptTextTemplateTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptTextTemplateTest.java similarity index 92% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptTextTemplateTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptTextTemplateTest.java index ea9eca6bb7..aaa5571d17 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/javascript/JavascriptTextTemplateTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/javascript/JavascriptTextTemplateTest.java @@ -1,4 +1,6 @@ -package com.bencodez.advancedcore.api.javascript; +package com.bencodez.advancedcore.tests.api.javascript; + +import com.bencodez.advancedcore.api.javascript.JavascriptTextTemplate; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/PlaceholderUtilsJavascriptBoundaryTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/messages/PlaceholderUtilsJavascriptBoundaryTest.java similarity index 93% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/PlaceholderUtilsJavascriptBoundaryTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/messages/PlaceholderUtilsJavascriptBoundaryTest.java index adc86acbce..6568939125 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/messages/PlaceholderUtilsJavascriptBoundaryTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/messages/PlaceholderUtilsJavascriptBoundaryTest.java @@ -1,4 +1,6 @@ -package com.bencodez.advancedcore.api.messages; +package com.bencodez.advancedcore.tests.api.messages; + +import com.bencodez.advancedcore.api.messages.PlaceholderUtils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/misc/PlayerManagerDamageItemTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/misc/PlayerManagerDamageItemTest.java similarity index 86% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/misc/PlayerManagerDamageItemTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/misc/PlayerManagerDamageItemTest.java index a43857c188..fa882a7e96 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/misc/PlayerManagerDamageItemTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/misc/PlayerManagerDamageItemTest.java @@ -1,4 +1,9 @@ -package com.bencodez.advancedcore.api.misc; +package com.bencodez.advancedcore.tests.api.misc; + +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; + +import com.bencodez.advancedcore.api.misc.MiscUtils; +import com.bencodez.advancedcore.api.misc.PlayerManager; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -25,6 +30,12 @@ import org.mockito.MockedStatic; class PlayerManagerDamageItemTest { + private static int sampleDamage(int attempts, int chance, long cap, java.util.random.RandomGenerator random) { + return ReflectiveAccess.invoke(PlayerManager.class, null, "sampleDamage", + new Class[] { int.class, int.class, long.class, java.util.random.RandomGenerator.class }, + attempts, chance, cap, random); + } + private final PlayerManager manager = PlayerManager.getInstance(); @Test @@ -104,7 +115,7 @@ void boundedSamplerRetainsBinomialMoments() { double sum = 0; double squared = 0; for (int i = 0; i < count; i++) { - int hits = PlayerManager.sampleDamage(1_000, 50, 1_001, random); + int hits = sampleDamage(1_000, 50, 1_001, random); sum += hits; squared += (double) hits * hits; } @@ -112,8 +123,8 @@ void boundedSamplerRetainsBinomialMoments() { double variance = squared / count - mean * mean; assertEquals(500, mean, 1); assertEquals(250, variance, 10); - assertEquals(0, PlayerManager.sampleDamage(100, 0, 10, random)); - assertEquals(10, PlayerManager.sampleDamage(Integer.MAX_VALUE, 100, 10, random)); + assertEquals(0, sampleDamage(100, 0, 10, random)); + assertEquals(10, sampleDamage(Integer.MAX_VALUE, 100, 10, random)); } private static final class Fixture { diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/permissions/PermissionAccess.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/permissions/PermissionAccess.java new file mode 100644 index 0000000000..85ef36dd19 --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/permissions/PermissionAccess.java @@ -0,0 +1,40 @@ +package com.bencodez.advancedcore.tests.api.permissions; + +import java.util.UUID; + +import com.bencodez.advancedcore.api.permissions.PermissionHandler; +import com.bencodez.advancedcore.api.permissions.PlayerPermissionHandler; +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; + +/** Typed calls also support Mockito stubbing and verification of the original methods. */ +final class PermissionAccess { + private PermissionAccess() { } + + static boolean isExpirationCurrent(PlayerPermissionHandler handler, String permission, long expiry) { + return ReflectiveAccess.invoke(PlayerPermissionHandler.class, handler, "isExpirationCurrent", + new Class[] { String.class, long.class }, permission, expiry); + } + + static void expirePermission(PlayerPermissionHandler handler, String permission, long expiry, boolean attachment) { + ReflectiveAccess.invoke(PlayerPermissionHandler.class, handler, "expirePermission", + new Class[] { String.class, long.class, boolean.class }, permission, expiry, attachment); + } + + static void scheduleExpiration(PermissionHandler handler, PlayerPermissionHandler player, String permission, + long expiry, long delay) { + ReflectiveAccess.invoke(PermissionHandler.class, handler, "scheduleExpiration", + new Class[] { PlayerPermissionHandler.class, String.class, long.class, long.class }, + player, permission, expiry, delay); + } + + static void dispatchExpiration(PermissionHandler handler, PlayerPermissionHandler player, String permission, long expiry) { + ReflectiveAccess.invoke(PermissionHandler.class, handler, "dispatchExpiration", + new Class[] { PlayerPermissionHandler.class, String.class, long.class }, player, permission, expiry); + } + + static void removePermissionIfEmpty(PermissionHandler handler, UUID uuid, PlayerPermissionHandler player, + boolean offline) { + ReflectiveAccess.invoke(PermissionHandler.class, handler, "removePermissionIfEmpty", + new Class[] { UUID.class, PlayerPermissionHandler.class, boolean.class }, uuid, player, offline); + } +} diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/permissions/PlayerPermissionHandlerThreadingTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/permissions/PlayerPermissionHandlerThreadingTest.java similarity index 86% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/permissions/PlayerPermissionHandlerThreadingTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/permissions/PlayerPermissionHandlerThreadingTest.java index fdd035cf48..bc335ef51c 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/permissions/PlayerPermissionHandlerThreadingTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/permissions/PlayerPermissionHandlerThreadingTest.java @@ -1,4 +1,9 @@ -package com.bencodez.advancedcore.api.permissions; +package com.bencodez.advancedcore.tests.api.permissions; + +import com.bencodez.advancedcore.api.permissions.PermissionHandler; +import com.bencodez.advancedcore.api.permissions.PlayerPermissionHandler; + +import static com.bencodez.advancedcore.tests.api.permissions.PermissionAccess.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; @@ -51,20 +56,20 @@ void entitySchedulerRejectionRetriesCurrentExpiration() { UUID uuid = UUID.randomUUID(); long expiry = System.currentTimeMillis(); when(handle.getUuid()).thenReturn(uuid); - when(handle.isExpirationCurrent("example.use", expiry)).thenReturn(true); + when(isExpirationCurrent(handle, "example.use", expiry)).thenReturn(true); Player player = mock(Player.class); doAnswer(call -> { ((Runnable) call.getArgument(1)).run(); return null; }) .when(scheduler).runTask(eq(plugin), any(Runnable.class)); doThrow(new IllegalStateException("entity retired")) .when(scheduler).runTask(eq(plugin), any(Runnable.class), eq(player)); - doNothing().when(manager).scheduleExpiration(handle, "example.use", expiry, 1_000L); + scheduleExpiration(doNothing().when(manager), handle, "example.use", expiry, 1_000L); try (MockedStatic bukkit = mockStatic(Bukkit.class)) { bukkit.when(Bukkit::getServer).thenReturn(mock(Server.class)); bukkit.when(() -> Bukkit.getPlayer(uuid)).thenReturn(player); - manager.dispatchExpiration(handle, "example.use", expiry); - verify(manager).scheduleExpiration(handle, "example.use", expiry, 1_000L); - verify(handle, never()).expirePermission(anyString(), anyLong(), anyBoolean()); + dispatchExpiration(manager, handle, "example.use", expiry); + scheduleExpiration(verify(manager), handle, "example.use", expiry, 1_000L); + expirePermission(verify(handle, never()), anyString(), anyLong(), anyBoolean()); } finally { manager.getTimer().shutdownNow(); } } @@ -82,7 +87,7 @@ void staleExpirationCannotRevokeExtendedPermission() { assertTrue(extendedExpiry >= firstExpiry); clearInvocations(attachment); - handler.expirePermission("example.use", firstExpiry, true); + expirePermission(handler, "example.use", firstExpiry, true); assertEquals(extendedExpiry, handler.getTimedPermissions().get("example.use")); verify(attachment, never()).unsetPermission("example.use"); @@ -99,7 +104,7 @@ void offOwnerExpirationUpdatesStateWithoutTouchingAttachment() { long expiry = System.currentTimeMillis() - 1L; handler.getTimedPermissions().put("example.use", expiry); clearInvocations(attachment); - handler.expirePermission("example.use", expiry, false); + expirePermission(handler, "example.use", expiry, false); assertFalse(handler.getTimedPermissions().containsKey("example.use")); verify(attachment, never()).unsetPermission(anyString()); @@ -118,9 +123,9 @@ void offlineExpirationDropsEmptyHandlerDespiteStaleAttachmentState() { handler.getTimedPermissions().put("example.use", expiry); clearInvocations(attachment); - handler.expirePermission("example.use", expiry, false); + expirePermission(handler, "example.use", expiry, false); - verify(manager).removePermissionIfEmpty(uuid, handler, true); + removePermissionIfEmpty(verify(manager), uuid, handler, true); verify(attachment, never()).unsetPermission(anyString()); verify(attachment, never()).setPermission(anyString(), anyBoolean()); } @@ -135,7 +140,7 @@ void expirationClearsAttachmentEntryInsteadOfInstallingDenial() { long expiry = System.currentTimeMillis() - 1L; handler.getTimedPermissions().put("example.use", expiry); clearInvocations(attachment); - handler.expirePermission("example.use", expiry, true); + expirePermission(handler, "example.use", expiry, true); verify(attachment).unsetPermission("example.use"); verify(attachment, never()).setPermission("example.use", false); } @@ -149,10 +154,10 @@ void earlyTimerReschedulesInsteadOfRevokingPermission() { long expiry = handler.getTimedPermissions().get("example.use"); clearInvocations(manager, attachment); - handler.expirePermission("example.use", expiry, true); + expirePermission(handler, "example.use", expiry, true); - assertTrue(handler.isExpirationCurrent("example.use", expiry)); - verify(manager).scheduleExpiration(eq(handler), eq("example.use"), eq(expiry), longThat(delay -> delay > 0)); + assertTrue(isExpirationCurrent(handler, "example.use", expiry)); + scheduleExpiration(verify(manager), eq(handler), eq("example.use"), eq(expiry), longThat(delay -> delay > 0)); verify(attachment, never()).unsetPermission(anyString()); } @@ -166,7 +171,7 @@ void oldTimedExpirationPreservesQueuedRegrant() { handler.getTimedPermissions().put("example.use", expiry); handler.addOfflinePerm("example.use", ParsedDuration.empty()); - handler.expirePermission("example.use", expiry, false); + expirePermission(handler, "example.use", expiry, false); handler.setAttachment(attachment); handler.onLogin(mock(Player.class)); @@ -233,23 +238,23 @@ void offlineExpirationCannotRaceACompletedLogin() { PlayerPermissionHandler handle = mock(PlayerPermissionHandler.class); when(handle.getUuid()).thenReturn(uuid); long expiry = System.currentTimeMillis() - 1L; - when(handle.isExpirationCurrent("example.use", expiry)).thenReturn(true); + when(isExpirationCurrent(handle, "example.use", expiry)).thenReturn(true); manager.getPermsToAdd().put(uuid, handle); java.util.concurrent.atomic.AtomicReference global = new java.util.concurrent.atomic.AtomicReference<>(); doAnswer(call -> { global.set(call.getArgument(1)); return null; }) .when(scheduler).runTask(eq(plugin), any(Runnable.class)); - doNothing().when(manager).scheduleExpiration(handle, "example.use", expiry, 1_000L); + scheduleExpiration(doNothing().when(manager), handle, "example.use", expiry, 1_000L); Player player = mock(Player.class); when(player.getUniqueId()).thenReturn(uuid); when(player.addAttachment(plugin)).thenReturn(mock(PermissionAttachment.class)); try (MockedStatic bukkit = mockStatic(Bukkit.class)) { bukkit.when(() -> Bukkit.getPlayer(uuid)).thenReturn(null); - manager.dispatchExpiration(handle, "example.use", expiry); + dispatchExpiration(manager, handle, "example.use", expiry); manager.login(player); global.get().run(); - verify(handle, never()).expirePermission("example.use", expiry, false); - verify(manager).scheduleExpiration(handle, "example.use", expiry, 1_000L); + expirePermission(verify(handle, never()), "example.use", expiry, false); + scheduleExpiration(verify(manager), handle, "example.use", expiry, 1_000L); } finally { manager.getTimer().shutdownNow(); } } diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/rewards/RewardOptionsTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/rewards/RewardOptionsTest.java similarity index 72% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/rewards/RewardOptionsTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/rewards/RewardOptionsTest.java index 64125590d4..589a81836c 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/rewards/RewardOptionsTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/rewards/RewardOptionsTest.java @@ -1,4 +1,9 @@ -package com.bencodez.advancedcore.api.rewards; +package com.bencodez.advancedcore.tests.api.rewards; + +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; + +import com.bencodez.advancedcore.api.rewards.Reward; +import com.bencodez.advancedcore.api.rewards.RewardOptions; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -9,8 +14,9 @@ class RewardOptionsTest { @Test void nestedDispatchPreservesCapturedLivePlayerState() { - RewardOptions nested = new RewardOptions().captureLivePlayerState(false, true) - .copyForNestedDispatch("parent/child:0"); + RewardOptions nested = ReflectiveAccess.invoke(RewardOptions.class, + new RewardOptions().captureLivePlayerState(false, true), "copyForNestedDispatch", + new Class[] { String.class }, "parent/child:0"); assertTrue(nested.isLivePlayerStateSet()); assertFalse(nested.isOnline()); diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/time/TimeCheckerRecurringTaskTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/time/TimeCheckerRecurringTaskTest.java similarity index 95% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/time/TimeCheckerRecurringTaskTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/time/TimeCheckerRecurringTaskTest.java index cfa8c56625..4a6c2fa64f 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/time/TimeCheckerRecurringTaskTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/time/TimeCheckerRecurringTaskTest.java @@ -1,4 +1,6 @@ -package com.bencodez.advancedcore.api.time; +package com.bencodez.advancedcore.tests.api.time; + +import com.bencodez.advancedcore.api.time.TimeChecker; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/presence/CompleteOnlinePresenceTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/CompleteOnlinePresenceTest.java similarity index 93% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/presence/CompleteOnlinePresenceTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/CompleteOnlinePresenceTest.java index 46a27eda29..e2a471a0e2 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/presence/CompleteOnlinePresenceTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/CompleteOnlinePresenceTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.api.user.presence; +package com.bencodez.advancedcore.tests.api.user.presence; + +import com.bencodez.advancedcore.api.user.presence.PlayerPresence; +import com.bencodez.advancedcore.api.user.presence.PlayerPresenceLookup; +import com.bencodez.advancedcore.api.user.presence.PlayerPresenceService; import static org.junit.jupiter.api.Assertions.*; import java.util.ArrayDeque; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/presence/PlayerHistoryStabilityTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/PlayerHistoryStabilityTest.java similarity index 89% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/presence/PlayerHistoryStabilityTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/PlayerHistoryStabilityTest.java index 132cb9df72..63d5dcef0d 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/presence/PlayerHistoryStabilityTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/PlayerHistoryStabilityTest.java @@ -1,4 +1,7 @@ -package com.bencodez.advancedcore.api.user.presence; +package com.bencodez.advancedcore.tests.api.user.presence; + +import com.bencodez.advancedcore.api.user.presence.BukkitPlayerPresenceLookup; +import com.bencodez.advancedcore.api.user.presence.PlayerPresence; import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayOutputStream; @@ -62,7 +65,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void legacyUuidAbsenceAlsoRescansKeyedScopesWithCollidingTimes() throws Exception { BasicFileAttributes attrs = actual(directory); BasicFileAttributes fixed = metadata(attrs, new Object(), attrs.creationTime()); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? fixed : actual(path))) { assertEquals(PlayerPresence.NEVER_JOINED, lookup.check(USER)); Files.createTempFile(directory, USER + "-", ".dat"); @@ -75,7 +78,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k BasicFileAttributes fixed = metadata(attrs, new Object(), attrs.creationTime()); UUID other = UUID.fromString("b3f2bd99-5325-4a51-a642-12674e147381"); Files.createTempFile(directory, USER + "-", ".dat"); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? fixed : actual(path))) { assertEquals(PlayerPresence.UNKNOWN, lookup.check(USER)); Files.createTempFile(directory, other + "-", ".dat"); @@ -86,7 +89,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void keyedScopeWithCollidingDirectoryTimesMustDiscoverNewOfflineHistory() throws Exception { BasicFileAttributes attrs = actual(directory); BasicFileAttributes fixed = metadata(attrs, new Object(), attrs.creationTime()); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? fixed : actual(path))) { assertEquals(PlayerPresence.NEVER_JOINED, lookup.checkOnlineHistory(USER)); UUID former = UUID.nameUUIDFromBytes("OfflinePlayer:FormerSynthetic".getBytes(java.nio.charset.StandardCharsets.UTF_8)); @@ -99,7 +102,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k UUID legacy = UUID.fromString("00000000-0000-4000-0000-000000000001"); assertEquals(4, legacy.version()); assertNotEquals(2, legacy.variant()); Files.write(directory.resolve(legacy + ".dat"), history(legacy)); - try (var lookup = new BukkitPlayerPresenceLookup(directory)) { + try (var lookup = PresenceLookupAccess.create(directory)) { assertEquals(PlayerPresence.UNKNOWN, lookup.checkOnlineHistory(USER)); } } @@ -109,7 +112,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k org.mockito.Mockito.when(player.getUniqueId()).thenReturn(UUID.fromString("00000000-0000-4000-0000-000000000001")); var event = org.mockito.Mockito.mock(org.bukkit.event.player.PlayerJoinEvent.class); org.mockito.Mockito.when(event.getPlayer()).thenReturn(player); - try (var lookup = new BukkitPlayerPresenceLookup(directory)) { + try (var lookup = PresenceLookupAccess.create(directory)) { lookup.onJoin(event); assertEquals(PlayerPresence.UNKNOWN, lookup.checkOnlineHistory(USER)); } @@ -117,7 +120,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void unsupportedOnlineAliasRetainsPositiveButCannotCertifyAbsentHistory() throws Exception { UUID legacy = UUID.fromString("00000000-0000-4000-0000-000000000001"); - try (var lookup = new BukkitPlayerPresenceLookup(directory)) { + try (var lookup = PresenceLookupAccess.create(directory)) { assertEquals(PlayerPresence.UNKNOWN, lookup.checkOnlineHistory(legacy)); Files.write(directory.resolve(legacy + ".dat"), history(legacy)); assertEquals(PlayerPresence.JOINED, lookup.checkOnlineHistory(legacy)); @@ -127,7 +130,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void completeOnlineOnlyScopeCanProveAbsenceWithoutChangingLegacyApi() throws Exception { UUID anotherOnline = UUID.fromString("b3f2bd99-5325-4a51-a642-12674e147381"); Files.write(directory.resolve(anotherOnline + ".dat"), history(anotherOnline)); - try (var lookup = new BukkitPlayerPresenceLookup(directory)) { + try (var lookup = PresenceLookupAccess.create(directory)) { assertEquals(PlayerPresence.NEVER_JOINED, lookup.checkOnlineHistory(USER)); assertEquals(PlayerPresence.NEVER_JOINED, lookup.check(USER)); } @@ -136,7 +139,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void formerOfflineNameBlocksOnlineNegativeWithoutGuessingOwnership() throws Exception { UUID former = UUID.nameUUIDFromBytes("OfflinePlayer:FormerSynthetic".getBytes(java.nio.charset.StandardCharsets.UTF_8)); byte[] retained = history(former); Path record = Files.write(directory.resolve(former + ".dat"), retained); - try (var lookup = new BukkitPlayerPresenceLookup(directory)) { + try (var lookup = PresenceLookupAccess.create(directory)) { assertEquals(PlayerPresence.NEVER_JOINED, lookup.check(USER)); assertEquals(PlayerPresence.UNKNOWN, lookup.checkOnlineHistory(USER)); assertArrayEquals(retained, Files.readAllBytes(record)); @@ -149,7 +152,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k UUID former = UUID.nameUUIDFromBytes("OfflinePlayer:FormerSynthetic".getBytes(java.nio.charset.StandardCharsets.UTF_8)); for (String suffix : new String[] { ".dat_old", ".dat.offline-read", "-temporary.dat", "_corrupted_1.dat_old" }) { Path retained = Files.write(directory.resolve(former + suffix), new byte[] {0}); - try (var lookup = new BukkitPlayerPresenceLookup(directory)) { + try (var lookup = PresenceLookupAccess.create(directory)) { assertEquals(PlayerPresence.UNKNOWN, lookup.checkOnlineHistory(USER), suffix); assertArrayEquals(new byte[] {0}, Files.readAllBytes(retained)); } finally { Files.delete(retained); } @@ -159,7 +162,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void nonOnlineUuidAndCaseInsensitiveRecoveryScopeCannotBecomeOnlineAbsence() throws Exception { UUID legacy = new UUID(0, 1); Path retained = Files.write(directory.resolve(legacy.toString().toUpperCase(java.util.Locale.ROOT) + "-SAVE.DAT"), new byte[] {0}); - try (var lookup = new BukkitPlayerPresenceLookup(directory)) { + try (var lookup = PresenceLookupAccess.create(directory)) { assertEquals(PlayerPresence.UNKNOWN, lookup.checkOnlineHistory(USER)); assertEquals(PlayerPresence.UNKNOWN, lookup.check(legacy)); assertArrayEquals(new byte[] {0}, Files.readAllBytes(retained)); @@ -168,7 +171,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void keylessScopeWithUnchangedTimesRescansNewOfflineRecords() throws Exception { BasicFileAttributes fixed = withoutKey(actual(directory)); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? fixed : withoutKey(actual(path)))) { assertEquals(PlayerPresence.NEVER_JOINED, lookup.checkOnlineHistory(USER)); UUID former = UUID.nameUUIDFromBytes("OfflinePlayer:FormerSynthetic".getBytes(java.nio.charset.StandardCharsets.UTF_8)); @@ -183,7 +186,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k org.mockito.Mockito.when(player.getUniqueId()).thenReturn(new UUID(0, 1)); var event = org.mockito.Mockito.mock(org.bukkit.event.player.PlayerJoinEvent.class); org.mockito.Mockito.when(event.getPlayer()).thenReturn(player); - try (var lookup = new BukkitPlayerPresenceLookup(directory)) { + try (var lookup = PresenceLookupAccess.create(directory)) { lookup.onJoin(event); assertEquals(PlayerPresence.UNKNOWN, lookup.checkOnlineHistory(USER)); assertEquals(PlayerPresence.JOINED, lookup.checkOnlineHistory(player.getUniqueId())); @@ -192,14 +195,14 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void completeKeylessRecordCanSupplyPositiveHistoryWithoutMutation() throws Exception { byte[] record = history(); Path file = Files.write(directory.resolve(USER + ".dat"), record); - try (var lookup = new BukkitPlayerPresenceLookup(directory, path -> withoutKey(actual(path)))) { + try (var lookup = PresenceLookupAccess.create(directory, path -> withoutKey(actual(path)))) { assertEquals(PlayerPresence.JOINED, lookup.check(USER)); assertArrayEquals(record, Files.readAllBytes(file)); } } @Test void keylessReadableUnchangedEmptyDirectoryCanSupplyNegativeHistory() { - try (var lookup = new BukkitPlayerPresenceLookup(directory, path -> withoutKey(actual(path)))) { + try (var lookup = PresenceLookupAccess.create(directory, path -> withoutKey(actual(path)))) { assertEquals(PlayerPresence.NEVER_JOINED, lookup.check(USER)); } } @@ -207,7 +210,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void readableValidCurrentRecordSurvivesUnreadableSibling() throws Exception { byte[] record = history(); Path file = Files.write(directory.resolve(USER + ".dat"), record); Path sibling = Files.write(directory.resolve(USER + ".dat_old"), new byte[] {0}); - try (var lookup = new BukkitPlayerPresenceLookup(directory, path -> { + try (var lookup = PresenceLookupAccess.create(directory, path -> { if (path.equals(sibling)) throw new AccessDeniedException("synthetic"); return actual(path); })) { @@ -219,7 +222,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void unreadableCurrentRecordDoesNotHideReadableBackupEvidence() throws Exception { Path current = Files.write(directory.resolve(USER + ".dat"), new byte[] {0}); byte[] record = history(); Path backup = Files.write(directory.resolve(USER + ".dat_old"), record); - try (var lookup = new BukkitPlayerPresenceLookup(directory, path -> { + try (var lookup = PresenceLookupAccess.create(directory, path -> { if (path.equals(current)) throw new AccessDeniedException("synthetic"); return actual(path); })) { @@ -228,7 +231,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k } } @Test void absentKeysAndUnavailableCreationMetadataStayUnknown() throws Exception { - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> metadata(actual(path), null, FileTime.fromMillis(0)))) { assertEquals(PlayerPresence.UNKNOWN, lookup.check(USER)); Files.write(directory.resolve(USER + ".dat"), history()); @@ -238,7 +241,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void changedKeylessDirectoryCreationCannotSupplyNegativeProof() { var reads = new java.util.concurrent.atomic.AtomicInteger(); - try (var lookup = new BukkitPlayerPresenceLookup(directory, path -> { + try (var lookup = PresenceLookupAccess.create(directory, path -> { var attrs = actual(path); FileTime created = attrs.creationTime(); if (path.equals(directory) && reads.incrementAndGet() > 1) @@ -252,7 +255,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void changedKeylessRecordCreationCannotSupplyPositiveProof() throws Exception { Path file = Files.write(directory.resolve(USER + ".dat"), history()); var reads = new java.util.concurrent.atomic.AtomicInteger(); - try (var lookup = new BukkitPlayerPresenceLookup(directory, path -> { + try (var lookup = PresenceLookupAccess.create(directory, path -> { var attrs = actual(path); FileTime created = attrs.creationTime(); if (path.equals(file) && reads.incrementAndGet() > 1) created = FileTime.fromMillis(created.toMillis() + 1000); @@ -265,7 +268,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void disappearingAvailableRecordKeyCannotSupplyPositiveProof() throws Exception { Path file = Files.write(directory.resolve(USER + ".dat"), history()); var reads = new java.util.concurrent.atomic.AtomicInteger(); - try (var lookup = new BukkitPlayerPresenceLookup(directory, path -> { + try (var lookup = PresenceLookupAccess.create(directory, path -> { var attrs = actual(path); if (path.equals(file) && reads.incrementAndGet() > 1) return withoutKey(attrs); return attrs; @@ -275,7 +278,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k } @Test void unreadableAbsentCandidateCannotSupplyNegativeProof() { - try (var lookup = new BukkitPlayerPresenceLookup(directory, path -> { + try (var lookup = PresenceLookupAccess.create(directory, path -> { if (path.equals(directory.resolve(USER + ".dat"))) throw new AccessDeniedException("synthetic"); return actual(path); })) { @@ -285,7 +288,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void keylessRecoveryRescansEvenWhenDirectoryTimestampsAreUnchanged() throws Exception { BasicFileAttributes unchangedScope = withoutKey(actual(directory)); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? unchangedScope : withoutKey(actual(path)))) { assertEquals(PlayerPresence.NEVER_JOINED, lookup.check(USER)); Files.createTempFile(directory, USER + "-", ".dat"); @@ -296,7 +299,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k @Test void keylessReplacementWithMatchingTimestampsCannotReuseAbsentRecovery() throws Exception { BasicFileAttributes unchangedScope = withoutKey(actual(directory)); Path displaced = directory.resolveSibling(directory.getFileName() + "-displaced"); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? unchangedScope : withoutKey(actual(path)))) { assertEquals(PlayerPresence.NEVER_JOINED, lookup.check(USER)); Files.move(directory, displaced); @@ -309,7 +312,7 @@ private static BasicFileAttributes metadata(BasicFileAttributes source, Object k } @Test void keylessCachedRecoveryIndexRefreshesAfterDirectoryChange() throws Exception { - try (var lookup = new BukkitPlayerPresenceLookup(directory, path -> withoutKey(actual(path)))) { + try (var lookup = PresenceLookupAccess.create(directory, path -> withoutKey(actual(path)))) { assertEquals(PlayerPresence.NEVER_JOINED, lookup.check(USER)); Path retained = Files.createTempFile(directory, USER + "-", ".dat"); Files.setLastModifiedTime(directory, FileTime.fromMillis(System.currentTimeMillis() + 2000)); diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/presence/PlayerPresenceBatchScanTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/PlayerPresenceBatchScanTest.java similarity index 90% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/presence/PlayerPresenceBatchScanTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/PlayerPresenceBatchScanTest.java index 861f7b6dc0..5a0d926e3b 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/presence/PlayerPresenceBatchScanTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/PlayerPresenceBatchScanTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.api.user.presence; +package com.bencodez.advancedcore.tests.api.user.presence; + +import com.bencodez.advancedcore.api.user.presence.BukkitPlayerPresenceLookup; +import com.bencodez.advancedcore.api.user.presence.PlayerPresence; +import com.bencodez.advancedcore.api.user.presence.PlayerPresenceService; import static org.junit.jupiter.api.Assertions.*; import java.nio.file.Files; @@ -22,7 +26,7 @@ private static BasicFileAttributes attributes(Path path) throws java.io.IOExcept var scans = new AtomicInteger(); var queued = new ArrayDeque(); var results = new ArrayList>(); - try (var lookup = new BukkitPlayerPresenceLookup(directory, PlayerPresenceBatchScanTest::attributes, + try (var lookup = PresenceLookupAccess.create(directory, PlayerPresenceBatchScanTest::attributes, path -> { scans.incrementAndGet(); return Files.newDirectoryStream(path); }); var service = new PlayerPresenceService(queued::add, lookup)) { for (int i = 0; i < 128; i++) @@ -37,7 +41,7 @@ private static BasicFileAttributes attributes(Path path) throws java.io.IOExcept UUID first = UUID.fromString("d4519fc4-a782-4d11-8235-617209238441"); UUID second = UUID.fromString("b3f2bd99-5325-4a51-a642-12674e147381"); var queued = new ArrayDeque(); - try (var lookup = new BukkitPlayerPresenceLookup(directory); + try (var lookup = PresenceLookupAccess.create(directory); var service = new PlayerPresenceService(queued::add, lookup)) { var earlier = service.checkAsync(first); queued.add(() -> { @@ -68,7 +72,7 @@ private BasicFileAttributes fixedDirectory() throws Exception { @Test void laterCohortRescansKeylessDirectoryEvenWhenTimesCollide() throws Exception { BasicFileAttributes fixed = fixedDirectory(); var worker = new ArrayDeque(); var opens = new AtomicInteger(); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? fixed : attributes(path), path -> { opens.incrementAndGet(); return Files.newDirectoryStream(path); }); var service = new PlayerPresenceService(worker::add, lookup)) { @@ -91,7 +95,7 @@ private void assertDirectRecoveryCandidates(boolean complete) throws Exception { ".dat.offline-read", ".dat_old.offline-read", ".DAT")) { BasicFileAttributes fixed = fixedDirectory(); Path retained = directory.resolve(FIRST + suffix); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? fixed : attributes(path), path -> { Files.write(retained, new byte[] {0}); return Files.newDirectoryStream(path); })) { @@ -103,7 +107,7 @@ private void assertDirectRecoveryCandidates(boolean complete) throws Exception { } @Test void unrelatedCanonicalRecordDoesNotInvalidateDirectAbsence() throws Exception { BasicFileAttributes fixed = fixedDirectory(); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? fixed : attributes(path), path -> { Files.write(directory.resolve(SECOND + ".dat"), new byte[] {0}); return Files.newDirectoryStream(path); @@ -116,7 +120,7 @@ private void assertDirectRecoveryCandidates(boolean complete) throws Exception { @Test void canonicalRecordAppearingAfterPointChecksCannotBecomeNegative() throws Exception { BasicFileAttributes fixed = fixedDirectory(); var worker = new ArrayDeque(); Path retained = directory.resolve(FIRST + ".dat"); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> path.equals(directory) ? fixed : attributes(path), path -> { Files.write(retained, new byte[] {0}); return Files.newDirectoryStream(path); }); var service = new PlayerPresenceService(worker::add, lookup)) { @@ -129,7 +133,7 @@ private void assertDirectRecoveryCandidates(boolean complete) throws Exception { UUID former = UUID.nameUUIDFromBytes("OfflinePlayer:FormerSyntheticBatch".getBytes(java.nio.charset.StandardCharsets.UTF_8)); Files.write(directory.resolve(former + ".dat"), new byte[] {0}); var worker = new ArrayDeque(); var opens = new AtomicInteger(); - try (var lookup = new BukkitPlayerPresenceLookup(directory, PlayerPresenceBatchScanTest::attributes, + try (var lookup = PresenceLookupAccess.create(directory, PlayerPresenceBatchScanTest::attributes, path -> { opens.incrementAndGet(); return Files.newDirectoryStream(path); }); var service = new PlayerPresenceService(worker::add, lookup)) { var individual = service.checkAsync(FIRST); @@ -144,7 +148,7 @@ private void assertDirectRecoveryCandidates(boolean complete) throws Exception { var worker = new ArrayDeque(); var opens = new AtomicInteger(); var aliases = new ArrayList(); for (int i = 0; i < 8; i++) aliases.add(new UUID(0x1234400080000000L, i)); - try (var lookup = new BukkitPlayerPresenceLookup(directory, PlayerPresenceBatchScanTest::attributes, + try (var lookup = PresenceLookupAccess.create(directory, PlayerPresenceBatchScanTest::attributes, path -> { opens.incrementAndGet(); return Files.newDirectoryStream(path); }); var service = new PlayerPresenceService(worker::add, lookup)) { var result = service.checkIdentitiesAsync(aliases); @@ -155,7 +159,7 @@ private void assertDirectRecoveryCandidates(boolean complete) throws Exception { } @Test void failedSharedScanCannotEraseObservedPositiveEvidence() throws Exception { var worker = new ArrayDeque(); - try (var lookup = new BukkitPlayerPresenceLookup(directory, PlayerPresenceBatchScanTest::attributes, + try (var lookup = PresenceLookupAccess.create(directory, PlayerPresenceBatchScanTest::attributes, path -> { throw new java.nio.file.AccessDeniedException("synthetic-history"); }); var service = new PlayerPresenceService(worker::add, lookup)) { var player = org.mockito.Mockito.mock(org.bukkit.entity.Player.class); @@ -168,7 +172,7 @@ private void assertDirectRecoveryCandidates(boolean complete) throws Exception { } @Test void retiredProviderDoesNotReadDirectoryForQueuedCohort() throws Exception { var worker = new ArrayDeque(); var reads = new AtomicInteger(); - try (var lookup = new BukkitPlayerPresenceLookup(directory, + try (var lookup = PresenceLookupAccess.create(directory, path -> { reads.incrementAndGet(); return attributes(path); }); var service = new PlayerPresenceService(worker::add, lookup)) { var result = service.checkAsync(FIRST); lookup.close(); drain(worker); @@ -178,7 +182,7 @@ private void assertDirectRecoveryCandidates(boolean complete) throws Exception { } @Test void closeDuringScanCannotPublishNegativeOrRepopulateRetiredLookup() throws Exception { var worker = new ArrayDeque(); var holder = new BukkitPlayerPresenceLookup[1]; - holder[0] = new BukkitPlayerPresenceLookup(directory, PlayerPresenceBatchScanTest::attributes, + holder[0] = PresenceLookupAccess.create(directory, PlayerPresenceBatchScanTest::attributes, path -> { holder[0].close(); return Files.newDirectoryStream(path); }); try (var lookup = holder[0]; var service = new PlayerPresenceService(worker::add, lookup)) { var result = service.checkAsync(FIRST); drain(worker); @@ -192,7 +196,7 @@ private void assertDirectRecoveryCandidates(boolean complete) throws Exception { if (submissions.incrementAndGet() == 2) throw new java.util.concurrent.RejectedExecutionException(); worker.add(task); }; - try (var lookup = new BukkitPlayerPresenceLookup(directory); + try (var lookup = PresenceLookupAccess.create(directory); var service = new PlayerPresenceService(executor, lookup)) { var rejected = service.checkAsync(FIRST); drain(worker); assertEquals(PlayerPresence.UNKNOWN, rejected.toCompletableFuture().join()); diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/PresenceLookupAccess.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/PresenceLookupAccess.java new file mode 100644 index 0000000000..debc1a1e10 --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/presence/PresenceLookupAccess.java @@ -0,0 +1,48 @@ +package com.bencodez.advancedcore.tests.api.user.presence; + +import java.io.IOException; +import java.lang.reflect.Proxy; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; + +import com.bencodez.advancedcore.api.user.presence.BukkitPlayerPresenceLookup; +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; + +/** Adapts test fault injectors to the existing nonpublic constructor interfaces. */ +final class PresenceLookupAccess { + private PresenceLookupAccess() { } + + @FunctionalInterface interface AttributeReader { BasicFileAttributes read(Path path) throws IOException; } + @FunctionalInterface interface DirectoryReader { DirectoryStream open(Path path) throws IOException; } + + static BukkitPlayerPresenceLookup create(Path path) { return new BukkitPlayerPresenceLookup(path); } + + static BukkitPlayerPresenceLookup create(Path path, AttributeReader attributes) { + Class reader = nested("AttributeReader"); + return ReflectiveAccess.construct(BukkitPlayerPresenceLookup.class, new Class[] { Path.class, reader }, + path, adapt(reader, "read", value -> attributes.read(value))); + } + + static BukkitPlayerPresenceLookup create(Path path, AttributeReader attributes, DirectoryReader directories) { + Class reader = nested("AttributeReader"); + Class directoryReader = nested("DirectoryReader"); + return ReflectiveAccess.construct(BukkitPlayerPresenceLookup.class, + new Class[] { Path.class, reader, directoryReader }, path, + adapt(reader, "read", value -> attributes.read(value)), + adapt(directoryReader, "open", value -> directories.open(value))); + } + + private static Class nested(String name) { + return ReflectiveAccess.type(BukkitPlayerPresenceLookup.class.getName() + "$" + name); + } + + @FunctionalInterface private interface Read { Object apply(Path path) throws IOException; } + + private static Object adapt(Class type, String name, Read read) { + return Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, (proxy, method, arguments) -> { + if (method.getName().equals(name)) return read.apply((Path) arguments[0]); + throw new AssertionError("Unexpected presence-reader method: " + method); + }); + } +} diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/userstorage/mysql/MySQLInitializationTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/userstorage/mysql/MySQLInitializationTest.java similarity index 98% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/userstorage/mysql/MySQLInitializationTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/userstorage/mysql/MySQLInitializationTest.java index 2b2dba8cad..53d0df7571 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/api/user/userstorage/mysql/MySQLInitializationTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/api/user/userstorage/mysql/MySQLInitializationTest.java @@ -1,4 +1,6 @@ -package com.bencodez.advancedcore.api.user.userstorage.mysql; +package com.bencodez.advancedcore.tests.api.user.userstorage.mysql; + +import com.bencodez.advancedcore.api.user.userstorage.mysql.MySQL; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/build/BuildInputPinningTest.java similarity index 98% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/build/BuildInputPinningTest.java index 3f16ce1af1..3d3e69bac5 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/build/BuildInputPinningTest.java @@ -1,4 +1,4 @@ -package com.bencodez.advancedcore.build; +package com.bencodez.advancedcore.tests.build; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/command/CommandLoaderBulkPermissionTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/command/CommandLoaderBulkPermissionTest.java similarity index 98% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/command/CommandLoaderBulkPermissionTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/command/CommandLoaderBulkPermissionTest.java index d9f574ce0e..3d576b6f17 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/command/CommandLoaderBulkPermissionTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/command/CommandLoaderBulkPermissionTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.command; +package com.bencodez.advancedcore.tests.command; + +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; + +import com.bencodez.advancedcore.command.CommandLoader; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -41,6 +45,12 @@ import com.bencodez.simpleapi.scheduler.BukkitScheduler; class CommandLoaderBulkPermissionTest { + private static void cacheUserAndReport(CommandLoader loader, CommandSender sender, String identifier, + AdvancedCoreUser user) { + ReflectiveAccess.invoke(CommandLoader.class, loader, "cacheUserAndReport", + new Class[] { CommandSender.class, String.class, AdvancedCoreUser.class }, sender, identifier, user); + } + @Test void forceCacheDefersSharedStorageFromFoliaGlobalScheduler() { AdvancedCorePlugin plugin = mock(AdvancedCorePlugin.class); when(plugin.getOptions()).thenReturn(mock(AdvancedCoreConfigOptions.class)); @@ -63,7 +73,7 @@ class CommandLoaderBulkPermissionTest { }).when(manager).deferSharedStorageResultFromPlatform(any(), any(), any()); doAnswer(call -> { callbacks.add(call.getArgument(1)); return null; }) .when(scheduler).runTask(any(), any()); - new CommandLoader(plugin).cacheUserAndReport(sender, "voter", user); + cacheUserAndReport(new CommandLoader(plugin), sender, "voter", user); verify(user, never()).cache(); assertEquals(1, storage.size()); Boolean result = storage.remove(0).get(); @@ -240,7 +250,7 @@ void forceCacheReportsOnlyTheDeferredPopulationOutcome() { org.mockito.ArgumentMatchers.isNull()); CommandLoader loader = new CommandLoader(plugin); - loader.cacheUserAndReport(sender, "voter", user); + cacheUserAndReport(loader, sender, "voter", user); verify(user, never()).cache(); verify(sender, never()).sendMessage(any(String.class)); @@ -251,7 +261,7 @@ void forceCacheReportsOnlyTheDeferredPopulationOutcome() { failures.remove(0); verify(sender).sendMessage(org.mockito.ArgumentMatchers.contains("Forced cached voter")); - loader.cacheUserAndReport(sender, "broken", user); + cacheUserAndReport(loader, sender, "broken", user); workers.remove(0); failures.remove(0).accept(new IllegalStateException("storage unavailable")); verify(sender).sendMessage(org.mockito.ArgumentMatchers.contains("Unable to cache broken")); @@ -259,7 +269,7 @@ void forceCacheReportsOnlyTheDeferredPopulationOutcome() { when(dataManager.deferSharedStorageResult(any(), any(), any(), org.mockito.ArgumentMatchers.isNull())) .thenThrow(new java.util.concurrent.RejectedExecutionException("manager stopped")); - loader.cacheUserAndReport(sender, "stopped", user); + cacheUserAndReport(loader, sender, "stopped", user); verify(user, org.mockito.Mockito.times(1)).cache(); verify(sender).sendMessage(org.mockito.ArgumentMatchers.contains("Unable to cache stopped")); } @@ -290,7 +300,7 @@ void deferredCommandFailureDoesNotCallPlayerFromWorkerWhenEntitySchedulerStops() try (org.mockito.MockedStatic bukkit = org.mockito.Mockito.mockStatic(org.bukkit.Bukkit.class)) { bukkit.when(org.bukkit.Bukkit::getServer).thenReturn(mock(org.bukkit.Server.class)); bukkit.when(org.bukkit.Bukkit::isPrimaryThread).thenReturn(true, true, false); - new CommandLoader(plugin).cacheUserAndReport(sender, "voter", user); + cacheUserAndReport(new CommandLoader(plugin), sender, "voter", user); ArgumentCaptor storage = ArgumentCaptor.forClass(Runnable.class); verify(worker).execute(storage.capture()); storage.getValue().run(); diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/command/gui/UserGUITest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/command/gui/UserGUITest.java similarity index 77% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/command/gui/UserGUITest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/command/gui/UserGUITest.java index 7bd15427e8..758c29112b 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/command/gui/UserGUITest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/command/gui/UserGUITest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.command.gui; +package com.bencodez.advancedcore.tests.command.gui; + +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; + +import com.bencodez.advancedcore.command.gui.UserGUI; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -23,19 +27,36 @@ import com.bencodez.simpleapi.scheduler.BukkitScheduler; class UserGUITest { + private static java.lang.reflect.Field pluginField() throws NoSuchFieldException { + var field = UserGUI.class.getDeclaredField("plugin"); + field.setAccessible(true); + return field; + } + + private static boolean isCurrentEditorTarget(Player player, String name) { + return ReflectiveAccess.invoke(UserGUI.class, UserGUI.getInstance(), "isCurrentEditorTarget", + new Class[] { Player.class, String.class }, player, name); + } + + private static void withResolvedEditorUser(Player player, String name, + java.util.function.Consumer action) { + ReflectiveAccess.invoke(UserGUI.class, UserGUI.getInstance(), "withResolvedEditorUser", + new Class[] { Player.class, String.class, java.util.function.Consumer.class }, player, name, action); + } + private AdvancedCorePlugin plugin; private AdvancedCorePlugin previousPlugin; @BeforeEach - void setUp() { - previousPlugin = UserGUI.getInstance().plugin; + void setUp() throws ReflectiveOperationException { + previousPlugin = (AdvancedCorePlugin) pluginField().get(UserGUI.getInstance()); plugin = mock(AdvancedCorePlugin.class); - UserGUI.getInstance().plugin = plugin; + pluginField().set(UserGUI.getInstance(), plugin); } @AfterEach - void tearDown() { - UserGUI.getInstance().plugin = previousPlugin; + void tearDown() throws ReflectiveOperationException { + pluginField().set(UserGUI.getInstance(), previousPlugin); } @Test @@ -45,7 +66,7 @@ void deferredEditorRequiresTheSameSelectedTarget() { try (MockedStatic players = mockStatic(PlayerUtils.class)) { players.when(() -> PlayerUtils.getPlayerMeta(plugin, player, "UserGUI")).thenReturn("OtherPlayer"); - assertFalse(UserGUI.getInstance().isCurrentEditorTarget(player, "OriginalPlayer")); + assertFalse(isCurrentEditorTarget(player, "OriginalPlayer")); } } @@ -54,7 +75,7 @@ void deferredEditorRechecksPermission() { Player player = mock(Player.class); when(player.hasPermission("AdvancedCore.UserEdit")).thenReturn(false); - assertFalse(UserGUI.getInstance().isCurrentEditorTarget(player, "OriginalPlayer")); + assertFalse(isCurrentEditorTarget(player, "OriginalPlayer")); verify(player).sendMessage("Not enough permissions"); } @@ -66,7 +87,7 @@ void deferredEditorAcceptsTheCurrentAuthorizedTarget() { try (MockedStatic players = mockStatic(PlayerUtils.class)) { players.when(() -> PlayerUtils.getPlayerMeta(plugin, player, "UserGUI")).thenReturn("OriginalPlayer"); - assertTrue(UserGUI.getInstance().isCurrentEditorTarget(player, "OriginalPlayer")); + assertTrue(isCurrentEditorTarget(player, "OriginalPlayer")); } } @@ -96,7 +117,7 @@ void editorResolvesUnknownTargetsAsynchronouslyBeforeConstructingUsers() { try (MockedStatic players = mockStatic(PlayerUtils.class)) { players.when(() -> PlayerUtils.getPlayerMeta(plugin, player, "UserGUI")).thenReturn("OfflineTarget"); - UserGUI.getInstance().withResolvedEditorUser(player, "OfflineTarget", resolved::set); + withResolvedEditorUser(player, "OfflineTarget", resolved::set); org.junit.jupiter.api.Assertions.assertNull(resolved.get()); success.get().accept(user); org.junit.jupiter.api.Assertions.assertNull(resolved.get()); @@ -134,7 +155,7 @@ void editorDispatchesResolutionFailuresToThePlayerEntityLane() { try (MockedStatic players = mockStatic(PlayerUtils.class)) { players.when(() -> PlayerUtils.getPlayerMeta(plugin, player, "UserGUI")).thenReturn("OfflineTarget"); - UserGUI.getInstance().withResolvedEditorUser(player, "OfflineTarget", user -> { }); + withResolvedEditorUser(player, "OfflineTarget", user -> { }); failure.get().accept(new IllegalStateException("storage unavailable")); verify(player, org.mockito.Mockito.never()).sendMessage(org.mockito.ArgumentMatchers.anyString()); entityCallback.get().run(); diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcDuplicateCanonicalColumnTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcDuplicateCanonicalColumnTest.java similarity index 73% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcDuplicateCanonicalColumnTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcDuplicateCanonicalColumnTest.java index 5b42f84503..1ddc650103 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcDuplicateCanonicalColumnTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcDuplicateCanonicalColumnTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.SqlUserStorage; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -21,9 +25,9 @@ class JdbcDuplicateCanonicalColumnTest { @Test void caseVariantAliasesCannotOverwriteOneCanonicalColumn() throws Exception { Connection connection = mock(Connection.class); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.SQLITE, UUID.randomUUID(), "Users", + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.SQLITE, UUID.randomUUID(), "Users", SqlUserSchema.builder().column("Points", "INTEGER", DataType.INTEGER).build(), - () -> connection, JdbcSqlUserStorage.Dialect.SQLITE, SqlBackendLogger.NO_OP); + () -> connection, JdbcStorageAccess.Dialect.SQLITE, SqlBackendLogger.NO_OP); HashMap values = new HashMap<>(); values.put("Points", new DataValueInt(1)); values.put("points", new DataValueInt(2)); diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcPostgresBitBooleanTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcPostgresBitBooleanTest.java similarity index 86% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcPostgresBitBooleanTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcPostgresBitBooleanTest.java index 3e114aa1d6..c87fc1d287 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcPostgresBitBooleanTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcPostgresBitBooleanTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.SqlUserStorage; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -39,8 +43,8 @@ void postgresIntegerBindingIsTargetTypedForLegacyTextColumns() throws Exception when(connection.prepareStatement(anyString())).thenReturn(exists, insert); when(insert.executeUpdate()).thenReturn(1); SqlUserSchema schema = SqlUserSchema.builder().column("Points", "INT DEFAULT '0'", DataType.INTEGER).build(); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema, - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", schema, + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); storage.write(UserStorage.MYSQL, "Points", new DataValueInt(17)); @@ -59,8 +63,8 @@ void postgresBooleanBindingIsTargetTypedForRetainedLegacyTextColumns() throws Ex when(connection.prepareStatement(anyString())).thenReturn(exists, insert); when(insert.executeUpdate()).thenReturn(1); SqlUserSchema schema = SqlUserSchema.builder().column("Enabled", "BOOLEAN", DataType.BOOLEAN).build(); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema, - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", schema, + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); storage.write(UserStorage.MYSQL, "Enabled", new DataValueBoolean(true)); @@ -84,8 +88,8 @@ void postgresBitUsesExplicitBitCastAndStringBindingOnInsert() throws Exception { return statement; }); SqlUserSchema schema = SqlUserSchema.builder().column("Flag", "BIT(1)", DataType.BOOLEAN).build(); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema, - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", schema, + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); storage.write(UserStorage.MYSQL, "Flag", new DataValueBoolean(true)); @@ -110,8 +114,8 @@ void postgresVarbitDeclarationUsesTheEquivalentVaryingBitCast() throws Exception when(connection.prepareStatement("INSERT INTO \"Users\" (\"uuid\", \"Flag\") VALUES (?, CAST(? AS BIT VARYING(5))) ON CONFLICT (\"uuid\") DO NOTHING")) .thenReturn(insert); SqlUserSchema schema = SqlUserSchema.builder().column("Flag", "VARBIT(5)", DataType.BOOLEAN).build(); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema, - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", schema, + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); storage.write(UserStorage.MYSQL, "Flag", new DataValueBoolean(true)); @@ -135,8 +139,8 @@ void postgresBitDeclarationsAllowWhitespaceBeforeWidthModifiers() throws Excepti when(connection.prepareStatement("INSERT INTO \"Users\" (\"uuid\", \"Flag\") VALUES (?, CAST(? AS " + castType + ")) ON CONFLICT (\"uuid\") DO NOTHING")).thenReturn(insert); SqlUserSchema schema = SqlUserSchema.builder().column("Flag", declaration, DataType.BOOLEAN).build(); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema, - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", schema, + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); storage.write(UserStorage.MYSQL, "Flag", new DataValueBoolean(true)); @@ -173,8 +177,8 @@ void postgresRetainsResolvedSchemaVarbitWidthForBooleanWrites() throws Exception .thenReturn(insert); when(insert.executeUpdate()).thenReturn(1); SqlUserSchema schema = SqlUserSchema.builder().column("Flag", "BOOLEAN", DataType.BOOLEAN).build(); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema, - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", schema, + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); storage.write(UserStorage.MYSQL, "Flag", new DataValueBoolean(true)); @@ -211,8 +215,8 @@ void postgresRetainedMetadataTreatsWildcardCharactersAsLiteralIdentifiers() thro .thenReturn(insert); when(insert.executeUpdate()).thenReturn(1); SqlUserSchema schema = SqlUserSchema.builder().column("Flag_%", "BOOLEAN", DataType.BOOLEAN).build(); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users_%", schema, - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users_%", schema, + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); storage.write(UserStorage.MYSQL, "Flag_%", new DataValueBoolean(true)); @@ -234,8 +238,8 @@ void postgresBitReadsOneAsTrue() throws Exception { when(metadata.getColumnLabel(1)).thenReturn("Flag"); when(result.getString(1)).thenReturn("1"); SqlUserSchema schema = SqlUserSchema.builder().column("Flag", "BIT(1)", DataType.BOOLEAN).build(); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema, - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", schema, + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); assertTrue(storage.readRow(UserStorage.MYSQL).get(0).getValue().getBoolean()); verify(result, never()).getInt(1); @@ -256,8 +260,8 @@ void postgresFixedWidthBitReadsAnyNonzeroValueAsTrue() throws Exception { when(metadata.getColumnLabel(1)).thenReturn("Flag"); when(result.getString(1)).thenReturn("10"); SqlUserSchema schema = SqlUserSchema.builder().column("Flag", "BIT(2)", DataType.BOOLEAN).build(); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema, - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", schema, + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); assertTrue(storage.readRow(UserStorage.MYSQL).get(0).getValue().getBoolean()); } diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcReviewRegressionTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcReviewRegressionTest.java similarity index 92% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcReviewRegressionTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcReviewRegressionTest.java index bc351d0049..4f58776903 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcReviewRegressionTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcReviewRegressionTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.SqlUserStorage; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; @@ -131,9 +135,9 @@ private void readInteger(SQLException failure, int expected) throws Exception { verify(connection).close(); } - private JdbcSqlUserStorage storage(Connection connection) { - return new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", SqlUserSchema.builder() + private SqlUserStorage storage(Connection connection) { + return JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", SqlUserSchema.builder() .column("Points", "INT DEFAULT '0'", DataType.INTEGER).build(), - () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); } } diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcSqlUserStorageDialectTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcSqlUserStorageDialectTest.java similarity index 87% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcSqlUserStorageDialectTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcSqlUserStorageDialectTest.java index a33c675b1b..b705c82b3b 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcSqlUserStorageDialectTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcSqlUserStorageDialectTest.java @@ -1,4 +1,9 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.SqlUserStorage; +import com.bencodez.advancedcore.core.user.storage.sql.MysqlUserBackend; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -28,7 +33,6 @@ import org.mockito.MockedConstruction; import com.bencodez.advancedcore.api.user.UserStorage; -import com.bencodez.advancedcore.core.user.storage.SqlUserStorage; import com.bencodez.simpleapi.sql.DataType; import com.bencodez.simpleapi.sql.data.DataValueBoolean; import com.bencodez.simpleapi.sql.data.DataValueString; @@ -53,8 +57,8 @@ class JdbcSqlUserStorageDialectTest { when(result.getInt(1)).thenReturn(7); SqlUserSchema schema = SqlUserSchema.builder().column("Points", "INT", DataType.INTEGER).build(); - List columns = new JdbcSqlUserStorage(UserStorage.MYSQL, UUID_VALUE, - "Users", schema, () -> connection, JdbcSqlUserStorage.Dialect.MYSQL, SqlBackendLogger.NO_OP) + List columns = JdbcStorageAccess.create(UserStorage.MYSQL, UUID_VALUE, + "Users", schema, () -> connection, JdbcStorageAccess.Dialect.MYSQL, SqlBackendLogger.NO_OP) .readRow(UserStorage.MYSQL); assertEquals("Points", columns.get(0).getName()); @@ -64,8 +68,8 @@ class JdbcSqlUserStorageDialectTest { @Test void postgresqlNewRowUsesTheAtomicInsertWithoutARedundantUpdate() throws Exception { RecordingJdbc jdbc = new RecordingJdbc(); SqlUserSchema schema = SqlUserSchema.builder().column("Vote \"Flag\"", "VARCHAR(5)", DataType.BOOLEAN).build(); - SqlUserStorage user = new JdbcSqlUserStorage(UserStorage.MYSQL, UUID_VALUE, "User \"Data\"", schema, - () -> jdbc.connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage user = JdbcStorageAccess.create(UserStorage.MYSQL, UUID_VALUE, "User \"Data\"", schema, + () -> jdbc.connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); assertTrue(user.readRow(UserStorage.MYSQL).isEmpty()); assertFalse(user.contains(UserStorage.MYSQL)); user.delete(UserStorage.MYSQL); @@ -90,8 +94,8 @@ class JdbcSqlUserStorageDialectTest { for (DbType type : List.of(DbType.MYSQL, DbType.MARIADB)) { RecordingJdbc jdbc = new RecordingJdbc(); SqlUserSchema schema = SqlUserSchema.builder().column("Vote `Flag`", "VARCHAR(5)", DataType.BOOLEAN).build(); - SqlUserStorage user = new JdbcSqlUserStorage(UserStorage.MYSQL, UUID_VALUE, "User `Data`", schema, - () -> jdbc.connection, JdbcSqlUserStorage.Dialect.fromDbType(type), SqlBackendLogger.NO_OP); + SqlUserStorage user = JdbcStorageAccess.create(UserStorage.MYSQL, UUID_VALUE, "User `Data`", schema, + () -> jdbc.connection, JdbcStorageAccess.Dialect.fromDbType(type), SqlBackendLogger.NO_OP); user.write(UserStorage.MYSQL, "Vote `Flag`", new DataValueBoolean(false)); assertEquals(List.of( "SELECT 1 FROM `User ``Data``` WHERE `uuid`=? LIMIT 1 FOR UPDATE", @@ -114,8 +118,8 @@ class JdbcSqlUserStorageDialectTest { when(insert.executeUpdate()).thenReturn(1); when(writeConnection.prepareStatement(anyString())).thenAnswer(call -> call.getArgument(0, String.class).startsWith("SELECT 1") ? exists : insert); SqlUserSchema schema = SqlUserSchema.builder().column("Flag", "TINYINT(1)", DataType.BOOLEAN).build(); - SqlUserStorage writer = new JdbcSqlUserStorage(UserStorage.MYSQL, UUID_VALUE, "Users", schema, - () -> writeConnection, JdbcSqlUserStorage.Dialect.MYSQL, SqlBackendLogger.NO_OP); + SqlUserStorage writer = JdbcStorageAccess.create(UserStorage.MYSQL, UUID_VALUE, "Users", schema, + () -> writeConnection, JdbcStorageAccess.Dialect.MYSQL, SqlBackendLogger.NO_OP); writer.write(UserStorage.MYSQL, "Flag", new DataValueBoolean(true)); verify(insert).setInt(2, 1); @@ -133,8 +137,8 @@ class JdbcSqlUserStorageDialectTest { // declaration. Decoding the returned representation keeps its true // value instead of asking the driver to coerce it to an integer. when(result.getString(1)).thenReturn("true"); - SqlUserStorage reader = new JdbcSqlUserStorage(UserStorage.MYSQL, UUID_VALUE, "Users", schema, - () -> readConnection, JdbcSqlUserStorage.Dialect.MYSQL, SqlBackendLogger.NO_OP); + SqlUserStorage reader = JdbcStorageAccess.create(UserStorage.MYSQL, UUID_VALUE, "Users", schema, + () -> readConnection, JdbcStorageAccess.Dialect.MYSQL, SqlBackendLogger.NO_OP); assertTrue(reader.readRow(UserStorage.MYSQL).get(0).getValue().getBoolean()); verify(result, never()).getInt(1); verify(result, never()).getBoolean(1); @@ -161,8 +165,8 @@ class JdbcSqlUserStorageDialectTest { SqlUserSchema schema = SqlUserSchema.builder() .column("Flag", "VARCHAR(5)", DataType.BOOLEAN).build(); - new JdbcSqlUserStorage(UserStorage.MYSQL, UUID_VALUE, "Users", schema, - () -> connection, JdbcSqlUserStorage.Dialect.MYSQL, SqlBackendLogger.NO_OP) + JdbcStorageAccess.create(UserStorage.MYSQL, UUID_VALUE, "Users", schema, + () -> connection, JdbcStorageAccess.Dialect.MYSQL, SqlBackendLogger.NO_OP) .write(UserStorage.MYSQL, "Flag", new DataValueBoolean(true)); verify(update).setInt(1, 1); @@ -199,7 +203,7 @@ class JdbcSqlUserStorageDialectTest { } @Test void dialectRejectsInvalidIdentifiersWithoutRejectingQuotedNames() { - for (JdbcSqlUserStorage.Dialect dialect : JdbcSqlUserStorage.Dialect.values()) { + for (JdbcStorageAccess.Dialect dialect : JdbcStorageAccess.Dialect.values()) { assertThrows(IllegalArgumentException.class, () -> dialect.quote(null)); assertThrows(IllegalArgumentException.class, () -> dialect.quote("")); assertThrows(IllegalArgumentException.class, () -> dialect.quote(" ")); @@ -212,8 +216,8 @@ class JdbcSqlUserStorageDialectTest { RecordingJdbc jdbc = new RecordingJdbc(); SqlUserSchema schema = SqlUserSchema.builder() .column("VoteRemindersLast", "BIGINT", DataType.STRING).build(); - SqlUserStorage user = new JdbcSqlUserStorage(UserStorage.MYSQL, UUID_VALUE, "Users", schema, - () -> jdbc.connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage user = JdbcStorageAccess.create(UserStorage.MYSQL, UUID_VALUE, "Users", schema, + () -> jdbc.connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); user.write(UserStorage.MYSQL, "VoteRemindersLast", new DataValueString("1727136000000")); diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcSqlUserStorageWriteOutcomeTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcSqlUserStorageWriteOutcomeTest.java similarity index 87% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcSqlUserStorageWriteOutcomeTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcSqlUserStorageWriteOutcomeTest.java index 9341a29f8e..9288b4c4d3 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/JdbcSqlUserStorageWriteOutcomeTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcSqlUserStorageWriteOutcomeTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.SqlUserStorage; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -112,8 +116,8 @@ class JdbcSqlUserStorageWriteOutcomeTest { when(connection.prepareStatement(anyString())).thenReturn(statement); when(statement.executeQuery()).thenThrow(sqlFailure); doThrow(loggingFailure).when(logger).warn(anyString(), same(sqlFailure)); - JdbcSqlUserStorage storage = new JdbcSqlUserStorage(UserStorage.SQLITE, USER, "Users", - SqlUserSchema.builder().build(), () -> connection, JdbcSqlUserStorage.Dialect.SQLITE, logger); + SqlUserStorage storage = JdbcStorageAccess.create(UserStorage.SQLITE, USER, "Users", + SqlUserSchema.builder().build(), () -> connection, JdbcStorageAccess.Dialect.SQLITE, logger); IllegalStateException result = assertThrows(IllegalStateException.class, () -> storage.contains(UserStorage.SQLITE)); @@ -123,17 +127,17 @@ class JdbcSqlUserStorageWriteOutcomeTest { } @Test void uuidMetadataCannotChangeBoundIdentityInAnyDialect() throws Exception { - for (JdbcSqlUserStorage.Dialect dialect : JdbcSqlUserStorage.Dialect.values()) { - Jdbc jdbc = new Jdbc(); UserStorage type = dialect == JdbcSqlUserStorage.Dialect.SQLITE ? UserStorage.SQLITE : UserStorage.MYSQL; - JdbcSqlUserStorage user = jdbc.user(type, dialect); + for (JdbcStorageAccess.Dialect dialect : JdbcStorageAccess.Dialect.values()) { + Jdbc jdbc = new Jdbc(); UserStorage type = dialect == JdbcStorageAccess.Dialect.SQLITE ? UserStorage.SQLITE : UserStorage.MYSQL; + SqlUserStorage user = jdbc.user(type, dialect); HashMap values = new HashMap<>(); values.put("uuid", new DataValueString(UUID.randomUUID().toString())); values.put("UUID", null); values.put("UuId", new DataValueString("not an identity")); values.put("Points", new DataValueInt(17)); HashMap before = new HashMap<>(values); user.writeValues(type, values); assertEquals(before, values); - assertEquals(dialect == JdbcSqlUserStorage.Dialect.SQLITE ? 1 : 2, jdbc.sql.size()); + assertEquals(dialect == JdbcStorageAccess.Dialect.SQLITE ? 1 : 2, jdbc.sql.size()); assertTrue(jdbc.sql.stream().noneMatch(sql -> sql.startsWith("UPDATE") || sql.contains("SET " + dialect.quote("uuid")))); - if (dialect == JdbcSqlUserStorage.Dialect.POSTGRESQL) verify(jdbc.insert).setObject(1, USER); else verify(jdbc.insert).setString(1, USER.toString()); - if (dialect == JdbcSqlUserStorage.Dialect.POSTGRESQL) verify(jdbc.insert).setObject(2, "17", Types.OTHER); + if (dialect == JdbcStorageAccess.Dialect.POSTGRESQL) verify(jdbc.insert).setObject(1, USER); else verify(jdbc.insert).setString(1, USER.toString()); + if (dialect == JdbcStorageAccess.Dialect.POSTGRESQL) verify(jdbc.insert).setObject(2, "17", Types.OTHER); else verify(jdbc.insert).setInt(2, 17); verify(jdbc.connection).commit(); } @@ -144,7 +148,7 @@ class JdbcSqlUserStorageWriteOutcomeTest { PreparedStatement statement = mock(PreparedStatement.class); ResultSet existing = mock(ResultSet.class); when(existing.next()).thenReturn(true); when(statement.executeQuery()).thenReturn(existing); List sql = new ArrayList<>(); when(connection.prepareStatement(anyString())).thenAnswer(call -> { sql.add(call.getArgument(0, String.class)); return statement; }); SqlUserSchema schema = SqlUserSchema.builder().column("A", "INTEGER", DataType.INTEGER).column("B", "INTEGER", DataType.INTEGER).build(); - JdbcSqlUserStorage user = new JdbcSqlUserStorage(UserStorage.MYSQL, USER, "Users", schema, () -> connection, JdbcSqlUserStorage.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); + SqlUserStorage user = JdbcStorageAccess.create(UserStorage.MYSQL, USER, "Users", schema, () -> connection, JdbcStorageAccess.Dialect.POSTGRESQL, SqlBackendLogger.NO_OP); HashMap values = new LinkedHashMap<>(); values.put("A", new DataValueInt(2)); values.put("B", new DataValueInt(2)); user.writeValues(UserStorage.MYSQL, values); assertEquals(List.of("SELECT 1 FROM \"Users\" WHERE \"uuid\"=? LIMIT 1 FOR UPDATE", "UPDATE \"Users\" SET \"A\"=?, \"B\"=? WHERE \"uuid\"=?"), sql); @@ -152,7 +156,7 @@ class JdbcSqlUserStorageWriteOutcomeTest { } @Test void uuidOnlyBulkIsNoOpButExplicitIdentityMutationIsRejected() throws Exception { - Jdbc jdbc = new Jdbc(); JdbcSqlUserStorage user = jdbc.user(UserStorage.SQLITE, JdbcSqlUserStorage.Dialect.SQLITE); + Jdbc jdbc = new Jdbc(); SqlUserStorage user = jdbc.user(UserStorage.SQLITE, JdbcStorageAccess.Dialect.SQLITE); HashMap values = new HashMap<>(); values.put("UUID", new DataValueString(UUID.randomUUID().toString())); user.writeValues(UserStorage.SQLITE, values); user.writeValues(UserStorage.SQLITE, new HashMap<>()); assertThrows(IllegalArgumentException.class, () -> user.write(UserStorage.SQLITE, "uuid", values.get("UUID"))); @@ -162,7 +166,7 @@ class JdbcSqlUserStorageWriteOutcomeTest { @Test void postgresPartialUpdateDoesNotReinsertAnExistingRequiredColumnRow() throws Exception { Jdbc jdbc = new Jdbc(); ResultSet row = mock(ResultSet.class); when(row.next()).thenReturn(true); when(jdbc.update.executeQuery()).thenReturn(row); - jdbc.user(UserStorage.MYSQL, JdbcSqlUserStorage.Dialect.POSTGRESQL).write(UserStorage.MYSQL, "Points", new DataValueInt(23)); + jdbc.user(UserStorage.MYSQL, JdbcStorageAccess.Dialect.POSTGRESQL).write(UserStorage.MYSQL, "Points", new DataValueInt(23)); assertTrue(jdbc.sql.stream().noneMatch(sql -> sql.startsWith("INSERT"))); verify(jdbc.update).setObject(1, "23", Types.OTHER); verify(jdbc.connection).commit(); verify(row).close(); } @@ -177,15 +181,15 @@ private static final class Jdbc { when(update.executeQuery()).thenReturn(mock(ResultSet.class)); when(connection.prepareStatement(anyString())).thenAnswer(invocation -> { String query = invocation.getArgument(0, String.class); sql.add(query); return query.startsWith("INSERT") ? insert : update; }); } - JdbcSqlUserStorage user(UserStorage type, JdbcSqlUserStorage.Dialect dialect) { - return new JdbcSqlUserStorage(type, USER, "Users", SqlUserSchema.builder().column("Points", "INTEGER", DataType.INTEGER).build(), () -> connection, dialect, logger); + SqlUserStorage user(UserStorage type, JdbcStorageAccess.Dialect dialect) { + return JdbcStorageAccess.create(type, USER, "Users", SqlUserSchema.builder().column("Points", "INTEGER", DataType.INTEGER).build(), () -> connection, dialect, logger); } void write() { try { when(insert.executeUpdate()).thenReturn(0); ResultSet existing = mock(ResultSet.class); when(existing.next()).thenReturn(true); when(update.executeQuery()).thenReturn(existing); } catch (SQLException impossible) { throw new AssertionError(impossible); } - user(UserStorage.SQLITE, JdbcSqlUserStorage.Dialect.SQLITE).write(UserStorage.SQLITE, "Points", new DataValueInt(17)); + user(UserStorage.SQLITE, JdbcStorageAccess.Dialect.SQLITE).write(UserStorage.SQLITE, "Points", new DataValueInt(17)); } } } diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcStorageAccess.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcStorageAccess.java new file mode 100644 index 0000000000..794aa3cb53 --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/JdbcStorageAccess.java @@ -0,0 +1,74 @@ +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.UUID; + +import com.bencodez.advancedcore.api.user.UserStorage; +import com.bencodez.advancedcore.core.user.storage.SqlUserStorage; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; +import com.bencodez.advancedcore.tests.support.ReflectiveAccess; +import com.bencodez.simpleapi.sql.mysql.DbType; + +/** Constructs the real package-private implementation, then uses its public storage contract. */ +final class JdbcStorageAccess { + private static final Class STORAGE = ReflectiveAccess.type( + "com.bencodez.advancedcore.core.user.storage.sql.JdbcSqlUserStorage"); + private static final Class DIALECT = ReflectiveAccess.type(STORAGE.getName() + "$Dialect"); + private static final Class CONNECTIONS = ReflectiveAccess.type(STORAGE.getName() + "$ConnectionProvider"); + + private JdbcStorageAccess() { } + + @FunctionalInterface + interface ConnectionProvider { Connection open() throws SQLException; } + + static SqlUserStorage create(UserStorage storage, UUID uuid, String table, SqlUserSchema schema, + ConnectionProvider connections, Dialect dialect, SqlBackendLogger logger) { + Object provider = Proxy.newProxyInstance(CONNECTIONS.getClassLoader(), new Class[] { CONNECTIONS }, + (proxy, method, arguments) -> { + if (method.getName().equals("open")) return connections.open(); + throw new AssertionError("Unexpected connection-provider method: " + method); + }); + return ReflectiveAccess.construct(STORAGE, + new Class[] { UserStorage.class, UUID.class, String.class, SqlUserSchema.class, + CONNECTIONS, DIALECT, SqlBackendLogger.class }, + storage, uuid, table, schema, provider, dialect.productionValue(), logger); + } + + /** Each handle refers to a real production enum constant; values() preserves production coverage. */ + static final class Dialect { + private static final Dialect[] ALL = java.util.Arrays.stream(DIALECT.getEnumConstants()) + .map(Dialect::new).toArray(Dialect[]::new); + static final Dialect SQLITE = named("SQLITE"); + static final Dialect MYSQL = named("MYSQL"); + static final Dialect POSTGRESQL = named("POSTGRESQL"); + + private final Object value; + + private Dialect(Object value) { this.value = value; } + + private Object productionValue() { return value; } + + static Dialect[] values() { return ALL.clone(); } + + private static Dialect named(String name) { + for (Dialect dialect : ALL) { + if (((Enum) dialect.value).name().equals(name)) return dialect; + } + throw new AssertionError("Missing production dialect: " + name); + } + + static Dialect fromDbType(DbType type) { + Object value = ReflectiveAccess.invoke(DIALECT, null, "fromDbType", new Class[] { DbType.class }, type); + return named(((Enum) value).name()); + } + + String quote(String identifier) { + return ReflectiveAccess.invoke(DIALECT, productionValue(), "quote", new Class[] { String.class }, identifier); + } + + @Override public String toString() { return value.toString(); } + } +} diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlBackendReviewRegressionTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlBackendReviewRegressionTest.java similarity index 97% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlBackendReviewRegressionTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlBackendReviewRegressionTest.java index d64961f0dd..58d5aa710f 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlBackendReviewRegressionTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlBackendReviewRegressionTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.sql.MysqlUserBackend; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlBorrowedSchemaReconciliationTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlBorrowedSchemaReconciliationTest.java similarity index 97% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlBorrowedSchemaReconciliationTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlBorrowedSchemaReconciliationTest.java index 3f9595c33d..f127be8add 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlBorrowedSchemaReconciliationTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlBorrowedSchemaReconciliationTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.sql.MysqlUserBackend; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlConcurrentSchemaTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlConcurrentSchemaTest.java similarity index 96% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlConcurrentSchemaTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlConcurrentSchemaTest.java index 40231c6c36..d4c8f17dbe 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlConcurrentSchemaTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlConcurrentSchemaTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.sql.MysqlUserBackend; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendAdmissionRaceTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendAdmissionRaceTest.java similarity index 92% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendAdmissionRaceTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendAdmissionRaceTest.java index dbb01e8dca..37b734b0eb 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendAdmissionRaceTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendAdmissionRaceTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.sql.MysqlUserBackend; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendLifecycleReviewTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendLifecycleReviewTest.java similarity index 94% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendLifecycleReviewTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendLifecycleReviewTest.java index d932bdfec1..213d825ef9 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendLifecycleReviewTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendLifecycleReviewTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.sql.MysqlUserBackend; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendSchemaExpansionTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendSchemaExpansionTest.java similarity index 98% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendSchemaExpansionTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendSchemaExpansionTest.java index f9d322fa20..d5860c6d0b 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendSchemaExpansionTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendSchemaExpansionTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.sql.MysqlUserBackend; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendUuidMigrationTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendUuidMigrationTest.java similarity index 97% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendUuidMigrationTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendUuidMigrationTest.java index 769f6e5598..88597a2d2b 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/MysqlUserBackendUuidMigrationTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/MysqlUserBackendUuidMigrationTest.java @@ -1,4 +1,8 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.sql.MysqlUserBackend; +import com.bencodez.advancedcore.core.user.storage.sql.SqlBackendLogger; +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/SqlUserSchemaDuplicateColumnTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/SqlUserSchemaDuplicateColumnTest.java similarity index 90% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/SqlUserSchemaDuplicateColumnTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/SqlUserSchemaDuplicateColumnTest.java index 619a77456f..54a34252e8 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/SqlUserSchemaDuplicateColumnTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/SqlUserSchemaDuplicateColumnTest.java @@ -1,4 +1,6 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/SqlUserSchemaReservedColumnTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/SqlUserSchemaReservedColumnTest.java similarity index 85% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/SqlUserSchemaReservedColumnTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/SqlUserSchemaReservedColumnTest.java index 36e5613daa..fef45f89ff 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/core/user/storage/sql/SqlUserSchemaReservedColumnTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/core/user/storage/sql/SqlUserSchemaReservedColumnTest.java @@ -1,4 +1,6 @@ -package com.bencodez.advancedcore.core.user.storage.sql; +package com.bencodez.advancedcore.tests.core.user.storage.sql; + +import com.bencodez.advancedcore.core.user.storage.sql.SqlUserSchema; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/listeners/PlayerJoinEventSessionTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/listeners/PlayerJoinEventSessionTest.java similarity index 95% rename from AdvancedCore/src/test/java/com/bencodez/advancedcore/listeners/PlayerJoinEventSessionTest.java rename to AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/listeners/PlayerJoinEventSessionTest.java index 66550011fa..d61ed8356f 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/listeners/PlayerJoinEventSessionTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/listeners/PlayerJoinEventSessionTest.java @@ -1,4 +1,6 @@ -package com.bencodez.advancedcore.listeners; +package com.bencodez.advancedcore.tests.listeners; + +import com.bencodez.advancedcore.listeners.PlayerJoinEvent; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/support/ReflectiveAccess.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/support/ReflectiveAccess.java new file mode 100644 index 0000000000..e7ec3d2da6 --- /dev/null +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/tests/support/ReflectiveAccess.java @@ -0,0 +1,41 @@ +package com.bencodez.advancedcore.tests.support; + +import java.lang.reflect.InvocationTargetException; + +/** Test-only access to existing nonpublic code; production visibility remains unchanged. */ +public final class ReflectiveAccess { + private ReflectiveAccess() { } + + public static Class type(String name) { + try { return Class.forName(name); } + catch (ClassNotFoundException failure) { throw new AssertionError(failure); } + } + + public static T construct(Class type, Class[] parameterTypes, Object... arguments) { + try { + var constructor = type.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + return cast(constructor.newInstance(arguments)); + } catch (InvocationTargetException failure) { throw propagate(failure.getCause()); } + catch (ReflectiveOperationException failure) { throw new AssertionError(failure); } + } + + public static T invoke(Class owner, Object target, String name, Class[] parameterTypes, + Object... arguments) { + try { + var method = owner.getDeclaredMethod(name, parameterTypes); + method.setAccessible(true); + return cast(method.invoke(target, arguments)); + } catch (InvocationTargetException failure) { throw propagate(failure.getCause()); } + catch (ReflectiveOperationException failure) { throw new AssertionError(failure); } + } + + @SuppressWarnings("unchecked") + private static T cast(Object value) { return (T) value; } + + // Preserve the original exception type for assertThrows and fault-injection tests. + @SuppressWarnings("unchecked") + private static RuntimeException propagate(Throwable failure) throws E { + throw (E) failure; + } +}