Volver al índice
src/main/java/ar/com/companeros/horror/HorrorSpeechTests.java
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) { }
}
}
}