You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-20 07:26:18 +00:00
Merge branch 'dev' into chore/deps-resilience-validation
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package com.eu.habbo;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ConsoleLogbackLayoutTest {
|
||||
@Test
|
||||
void consolePatternKeepsStartupMessagesReadable() throws Exception {
|
||||
String logback = Files.readString(Path.of("src/main/resources/logback.xml"));
|
||||
|
||||
assertTrue(logback.contains("morningstarLevel"), "console should use the adaptive level formatter");
|
||||
assertTrue(logback.contains("morningstarLogger"), "console should use the adaptive logger formatter");
|
||||
assertTrue(logback.contains("| %msg%n"), "console should leave a clear message column");
|
||||
assertFalse(logback.contains("%-36logger{36}"), "wide package loggers waste console space");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.eu.habbo;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class EmulatorStartupConsoleTest {
|
||||
@Test
|
||||
void startupHeroUsesUniversalAsciiLayout() {
|
||||
String hero = Emulator.startupHero();
|
||||
|
||||
assertTrue(hero.contains("__ __ ___ ____"));
|
||||
assertTrue(hero.contains("MORNINGSTAR EXTENDED"));
|
||||
assertTrue(hero.contains("Version"));
|
||||
assertTrue(hero.contains("Build"));
|
||||
assertFalse(hero.contains("\u001B["), "startup hero must not require ANSI support");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startupHeroCanRenderStyledLayoutWhenAnsiIsAvailable() {
|
||||
String hero = Emulator.startupHero(true);
|
||||
|
||||
assertTrue(hero.contains("\u001B["), "styled hero should include ANSI colors");
|
||||
assertTrue(hero.contains("[OK] MORNINGSTAR EXTENDED"));
|
||||
assertTrue(hero.contains("[JVM]"));
|
||||
assertTrue(hero.endsWith("\u001B[0m\n"), "styled hero should reset terminal attributes");
|
||||
}
|
||||
|
||||
@Test
|
||||
void consoleStyleAutoDetectsWindowsTerminal() {
|
||||
assertTrue(Emulator.shouldStyleConsole(
|
||||
Map.of("WT_SESSION", "abc123"),
|
||||
true,
|
||||
"Windows 11",
|
||||
"auto"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void consoleStyleFallsBackWhenOutputIsNotInteractive() {
|
||||
assertFalse(Emulator.shouldStyleConsole(
|
||||
Map.of("WT_SESSION", "abc123"),
|
||||
false,
|
||||
"Windows 11",
|
||||
"auto"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void consoleStyleCanBeForcedOff() {
|
||||
assertFalse(Emulator.shouldStyleConsole(
|
||||
Map.of("WT_SESSION", "abc123"),
|
||||
true,
|
||||
"Windows 11",
|
||||
"plain"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void windowsAnsiModeInstallsJansiBeforePrintingStartupHero() throws Exception {
|
||||
String source = Files.readString(Path.of("src/main/java/com/eu/habbo/Emulator.java"));
|
||||
|
||||
assertTrue(source.contains("AnsiConsole.systemInstall()"),
|
||||
"forced ANSI mode must install the Jansi bridge for Windows CMD/System.out");
|
||||
assertTrue(source.contains("configureAnsiConsole(styledConsole)"),
|
||||
"console bridge must be configured before startupHero is printed");
|
||||
assertTrue(source.indexOf("configureAnsiConsole(styledConsole)") < source.indexOf("startupHero(styledConsole)"),
|
||||
"Jansi must be installed before writing ANSI startup output");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registersGuiEnabledBeforeReadingIt() throws Exception {
|
||||
String source = Files.readString(Path.of("src/main/java/com/eu/habbo/Emulator.java"));
|
||||
|
||||
assertTrue(source.contains("register(\"gui.enabled\", \"0\")"),
|
||||
"gui.enabled must be registered disabled by default so it does not log missing config errors or start the UI unexpectedly");
|
||||
assertTrue(source.contains("register(\"gui.autostart.enabled\", \"0\")"),
|
||||
"GUI autostart must use a new disabled-by-default key so old gui.enabled=1 settings do not launch the current UI");
|
||||
assertTrue(source.indexOf("register(\"gui.autostart.enabled\", \"0\")") < source.indexOf("shouldLaunchGui()"),
|
||||
"GUI autostart must be registered before the launch decision");
|
||||
assertFalse(source.contains("getBoolean(\"gui.enabled\", true)"),
|
||||
"GUI must not use a true fallback");
|
||||
assertFalse(source.contains("getBoolean(\"gui.enabled\", false)"),
|
||||
"legacy gui.enabled must not control startup anymore");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.eu.habbo.core;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CommandDescriptionTextsContractTest {
|
||||
private static final Path FULL_DATABASE = Path.of("../Default Database/FullDatabase.sql");
|
||||
private static final Path LIVE_SCHEMA_UPDATE = Path.of("../Database Updates/003_live_required_schema.sql");
|
||||
|
||||
private static final List<String> REQUIRED_DESCRIPTION_KEYS = List.of(
|
||||
"commands.description.acc_modtool_room_info",
|
||||
"commands.description.cmd_add_youtube_playlist",
|
||||
"commands.description.cmd_disablemassmentions",
|
||||
"commands.description.cmd_disablementions",
|
||||
"commands.description.cmd_give_prefix",
|
||||
"commands.description.cmd_hidewired",
|
||||
"commands.description.cmd_list_prefixes",
|
||||
"commands.description.cmd_remove_prefix",
|
||||
"commands.description.cmd_setroom_template",
|
||||
"commands.description.cmd_update_youtube_playlists"
|
||||
);
|
||||
|
||||
@Test
|
||||
void fullDatabaseDefinesCommandDescriptionsUsedByCommandsList() throws IOException {
|
||||
assertContainsAllDescriptionKeys(Files.readString(FULL_DATABASE), "FullDatabase.sql");
|
||||
}
|
||||
|
||||
@Test
|
||||
void liveSchemaUpdateBackfillsCommandDescriptionsForExistingDatabases() throws IOException {
|
||||
assertContainsAllDescriptionKeys(Files.readString(LIVE_SCHEMA_UPDATE), "003_live_required_schema.sql");
|
||||
}
|
||||
|
||||
private static void assertContainsAllDescriptionKeys(String source, String fileName) {
|
||||
for (String key : REQUIRED_DESCRIPTION_KEYS) {
|
||||
assertTrue(source.contains("'" + key + "'"),
|
||||
fileName + " must define " + key + " to avoid TextsManager missing-key logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.eu.habbo.core;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CommandTextLookupContractTest {
|
||||
private static final Path TEXTS_MANAGER = Path.of("src/main/java/com/eu/habbo/core/TextsManager.java");
|
||||
private static final Path COMMANDS_COMMAND = Path.of("src/main/java/com/eu/habbo/habbohotel/commands/CommandsCommand.java");
|
||||
private static final Path AVAILABLE_COMMANDS_COMPOSER = Path.of(
|
||||
"src/main/java/com/eu/habbo/messages/outgoing/commands/AvailableCommandsComposer.java");
|
||||
|
||||
@Test
|
||||
void textsManagerExposesQuietFallbackLookupForOptionalTexts() throws IOException {
|
||||
String source = Files.readString(TEXTS_MANAGER);
|
||||
|
||||
assertTrue(source.contains("public String getValueQuietly(String key, String defaultValue)"));
|
||||
assertTrue(source.contains("return this.texts.getProperty(key, defaultValue);"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void commandListsUseQuietDescriptionLookups() throws IOException {
|
||||
String commandsCommand = Files.readString(COMMANDS_COMMAND);
|
||||
String availableCommandsComposer = Files.readString(AVAILABLE_COMMANDS_COMPOSER);
|
||||
|
||||
assertTrue(commandsCommand.contains("getValueQuietly(textKey, \"\")"),
|
||||
":commands should not log an error when an optional command description is missing");
|
||||
assertTrue(availableCommandsComposer.contains("getValueQuietly(\"commands.description.\" + cmd.permission, cmd.permission)"),
|
||||
"available commands composer should not log an error when an optional command description is missing");
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.eu.habbo.habbohotel.bots;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class BotPickupOwnershipContractTest {
|
||||
private static String source() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/bots/BotManager.java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void roomOwnerPickupReturnsBotToOriginalOwner() throws Exception {
|
||||
String source = source();
|
||||
|
||||
assertTrue(source.contains("HabboInfo receiverInfo = resolvePickupReceiver(bot, habbo);"),
|
||||
"bot pickup should resolve the receiver without blindly using the picker");
|
||||
assertTrue(source.contains("private HabboInfo resolvePickupReceiver(Bot bot, Habbo picker)"),
|
||||
"bot pickup receiver logic should be centralized");
|
||||
assertTrue(source.contains("return Emulator.getGameEnvironment().getHabboManager().getHabboInfo(bot.getOwnerId());"),
|
||||
"when a room owner picks up someone else's bot, it should return to the original bot owner");
|
||||
assertTrue(source.contains("Room botRoom = bot.getRoom();"),
|
||||
"pickup should remove the bot from the bot's current room, not the receiver's current room");
|
||||
assertTrue(source.contains("botRoom.removeBot(bot);"),
|
||||
"bot removal should work even when the original owner is offline");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.eu.habbo.habbohotel.catalog;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class VoucherClaimContractTest {
|
||||
private static String voucherSource() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/catalog/Voucher.java"));
|
||||
}
|
||||
|
||||
private static String catalogManagerSource() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void voucherClaimIsSynchronizedAndPersistsBeforeRewardEligibility() throws Exception {
|
||||
String source = voucherSource();
|
||||
|
||||
assertTrue(source.contains("public synchronized ClaimResult claimForUser(int userId)"),
|
||||
"voucher claim should check limits and persist history under a per-voucher lock");
|
||||
assertTrue(source.contains("private boolean insertHistoryEntry"),
|
||||
"history insert should report database failure to the caller");
|
||||
|
||||
int insertCall = source.indexOf("insertHistoryEntry(userId, timestamp)");
|
||||
int memoryAppend = source.indexOf("this.history.add(new VoucherHistoryEntry(this.id, userId, timestamp))");
|
||||
|
||||
assertTrue(insertCall > -1, "claimForUser must persist the history row");
|
||||
assertTrue(memoryAppend > insertCall,
|
||||
"in-memory history must only be updated after the database insert succeeds");
|
||||
}
|
||||
|
||||
@Test
|
||||
void catalogRewardsOnlyAfterVoucherClaimSucceeds() throws Exception {
|
||||
String source = catalogManagerSource();
|
||||
|
||||
int claim = source.indexOf("Voucher.ClaimResult claimResult = voucher.claimForUser");
|
||||
int claimedGuard = source.indexOf("case CLAIMED", claim);
|
||||
int pointsGrant = source.indexOf("client.getHabbo().givePoints", claim);
|
||||
int creditsGrant = source.indexOf("client.getHabbo().giveCredits", claim);
|
||||
|
||||
assertTrue(claim > -1, "CatalogManager must claim the voucher before applying rewards");
|
||||
assertTrue(claimedGuard > claim, "voucher rewards should only continue for a CLAIMED result");
|
||||
assertTrue(pointsGrant > claimedGuard, "points must be granted only after CLAIMED");
|
||||
assertTrue(creditsGrant > claimedGuard, "credits must be granted only after CLAIMED");
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.eu.habbo.habbohotel.catalog.marketplace;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class MarketPlaceCreditClaimContractTest {
|
||||
private static String marketPlaceSource() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlace.java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void soldOfferIsDetachedBeforeCreditsAreGranted() throws Exception {
|
||||
String source = marketPlaceSource();
|
||||
int getCreditsStart = source.indexOf("public static void getCredits");
|
||||
int removeUserCall = source.indexOf("removeUser(offer)", getCreditsStart);
|
||||
int creditAccumulator = source.indexOf("credits += offer.getPrice()", getCreditsStart);
|
||||
int inventoryRemoval = source.indexOf("removeMarketplaceOffer(offer)", getCreditsStart);
|
||||
|
||||
assertTrue(getCreditsStart > -1, "MarketPlace.getCredits must exist");
|
||||
assertTrue(removeUserCall > -1, "Sold marketplace offers must be detached in the database");
|
||||
assertTrue(removeUserCall < creditAccumulator,
|
||||
"Credits must not be granted until the sold offer is detached from the seller in the database");
|
||||
assertTrue(removeUserCall < inventoryRemoval,
|
||||
"The in-memory sold offer should remain claimable if the database detach fails");
|
||||
}
|
||||
|
||||
@Test
|
||||
void detachFailureIsObservableByCaller() throws Exception {
|
||||
String source = marketPlaceSource();
|
||||
|
||||
assertTrue(source.contains("private static boolean removeUser"),
|
||||
"removeUser must report whether the marketplace ownership update succeeded");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.eu.habbo.habbohotel.guilds;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import com.eu.habbo.messages.ClientMessage;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class GuildBadgeBuilderTest {
|
||||
@Test
|
||||
void buildsBadgeFromFlatPartTriplets() {
|
||||
ClientMessage packet = messageWithInts(
|
||||
1, 2, 4,
|
||||
35, 8, 0
|
||||
);
|
||||
|
||||
assertEquals("b001024s035080", GuildBadgeBuilder.readBadge(packet, 6));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCountThatDoesNotRepresentCompleteTriplets() {
|
||||
ClientMessage packet = messageWithInts(1, 2, 4);
|
||||
|
||||
assertNull(GuildBadgeBuilder.readBadge(packet, 4));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsPayloadShorterThanDeclaredCount() {
|
||||
ClientMessage packet = messageWithInts(1, 2);
|
||||
|
||||
assertNull(GuildBadgeBuilder.readBadge(packet, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsTooManyBadgeParts() {
|
||||
ClientMessage packet = messageWithInts(
|
||||
1, 1, 4,
|
||||
2, 1, 4,
|
||||
3, 1, 4,
|
||||
4, 1, 4,
|
||||
5, 1, 4,
|
||||
6, 1, 4
|
||||
);
|
||||
|
||||
assertNull(GuildBadgeBuilder.readBadge(packet, 18));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsPartValuesOutsideBadgeCodeRanges() {
|
||||
assertNull(GuildBadgeBuilder.readBadge(messageWithInts(1000, 1, 4), 3));
|
||||
assertNull(GuildBadgeBuilder.readBadge(messageWithInts(1, 100, 4), 3));
|
||||
assertNull(GuildBadgeBuilder.readBadge(messageWithInts(1, 1, 9), 3));
|
||||
}
|
||||
|
||||
private static ClientMessage messageWithInts(int... values) {
|
||||
var buffer = Unpooled.buffer(values.length * Integer.BYTES);
|
||||
for (int value : values) {
|
||||
buffer.writeInt(value);
|
||||
}
|
||||
return new ClientMessage(0, buffer);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.eu.habbo.habbohotel.guilds;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class GuildManagerMembershipContractTest {
|
||||
private static String guildManagerSource() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/guilds/GuildManager.java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptRequestOnlyPromotesPendingMembershipRows() throws Exception {
|
||||
String source = guildManagerSource();
|
||||
|
||||
assertTrue(source.contains("UPDATE guilds_members SET level_id = ?, member_since = ? WHERE user_id = ? AND guild_id = ? AND level_id = ?"),
|
||||
"accepting a guild request must only promote rows still in REQUESTED state");
|
||||
assertTrue(source.contains("statement.setInt(5, GuildRank.REQUESTED.type);"),
|
||||
"the accept-request update must bind the expected REQUESTED rank guard");
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class RentableSpaceChargeContractTest {
|
||||
@Test
|
||||
void rentingSpaceChargesCreditsBeforeMarkingSpaceRented() throws Exception {
|
||||
String source = Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionRentableSpace.java"));
|
||||
int rentMethod = source.indexOf("public void rent(Habbo habbo)");
|
||||
|
||||
assertTrue(rentMethod >= 0, "InteractionRentableSpace must keep explicit rent handling");
|
||||
|
||||
String rentHandling = source.substring(rentMethod, Math.min(source.length(), rentMethod + 1400));
|
||||
|
||||
assertTrue(rentHandling.contains("int cost = this.rentCost();"),
|
||||
"Rent cost must be computed once before charging");
|
||||
assertTrue(rentHandling.contains("boolean hasInfiniteCredits = habbo.hasPermission(Permission.ACC_INFINITE_CREDITS);"),
|
||||
"Renting must honor infinite-credit staff permission before charging");
|
||||
assertTrue(rentHandling.contains("!hasInfiniteCredits && habbo.getHabboInfo().getCredits() < cost"),
|
||||
"Renting must reject non-staff users without enough credits for the computed cost");
|
||||
assertTrue(rentHandling.contains("habbo.giveCredits(-cost);"),
|
||||
"Renting must deduct the computed credit cost");
|
||||
assertTrue(rentHandling.indexOf("habbo.giveCredits(-cost);") < rentHandling.indexOf("this.setRenterId"),
|
||||
"Credits must be charged before the rentable space is marked as rented");
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.eu.habbo.habbohotel.rooms;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class RoomTradeManagerContractTest {
|
||||
private static String roomTradeManagerSource() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/rooms/RoomTradeManager.java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void startTradeRejectsParticipantsAlreadyInActiveTradeInsideLock() throws Exception {
|
||||
String source = roomTradeManagerSource();
|
||||
int synchronizedBlock = source.indexOf("synchronized (this.activeTrades)");
|
||||
int activeGuard = source.indexOf("hasActiveTrade(userOne) || this.hasActiveTrade(userTwo)");
|
||||
int addTrade = source.indexOf("this.activeTrades.add(trade)");
|
||||
|
||||
assertTrue(synchronizedBlock > -1, "RoomTradeManager.startTrade must lock activeTrades before mutation");
|
||||
assertTrue(activeGuard > synchronizedBlock,
|
||||
"startTrade must check both participants for an existing active trade while holding the activeTrades lock");
|
||||
assertTrue(activeGuard < addTrade,
|
||||
"duplicate participant guard must run before a new RoomTrade is added");
|
||||
assertTrue(source.contains("private boolean hasActiveTrade(Habbo user)"),
|
||||
"active trade lookup should be reusable under the same activeTrades lock");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.eu.habbo.habbohotel.rooms;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class RoomTradeSafetyContractTest {
|
||||
private static String roomTradeSource() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/rooms/RoomTrade.java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sqlFailureStopsBeforeInventoryTransfer() throws Exception {
|
||||
String source = roomTradeSource();
|
||||
int catchIndex = source.indexOf("catch (SQLException e)");
|
||||
int inventoryTransferIndex = source.indexOf("THashSet<HabboItem> itemsUserOne");
|
||||
|
||||
assertTrue(catchIndex > -1, "RoomTrade must handle SQL failures explicitly");
|
||||
assertTrue(inventoryTransferIndex > catchIndex, "Inventory transfer should happen after SQL ownership updates");
|
||||
assertTrue(source.substring(catchIndex, inventoryTransferIndex).contains("return false"),
|
||||
"SQL failures must abort the trade before in-memory inventory/credit transfer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void itemOwnersChangeOnlyAfterDatabaseBatchSucceeds() throws Exception {
|
||||
String source = roomTradeSource();
|
||||
int firstOwnerMutation = source.indexOf("item.setUserId(");
|
||||
int batchExecution = source.indexOf("statement.executeBatch();");
|
||||
|
||||
assertTrue(firstOwnerMutation > -1, "RoomTrade should update in-memory item owners after commit");
|
||||
assertTrue(batchExecution > -1, "RoomTrade should persist item owner changes with a batch update");
|
||||
assertTrue(firstOwnerMutation > batchExecution,
|
||||
"In-memory item owners must not change until the database batch has succeeded");
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.eu.habbo.habbohotel.users.infostand;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class InfostandBackgroundManagerTest {
|
||||
@Test
|
||||
void summaryKeepsStartupLogCompact() {
|
||||
assertEquals(
|
||||
"Infostand Background Manager -> Loaded! (260 assets)",
|
||||
InfostandBackgroundManager.summary(188, 22, 9, 16, 25));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.eu.habbo.messages;
|
||||
|
||||
import com.eu.habbo.messages.incoming.Incoming;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PacketNamesContractTest {
|
||||
@Test
|
||||
void incomingPacketNameIdsAreUnique() throws Exception {
|
||||
assertPublicFinalPacketIdsAreUnique(Incoming.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void outgoingPacketNameIdsAreUnique() throws Exception {
|
||||
assertPublicFinalPacketIdsAreUnique(Outgoing.class);
|
||||
}
|
||||
|
||||
private static void assertPublicFinalPacketIdsAreUnique(Class<?> packetClass) throws Exception {
|
||||
Map<Integer, String> seen = new HashMap<>();
|
||||
Map<Integer, String> duplicates = new HashMap<>();
|
||||
|
||||
for (Field field : packetClass.getFields()) {
|
||||
int modifiers = field.getModifiers();
|
||||
if (!Modifier.isPublic(modifiers)
|
||||
|| !Modifier.isStatic(modifiers)
|
||||
|| !Modifier.isFinal(modifiers)
|
||||
|| field.getType() != int.class) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int packetId = field.getInt(null);
|
||||
if (packetId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String previous = seen.putIfAbsent(packetId, field.getName());
|
||||
if (previous != null) {
|
||||
duplicates.put(packetId, previous + " / " + field.getName());
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(duplicates.isEmpty(), packetClass.getSimpleName() + " has duplicate packet IDs: " + duplicates);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CatalogAdminOfferMutationContractTest {
|
||||
private static final Path CREATE_SOURCE = Path.of(
|
||||
"src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminCreateOfferEvent.java");
|
||||
private static final Path SAVE_SOURCE = Path.of(
|
||||
"src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminSaveOfferEvent.java");
|
||||
|
||||
@Test
|
||||
void createAndSaveValidatePayloadAndTargetPageBeforeWriting() throws IOException {
|
||||
String create = Files.readString(CREATE_SOURCE);
|
||||
String save = Files.readString(SAVE_SOURCE);
|
||||
|
||||
assertTrue(create.contains("CatalogAdminOfferPayload.validate("));
|
||||
assertTrue(save.contains("CatalogAdminOfferPayload.validate("));
|
||||
assertTrue(create.contains("getCatalogPage(payload.pageId, payload.pageType) == null"));
|
||||
assertTrue(save.contains("getCatalogPage(payload.pageId, payload.pageType) == null"));
|
||||
|
||||
int createValidation = create.indexOf("CatalogAdminOfferPayload.validate(");
|
||||
int createInsert = create.indexOf("INSERT INTO catalog_items");
|
||||
int saveValidation = save.indexOf("CatalogAdminOfferPayload.validate(");
|
||||
int saveUpdate = save.indexOf("UPDATE catalog_items");
|
||||
|
||||
assertTrue(createValidation < createInsert, "create offer should validate before insert SQL is prepared");
|
||||
assertTrue(saveValidation < saveUpdate, "save offer should validate before update SQL is prepared");
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveOfferReportsMissingRowsInsteadOfAlwaysSucceeding() throws IOException {
|
||||
String save = Files.readString(SAVE_SOURCE);
|
||||
|
||||
assertTrue(save.contains("statement.executeUpdate() == 0"));
|
||||
assertTrue(save.contains("Offer not found: "));
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import com.eu.habbo.habbohotel.catalog.CatalogPageType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CatalogAdminOfferPayloadTest {
|
||||
@Test
|
||||
void acceptsAndNormalizesValidOfferPayload() {
|
||||
CatalogAdminOfferPayload payload = CatalogAdminOfferPayload.validate(
|
||||
42, "1, 2,3", "Rare Chair", 100, 5, 0, 1, 0,
|
||||
"extra", true, 0, 0, 10, CatalogPageType.NORMAL);
|
||||
|
||||
assertNotNull(payload);
|
||||
assertEquals("1,2,3", payload.itemIds);
|
||||
assertEquals("Rare Chair", payload.catalogName);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInvalidItemIdsAndNegativeEconomyValues() {
|
||||
assertNull(CatalogAdminOfferPayload.validate(42, "1,abc", "Name", 0, 0, 0, 1, 0,
|
||||
"", false, 0, 0, 0, CatalogPageType.NORMAL));
|
||||
assertNull(CatalogAdminOfferPayload.validate(42, "1", "Name", -1, 0, 0, 1, 0,
|
||||
"", false, 0, 0, 0, CatalogPageType.NORMAL));
|
||||
assertNull(CatalogAdminOfferPayload.validate(42, "1", "Name", 0, 0, 0, 0, 0,
|
||||
"", false, 0, 0, 0, CatalogPageType.NORMAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderOffersStillRequireSafeCommonFields() {
|
||||
assertNotNull(CatalogAdminOfferPayload.validate(42, "", "BC Offer", -1, -1, -1, -1, -1,
|
||||
"", false, -1, -1, 0, CatalogPageType.BUILDER));
|
||||
assertNull(CatalogAdminOfferPayload.validate(0, "1", "BC Offer", 0, 0, 0, 1, 0,
|
||||
"", false, 0, 0, 0, CatalogPageType.BUILDER));
|
||||
assertNull(CatalogAdminOfferPayload.validate(42, "1", "", 0, 0, 0, 1, 0,
|
||||
"", false, 0, 0, 0, CatalogPageType.BUILDER));
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CatalogAdminPageMutationContractTest {
|
||||
private static final Path CREATE_SOURCE = Path.of(
|
||||
"src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminCreatePageEvent.java");
|
||||
private static final Path SAVE_SOURCE = Path.of(
|
||||
"src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminSavePageEvent.java");
|
||||
private static final Path MOVE_SOURCE = Path.of(
|
||||
"src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminMovePageEvent.java");
|
||||
private static final Path DELETE_SOURCE = Path.of(
|
||||
"src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminDeletePageEvent.java");
|
||||
|
||||
@Test
|
||||
void pageParentChecksStayWithinTheSameCatalogPageType() throws IOException {
|
||||
String create = Files.readString(CREATE_SOURCE);
|
||||
String save = Files.readString(SAVE_SOURCE);
|
||||
String move = Files.readString(MOVE_SOURCE);
|
||||
|
||||
assertTrue(create.contains("getCatalogPage(parentId, pageType)"));
|
||||
assertTrue(save.contains("getCatalogPage(parentId, pageType)"));
|
||||
assertTrue(save.contains("getCatalogPage(current, pageType)"));
|
||||
assertTrue(move.contains("getCatalogPage(newParentId, pageType)"));
|
||||
assertTrue(move.contains("getCatalogPage(current, pageType)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void movePageValidatesTargetBeforeTogglingVisibilityOrEnabledState() throws IOException {
|
||||
String move = Files.readString(MOVE_SOURCE);
|
||||
|
||||
int pageLookup = move.indexOf("getCatalogPage(pageId, pageType)");
|
||||
int enabledToggle = move.indexOf("SET enabled = IF");
|
||||
int visibleToggle = move.indexOf("SET visible = IF");
|
||||
|
||||
assertTrue(pageLookup >= 0, "move page should load the page before mutating it");
|
||||
assertTrue(pageLookup < enabledToggle, "enabled toggle must not run before page existence is checked");
|
||||
assertTrue(pageLookup < visibleToggle, "visible toggle must not run before page existence is checked");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pageMutationsReportMissingRowsInsteadOfAlwaysSucceeding() throws IOException {
|
||||
String save = Files.readString(SAVE_SOURCE);
|
||||
String move = Files.readString(MOVE_SOURCE);
|
||||
String delete = Files.readString(DELETE_SOURCE);
|
||||
|
||||
assertTrue(save.contains("statement.executeUpdate() == 0"));
|
||||
assertTrue(move.contains("statement.executeUpdate() == 0"));
|
||||
assertTrue(delete.contains("statement.executeUpdate() == 0"));
|
||||
}
|
||||
}
|
||||
+29
@@ -78,4 +78,33 @@ class FurniDataManagerTest {
|
||||
assertEquals(assetBase.resolve("gamedata").resolve("FurnitureData.json"), source.path());
|
||||
assertFalse(source.directory());
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefersRendererConfigOverLegacyFurnidataPath(@TempDir Path dir) throws Exception {
|
||||
Path legacy = dir.resolve("legacy").resolve("FurnitureData.json");
|
||||
Files.createDirectories(legacy.getParent());
|
||||
Files.writeString(legacy, "{}");
|
||||
|
||||
Path assetBase = dir.resolve("nitro-assets");
|
||||
Path rendererSource = assetBase.resolve("gamedata").resolve("FurnitureData.json");
|
||||
Files.createDirectories(rendererSource.getParent());
|
||||
Files.writeString(rendererSource, "{}");
|
||||
|
||||
Path rendererConfig = dir.resolve("renderer-config.json");
|
||||
Files.writeString(rendererConfig, """
|
||||
{
|
||||
"gamedata.url": "http://localhost:5173/nitro-assets/gamedata",
|
||||
"furnidata.url": "${gamedata.url}/FurnitureData.json?t=%timestamp%"
|
||||
}
|
||||
""");
|
||||
|
||||
FurnidataSourceResolver.Source source = FurnidataSourceResolver.resolveConfigured(
|
||||
legacy.toString(),
|
||||
rendererConfig.toString(),
|
||||
assetBase.toString());
|
||||
|
||||
assertTrue(source.ok());
|
||||
assertEquals(rendererSource, source.path());
|
||||
assertEquals("renderer-config furnidata.url", source.message());
|
||||
}
|
||||
}
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.eu.habbo.messages.incoming.furnieditor;
|
||||
|
||||
import com.google.gson.JsonParser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class FurniEditorUpdatePayloadTest {
|
||||
@Test
|
||||
void acceptsSafeEditorFields() {
|
||||
FurniEditorUpdatePayload payload = FurniEditorUpdatePayload.validate(JsonParser.parseString("""
|
||||
{
|
||||
"publicName": "Rare Chair",
|
||||
"type": "s",
|
||||
"width": 2,
|
||||
"length": 1,
|
||||
"stackHeight": 1.5,
|
||||
"allowTrade": true,
|
||||
"interactionModesCount": 3
|
||||
}
|
||||
""").getAsJsonObject());
|
||||
|
||||
assertTrue(payload.valid());
|
||||
assertEquals(7, payload.values.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsOutOfRangeAndOversizedFields() {
|
||||
assertFalse(FurniEditorUpdatePayload.validate(JsonParser.parseString("{\"width\":-1}").getAsJsonObject()).valid());
|
||||
assertFalse(FurniEditorUpdatePayload.validate(JsonParser.parseString("{\"stackHeight\":1000}").getAsJsonObject()).valid());
|
||||
assertFalse(FurniEditorUpdatePayload.validate(JsonParser.parseString("{\"allowTrade\":2}").getAsJsonObject()).valid());
|
||||
assertFalse(FurniEditorUpdatePayload.validate(JsonParser.parseString("{\"publicName\":\"" + "x".repeat(57) + "\"}").getAsJsonObject()).valid());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoresUnknownFieldsButRequiresAtLeastOneValidField() {
|
||||
FurniEditorUpdatePayload payload = FurniEditorUpdatePayload.validate(
|
||||
JsonParser.parseString("{\"itemName\":\"blocked\",\"unknown\":true}").getAsJsonObject());
|
||||
|
||||
assertFalse(payload.valid());
|
||||
assertEquals("No valid fields to update", payload.error);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildsCatalogItemIdsTokenPattern() {
|
||||
assertEquals("%,12,%", FurniEditorHelper.catalogItemIdsTokenPattern(12));
|
||||
assertTrue((",112,12,13,").contains(",12,"));
|
||||
assertFalse((",112,13,").contains(",12,"));
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.eu.habbo.messages.incoming.polls;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PollRoomScopeContractTest {
|
||||
@Test
|
||||
void pollHandlersRequireMatchingCurrentRoomPoll() throws Exception {
|
||||
assertRequiresMatchingRoomPoll("AnswerPollEvent.java");
|
||||
assertRequiresMatchingRoomPoll("CancelPollEvent.java");
|
||||
assertRequiresMatchingRoomPoll("GetPollDataEvent.java");
|
||||
}
|
||||
|
||||
private void assertRequiresMatchingRoomPoll(String fileName) throws Exception {
|
||||
String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/polls/" + fileName));
|
||||
int packetPollId = source.indexOf("int pollId = this.packet.readInt();");
|
||||
int pollLookup = source.indexOf("getPoll(pollId)");
|
||||
|
||||
assertTrue(packetPollId >= 0, fileName + " must read the poll id from the packet");
|
||||
assertTrue(pollLookup >= 0, fileName + " must look up the requested poll explicitly");
|
||||
|
||||
String guardedSection = source.substring(packetPollId, pollLookup);
|
||||
|
||||
assertTrue(guardedSection.contains("getCurrentRoom()"),
|
||||
fileName + " must bind poll actions to the caller's current room");
|
||||
assertTrue(guardedSection.contains("room == null || room.getPollId() != pollId"),
|
||||
fileName + " must reject poll ids that are not active in the current room");
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.eu.habbo.messages.incoming.rooms.items;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class MonsterPlantSeedOwnershipContractTest {
|
||||
@Test
|
||||
void monsterPlantSeedsCanOnlyBeRedeemedByTheirOwner() throws Exception {
|
||||
String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleFloorItemEvent.java"));
|
||||
int seedBranch = source.indexOf("item instanceof InteractionMonsterPlantSeed");
|
||||
|
||||
assertTrue(seedBranch >= 0, "ToggleFloorItemEvent must keep monsterplant seed handling explicit");
|
||||
|
||||
String seedHandling = source.substring(seedBranch, Math.min(source.length(), seedBranch + 1400));
|
||||
|
||||
String ownershipGuard = "if (item.getUserId() != this.client.getHabbo().getHabboInfo().getId())";
|
||||
|
||||
assertTrue(seedHandling.contains(ownershipGuard),
|
||||
"Monsterplant seed redemption must reject callers who do not own the seed");
|
||||
assertTrue(seedHandling.contains("createMonsterplant"),
|
||||
"Monsterplant seed handling must create the pet inside the guarded branch");
|
||||
assertTrue(seedHandling.indexOf(ownershipGuard) < seedHandling.indexOf("createMonsterplant"),
|
||||
"Ownership rejection must happen before creating the pet");
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.eu.habbo.messages.incoming.rooms.items;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class RedeemClothingContractTest {
|
||||
private static String source() throws Exception {
|
||||
return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/items/RedeemClothingEvent.java"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clothingIsGrantedBeforeVoucherFurnitureIsConsumed() throws Exception {
|
||||
String source = source();
|
||||
|
||||
int grantCall = source.indexOf("grantClothing(");
|
||||
int roomRemoval = source.indexOf("removeHabboItem(item)");
|
||||
int deleteItem = source.indexOf("new QueryDeleteHabboItem(item.getId())");
|
||||
|
||||
assertTrue(source.contains("private boolean grantClothing(int clothingId)"),
|
||||
"clothing DB insert should report whether the grant succeeded");
|
||||
assertTrue(grantCall > -1, "redeem path should call grantClothing before consuming the item");
|
||||
assertTrue(grantCall < roomRemoval, "room item must not be removed before the clothing grant succeeds");
|
||||
assertTrue(grantCall < deleteItem, "voucher furniture must not be deleted before the clothing grant succeeds");
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.eu.habbo.messages.incoming.rooms.users;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class RoomModerationScopeContractTest {
|
||||
@Test
|
||||
void roomUserBanAndMuteAreScopedToCurrentRoom() throws Exception {
|
||||
Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/users");
|
||||
|
||||
for (String handler : new String[]{"RoomUserBanEvent.java", "RoomUserMuteEvent.java", "UnbanRoomUserEvent.java"}) {
|
||||
String source = Files.readString(base.resolve(handler));
|
||||
|
||||
assertTrue(source.contains("getCurrentRoom()"),
|
||||
handler + " must authorize room moderation against the user's current room");
|
||||
assertTrue(source.contains("room.getId() != roomId"),
|
||||
handler + " must reject client-supplied room ids that do not match the current room");
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.eu.habbo.messages.incoming.rooms.users;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class RoomUserRemoveRightsContractTest {
|
||||
private static final Path SOURCE = Path.of(
|
||||
"src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserRemoveRightsEvent.java");
|
||||
|
||||
@Test
|
||||
void removeRightsBatchIsBoundedAndRequiresCompletePayload() throws IOException {
|
||||
String source = Files.readString(SOURCE);
|
||||
|
||||
assertTrue(source.contains("private static final int MAX_RIGHTS_REMOVALS = 100;"));
|
||||
assertTrue(source.contains("PacketGuard.isCountInRange(amount, 1, MAX_RIGHTS_REMOVALS)"));
|
||||
assertTrue(source.contains("PacketGuard.hasFixedWidthEntries(this.packet, amount, BYTES_PER_USER_ID)"));
|
||||
|
||||
int guardIndex = source.indexOf("PacketGuard.isCountInRange(amount, 1, MAX_RIGHTS_REMOVALS)");
|
||||
int payloadIndex = source.indexOf("PacketGuard.hasFixedWidthEntries(this.packet, amount, BYTES_PER_USER_ID)");
|
||||
int readIndex = source.indexOf("int userId = this.packet.readInt();");
|
||||
int removeIndex = source.indexOf("room.removeRights(userId);");
|
||||
|
||||
assertTrue(guardIndex < readIndex, "batch size should be validated before reading user ids");
|
||||
assertTrue(payloadIndex < readIndex, "payload length should be validated before reading user ids");
|
||||
assertTrue(readIndex < removeIndex, "rights should only be removed after reading a validated user id");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.eu.habbo.util.logback;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ConsoleStyleTest {
|
||||
@Test
|
||||
void formatsLevelWithIconAndColorWhenStyled() {
|
||||
String formatted = ConsoleStyle.level(Level.WARN, true);
|
||||
|
||||
assertTrue(formatted.contains("\u001B["));
|
||||
assertTrue(formatted.contains("[!] WARN "));
|
||||
assertTrue(formatted.endsWith("\u001B[0m"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatsLevelAsPlainTextWhenNotStyled() {
|
||||
assertEquals("WARN ", ConsoleStyle.level(Level.WARN, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatsLoggerWithColorWhenStyled() {
|
||||
String formatted = ConsoleStyle.logger("com.eu.habbo.networking.Server", true);
|
||||
|
||||
assertTrue(formatted.contains("\u001B["));
|
||||
assertTrue(formatted.contains("Server"));
|
||||
assertTrue(formatted.endsWith("\u001B[0m"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsLoggerPlainAndCompactWhenNotStyled() {
|
||||
assertEquals("Server ", ConsoleStyle.logger("com.eu.habbo.networking.Server", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void honorsPlainOverrideEvenInWindowsTerminal() {
|
||||
assertFalse(ConsoleStyle.isEnabled(
|
||||
Map.of("WT_SESSION", "abc123"),
|
||||
true,
|
||||
"Windows 11",
|
||||
"plain"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user