Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,21 @@ version = finalRevision
val javaVersion = "25"
val junitVersion = "5.10.2"
val mockitoVersion = "5.11.0"
// MockBukkit's modern, per-Minecraft-version artifacts are published to Paper's Maven repo
// under org.mockbukkit.mockbukkit (see the testImplementation coordinate below). The closest
// available to Paper 26.2 is the 26.1.2 line (no 26.2 build exists yet); 4.113.2 is the latest.
// TODO(blocked): switch the testImplementation coordinate below to mockbukkit-v26.2 once it is
// published. Until then the test suite cannot run against the 26.2 API — MockBukkit's registry
// mock throws on 26.2's new `minecraft:sulfur_cube_archetype` registry. The code still compiles
// against mockbukkit-v26.1.2; only the test *run* is blocked. This is the sole remaining change.
val mockBukkitVersion = "4.113.2"
val mongodbVersion = "3.12.12"
val mariadbVersion = "3.0.5"
val mysqlVersion = "8.0.27"
val postgresqlVersion = "42.2.18"
val hikaricpVersion = "5.0.1"
// Compile against the latest stable 26.1.2 dev bundle. This is the newest Paper API that has a
// matching MockBukkit release (mockbukkit-v26.1.2); MockBukkit does not yet support 26.2's new
// registries. Minecraft 26.2 is still fully supported at runtime (see ServerCompatibility and
// the Modrinth game-versions list); 26.2-only blocks/entities are accessed via Enums.getIfPresent.
val paperVersion = "26.1.2.build.72-stable"
// Compile against the literal 26.2 dev bundle so EntityType.SULFUR_CUBE and the new 26.2
// materials are available at compile time. Pairs with mockbukkit-v26.2 (see above) once that
// MockBukkit build exists; until then the test suite is red (registry mismatch), which is why
// this PR is a draft.
val paperVersion = "26.2.build.25-alpha"
val bstatsVersion = "3.0.0"
val vaultVersion = "1.7.1"
val levelVersion = "2.21.3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ public class BucketListener extends FlagListener {

