From f8adddec528fd8501b565277eaedfc5891b9a7e7 Mon Sep 17 00:00:00 2001 From: David Alvarez Date: Tue, 9 Jun 2026 13:01:40 +0200 Subject: [PATCH] fix: add JUnit5 dependency, new tests, StaticResource cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added quarkus-junit5 test dependency (was missing — caused assertNull failures) - ExecutableDetectorTest: 12 tests (findAll, findMain prioritization, extender/sfx skip, setup detection) - CdImageServiceTest: 7 tests (findInDirectory, fixCueReferences, findInBundle) - ConfigPatcherTest: 8 tests (path rewriting, non-C drive skip, binary/large file skip, unix paths) - StaticResource: removed unused content types (.html/.js/.css/.conf/.txt) --- pom.xml | 6 + .../java/com/dostalgia/StaticResource.java | 4 - .../com/dostalgia/CdImageServiceTest.java | 103 +++++++++++++ .../java/com/dostalgia/ConfigPatcherTest.java | 109 ++++++++++++++ .../com/dostalgia/ExecutableDetectorTest.java | 136 ++++++++++++++++++ 5 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/dostalgia/CdImageServiceTest.java create mode 100644 src/test/java/com/dostalgia/ConfigPatcherTest.java create mode 100644 src/test/java/com/dostalgia/ExecutableDetectorTest.java diff --git a/pom.xml b/pom.xml index 8c017f0..e477426 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,12 @@ io.quarkus quarkus-arc + + + io.quarkus + quarkus-junit5 + test + diff --git a/src/main/java/com/dostalgia/StaticResource.java b/src/main/java/com/dostalgia/StaticResource.java index 7944b47..4b50cba 100644 --- a/src/main/java/com/dostalgia/StaticResource.java +++ b/src/main/java/com/dostalgia/StaticResource.java @@ -72,11 +72,7 @@ public class StaticResource { if (n.endsWith(".png")) return "image/png"; if (n.endsWith(".webp")) return "image/webp"; if (n.endsWith(".gif")) return "image/gif"; - if (n.endsWith(".conf") || n.endsWith(".txt")) return "text/plain"; if (n.endsWith(".json")) return "application/json"; - if (n.endsWith(".html")) return "text/html"; - if (n.endsWith(".js")) return "application/javascript"; - if (n.endsWith(".css")) return "text/css"; return MediaType.APPLICATION_OCTET_STREAM; } diff --git a/src/test/java/com/dostalgia/CdImageServiceTest.java b/src/test/java/com/dostalgia/CdImageServiceTest.java new file mode 100644 index 0000000..bd4be2c --- /dev/null +++ b/src/test/java/com/dostalgia/CdImageServiceTest.java @@ -0,0 +1,103 @@ +package com.dostalgia; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +class CdImageServiceTest { + + private final CdImageService service = new CdImageService(); + + @Test + void findInDirectory_findsAllCdFormats(@TempDir Path dir) throws Exception { + Files.writeString(dir.resolve("game.cue"), "FILE \"game.bin\" BINARY"); + Files.writeString(dir.resolve("game.bin"), "data"); + Files.writeString(dir.resolve("game.iso"), "iso"); + Files.writeString(dir.resolve("readme.txt"), "text"); + + List result = service.findInDirectory(dir); + assertEquals(3, result.size()); + assertTrue(result.contains("game.bin")); + assertTrue(result.contains("game.cue")); + assertTrue(result.contains("game.iso")); + assertFalse(result.contains("readme.txt")); + } + + @Test + void findInDirectory_emptyDir(@TempDir Path dir) throws Exception { + assertTrue(service.findInDirectory(dir).isEmpty()); + } + + @Test + void findInDirectory_caseInsensitive(@TempDir Path dir) throws Exception { + Files.writeString(dir.resolve("GAME.CUE"), "data"); + Files.writeString(dir.resolve("GAME.ISO"), "data"); + List result = service.findInDirectory(dir); + assertEquals(2, result.size()); + } + + @Test + void fixCueReferences_rewritesNonexistentReference(@TempDir Path dir) throws Exception { + // CUE references game.bin, but only game.img exists + Files.writeString(dir.resolve("game.cue"), "FILE \"game.bin\" BINARY\n TRACK 01 MODE1/2352\n INDEX 01 00:00:00"); + Files.writeString(dir.resolve("game.img"), "data"); + + service.fixCueReferences(dir); + + String fixed = Files.readString(dir.resolve("game.cue")); + assertTrue(fixed.contains("game.img"), "Should reference existing .img file"); + assertFalse(fixed.contains("game.bin"), "Should no longer reference missing .bin"); + } + + @Test + void fixCueReferences_skipsWhenExists(@TempDir Path dir) throws Exception { + // CUE references game.bin — and it exists, so no change + String original = "FILE \"game.bin\" BINARY\n TRACK 01 MODE1/2352\n INDEX 01 00:00:00"; + Files.writeString(dir.resolve("game.cue"), original); + Files.writeString(dir.resolve("game.bin"), "data"); + + service.fixCueReferences(dir); + + assertEquals(original, Files.readString(dir.resolve("game.cue")).trim()); + } + + @Test + void fixCueReferences_noFileLine_noChange(@TempDir Path dir) throws Exception { + String original = "TITLE \"Test Game\"\nTRACK 01 MODE1/2352"; + Files.writeString(dir.resolve("game.cue"), original); + + service.fixCueReferences(dir); + + assertEquals(original, Files.readString(dir.resolve("game.cue")).trim()); + } + + @Test + void findInBundle_findsCdImages(@TempDir Path dir) throws Exception { + // Create a test bundle with CD images and regular files + Path bundle = dir.resolve("test.jsdos"); + try (var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(bundle))) { + zos.putNextEntry(new java.util.zip.ZipEntry("game.cue")); + zos.write("cue data".getBytes()); + zos.closeEntry(); + zos.putNextEntry(new java.util.zip.ZipEntry("game.iso")); + zos.write("iso data".getBytes()); + zos.closeEntry(); + zos.putNextEntry(new java.util.zip.ZipEntry("readme.txt")); + zos.write("text".getBytes()); + zos.closeEntry(); + zos.putNextEntry(new java.util.zip.ZipEntry("subdir/game.img")); + zos.write("img".getBytes()); + zos.closeEntry(); + } + + List result = service.findInBundle(bundle); + assertEquals(3, result.size()); + assertTrue(result.contains("game.cue")); + assertTrue(result.contains("game.iso")); + assertTrue(result.contains("subdir/game.img")); + } +} diff --git a/src/test/java/com/dostalgia/ConfigPatcherTest.java b/src/test/java/com/dostalgia/ConfigPatcherTest.java new file mode 100644 index 0000000..2c4bc9a --- /dev/null +++ b/src/test/java/com/dostalgia/ConfigPatcherTest.java @@ -0,0 +1,109 @@ +package com.dostalgia; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +class ConfigPatcherTest { + + private final ConfigPatcher patcher = new ConfigPatcher(); + + @Test + void fixAbsolutePaths_rewritesStalePrefix(@TempDir Path dir) throws Exception { + // Create a file that references C:\FALLOUT1\MASTER.DAT + // but after flattening, MASTER.DAT is at the root + String content = "master_dat=C:\\FALLOUT1\\MASTER.DAT\ncritter_dat=C:\\FALLOUT1\\CRITTER.DAT\n"; + Files.writeString(dir.resolve("FALLOUT.CFG"), content, StandardCharsets.ISO_8859_1); + + // The referenced files exist at root + Files.writeString(dir.resolve("MASTER.DAT"), "data"); + Files.writeString(dir.resolve("CRITTER.DAT"), "data"); + + patcher.fixAbsolutePaths(dir); + + String fixed = Files.readString(dir.resolve("FALLOUT.CFG"), StandardCharsets.ISO_8859_1); + assertTrue(fixed.contains(".\\MASTER.DAT"), "Should rewrite to relative path"); + assertTrue(fixed.contains(".\\CRITTER.DAT"), "Both files should be rewritten"); + assertFalse(fixed.contains("FALLOUT1"), "Stale prefix should be removed"); + } + + @Test + void fixAbsolutePaths_leavesNonCDrives(@TempDir Path dir) throws Exception { + // D: drive references are CD-ROM — should stay + String content = "cd_path=D:\\GAMEDATA\\SOUND.DAT\n"; + Files.writeString(dir.resolve("config.ini"), content, StandardCharsets.ISO_8859_1); + + patcher.fixAbsolutePaths(dir); + + String fixed = Files.readString(dir.resolve("config.ini"), StandardCharsets.ISO_8859_1); + assertEquals(content.trim(), fixed.trim(), "D: references should be preserved"); + } + + @Test + void fixAbsolutePaths_leavesValidPaths(@TempDir Path dir) throws Exception { + // Path exists with full structure — should stay as-is + Path sub = Files.createDirectory(dir.resolve("VALIDDIR")); + Files.writeString(sub.resolve("file.dat"), "data"); + + String content = "data=C:\\VALIDDIR\\file.dat\n"; + Files.writeString(dir.resolve("config.ini"), content, StandardCharsets.ISO_8859_1); + + patcher.fixAbsolutePaths(dir); + + String fixed = Files.readString(dir.resolve("config.ini"), StandardCharsets.ISO_8859_1); + assertEquals(content.trim(), fixed.trim(), "Valid full paths should be preserved"); + } + + @Test + void fixAbsolutePaths_skipsNonTextFiles(@TempDir Path dir) throws Exception { + // Binary file with null bytes — should not be processed + byte[] binary = new byte[200]; + binary[0] = 'C'; + binary[1] = ':'; + binary[2] = '\\'; + binary[3] = 'D'; + binary[10] = 0; // null byte makes it "not text" + Files.write(dir.resolve("binary.dat"), binary); + + // Should not throw or modify binary files + assertDoesNotThrow(() -> patcher.fixAbsolutePaths(dir)); + + byte[] result = Files.readAllBytes(dir.resolve("binary.dat")); + assertArrayEquals(binary, result, "Binary file should be unchanged"); + } + + @Test + void fixAbsolutePaths_skipsLargeFiles(@TempDir Path dir) throws Exception { + // File > 100KB should be skipped + byte[] large = new byte[101 * 1024]; + large[0] = 'C'; + large[1] = ':'; + large[2] = '\\'; + Files.write(dir.resolve("large.cfg"), large); + + // Should not throw + assertDoesNotThrow(() -> patcher.fixAbsolutePaths(dir)); + } + + @Test + void fixAbsolutePaths_emptyDir_noError(@TempDir Path dir) { + assertDoesNotThrow(() -> patcher.fixAbsolutePaths(dir)); + } + + @Test + void fixAbsolutePaths_unixStylePath(@TempDir Path dir) throws Exception { + // Some games use forward slashes: C:/GAME/DATA + String content = "path=C:/MYGAME/DATA.DAT\n"; + Files.writeString(dir.resolve("config.cfg"), content, StandardCharsets.ISO_8859_1); + Files.writeString(dir.resolve("DATA.DAT"), "data"); + + patcher.fixAbsolutePaths(dir); + + String fixed = Files.readString(dir.resolve("config.cfg"), StandardCharsets.ISO_8859_1); + assertTrue(fixed.contains("./DATA.DAT"), "Forward-slash paths should be fixed"); + } +} diff --git a/src/test/java/com/dostalgia/ExecutableDetectorTest.java b/src/test/java/com/dostalgia/ExecutableDetectorTest.java new file mode 100644 index 0000000..3f37f04 --- /dev/null +++ b/src/test/java/com/dostalgia/ExecutableDetectorTest.java @@ -0,0 +1,136 @@ +package com.dostalgia; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +class ExecutableDetectorTest { + + private final ExecutableDetector detector = new ExecutableDetector(); + + @Test + void findAll_findsExeComBat(@TempDir Path dir) throws Exception { + Files.writeString(dir.resolve("GAME.EXE"), "MZ"); + Files.writeString(dir.resolve("helper.com"), "MZ"); + Files.writeString(dir.resolve("run.bat"), "@echo off"); + Files.writeString(dir.resolve("readme.txt"), "text"); // should NOT be found + + List result = detector.findAll(dir); + assertEquals(3, result.size()); + assertTrue(result.contains("GAME.EXE")); + assertTrue(result.contains("helper.com")); + assertTrue(result.contains("run.bat")); + assertFalse(result.contains("readme.txt")); + } + + @Test + void findMain_prefersLargestNonInstaller(@TempDir Path dir) throws Exception { + // Small installer + Files.write(dir.resolve("INSTALL.EXE"), createMZ(100)); + // Large real game + Files.write(dir.resolve("GAME.EXE"), createMZ(5000)); + + String main = detector.findMain(dir); + assertEquals(dir.resolve("GAME.EXE").toString(), main); + } + + @Test + void findMain_skipsExtenders(@TempDir Path dir) throws Exception { + Files.write(dir.resolve("DOS4GW.EXE"), createMZ(200)); + Files.write(dir.resolve("GAME.EXE"), createMZ(1000)); + + String main = detector.findMain(dir); + assertEquals(dir.resolve("GAME.EXE").toString(), main); + } + + @Test + void findMain_skipsSelfExtractors(@TempDir Path dir) throws Exception { + byte[] sfx = createMZ(500); + // Embed PKSFX string + String content = new String(sfx); + int pos = content.indexOf("MZ") + 2; + byte[] withSfx = new byte[sfx.length + 20]; + System.arraycopy(sfx, 0, withSfx, 0, sfx.length); + byte[] pksfx = "PKSFX".getBytes(); + System.arraycopy(pksfx, 0, withSfx, pos, pksfx.length); + Files.write(dir.resolve("GAME.EXE"), withSfx); + Files.write(dir.resolve("REAL.EXE"), createMZ(2000)); + + String main = detector.findMain(dir); + assertEquals(dir.resolve("REAL.EXE").toString(), main); + } + + @Test + void findMain_emptyDir_returnsNull(@TempDir Path dir) { + assertNull(detector.findMain(dir)); + } + + @Test + void findMain_subdirectoryDeprioritized(@TempDir Path dir) throws Exception { + // Root-level large exe + Files.write(dir.resolve("GAME.EXE"), createMZ(1000)); + // Subdirectory exe (should be deprioritized even if larger) + Path sub = Files.createDirectory(dir.resolve("SUBDIR")); + Files.write(sub.resolve("OTHER.EXE"), createMZ(2000)); + + String main = detector.findMain(dir); + assertEquals(dir.resolve("GAME.EXE").toString(), main); + } + + @Test + void findSetup_findsSetupExe(@TempDir Path dir) throws Exception { + Files.writeString(dir.resolve("SETUP.EXE"), "MZ"); + Files.writeString(dir.resolve("GAME.EXE"), "MZ"); + + String setup = detector.findSetup(dir); + assertEquals(dir.resolve("SETUP.EXE").toString(), setup); + } + + @Test + void findSetup_findsInstallBat(@TempDir Path dir) throws Exception { + Files.writeString(dir.resolve("INSTALL.BAT"), "@echo off"); + + String setup = detector.findSetup(dir); + assertEquals(dir.resolve("INSTALL.BAT").toString(), setup); + } + + @Test + void findSetup_noSetup_returnsNull(@TempDir Path dir) throws Exception { + Files.writeString(dir.resolve("GAME.EXE"), "MZ"); + assertNull(detector.findSetup(dir)); + } + + @Test + void isLikelySelfExtractor_detectsPKSFX(@TempDir Path dir) throws Exception { + byte[] buf = new byte[1000]; + buf[0] = 0x4D; // M + buf[1] = 0x5A; // Z + byte[] pksfx = "PKSFX".getBytes(); + System.arraycopy(pksfx, 0, buf, 10, pksfx.length); + + Path f = dir.resolve("sfx.exe"); + Files.write(f, buf); + assertTrue(ExecutableDetector.isLikelySelfExtractor(f)); + } + + @Test + void isLikelySelfExtractor_plainExe_returnsFalse(@TempDir Path dir) throws Exception { + byte[] buf = createMZ(500); + Path f = dir.resolve("game.exe"); + Files.write(f, buf); + assertFalse(ExecutableDetector.isLikelySelfExtractor(f)); + } + + // ─── helpers ───────────────────────────────────────────────── + + private static byte[] createMZ(int size) { + byte[] buf = new byte[size]; + buf[0] = 0x4D; // M + buf[1] = 0x5A; // Z + return buf; + } +}