Initial commit
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
package com.livingworld.config;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SimulationConfig}.
|
||||
*/
|
||||
class SimulationConfigTest {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Default values
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Default values")
|
||||
class DefaultValues {
|
||||
|
||||
@Test
|
||||
void defaultRegionSizeChunks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
assertEquals(8, config.getRegionSizeChunks());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultSimulationIntervalTicks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
assertEquals(100, config.getSimulationIntervalTicks());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMaxRegionsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
assertEquals(50, config.getMaxRegionsPerCycle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultMaxMillisecondsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
assertEquals(25, config.getMaxMillisecondsPerCycle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultEmergencyStopMilliseconds() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
assertEquals(40, config.getEmergencyStopMilliseconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultEnableDebugCommands() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
assertTrue(config.isEnableDebugCommands());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultEnableProfiler() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
assertTrue(config.isEnableProfiler());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Validation – valid values should pass
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Validation with valid values")
|
||||
class ValidValues {
|
||||
|
||||
@Test
|
||||
void defaultsAreValid() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.validate(); // Should not throw
|
||||
}
|
||||
|
||||
@Test
|
||||
void minimumRegionSizeChunks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setRegionSizeChunks(1);
|
||||
config.validate(); // Should not throw
|
||||
}
|
||||
|
||||
@Test
|
||||
void minimumSimulationIntervalTicks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setSimulationIntervalTicks(1);
|
||||
config.validate(); // Should not throw
|
||||
}
|
||||
|
||||
@Test
|
||||
void minimumMaxRegionsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setMaxRegionsPerCycle(1);
|
||||
config.validate(); // Should not throw
|
||||
}
|
||||
|
||||
@Test
|
||||
void minimumMaxMillisecondsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setMaxMillisecondsPerCycle(1);
|
||||
config.validate(); // Should not throw
|
||||
}
|
||||
|
||||
@Test
|
||||
void emergencyStopEqualsMaxMilliseconds() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setEmergencyStopMilliseconds(25);
|
||||
config.setMaxMillisecondsPerCycle(25);
|
||||
config.validate(); // Should not throw (equal is allowed)
|
||||
}
|
||||
|
||||
@Test
|
||||
void emergencyStopGreaterThanMaxMilliseconds() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setEmergencyStopMilliseconds(50);
|
||||
config.setMaxMillisecondsPerCycle(25);
|
||||
config.validate(); // Should not throw
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Validation – invalid values should fail
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Validation with invalid values")
|
||||
class InvalidValues {
|
||||
|
||||
@Test
|
||||
void zeroRegionSizeChunks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setRegionSizeChunks(0);
|
||||
assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeRegionSizeChunks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setRegionSizeChunks(-5);
|
||||
assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void zeroSimulationIntervalTicks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setSimulationIntervalTicks(0);
|
||||
assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeSimulationIntervalTicks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setSimulationIntervalTicks(-10);
|
||||
assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void zeroMaxRegionsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setMaxRegionsPerCycle(0);
|
||||
assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeMaxRegionsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setMaxRegionsPerCycle(-1);
|
||||
assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void zeroMaxMillisecondsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setMaxMillisecondsPerCycle(0);
|
||||
assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeMaxMillisecondsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setMaxMillisecondsPerCycle(-10);
|
||||
assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void emergencyStopLessThanMaxMilliseconds() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setEmergencyStopMilliseconds(20);
|
||||
config.setMaxMillisecondsPerCycle(25);
|
||||
assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void emergencyStopMessageContainsValues() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setEmergencyStopMilliseconds(10);
|
||||
config.setMaxMillisecondsPerCycle(30);
|
||||
final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> config.validate());
|
||||
assertTrue(exception.getMessage().contains("emergencyStopMilliseconds"));
|
||||
assertTrue(exception.getMessage().contains("maxMillisecondsPerCycle"));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Setters
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Setters")
|
||||
class Setters {
|
||||
|
||||
@Test
|
||||
void setRegionSizeChunks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setRegionSizeChunks(16);
|
||||
assertEquals(16, config.getRegionSizeChunks());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSimulationIntervalTicks() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setSimulationIntervalTicks(50);
|
||||
assertEquals(50, config.getSimulationIntervalTicks());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setMaxRegionsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setMaxRegionsPerCycle(100);
|
||||
assertEquals(100, config.getMaxRegionsPerCycle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setMaxMillisecondsPerCycle() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setMaxMillisecondsPerCycle(50);
|
||||
assertEquals(50, config.getMaxMillisecondsPerCycle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setEmergencyStopMilliseconds() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setEmergencyStopMilliseconds(60);
|
||||
assertEquals(60, config.getEmergencyStopMilliseconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setEnableDebugCommandsFalse() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setEnableDebugCommands(false);
|
||||
assertFalse(config.isEnableDebugCommands());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setEnableProfilerFalse() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
config.setEnableProfiler(false);
|
||||
assertFalse(config.isEnableProfiler());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// toString
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("toString should not throw")
|
||||
void toStringShouldNotThrow() {
|
||||
final SimulationConfig config = new SimulationConfig();
|
||||
final String result = config.toString();
|
||||
assertTrue(result.contains("SimulationConfig"));
|
||||
assertTrue(result.contains("regionSizeChunks=8"));
|
||||
assertTrue(result.contains("simulationIntervalTicks=100"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package com.livingworld.core.services;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ServiceRegistry}.
|
||||
*/
|
||||
class ServiceRegistryTest {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Registration and retrieval
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Registration and retrieval")
|
||||
class RegistrationAndRetrieval {
|
||||
|
||||
@Test
|
||||
void registerAndGet() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("test-service", String.class);
|
||||
registry.register(key, "hello");
|
||||
String result = registry.get(key);
|
||||
assertEquals("hello", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findReturnsPresentService() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("test-service-2", Integer.class);
|
||||
registry.register(key, 42);
|
||||
var found = registry.find(key);
|
||||
assertTrue(found.isPresent());
|
||||
assertEquals(42, found.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findReturnsEmptyForUnregistered() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("unregistered", Boolean.class);
|
||||
var found = registry.find(key);
|
||||
assertFalse(found.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isRegisteredReturnsTrueAfterRegistration() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("test-service-3", Object.class);
|
||||
registry.register(key, new Object());
|
||||
assertTrue(registry.isRegistered(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isRegisteredReturnsFalseForUnregistered() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("unregistered-2", Number.class);
|
||||
assertFalse(registry.isRegistered(key));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Null argument validation
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Null argument validation")
|
||||
class NullArgumentValidation {
|
||||
|
||||
@Test
|
||||
void registerWithNullKeyThrows() {
|
||||
var registry = new ServiceRegistry();
|
||||
assertThrows(IllegalArgumentException.class, () -> registry.register(null, "service"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithNullServiceThrows() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("null-service", String.class);
|
||||
assertThrows(IllegalArgumentException.class, () -> registry.register(key, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithNullKeyThrows() {
|
||||
var registry = new ServiceRegistry();
|
||||
assertThrows(IllegalArgumentException.class, () -> registry.get(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void findWithNullKeyThrows() {
|
||||
var registry = new ServiceRegistry();
|
||||
assertThrows(IllegalArgumentException.class, () -> registry.find(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isRegisteredWithNullKeyThrows() {
|
||||
var registry = new ServiceRegistry();
|
||||
assertThrows(IllegalArgumentException.class, () -> registry.isRegistered(null));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Duplicate registration rejection
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Duplicate registration rejection")
|
||||
class DuplicateRegistrationRejection {
|
||||
|
||||
@Test
|
||||
void duplicateKeyThrows() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("duplicate", String.class);
|
||||
registry.register(key, "first");
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> registry.register(key, "second"));
|
||||
assertTrue(exception.getMessage().contains("already registered"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void differentKeysDoNotConflict() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key1 = new ServiceKey<>("key-1", String.class);
|
||||
var key2 = new ServiceKey<>("key-2", Integer.class);
|
||||
registry.register(key1, "hello");
|
||||
registry.register(key2, 42);
|
||||
assertEquals("hello", registry.get(key1));
|
||||
assertEquals(42, registry.get(key2));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Lock mechanism
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Lock mechanism")
|
||||
class LockMechanism {
|
||||
|
||||
@Test
|
||||
void isLockedReturnsFalseByDefault() {
|
||||
var registry = new ServiceRegistry();
|
||||
assertFalse(registry.isLocked());
|
||||
}
|
||||
|
||||
@Test
|
||||
void lockSetsLockedState() {
|
||||
var registry = new ServiceRegistry();
|
||||
registry.lock();
|
||||
assertTrue(registry.isLocked());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerAfterLockThrows() {
|
||||
var registry = new ServiceRegistry();
|
||||
registry.lock();
|
||||
var key = new ServiceKey<>("after-lock", String.class);
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> registry.register(key, "service"));
|
||||
assertTrue(exception.getMessage().contains("locked"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerBeforeLockThenRegisterAfterThrows() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key1 = new ServiceKey<>("before-lock", String.class);
|
||||
registry.register(key1, "before");
|
||||
registry.lock();
|
||||
var key2 = new ServiceKey<>("after-lock-2", Integer.class);
|
||||
assertThrows(IllegalStateException.class, () -> registry.register(key2, 42));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAndFindWorkAfterLock() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("locked-service", String.class);
|
||||
registry.register(key, "locked");
|
||||
registry.lock();
|
||||
assertEquals("locked", registry.get(key));
|
||||
assertTrue(registry.find(key).isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// get() throws IllegalStateException for missing service
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("get() throws IllegalStateException for missing service")
|
||||
class GetMissingThrows {
|
||||
|
||||
@Test
|
||||
void getUnregisteredServiceThrows() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("missing", String.class);
|
||||
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> registry.get(key));
|
||||
assertTrue(exception.getMessage().contains("No service registered"));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// No Minecraft dependencies
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Plain Java - no Minecraft imports")
|
||||
class PlainJava {
|
||||
|
||||
@Test
|
||||
void registryIsPureJava() {
|
||||
var registry = new ServiceRegistry();
|
||||
var key = new ServiceKey<>("plain", String.class);
|
||||
registry.register(key, "test");
|
||||
assertEquals("test", registry.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package com.livingworld.events;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link LivingWorldEventBus}.
|
||||
*/
|
||||
public class LivingWorldEventBusTest {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Registration tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void registerAddsListenerForEventType() {
|
||||
LivingWorldEventBus bus = new LivingWorldEventBus();
|
||||
LivingWorldEventListener listener = event -> {};
|
||||
|
||||
assertDoesNotThrow(() -> bus.register("weather_change", listener));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerPreservesRegistrationOrder() {
|
||||
LivingWorldEventBus bus = new LivingWorldEventBus();
|
||||
AtomicBoolean firstCalled = new AtomicBoolean(false);
|
||||
AtomicBoolean secondCalled = new AtomicBoolean(false);
|
||||
|
||||
LivingWorldEventListener first = event -> firstCalled.set(true);
|
||||
LivingWorldEventListener second = event -> secondCalled.set(true);
|
||||
|
||||
bus.register("weather_change", first);
|
||||
bus.register("weather_change", second);
|
||||
|
||||
BaseLivingWorldEvent event = new BaseLivingWorldEvent("weather_change", 0L, "core");
|
||||
publishEvent(bus, "weather_change", event);
|
||||
|
||||
assertTrue(firstCalled.get());
|
||||
assertTrue(secondCalled.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerBlankEventTypeThrows() {
|
||||
LivingWorldEventBus bus = new LivingWorldEventBus();
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
bus.register(" ", null);
|
||||
});
|
||||
|
||||
assertEquals("eventType must not be null or blank", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerNullListenerThrows() {
|
||||
LivingWorldEventBus bus = new LivingWorldEventBus();
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
bus.register("weather_change", null);
|
||||
});
|
||||
|
||||
assertEquals("listener must not be null", exception.getMessage());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Publishing tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void publishDispatchesToRegisteredListeners() {
|
||||
LivingWorldEventBus bus = new LivingWorldEventBus();
|
||||
AtomicBoolean listenerCalled = new AtomicBoolean(false);
|
||||
|
||||
bus.register("weather_change", event -> listenerCalled.set(true));
|
||||
|
||||
BaseLivingWorldEvent event = new BaseLivingWorldEvent(
|
||||
"weather_change", 100L, "core"
|
||||
);
|
||||
|
||||
publishEvent(bus, "weather_change", event);
|
||||
|
||||
assertTrue(listenerCalled.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishUnknownEventTypeDoesNotCrash() {
|
||||
LivingWorldEventBus bus = new LivingWorldEventBus();
|
||||
BaseLivingWorldEvent event = new BaseLivingWorldEvent("unknown_type_123", 100L, "core");
|
||||
|
||||
assertDoesNotThrow(() -> publishEvent(bus, "unknown_type_123", event));
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishNullEventThrows() {
|
||||
LivingWorldEventBus bus = new LivingWorldEventBus();
|
||||
|
||||
assertThrowsWithMessage(() -> { try { publishEvent(bus, "weather_change", null); } catch (RuntimeException e) { throw e; } },
|
||||
IllegalArgumentException.class, "event must not be null");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Metrics tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void getPublishedEventCountIncrementsOnPublish() {
|
||||
LivingWorldEventBus bus = new LivingWorldEventBus();
|
||||
|
||||
BaseLivingWorldEvent event1 = new BaseLivingWorldEvent("weather_change", 100L, "core");
|
||||
bus.publish(event1);
|
||||
|
||||
assertEquals(1, bus.getPublishedEventCount());
|
||||
|
||||
// Clear suppression to allow publishing events with different ticks
|
||||
bus.clearSuppressionForCurrentTick();
|
||||
|
||||
BaseLivingWorldEvent event2 = new BaseLivingWorldEvent("soil_moisture", 200L, "core");
|
||||
bus.publish(event2);
|
||||
|
||||
assertEquals(2, bus.getPublishedEventCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getListenerCountReturnsCorrectNumberOfListeners() {
|
||||
LivingWorldEventBus bus = new LivingWorldEventBus();
|
||||
|
||||
bus.register("weather_change", event -> {});
|
||||
bus.register("weather_change", event -> {});
|
||||
bus.register("pollution_level", event -> {});
|
||||
|
||||
assertEquals(2, bus.getListenerCount("weather_change"));
|
||||
assertEquals(1, bus.getListenerCount("pollution_level"));
|
||||
assertEquals(0, bus.getListenerCount("unknown_event"));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Helper methods (kept in test file for simplicity)
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private void publishEvent(LivingWorldEventBus bus, String eventType, LivingWorldEvent event) {
|
||||
// Use reflection to call the package-private publish method
|
||||
try {
|
||||
java.lang.reflect.Method method = LivingWorldEventBus.class.getDeclaredMethod(
|
||||
"publish", LivingWorldEvent.class
|
||||
);
|
||||
method.setAccessible(true);
|
||||
if (event == null) {
|
||||
method.invoke(bus, new Object[]{null});
|
||||
} else {
|
||||
method.invoke(bus, event);
|
||||
}
|
||||
} catch (java.lang.reflect.InvocationTargetException e) {
|
||||
// Unwrap the target exception for proper test assertions
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof RuntimeException) {
|
||||
throw (RuntimeException) cause;
|
||||
} else if (cause != null) {
|
||||
throw new RuntimeException(cause);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertThrowsWithMessage(Runnable runnable, Class<? extends Throwable> expectedType, String expectedMessage) {
|
||||
try {
|
||||
runnable.run();
|
||||
fail("Expected " + expectedType.getSimpleName() + " but no exception was thrown");
|
||||
} catch (AssertionError e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
if (expectedType.isInstance(t)) {
|
||||
assertEquals(expectedMessage, t.getMessage());
|
||||
} else {
|
||||
throw new AssertionError("Expected " + expectedType.getSimpleName() + " but got " + t.getClass().getSimpleName() + ": " + t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
package com.livingworld.regions;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RegionCoordinate}.
|
||||
*/
|
||||
class RegionCoordinateTest {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Validation – dimensionId
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("dimensionId validation")
|
||||
class DimensionIdValidation {
|
||||
|
||||
@Test
|
||||
void nullDimensionIdThrows() {
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
() -> new RegionCoordinate(null, 0, 0));
|
||||
assertTrue(ex.getMessage().contains("dimensionId"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blankDimensionIdThrows() {
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
() -> new RegionCoordinate("", 0, 0));
|
||||
assertTrue(ex.getMessage().contains("dimensionId"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whitespaceOnlyDimensionIdThrows() {
|
||||
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
|
||||
() -> new RegionCoordinate(" ", 0, 0));
|
||||
assertTrue(ex.getMessage().contains("dimensionId"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validDimensionIdAccepted() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0);
|
||||
assertEquals("overworld", rc.dimensionId());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Validation – regionSizeChunks
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("regionSizeChunks validation")
|
||||
class RegionSizeValidation {
|
||||
|
||||
@Test
|
||||
void zeroRegionSizeThrows() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> RegionCoordinate.fromChunk("overworld", 0, 0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeRegionSizeThrows() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> RegionCoordinate.fromChunk("overworld", 0, 0, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneRegionSizeAccepted() {
|
||||
RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", 5, 5, 1);
|
||||
assertEquals(5, rc.x());
|
||||
assertEquals(5, rc.z());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// fromBlock – specified test cases
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("fromBlock coordinate mapping")
|
||||
class FromBlockMapping {
|
||||
|
||||
/** Block 0,0 maps to region 0,0 */
|
||||
@Test
|
||||
void blockZeroZeroMapsToRegionZeroZero() {
|
||||
RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", 0, 0, 8);
|
||||
assertEquals(0, rc.x());
|
||||
assertEquals(0, rc.z());
|
||||
}
|
||||
|
||||
/** Block 127,127 maps to region 0,0 with region size 8 chunks */
|
||||
@Test
|
||||
void block127_127MapsToRegionZeroZero() {
|
||||
// chunk = floorDiv(block, 16) → floorDiv(127,16) = 7
|
||||
// region = floorDiv(chunk, 8) → floorDiv(7,8) = 0
|
||||
RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", 127, 127, 8);
|
||||
assertEquals(0, rc.x());
|
||||
assertEquals(0, rc.z());
|
||||
}
|
||||
|
||||
/** Block 128,0 maps to region 1,0 */
|
||||
@Test
|
||||
void block128ZeroMapsToRegionOneZero() {
|
||||
// chunk = floorDiv(128,16) = 8
|
||||
// region = floorDiv(8,8) = 1
|
||||
RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", 128, 0, 8);
|
||||
assertEquals(1, rc.x());
|
||||
assertEquals(0, rc.z());
|
||||
}
|
||||
|
||||
/** Block -1,0 maps to region -1,0 */
|
||||
@Test
|
||||
void blockMinusOneZeroMapsToRegionMinusOneZero() {
|
||||
// chunk = floorDiv(-1,16) = -1
|
||||
// region = floorDiv(-1,8) = -1
|
||||
RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", -1, 0, 8);
|
||||
assertEquals(-1, rc.x());
|
||||
assertEquals(0, rc.z());
|
||||
}
|
||||
|
||||
/** Block -128,0 maps to region -1,0 */
|
||||
@Test
|
||||
void blockMinus128ZeroMapsToRegionMinusOneZero() {
|
||||
// chunk = floorDiv(-128,16) = -8
|
||||
// region = floorDiv(-8,8) = -1
|
||||
RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", -128, 0, 8);
|
||||
assertEquals(-1, rc.x());
|
||||
assertEquals(0, rc.z());
|
||||
}
|
||||
|
||||
/** Block -129,0 maps to region -2,0 */
|
||||
@Test
|
||||
void blockMinus129ZeroMapsToRegionMinusTwoZero() {
|
||||
// chunk = floorDiv(-129,16) = -9
|
||||
// region = floorDiv(-9,8) = -2
|
||||
RegionCoordinate rc = RegionCoordinate.fromBlock("overworld", -129, 0, 8);
|
||||
assertEquals(-2, rc.x());
|
||||
assertEquals(0, rc.z());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// fromChunk – basic checks
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("fromChunk coordinate mapping")
|
||||
class FromChunkMapping {
|
||||
|
||||
@Test
|
||||
void chunkZeroZeroMapsToRegionZeroZero() {
|
||||
RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", 0, 0, 8);
|
||||
assertEquals(0, rc.x());
|
||||
assertEquals(0, rc.z());
|
||||
}
|
||||
|
||||
@Test
|
||||
void chunkSevenSevenMapsToRegionZeroZero() {
|
||||
RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", 7, 7, 8);
|
||||
assertEquals(0, rc.x());
|
||||
assertEquals(0, rc.z());
|
||||
}
|
||||
|
||||
@Test
|
||||
void chunkEightEightMapsToRegionOneOne() {
|
||||
RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", 8, 8, 8);
|
||||
assertEquals(1, rc.x());
|
||||
assertEquals(1, rc.z());
|
||||
}
|
||||
|
||||
@Test
|
||||
void chunkMinusOneMinusOneMapsToRegionMinusOneMinusOne() {
|
||||
RegionCoordinate rc = RegionCoordinate.fromChunk("overworld", -1, -1, 8);
|
||||
assertEquals(-1, rc.x());
|
||||
assertEquals(-1, rc.z());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Boundary methods – min/max chunk coordinates
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("minChunkX / minChunkZ / maxChunkX / maxChunkZ")
|
||||
class ChunkBoundaryMethods {
|
||||
|
||||
@Test
|
||||
void regionZeroBoundariesWithSizeEight() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0);
|
||||
assertEquals(0, rc.minChunkX(8));
|
||||
assertEquals(0, rc.minChunkZ(8));
|
||||
assertEquals(7, rc.maxChunkX(8));
|
||||
assertEquals(7, rc.maxChunkZ(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regionOneBoundariesWithSizeEight() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 1, 1);
|
||||
assertEquals(8, rc.minChunkX(8));
|
||||
assertEquals(8, rc.minChunkZ(8));
|
||||
assertEquals(15, rc.maxChunkX(8));
|
||||
assertEquals(15, rc.maxChunkZ(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regionMinusOneBoundariesWithSizeEight() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", -1, -1);
|
||||
assertEquals(-8, rc.minChunkX(8));
|
||||
assertEquals(-8, rc.minChunkZ(8));
|
||||
assertEquals(-1, rc.maxChunkX(8));
|
||||
assertEquals(-1, rc.maxChunkZ(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidRegionSizeThrowsInBoundaryMethods() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0);
|
||||
assertThrows(IllegalArgumentException.class, () -> rc.minChunkX(0));
|
||||
assertThrows(IllegalArgumentException.class, () -> rc.minChunkZ(-1));
|
||||
assertThrows(IllegalArgumentException.class, () -> rc.maxChunkX(0));
|
||||
assertThrows(IllegalArgumentException.class, () -> rc.maxChunkZ(-5));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// centerBlock methods
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("centerBlockX / centerBlockZ")
|
||||
class CenterBlockMethods {
|
||||
|
||||
/** Region 0 with size 8: chunks 0-7, blocks 0-127 → centre = floorDiv(0+127,2) = 63 */
|
||||
@Test
|
||||
void centerOfRegionZero() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0);
|
||||
assertEquals(63, rc.centerBlockX(8));
|
||||
assertEquals(63, rc.centerBlockZ(8));
|
||||
}
|
||||
|
||||
/** Region 1 with size 8: chunks 8-15, blocks 128-255 → centre = floorDiv(128+255,2) = 191 */
|
||||
@Test
|
||||
void centerOfRegionOne() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 1, 1);
|
||||
assertEquals(191, rc.centerBlockX(8));
|
||||
assertEquals(191, rc.centerBlockZ(8));
|
||||
}
|
||||
|
||||
/** Region -1 with size 8: chunks -8 to -1, blocks -128 to -1 → centre = floorDiv(-128+-1,2) = -65 */
|
||||
@Test
|
||||
void centerOfRegionMinusOne() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", -1, -1);
|
||||
assertEquals(-65, rc.centerBlockX(8));
|
||||
assertEquals(-65, rc.centerBlockZ(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidRegionSizeThrowsInCenterMethods() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0);
|
||||
assertThrows(IllegalArgumentException.class, () -> rc.centerBlockX(0));
|
||||
assertThrows(IllegalArgumentException.class, () -> rc.centerBlockZ(-1));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// stableId
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("stableId")
|
||||
class StableIdTests {
|
||||
|
||||
@Test
|
||||
void stableIdIsStable() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 1, -2);
|
||||
String id1 = rc.stableId();
|
||||
String id2 = rc.stableId();
|
||||
assertEquals(id1, id2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void stableIdFormat() {
|
||||
RegionCoordinate rc = new RegionCoordinate("the_nether", 3, 5);
|
||||
assertEquals("the_nether:3:5", rc.stableId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeCoordinatesInStableId() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", -1, -2);
|
||||
assertEquals("overworld:-1:-2", rc.stableId());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// HashMap key usage
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("HashMap key compatibility")
|
||||
class HashMapKeyTests {
|
||||
|
||||
@Test
|
||||
void worksAsHashMapKey() {
|
||||
Map<RegionCoordinate, String> map = new HashMap<>();
|
||||
RegionCoordinate key1 = new RegionCoordinate("overworld", 0, 0);
|
||||
RegionCoordinate key2 = new RegionCoordinate("overworld", 0, 0);
|
||||
RegionCoordinate key3 = new RegionCoordinate("overworld", 1, 0);
|
||||
|
||||
map.put(key1, "value1");
|
||||
assertEquals("value1", map.get(key2)); // same logical key
|
||||
assertNotEquals("value1", map.get(key3)); // different key
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsAndHashCodeContract() {
|
||||
RegionCoordinate rc1 = new RegionCoordinate("overworld", 0, 0);
|
||||
RegionCoordinate rc2 = new RegionCoordinate("overworld", 0, 0);
|
||||
|
||||
assertEquals(rc1, rc2);
|
||||
assertEquals(rc1.hashCode(), rc2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void notEqualToDifferentDimension() {
|
||||
RegionCoordinate rc1 = new RegionCoordinate("overworld", 0, 0);
|
||||
RegionCoordinate rc2 = new RegionCoordinate("nether", 0, 0);
|
||||
|
||||
assertFalse(rc1.equals(rc2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void notEqualToDifferentType() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0);
|
||||
assertFalse(rc.equals("not a region coordinate"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeEquals() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 0, 0);
|
||||
assertFalse(rc.equals(null));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// toString
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("toString")
|
||||
class ToStringTests {
|
||||
|
||||
@Test
|
||||
void toStringContainsAllFields() {
|
||||
RegionCoordinate rc = new RegionCoordinate("overworld", 1, -3);
|
||||
String s = rc.toString();
|
||||
assertTrue(s.contains("RegionCoordinate"));
|
||||
assertTrue(s.contains("overworld"));
|
||||
assertTrue(s.contains("x=1"));
|
||||
assertTrue(s.contains("z=-3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringShouldNotThrow() {
|
||||
RegionCoordinate rc = new RegionCoordinate("the_end", 0, 0);
|
||||
assertDoesNotThrow(() -> rc.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.livingworld.regions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link RegionFactory}.
|
||||
*/
|
||||
class RegionFactoryTest {
|
||||
|
||||
private final RegionFactory factory = new RegionFactory();
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Null handling
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionWithNullCoordinateThrows() {
|
||||
IllegalArgumentException exception = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> factory.createNewRegion(null, 0L)
|
||||
);
|
||||
assertEquals("coordinate must not be null", exception.getMessage());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Basic creation
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionReturnsValidRegion() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
long tick = 42L;
|
||||
|
||||
Region region = factory.createNewRegion(coordinate, tick);
|
||||
|
||||
assertNotNull(region);
|
||||
assertDoesNotThrow(() -> region.validate());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// UUID assignment
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionAssignsRandomUUID() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
|
||||
Region region1 = factory.createNewRegion(coordinate, 0L);
|
||||
Region region2 = factory.createNewRegion(coordinate, 0L);
|
||||
|
||||
assertNotEquals(region1.getId(), region2.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createNewRegionUUIDIsNotNull() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
Region region = factory.createNewRegion(coordinate, 0L);
|
||||
|
||||
assertNotNull(region.getId());
|
||||
assertInstanceOf(UUID.class, region.getId());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Coordinate assignment
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionUsesArgumentCoordinate() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 5, -3);
|
||||
Region region = factory.createNewRegion(coordinate, 0L);
|
||||
|
||||
assertEquals(coordinate, region.getCoordinate());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Lifecycle state
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionStartsActive() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
Region region = factory.createNewRegion(coordinate, 0L);
|
||||
|
||||
assertEquals(RegionLifecycleState.ACTIVE, region.getLifecycleState());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Simulation tick values
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionSetsCreatedAtSimulationTick() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
long tick = 12345L;
|
||||
Region region = factory.createNewRegion(coordinate, tick);
|
||||
|
||||
assertEquals(tick, region.getCreatedAtSimulationTick());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createNewRegionSetsLastUpdatedSimulationTick() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
long tick = 12345L;
|
||||
Region region = factory.createNewRegion(coordinate, tick);
|
||||
|
||||
assertEquals(tick, region.getLastUpdatedSimulationTick());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Dirty flag
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionIsDirty() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
Region region = factory.createNewRegion(coordinate, 0L);
|
||||
|
||||
assertTrue(region.isDirty());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Default flags
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionHasDefaultFlags() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
Region region = factory.createNewRegion(coordinate, 0L);
|
||||
|
||||
RegionFlags flags = region.getFlags();
|
||||
assertNotNull(flags);
|
||||
assertFalse(flags.isHasPlayerActivity());
|
||||
assertFalse(flags.isHasHighPollution());
|
||||
assertFalse(flags.isHasLowSoilQuality());
|
||||
assertFalse(flags.isHasActiveEcosystemEvent());
|
||||
assertFalse(flags.isForceLoadedBySimulation());
|
||||
assertFalse(flags.isCorrupted());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Default metrics
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionHasDefaultMetrics() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
Region region = factory.createNewRegion(coordinate, 0L);
|
||||
|
||||
RegionMetrics metrics = region.getMetrics();
|
||||
assertNotNull(metrics);
|
||||
|
||||
assertEquals(60.0, metrics.getEcosystemHealth());
|
||||
assertEquals(0.0, metrics.getPollutionScore());
|
||||
assertEquals(60.0, metrics.getSoilQuality());
|
||||
assertEquals(60.0, metrics.getWaterQuality());
|
||||
assertEquals(50.0, metrics.getVegetationPressure());
|
||||
assertEquals(0.0, metrics.getResourceDepletion());
|
||||
assertEquals(50.0, metrics.getRecoveryPressure());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Empty module data
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionHasEmptyModuleData() {
|
||||
RegionCoordinate coordinate = new RegionCoordinate("overworld", 0, 0);
|
||||
Region region = factory.createNewRegion(coordinate, 0L);
|
||||
|
||||
RegionModuleData moduleData = region.getModuleData();
|
||||
assertNotNull(moduleData);
|
||||
assertTrue(moduleData.moduleIds().isEmpty());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Different dimensions
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void createNewRegionWorksForDifferentDimensions() {
|
||||
RegionCoordinate coordOverworld = new RegionCoordinate("overworld", 0, 0);
|
||||
RegionCoordinate coordNether = new RegionCoordinate("the_nether", 1, 2);
|
||||
RegionCoordinate coordEnd = new RegionCoordinate("end", -3, -4);
|
||||
|
||||
Region regionOverworld = factory.createNewRegion(coordOverworld, 0L);
|
||||
Region regionNether = factory.createNewRegion(coordNether, 0L);
|
||||
Region regionEnd = factory.createNewRegion(coordEnd, 0L);
|
||||
|
||||
assertEquals(coordOverworld, regionOverworld.getCoordinate());
|
||||
assertEquals(coordNether, regionNether.getCoordinate());
|
||||
assertEquals(coordEnd, regionEnd.getCoordinate());
|
||||
|
||||
assertNotEquals(regionOverworld.getId(), regionNether.getId());
|
||||
assertNotEquals(regionNether.getId(), regionEnd.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.livingworld.regions;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RegionFlags}.
|
||||
*/
|
||||
class RegionFlagsTest {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Default values
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Default values")
|
||||
class DefaultValues {
|
||||
|
||||
@Test
|
||||
void allFlagsDefaultToFalse() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
assertFalse(flags.isHasPlayerActivity());
|
||||
assertFalse(flags.isHasHighPollution());
|
||||
assertFalse(flags.isHasLowSoilQuality());
|
||||
assertFalse(flags.isHasActiveEcosystemEvent());
|
||||
assertFalse(flags.isForceLoadedBySimulation());
|
||||
assertFalse(flags.isCorrupted());
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceIsNotNull() {
|
||||
assertNotNull(new RegionFlags());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Getters and Setters
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Getters and Setters")
|
||||
class GetterSetterTests {
|
||||
|
||||
@Test
|
||||
void hasPlayerActivity() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
flags.setHasPlayerActivity(true);
|
||||
assertTrue(flags.isHasPlayerActivity());
|
||||
flags.setHasPlayerActivity(false);
|
||||
assertFalse(flags.isHasPlayerActivity());
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasHighPollution() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
flags.setHasHighPollution(true);
|
||||
assertTrue(flags.isHasHighPollution());
|
||||
flags.setHasHighPollution(false);
|
||||
assertFalse(flags.isHasHighPollution());
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasLowSoilQuality() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
flags.setHasLowSoilQuality(true);
|
||||
assertTrue(flags.isHasLowSoilQuality());
|
||||
flags.setHasLowSoilQuality(false);
|
||||
assertFalse(flags.isHasLowSoilQuality());
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasActiveEcosystemEvent() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
flags.setHasActiveEcosystemEvent(true);
|
||||
assertTrue(flags.isHasActiveEcosystemEvent());
|
||||
flags.setHasActiveEcosystemEvent(false);
|
||||
assertFalse(flags.isHasActiveEcosystemEvent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void forceLoadedBySimulation() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
flags.setForceLoadedBySimulation(true);
|
||||
assertTrue(flags.isForceLoadedBySimulation());
|
||||
flags.setForceLoadedBySimulation(false);
|
||||
assertFalse(flags.isForceLoadedBySimulation());
|
||||
}
|
||||
|
||||
@Test
|
||||
void corrupted() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
flags.setCorrupted(true);
|
||||
assertTrue(flags.isCorrupted());
|
||||
flags.setCorrupted(false);
|
||||
assertFalse(flags.isCorrupted());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// copy()
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("copy()")
|
||||
class CopyTests {
|
||||
|
||||
@Test
|
||||
void copyReturnsIndependentInstance() {
|
||||
RegionFlags original = new RegionFlags();
|
||||
original.setHasPlayerActivity(true);
|
||||
original.setCorrupted(true);
|
||||
|
||||
RegionFlags copy = original.copy();
|
||||
assertNotNull(copy);
|
||||
assertNotSame(original, copy);
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPreservesAllFlagValues() {
|
||||
RegionFlags original = new RegionFlags();
|
||||
original.setHasPlayerActivity(true);
|
||||
original.setHasHighPollution(true);
|
||||
original.setHasLowSoilQuality(true);
|
||||
original.setHasActiveEcosystemEvent(true);
|
||||
original.setForceLoadedBySimulation(true);
|
||||
original.setCorrupted(true);
|
||||
|
||||
RegionFlags copy = original.copy();
|
||||
assertTrue(copy.isHasPlayerActivity());
|
||||
assertTrue(copy.isHasHighPollution());
|
||||
assertTrue(copy.isHasLowSoilQuality());
|
||||
assertTrue(copy.isHasActiveEcosystemEvent());
|
||||
assertTrue(copy.isForceLoadedBySimulation());
|
||||
assertTrue(copy.isCorrupted());
|
||||
}
|
||||
|
||||
@Test
|
||||
void modifyingCopyDoesNotAffectOriginal() {
|
||||
RegionFlags original = new RegionFlags();
|
||||
original.setHasPlayerActivity(true);
|
||||
original.setCorrupted(false);
|
||||
|
||||
RegionFlags copy = original.copy();
|
||||
copy.setHasPlayerActivity(false);
|
||||
copy.setCorrupted(true);
|
||||
|
||||
assertTrue(original.isHasPlayerActivity());
|
||||
assertFalse(original.isCorrupted());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// clearTransientFlags()
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("clearTransientFlags()")
|
||||
class ClearTransientFlagTests {
|
||||
|
||||
@Test
|
||||
void clearsTransientFlagsOnly() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
flags.setHasPlayerActivity(true);
|
||||
flags.setHasActiveEcosystemEvent(true);
|
||||
flags.setHasHighPollution(true);
|
||||
flags.setHasLowSoilQuality(true);
|
||||
flags.setForceLoadedBySimulation(true);
|
||||
flags.setCorrupted(true);
|
||||
|
||||
flags.clearTransientFlags();
|
||||
|
||||
// Transient flags cleared
|
||||
assertFalse(flags.isHasPlayerActivity());
|
||||
assertFalse(flags.isHasActiveEcosystemEvent());
|
||||
|
||||
// Persistent flags preserved
|
||||
assertTrue(flags.isHasHighPollution());
|
||||
assertTrue(flags.isHasLowSoilQuality());
|
||||
assertTrue(flags.isForceLoadedBySimulation());
|
||||
assertTrue(flags.isCorrupted());
|
||||
}
|
||||
|
||||
@Test
|
||||
void safeToCallWhenAllFlagsAlreadyFalse() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
flags.clearTransientFlags(); // should not throw
|
||||
assertFalse(flags.isHasPlayerActivity());
|
||||
assertFalse(flags.isHasActiveEcosystemEvent());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// toString
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("toString")
|
||||
class ToStringTests {
|
||||
|
||||
@Test
|
||||
void toStringContainsFlagNames() {
|
||||
RegionFlags flags = new RegionFlags();
|
||||
flags.setHasPlayerActivity(true);
|
||||
String s = flags.toString();
|
||||
assertTrue(s.contains("hasPlayerActivity=true"));
|
||||
assertTrue(s.contains("corrupted=false"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package com.livingworld.regions;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link RegionLifecycleController}.
|
||||
*/
|
||||
class RegionLifecycleControllerTest {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Allowed transitions
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Allowed transitions")
|
||||
class AllowedTransitions {
|
||||
|
||||
@Test
|
||||
@DisplayName("UNLOADED -> LOADING is allowed")
|
||||
void unloadedToLoading() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.UNLOADED, RegionLifecycleState.LOADING));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("LOADING -> ACTIVE is allowed")
|
||||
void loadingToActive() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.LOADING, RegionLifecycleState.ACTIVE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("LOADING -> FAILED is allowed")
|
||||
void loadingToFailed() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.LOADING, RegionLifecycleState.FAILED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ACTIVE -> DIRTY is allowed")
|
||||
void activeToDirty() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.ACTIVE, RegionLifecycleState.DIRTY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DIRTY -> SAVING is allowed")
|
||||
void dirtyToSaving() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.DIRTY, RegionLifecycleState.SAVING));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SAVING -> ACTIVE is allowed")
|
||||
void savingToActive() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.SAVING, RegionLifecycleState.ACTIVE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SAVING -> FAILED is allowed")
|
||||
void savingToFailed() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.SAVING, RegionLifecycleState.FAILED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ACTIVE -> UNLOADING is allowed")
|
||||
void activeToUnloading() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.ACTIVE, RegionLifecycleState.UNLOADING));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("UNLOADING -> UNLOADED is allowed")
|
||||
void unloadingToUnloaded() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.UNLOADING, RegionLifecycleState.UNLOADED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("FAILED -> LOADING is allowed")
|
||||
void failedToLoading() {
|
||||
assertTrue(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.FAILED, RegionLifecycleState.LOADING));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Disallowed transitions
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Disallowed transitions")
|
||||
class DisallowedTransitions {
|
||||
|
||||
private static final String[] STATES = {
|
||||
"UNLOADED", "LOADING", "ACTIVE", "DIRTY", "SAVING", "FAILED", "UNLOADING"
|
||||
};
|
||||
|
||||
@Test
|
||||
@DisplayName("Same state is not allowed (no self-transitions)")
|
||||
void sameStateNotAllowed() {
|
||||
for (String state : STATES) {
|
||||
assertFalse(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.valueOf(state), RegionLifecycleState.valueOf(state)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("UNLOADED -> ACTIVE is not allowed")
|
||||
void unloadedToActive() {
|
||||
assertFalse(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.UNLOADED, RegionLifecycleState.ACTIVE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ACTIVE -> LOADING is not allowed")
|
||||
void activeToLoading() {
|
||||
assertFalse(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.ACTIVE, RegionLifecycleState.LOADING));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("FAILED -> UNLOADED is not allowed")
|
||||
void failedToUnloaded() {
|
||||
assertFalse(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.FAILED, RegionLifecycleState.UNLOADED));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ACTIVE -> SAVING is not allowed")
|
||||
void activeToSaving() {
|
||||
assertFalse(RegionLifecycleController.canTransition(
|
||||
RegionLifecycleState.ACTIVE, RegionLifecycleState.SAVING));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("All invalid transitions throw IllegalStateException on transition()")
|
||||
void invalidTransitionsThrowException() {
|
||||
Region region1 = new Region(
|
||||
java.util.UUID.randomUUID(),
|
||||
new RegionCoordinate("minecraft:overworld", 0, 0),
|
||||
RegionLifecycleState.ACTIVE, 0, 0, false,
|
||||
new RegionFlags(), new RegionMetrics(), new RegionModuleData());
|
||||
|
||||
// ACTIVE -> LOADING (invalid)
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
RegionLifecycleController.transition(region1, RegionLifecycleState.LOADING));
|
||||
|
||||
// ACTIVE -> SAVING (invalid)
|
||||
Region region2 = new Region(
|
||||
java.util.UUID.randomUUID(),
|
||||
new RegionCoordinate("minecraft:overworld", 0, 0),
|
||||
RegionLifecycleState.ACTIVE, 0, 0, false,
|
||||
new RegionFlags(), new RegionMetrics(), new RegionModuleData());
|
||||
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
RegionLifecycleController.transition(region2, RegionLifecycleState.SAVING));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Null handling
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Null handling")
|
||||
class NullHandling {
|
||||
|
||||
@Test
|
||||
@DisplayName("canTransition throws IllegalArgumentException for null from")
|
||||
void canTransitionNullFrom() {
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
RegionLifecycleController.canTransition(null, RegionLifecycleState.ACTIVE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("canTransition throws IllegalArgumentException for null to")
|
||||
void canTransitionNullTo() {
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
RegionLifecycleController.canTransition(RegionLifecycleState.UNLOADED, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("transition throws IllegalArgumentException for null region")
|
||||
void transitionNullRegion() {
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
RegionLifecycleController.transition(null, RegionLifecycleState.LOADING));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("transition throws IllegalArgumentException for null target")
|
||||
void transitionNullTarget() {
|
||||
Region region = new Region(
|
||||
java.util.UUID.randomUUID(),
|
||||
new RegionCoordinate("minecraft:overworld", 0, 0),
|
||||
RegionLifecycleState.UNLOADED, 0, 0, false,
|
||||
new RegionFlags(), new RegionMetrics(), new RegionModuleData());
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
RegionLifecycleController.transition(region, null));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// transition() side effects
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("transition() side effects")
|
||||
class TransitionSideEffects {
|
||||
|
||||
@Test
|
||||
@DisplayName("transition() updates lifecycle state and marks dirty")
|
||||
void transitionUpdatesStateAndMarksDirty() {
|
||||
Region region = new Region(
|
||||
java.util.UUID.randomUUID(),
|
||||
new RegionCoordinate("minecraft:overworld", 0, 0),
|
||||
RegionLifecycleState.UNLOADED, 0, 0, false,
|
||||
new RegionFlags(), new RegionMetrics(), new RegionModuleData());
|
||||
|
||||
assertFalse(region.isDirty());
|
||||
assertEquals(RegionLifecycleState.UNLOADED, region.getLifecycleState());
|
||||
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.LOADING);
|
||||
|
||||
assertEquals(RegionLifecycleState.LOADING, region.getLifecycleState());
|
||||
assertTrue(region.isDirty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("transition() updates state through full load-save-unload cycle")
|
||||
void fullCycleTransition() {
|
||||
Region region = new Region(
|
||||
java.util.UUID.randomUUID(),
|
||||
new RegionCoordinate("minecraft:overworld", 0, 0),
|
||||
RegionLifecycleState.UNLOADED, 0, 0, false,
|
||||
new RegionFlags(), new RegionMetrics(), new RegionModuleData());
|
||||
|
||||
// UNLOADED -> LOADING
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.LOADING);
|
||||
assertEquals(RegionLifecycleState.LOADING, region.getLifecycleState());
|
||||
|
||||
// LOADING -> ACTIVE
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.ACTIVE);
|
||||
assertEquals(RegionLifecycleState.ACTIVE, region.getLifecycleState());
|
||||
|
||||
// ACTIVE -> DIRTY
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.DIRTY);
|
||||
assertEquals(RegionLifecycleState.DIRTY, region.getLifecycleState());
|
||||
|
||||
// DIRTY -> SAVING
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.SAVING);
|
||||
assertEquals(RegionLifecycleState.SAVING, region.getLifecycleState());
|
||||
|
||||
// SAVING -> ACTIVE (successful save)
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.ACTIVE);
|
||||
assertEquals(RegionLifecycleState.ACTIVE, region.getLifecycleState());
|
||||
|
||||
// ACTIVE -> UNLOADING
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.UNLOADING);
|
||||
assertEquals(RegionLifecycleState.UNLOADING, region.getLifecycleState());
|
||||
|
||||
// UNLOADING -> UNLOADED
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.UNLOADED);
|
||||
assertEquals(RegionLifecycleState.UNLOADED, region.getLifecycleState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("transition() allows FAILED -> LOADING recovery")
|
||||
void failedToLoadingRecovery() {
|
||||
Region region = new Region(
|
||||
java.util.UUID.randomUUID(),
|
||||
new RegionCoordinate("minecraft:overworld", 0, 0),
|
||||
RegionLifecycleState.FAILED, 0, 0, false,
|
||||
new RegionFlags(), new RegionMetrics(), new RegionModuleData());
|
||||
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.LOADING);
|
||||
assertEquals(RegionLifecycleState.LOADING, region.getLifecycleState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("transition() allows SAVING -> FAILED error path")
|
||||
void savingToFailedErrorPath() {
|
||||
Region region = new Region(
|
||||
java.util.UUID.randomUUID(),
|
||||
new RegionCoordinate("minecraft:overworld", 0, 0),
|
||||
RegionLifecycleState.ACTIVE, 0, 0, false,
|
||||
new RegionFlags(), new RegionMetrics(), new RegionModuleData());
|
||||
|
||||
// ACTIVE -> DIRTY
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.DIRTY);
|
||||
assertEquals(RegionLifecycleState.DIRTY, region.getLifecycleState());
|
||||
|
||||
// DIRTY -> SAVING
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.SAVING);
|
||||
assertEquals(RegionLifecycleState.SAVING, region.getLifecycleState());
|
||||
|
||||
// SAVING -> FAILED (save error)
|
||||
RegionLifecycleController.transition(region, RegionLifecycleState.FAILED);
|
||||
assertEquals(RegionLifecycleState.FAILED, region.getLifecycleState());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
package com.livingworld.regions;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Comprehensive tests for {@link RegionMetrics}.
|
||||
*/
|
||||
class RegionMetricsTest {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Default Values Tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Default Values")
|
||||
class DefaultValues {
|
||||
|
||||
@Test
|
||||
@DisplayName("defaults() returns correct ecosystem health")
|
||||
void defaultsEcosystemHealth() {
|
||||
RegionMetrics metrics = RegionMetrics.defaults();
|
||||
assertEquals(60, metrics.getEcosystemHealth(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaults() returns correct pollution score")
|
||||
void defaultsPollutionScore() {
|
||||
RegionMetrics metrics = RegionMetrics.defaults();
|
||||
assertEquals(0, metrics.getPollutionScore(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaults() returns correct soil quality")
|
||||
void defaultsSoilQuality() {
|
||||
RegionMetrics metrics = RegionMetrics.defaults();
|
||||
assertEquals(60, metrics.getSoilQuality(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaults() returns correct water quality")
|
||||
void defaultsWaterQuality() {
|
||||
RegionMetrics metrics = RegionMetrics.defaults();
|
||||
assertEquals(60, metrics.getWaterQuality(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaults() returns correct vegetation pressure")
|
||||
void defaultsVegetationPressure() {
|
||||
RegionMetrics metrics = RegionMetrics.defaults();
|
||||
assertEquals(50, metrics.getVegetationPressure(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaults() returns correct resource depletion")
|
||||
void defaultsResourceDepletion() {
|
||||
RegionMetrics metrics = RegionMetrics.defaults();
|
||||
assertEquals(0, metrics.getResourceDepletion(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaults() returns correct recovery pressure")
|
||||
void defaultsRecoveryPressure() {
|
||||
RegionMetrics metrics = RegionMetrics.defaults();
|
||||
assertEquals(50, metrics.getRecoveryPressure(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("default constructor initializes all fields to 0")
|
||||
void defaultConstructorAllZeros() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
assertEquals(0, metrics.getEcosystemHealth(), 0.001);
|
||||
assertEquals(0, metrics.getPollutionScore(), 0.001);
|
||||
assertEquals(0, metrics.getSoilQuality(), 0.001);
|
||||
assertEquals(0, metrics.getWaterQuality(), 0.001);
|
||||
assertEquals(0, metrics.getVegetationPressure(), 0.001);
|
||||
assertEquals(0, metrics.getResourceDepletion(), 0.001);
|
||||
assertEquals(0, metrics.getRecoveryPressure(), 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Getter/Setter Tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Getter and Setter Clamping")
|
||||
class GetterSetterClamping {
|
||||
|
||||
@Test
|
||||
@DisplayName("setter clamps values above 100 to 100")
|
||||
void setterClampsAboveMax() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setEcosystemHealth(150);
|
||||
assertEquals(100, metrics.getEcosystemHealth(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("setter clamps values below 0 to 0")
|
||||
void setterClampsBelowMin() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setPollutionScore(-50);
|
||||
assertEquals(0, metrics.getPollutionScore(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("setter accepts exact boundary values")
|
||||
void setterAcceptsBoundaries() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setSoilQuality(0);
|
||||
assertEquals(0, metrics.getSoilQuality(), 0.001);
|
||||
metrics.setSoilQuality(100);
|
||||
assertEquals(100, metrics.getSoilQuality(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("setter accepts normal values")
|
||||
void setterAcceptsNormalValues() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setWaterQuality(42.5);
|
||||
assertEquals(42.5, metrics.getWaterQuality(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("all setters clamp correctly")
|
||||
void allSettersClamp() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setVegetationPressure(-10);
|
||||
assertEquals(0, metrics.getVegetationPressure(), 0.001);
|
||||
|
||||
metrics.setResourceDepletion(200);
|
||||
assertEquals(100, metrics.getResourceDepletion(), 0.001);
|
||||
|
||||
metrics.setRecoveryPressure(-5);
|
||||
assertEquals(0, metrics.getRecoveryPressure(), 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Normalize Tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Normalize")
|
||||
class NormalizeTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("normalize() clamps values above 100")
|
||||
void normalizeClampsAboveMax() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setEcosystemHealth(150);
|
||||
metrics.setPollutionScore(-20);
|
||||
metrics.normalize();
|
||||
assertEquals(100, metrics.getEcosystemHealth(), 0.001);
|
||||
assertEquals(0, metrics.getPollutionScore(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("normalize() clamps values below 0")
|
||||
void normalizeClampsBelowMin() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setEcosystemHealth(-50);
|
||||
metrics.setPollutionScore(200);
|
||||
metrics.normalize();
|
||||
assertEquals(0, metrics.getEcosystemHealth(), 0.001);
|
||||
assertEquals(100, metrics.getPollutionScore(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("normalize() leaves valid values unchanged")
|
||||
void normalizeLeavesValidUnchanged() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setEcosystemHealth(50);
|
||||
metrics.setPollutionScore(30);
|
||||
metrics.normalize();
|
||||
assertEquals(50, metrics.getEcosystemHealth(), 0.001);
|
||||
assertEquals(30, metrics.getPollutionScore(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("normalize() returns this for chaining")
|
||||
void normalizeReturnsThis() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
RegionMetrics result = metrics.normalize();
|
||||
assertTrue(result == metrics, "normalize() should return the same instance");
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Copy Tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Copy")
|
||||
class CopyTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("copy() returns independent instance")
|
||||
void copyReturnsIndependentInstance() {
|
||||
RegionMetrics original = new RegionMetrics();
|
||||
original.setEcosystemHealth(80);
|
||||
original.setPollutionScore(30);
|
||||
|
||||
RegionMetrics copy = original.copy();
|
||||
|
||||
assertEquals(80, copy.getEcosystemHealth(), 0.001);
|
||||
assertEquals(30, copy.getPollutionScore(), 0.001);
|
||||
|
||||
// Modify the copy
|
||||
copy.setEcosystemHealth(90);
|
||||
|
||||
// Original should be unchanged
|
||||
assertEquals(80, original.getEcosystemHealth(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("copy() returns a different object")
|
||||
void copyReturnsDifferentObject() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setSoilQuality(75);
|
||||
RegionMetrics copy = metrics.copy();
|
||||
assertNotSame(metrics, copy);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("copy() copies all fields")
|
||||
void copyAllFields() {
|
||||
RegionMetrics original = new RegionMetrics();
|
||||
original.setEcosystemHealth(10);
|
||||
original.setPollutionScore(20);
|
||||
original.setSoilQuality(30);
|
||||
original.setWaterQuality(40);
|
||||
original.setVegetationPressure(50);
|
||||
original.setResourceDepletion(60);
|
||||
original.setRecoveryPressure(70);
|
||||
|
||||
RegionMetrics copy = original.copy();
|
||||
|
||||
assertEquals(original.getEcosystemHealth(), copy.getEcosystemHealth(), 0.001);
|
||||
assertEquals(original.getPollutionScore(), copy.getPollutionScore(), 0.001);
|
||||
assertEquals(original.getSoilQuality(), copy.getSoilQuality(), 0.001);
|
||||
assertEquals(original.getWaterQuality(), copy.getWaterQuality(), 0.001);
|
||||
assertEquals(original.getVegetationPressure(), copy.getVegetationPressure(), 0.001);
|
||||
assertEquals(original.getResourceDepletion(), copy.getResourceDepletion(), 0.001);
|
||||
assertEquals(original.getRecoveryPressure(), copy.getRecoveryPressure(), 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// ApplyDelta Tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Apply Delta")
|
||||
class ApplyDeltaTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("applyDelta() modifies all fields correctly")
|
||||
void applyDeltaAllFields() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setEcosystemHealth(50);
|
||||
metrics.setPollutionScore(20);
|
||||
metrics.setSoilQuality(80);
|
||||
|
||||
metrics.applyDelta(10, -5, 15, 0, 0, 0, 0);
|
||||
|
||||
assertEquals(60, metrics.getEcosystemHealth(), 0.001);
|
||||
assertEquals(15, metrics.getPollutionScore(), 0.001);
|
||||
assertEquals(95, metrics.getSoilQuality(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applyDelta() clamps results above max")
|
||||
void applyDeltaClampsAboveMax() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setEcosystemHealth(95);
|
||||
metrics.applyDelta(20, 0, 0, 0, 0, 0, 0);
|
||||
assertEquals(100, metrics.getEcosystemHealth(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applyDelta() clamps results below min")
|
||||
void applyDeltaClampsBelowMin() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setPollutionScore(5);
|
||||
metrics.applyDelta(-10, 0, 0, 0, 0, 0, 0);
|
||||
assertEquals(0, metrics.getEcosystemHealth(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applyDelta() returns this for chaining")
|
||||
void applyDeltaReturnsThis() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
RegionMetrics result = metrics.applyDelta(0, 0, 0, 0, 0, 0, 0);
|
||||
assertTrue(result == metrics);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applyEcosystemHealthDelta() modifies only ecosystem health")
|
||||
void applySingleDelta() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setEcosystemHealth(50);
|
||||
metrics.setPollutionScore(30);
|
||||
metrics.applyEcosystemHealthDelta(10);
|
||||
assertEquals(60, metrics.getEcosystemHealth(), 0.001);
|
||||
assertEquals(30, metrics.getPollutionScore(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applyPollutionScoreDelta() clamps correctly")
|
||||
void applySingleDeltaClamps() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setPollutionScore(95);
|
||||
metrics.applyPollutionScoreDelta(20);
|
||||
assertEquals(100, metrics.getPollutionScore(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applySoilQualityDelta() clamps below min")
|
||||
void applySingleDeltaClampsBelow() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setSoilQuality(5);
|
||||
metrics.applySoilQualityDelta(-10);
|
||||
assertEquals(0, metrics.getSoilQuality(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applyWaterQualityDelta() works correctly")
|
||||
void applyWaterQualityDelta() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setWaterQuality(50);
|
||||
metrics.applyWaterQualityDelta(25);
|
||||
assertEquals(75, metrics.getWaterQuality(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applyVegetationPressureDelta() works correctly")
|
||||
void applyVegetationPressureDelta() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setVegetationPressure(30);
|
||||
metrics.applyVegetationPressureDelta(-10);
|
||||
assertEquals(20, metrics.getVegetationPressure(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applyResourceDepletionDelta() works correctly")
|
||||
void applyResourceDepletionDelta() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setResourceDepletion(40);
|
||||
metrics.applyResourceDepletionDelta(60);
|
||||
assertEquals(100, metrics.getResourceDepletion(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("applyRecoveryPressureDelta() works correctly")
|
||||
void applyRecoveryPressureDelta() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setRecoveryPressure(25);
|
||||
metrics.applyRecoveryPressureDelta(-15);
|
||||
assertEquals(10, metrics.getRecoveryPressure(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("individual apply methods return this for chaining")
|
||||
void individualApplyMethodsReturnThis() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
assertTrue(metrics.applyEcosystemHealthDelta(5) == metrics);
|
||||
assertTrue(metrics.applyPollutionScoreDelta(5) == metrics);
|
||||
assertTrue(metrics.applySoilQualityDelta(5) == metrics);
|
||||
assertTrue(metrics.applyWaterQualityDelta(5) == metrics);
|
||||
assertTrue(metrics.applyVegetationPressureDelta(5) == metrics);
|
||||
assertTrue(metrics.applyResourceDepletionDelta(5) == metrics);
|
||||
assertTrue(metrics.applyRecoveryPressureDelta(5) == metrics);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Constants Tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("Constants")
|
||||
class ConstantsTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("MIN_VALUE is 0")
|
||||
void minValue() {
|
||||
assertEquals(0, RegionMetrics.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("MAX_VALUE is 100")
|
||||
void maxValue() {
|
||||
assertEquals(100, RegionMetrics.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// toString Tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Nested
|
||||
@DisplayName("toString")
|
||||
class ToStringTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("toString() includes all field names and values")
|
||||
void toStringIncludesAllFields() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
metrics.setEcosystemHealth(60);
|
||||
metrics.setPollutionScore(10);
|
||||
String str = metrics.toString();
|
||||
|
||||
assertTrue(str.contains("ecosystemHealth"));
|
||||
assertTrue(str.contains("pollutionScore"));
|
||||
assertTrue(str.contains("soilQuality"));
|
||||
assertTrue(str.contains("waterQuality"));
|
||||
assertTrue(str.contains("vegetationPressure"));
|
||||
assertTrue(str.contains("resourceDepletion"));
|
||||
assertTrue(str.contains("recoveryPressure"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toString() starts with class name")
|
||||
void toStringStartsWithClassName() {
|
||||
RegionMetrics metrics = new RegionMetrics();
|
||||
assertTrue(metrics.toString().startsWith("RegionMetrics{"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user