/**
* The Sulfur Cube entity type (Minecraft 26.2), resolved at runtime so the code still
* compiles against earlier API versions. {@code null} when absent. Non-final so tests can
* inject a stand-in type (the JVM constant-folds {@code static final} fields).
* compiles against earlier API versions. {@code null} when absent.
*/
@SuppressWarnings("java:S3008") // non-final by design; see Javadoc (test injection)
private static EntityType SULFUR_CUBE = Enums.getIfPresent(EntityType.class, "SULFUR_CUBE")
private static final EntityType SULFUR_CUBE = Enums.getIfPresent(EntityType.class, "SULFUR_CUBE")
.orNull();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,9 @@ public class EntityInteractListener extends FlagListener {

/**
* The Sulfur Cube entity type (Minecraft 26.2), resolved at runtime so the code still
* compiles against earlier API versions. {@code null} when absent. Non-final so tests can
* inject a stand-in type (the JVM constant-folds {@code static final} fields).
* compiles against earlier API versions. {@code null} when absent.
*/
@SuppressWarnings("java:S3008") // non-final by design; see Javadoc (test injection)
private static EntityType SULFUR_CUBE = Enums.getIfPresent(EntityType.class, "SULFUR_CUBE")
private static final EntityType SULFUR_CUBE = Enums.getIfPresent(EntityType.class, "SULFUR_CUBE")
.orNull();

@EventHandler(priority = EventPriority.LOW, ignoreCancelled=true)
Expand Down
6 changes: 1 addition & 5 deletions src/main/java/world/bentobox/bentobox/util/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,8 @@ public class Util {
* The Sulfur Cube entity type (Minecraft 26.2), resolved at runtime so the code still
* compiles against earlier API versions where the constant does not exist. {@code null}
* when absent, in which case the {@code ==} comparisons against it are simply false.
* Intentionally non-final: assigned once at class load, but left non-final so tests can
* inject a stand-in type (the JVM constant-folds {@code static final} fields, defeating
* reflective injection).
*/
@SuppressWarnings("java:S3008") // non-final by design; see Javadoc (test injection)
private static EntityType SULFUR_CUBE = Enums.getIfPresent(EntityType.class, "SULFUR_CUBE")
private static final EntityType SULFUR_CUBE = Enums.getIfPresent(EntityType.class, "SULFUR_CUBE")
.orNull();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,68 +252,40 @@ void testOnTropicalFishScoopingFishWaterBucketNotAllowed() {

/**
* A Sulfur Cube (Minecraft 26.2) is picked up with an empty bucket; this is blocked by the
* BUCKET flag when not allowed. The 26.2 EntityType constant is absent in the test API, so
* MAGMA_CUBE is injected as a stand-in for SULFUR_CUBE.
* BUCKET flag when not allowed.
*/
@Test
void testOnSulfurCubeBucketingNotAllowed() throws Exception {
void testOnSulfurCubeBucketingNotAllowed() {
when(island.isAllowed(any(), any())).thenReturn(false);
EntityType standIn = EntityType.MAGMA_CUBE;
Object previous = getStaticField(BucketListener.class, "SULFUR_CUBE");
setStaticField(BucketListener.class, "SULFUR_CUBE", standIn);
try {
Entity cube = mock(Entity.class);
when(cube.getLocation()).thenReturn(location);
when(cube.getType()).thenReturn(standIn);
PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, cube);
PlayerInventory inv = mock(PlayerInventory.class);
ItemStack item = mock(ItemStack.class);
when(item.getType()).thenReturn(Material.BUCKET);
when(inv.getItemInMainHand()).thenReturn(item);
when(mockPlayer.getInventory()).thenReturn(inv);
l.onTropicalFishScooping(e);
assertTrue(e.isCancelled());
verify(notifier).notify(any(), eq("protection.protected"));
} finally {
setStaticField(BucketListener.class, "SULFUR_CUBE", previous);
}
Entity cube = mock(Entity.class);
when(cube.getLocation()).thenReturn(location);
when(cube.getType()).thenReturn(EntityType.SULFUR_CUBE);
PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, cube);
PlayerInventory inv = mock(PlayerInventory.class);
ItemStack item = mock(ItemStack.class);
when(item.getType()).thenReturn(Material.BUCKET);
when(inv.getItemInMainHand()).thenReturn(item);
when(mockPlayer.getInventory()).thenReturn(inv);
l.onTropicalFishScooping(e);
assertTrue(e.isCancelled());
verify(notifier).notify(any(), eq("protection.protected"));
}

/**
* Bucketing a Sulfur Cube is allowed when the island permits the BUCKET flag.
*/
@Test
void testOnSulfurCubeBucketingAllowed() throws Exception {
EntityType standIn = EntityType.MAGMA_CUBE;
Object previous = getStaticField(BucketListener.class, "SULFUR_CUBE");
setStaticField(BucketListener.class, "SULFUR_CUBE", standIn);
try {
Entity cube = mock(Entity.class);
when(cube.getLocation()).thenReturn(location);
when(cube.getType()).thenReturn(standIn);
PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, cube);
PlayerInventory inv = mock(PlayerInventory.class);
ItemStack item = mock(ItemStack.class);
when(item.getType()).thenReturn(Material.BUCKET);
when(inv.getItemInMainHand()).thenReturn(item);
when(mockPlayer.getInventory()).thenReturn(inv);
l.onTropicalFishScooping(e);
assertFalse(e.isCancelled());
} finally {
setStaticField(BucketListener.class, "SULFUR_CUBE", previous);
}
}

private static Object getStaticField(Class<?> clazz, String name) throws Exception {
java.lang.reflect.Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
return f.get(null);
}

@SuppressWarnings("java:S3011")
private static void setStaticField(Class<?> clazz, String name, Object value) throws Exception {
java.lang.reflect.Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
f.set(null, value);
void testOnSulfurCubeBucketingAllowed() {
Entity cube = mock(Entity.class);
when(cube.getLocation()).thenReturn(location);
when(cube.getType()).thenReturn(EntityType.SULFUR_CUBE);
PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, cube);
PlayerInventory inv = mock(PlayerInventory.class);
ItemStack item = mock(ItemStack.class);
when(item.getType()).thenReturn(Material.BUCKET);
when(inv.getItemInMainHand()).thenReturn(item);
when(mockPlayer.getInventory()).thenReturn(inv);
l.onTropicalFishScooping(e);
assertFalse(e.isCancelled());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -399,66 +399,38 @@ void testOnPlayerInteractEntityCopperGolemNameTagNoInteraction() {

/**
* Giving a block to a Sulfur Cube (Minecraft 26.2) for it to absorb is treated as placing a
* block and must be blocked by the PLACE_BLOCKS flag when not allowed. The 26.2 EntityType
* constant is absent in the test API, so MAGMA_CUBE is injected as a stand-in for SULFUR_CUBE.
* block and must be blocked by the PLACE_BLOCKS flag when not allowed.
*/
@Test
void testOnPlayerInteractEntitySulfurCubeBlockAbsorptionNotAllowed() throws Exception {
EntityType standIn = EntityType.MAGMA_CUBE;
Object previous = getStaticField(EntityInteractListener.class, "SULFUR_CUBE");
setStaticField(EntityInteractListener.class, "SULFUR_CUBE", standIn);
try {
clickedEntity = mock(Entity.class);
when(clickedEntity.getLocation()).thenReturn(location);
when(clickedEntity.getType()).thenReturn(standIn);
ItemStack block = mock(ItemStack.class);
when(block.getType()).thenReturn(Material.STONE);
when(inv.getItemInMainHand()).thenReturn(block);
PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, clickedEntity, hand);
eil.onPlayerInteractEntity(e);
verify(notifier).notify(any(), eq("protection.protected"));
assertTrue(e.isCancelled());
} finally {
setStaticField(EntityInteractListener.class, "SULFUR_CUBE", previous);
}
void testOnPlayerInteractEntitySulfurCubeBlockAbsorptionNotAllowed() {
clickedEntity = mock(Entity.class);
when(clickedEntity.getLocation()).thenReturn(location);
when(clickedEntity.getType()).thenReturn(EntityType.SULFUR_CUBE);
ItemStack block = mock(ItemStack.class);
when(block.getType()).thenReturn(Material.STONE);
when(inv.getItemInMainHand()).thenReturn(block);
PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, clickedEntity, hand);
eil.onPlayerInteractEntity(e);
verify(notifier).notify(any(), eq("protection.protected"));
assertTrue(e.isCancelled());
}

/**
* Giving a block to a Sulfur Cube is allowed when the island permits PLACE_BLOCKS.
*/
@Test
void testOnPlayerInteractEntitySulfurCubeBlockAbsorptionAllowed() throws Exception {
void testOnPlayerInteractEntitySulfurCubeBlockAbsorptionAllowed() {
when(island.isAllowed(any(User.class), any())).thenReturn(true);
EntityType standIn = EntityType.MAGMA_CUBE;
Object previous = getStaticField(EntityInteractListener.class, "SULFUR_CUBE");
setStaticField(EntityInteractListener.class, "SULFUR_CUBE", standIn);
try {
clickedEntity = mock(Entity.class);
when(clickedEntity.getLocation()).thenReturn(location);
when(clickedEntity.getType()).thenReturn(standIn);
ItemStack block = mock(ItemStack.class);
when(block.getType()).thenReturn(Material.STONE);
when(inv.getItemInMainHand()).thenReturn(block);
PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, clickedEntity, hand);
eil.onPlayerInteractEntity(e);
verify(notifier, never()).notify(any(), eq("protection.protected"));
assertFalse(e.isCancelled());
} finally {
setStaticField(EntityInteractListener.class, "SULFUR_CUBE", previous);
}
}

private static Object getStaticField(Class<?> clazz, String name) throws Exception {
java.lang.reflect.Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
return f.get(null);
}

@SuppressWarnings("java:S3011")
private static void setStaticField(Class<?> clazz, String name, Object value) throws Exception {
java.lang.reflect.Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
f.set(null, value);
clickedEntity = mock(Entity.class);
when(clickedEntity.getLocation()).thenReturn(location);
when(clickedEntity.getType()).thenReturn(EntityType.SULFUR_CUBE);
ItemStack block = mock(ItemStack.class);
when(block.getType()).thenReturn(Material.STONE);
when(inv.getItemInMainHand()).thenReturn(block);
PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, clickedEntity, hand);
eil.onPlayerInteractEntity(e);
verify(notifier, never()).notify(any(), eq("protection.protected"));
assertFalse(e.isCancelled());
}

}
41 changes: 9 additions & 32 deletions src/test/java/world/bentobox/bentobox/util/UtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,40 +49,17 @@ public void tearDown() throws Exception {
}

