package ar.com.companeros.horror; import ar.com.companeros.Companeros; import java.io.IOException; import java.nio.file.*; import java.util.UUID; import net.minecraft.gametest.framework.*; import net.minecraftforge.gametest.GameTestHolder; import net.minecraftforge.gametest.PrefixGameTestTemplate; /** Fronteras de voz: ni texto libre ni respuestas HTTP obtienen rutas o permisos. */ @GameTestHolder(Companeros.ID) @PrefixGameTestTemplate(false) public final class HorrorSpeechTests { @GameTest(template = "empty", timeoutTicks = 20) public void voiceEndpointsAndTextCannotSmuggleOperations(GameTestHelper helper) { for (String endpoint : new String[]{"file:///C:/secret", "http://example.com/speech", "https://user:password@example.com/speech", "https://example.com/speech#fragment"}) { try { HorrorSpeech.permittedEndpoint(endpoint); helper.fail("Se aceptó una URL de voz insegura"); } catch (IllegalArgumentException expected) { } } helper.assertTrue(HorrorSpeech.permittedEndpoint("http://127.0.0.1:8775/speech").getHost().equals("127.0.0.1") && HorrorSpeech.permittedEndpoint("https://voice.example/speech").getScheme().equals("https") && HorrorSpeech.validText("La piedra todavía me recuerda.") && !HorrorSpeech.validText("x".repeat(321)) && !HorrorSpeech.validText("voz\u0000oculta"), "El operador elige el servicio; el texto queda acotado sin controles ocultos"); helper.succeed(); } @GameTest(template = "empty", timeoutTicks = 20) public void generatedAudioRequiresVorbisAndPacketOwnsItsBytes(GameTestHelper helper) { try (var stream = getClass().getResourceAsStream("/assets/companeros/sounds/parasite_voice.ogg")) { helper.assertTrue(stream != null, "Existe una voz propia empaquetada"); byte[] audio = stream.readAllBytes(); helper.assertTrue(HorrorSpeech.validAudio(audio), "La voz generada respeta el formato y límite del canal"); var packet = new HorrorNetwork.Speech(UUID.randomUUID(), 0, 64, 0, audio); audio[0] = 0; byte[] escaped = packet.audio(); escaped[1] = 0; helper.assertTrue(HorrorSpeech.validAudio(packet.audio()), "El emisor y el consumidor no pueden alterar los bytes de un paquete ya validado"); helper.assertTrue(!HorrorSpeech.validAudio(new byte[HorrorSpeech.MAX_AUDIO_BYTES + 1]) && !HorrorSpeech.validAudio(new byte[]{'O', 'g', 'g', 'S'}), "No se acepta una firma vacía ni un audio que exceda el límite"); try { new HorrorNetwork.Speech(UUID.randomUUID(), Double.NaN, 0, 0, packet.audio()); helper.fail("Se aceptaron coordenadas inválidas"); } catch (IllegalArgumentException expected) { } helper.succeed(); } catch (IOException error) { throw new IllegalStateException(error); } } @GameTest(template = "empty", timeoutTicks = 20) public void voiceConfigStartsClosedAndRejectsUnknownFields(GameTestHelper helper) { Path root = null; try { root = Files.createTempDirectory("raps-voice-fixture-"); var config = HorrorSpeech.load(root); helper.assertTrue(!config.enabled() && config.textFallback() && config.endpoint().isEmpty(), "No hay peticiones externas al instalar una configuración nueva"); Path file = root.resolve("voice.json"); Files.writeString(file, "{\"schemaVersion\":1,\"command\":\"shell\"}"); try { HorrorSpeech.load(root); helper.fail("La configuración aceptó un campo de operación externo"); } catch (IOException expected) { } Files.writeString(file, "x".repeat(4097)); try { HorrorSpeech.load(root); helper.fail("Se aceptó un archivo fuera de límite"); } catch (IOException expected) { } helper.succeed(); } catch (IOException error) { throw new IllegalStateException(error); } finally { if (root != null) try { Files.deleteIfExists(root.resolve("voice.json")); Files.deleteIfExists(root); } catch (IOException ignored) { } } } }