/**
* Sulfur Cube (Minecraft 26.2) is slime-like but passive. When the SULFUR_CUBE entity type is
* present, {@link Util#isPassiveEntity(org.bukkit.entity.Entity)} must classify it as passive
* and {@link Util#isHostileEntity(org.bukkit.entity.Entity)} must not treat it as hostile,
* even though it implements {@link Slime}.
* <p>
* The 26.2 EntityType constant does not exist in the test API, so an existing slime-like type
* (MAGMA_CUBE) is injected into Util's resolved SULFUR_CUBE field as a stand-in.
* Sulfur Cube (Minecraft 26.2) is slime-like but passive:
* {@link Util#isPassiveEntity(org.bukkit.entity.Entity)} must classify it as passive and
* {@link Util#isHostileEntity(org.bukkit.entity.Entity)} must not treat it as hostile, even
* though it implements {@link Slime}.
*/
@Test
void testSulfurCubeClassifiedAsPassiveNotHostile() throws Exception {
EntityType standIn = EntityType.MAGMA_CUBE;
Object previous = getStaticField(Util.class, "SULFUR_CUBE");
setStaticField(Util.class, "SULFUR_CUBE", standIn);
try {
Slime sulfurCube = mock(Slime.class);
when(sulfurCube.getType()).thenReturn(standIn);
assertTrue(Util.isPassiveEntity(sulfurCube));
assertFalse(Util.isHostileEntity(sulfurCube));
} finally {
setStaticField(Util.class, "SULFUR_CUBE", previous);
}
}

private static Object getStaticField(Class<?> clazz, String name) throws Exception {
java.lang.reflect.Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
return f.get(null);
}

@SuppressWarnings("java:S3011")
private static void setStaticField(Class<?> clazz, String name, Object value) throws Exception {
java.lang.reflect.Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
f.set(null, value);
void testSulfurCubeClassifiedAsPassiveNotHostile() {
Slime sulfurCube = mock(Slime.class);
when(sulfurCube.getType()).thenReturn(EntityType.SULFUR_CUBE);
assertTrue(Util.isPassiveEntity(sulfurCube));
assertFalse(Util.isHostileEntity(sulfurCube));
}

// ---- blockFaceToFloat ----
Expand Down
Loading