Volver al índice

src/main/java/ar/com/companeros/identity/SkinProfileTests.java

package ar.com.companeros.identity;

import ar.com.companeros.Companeros;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import java.nio.charset.StandardCharsets;
import java.util.*;
import net.minecraft.gametest.framework.*;
import net.minecraftforge.gametest.*;

@GameTestHolder(Companeros.ID)
@PrefixGameTestTemplate(false)
public final class SkinProfileTests {
    private static final String TEXTURE = "https://textures.minecraft.net/texture/" + "a".repeat(64);
    @GameTest(template = "empty")
    public void steveOverridePersistsBeforeInfectionAndHostAppearanceCanBeRestored(GameTestHelper helper) {
        UUID host = PlayerProfileIds.offline("Xenon");
        var state = new ar.com.companeros.horror.HorrorState(0);
        state.appearanceOverride(SkinSources.parasiteOverride("Steve"));
        var restored = ar.com.companeros.horror.HorrorState.load(state.save(new net.minecraft.nbt.CompoundTag()));
        helper.assertTrue(!restored.active() && restored.appearanceOverride().equals("steve")
            && SkinSources.parasiteOverride("default").equals("steve")
            && SkinSources.parasiteSource(restored.appearanceOverride(), host, TEXTURE).equals("steve"), "Steve local se conserva antes de infectar y prevalece sobre otra skin");
        restored.appearanceOverride(SkinSources.parasiteOverride("host"));
        helper.assertTrue(SkinSources.parasiteSource(restored.appearanceOverride(), host, TEXTURE).equals(TEXTURE)
            && SkinSources.parasiteSource("", host, "").equals(host.toString())
            && SkinSources.parasiteSource("", null, "").equals("steve"), "Restaurar huésped recupera selección o vanilla por UUID; sin huésped es Steve");
        helper.assertTrue(rejected(() -> state.appearanceOverride("https://example.com"))
            && rejected(() -> SkinSources.parasiteOverride("../../skin")), "El comando local no puede elegir endpoints ni archivos");
        helper.succeed();
    }
    @GameTest(template = "empty")
    public void skinSourcesAllowOnlyMojangAccountsAndExactOfficialTextures(GameTestHelper helper) {
        helper.assertTrue(SkinSources.profile("Neon").equals("Neon")
            && SkinSources.profile("68e79ab569dd350ba1d373b2f9b89fb1").equals("68e79ab5-69dd-350b-a1d3-73b2f9b89fb1")
            && SkinSources.profile("DEFAULT").equals("default")
            && SkinSources.texture(TEXTURE.replace("https:", "http:")).equals(TEXTURE), "Nombres y UUID canónicos; CDN siempre HTTPS");
        for (String input : List.of("../secret", "https://example.com/skin.png", " Neon", "Neon\n", "a".repeat(37)))
            helper.assertTrue(rejected(() -> SkinSources.profile(input)), "La skin no elige rutas ni endpoints");
        for (String input : List.of(TEXTURE + "?token=secret", TEXTURE + "#fragment", TEXTURE.replace("textures.minecraft.net", "textures.minecraft.net.evil.example"),
                TEXTURE.replace("textures.minecraft.net", "user:secret@textures.minecraft.net"), TEXTURE.replace(".net/", ".net:443/"), "file:///skin.png"))
            helper.assertTrue(rejected(() -> SkinSources.texture(input)), "CDN sin credenciales, query ni puertos alternativos");
        helper.succeed();
    }
    @GameTest(template = "empty")
    public void skinSelectionsPersistAcrossWorldsWithoutReplacingPlayerIdentity(GameTestHelper helper) {
        UUID actor = PlayerProfileIds.offline("Neon"), owner = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5");
        SkinProfiles.Selection selected = new SkinProfiles.Selection("Example", owner, TEXTURE, property(), "test-signature", 123456L);
        Map<UUID, SkinProfiles.Selection> restored = SkinProfiles.decode(SkinProfiles.encode(Map.of(actor, selected)));
        GameProfile profile = new GameProfile(actor, "Neon"); profile.getProperties().put("textures", restored.get(actor).property());
        helper.assertTrue(restored.get(actor).equals(selected) && profile.getId().equals(actor) && profile.getName().equals("Neon")
            && !actor.equals(selected.profileId()) && profile.getProperties().containsKey("textures"), "Skin de otra cuenta mantiene UUID, nombre y fecha propia");
        helper.assertTrue(rejected(() -> SkinProfiles.decode("{\"schema\":2,\"agents\":{}}"))
            && rejected(() -> SkinProfiles.decode("{\"schema\":1,\"agents\":{},\"endpoint\":\"http://localhost\"}"))
            && rejected(() -> new SkinProfiles.Selection("Example", owner, TEXTURE.replace('a', 'b'), property(), "test-signature", 0)), "Esquema cerrado y URL coincidente con propiedad");
        helper.succeed();
    }
    @GameTest(template = "empty")
    public void hostAtlasPreservesFaceHairAndClothesAtEveryMutationStage(GameTestHelper helper) {
        int[] source = new int[64 * 64];
        for (int y = 8; y < 16; y++) for (int x = 8; x < 16; x++) source[y * 64 + x] = 0xFFCC9977;
        source[9 * 64 + 9] = 0xFF302018; // El huésped ya tenía este peinado.
        source[8 * 64 + 41] = 0xFF4F321F; // Capa exterior de pelo.
        source[23 * 64 + 21] = 0xFF246080; // Camisa.
        int[] before = source.clone();
        int[] initial = HostSkinAtlas.compose(source, 64, 0);
        helper.assertTrue(initial.length == 128 * 128 && initial[9 * 128 + 9] == 0xFF302018
            && initial[8 * 128 + 9] == 0xFF4F321F && initial[23 * 128 + 21] == 0xFF246080, "Cara, pelo superior y ropa pertenecen al huésped");
        for (int stage = 1; stage <= 4; stage++) {
            int[] mutation = HostSkinAtlas.compose(source, 64, stage);
            helper.assertTrue(mutation[9 * 128 + 9] == initial[9 * 128 + 9]
                && mutation[8 * 128 + 9] == initial[8 * 128 + 9] && mutation[23 * 128 + 21] == initial[23 * 128 + 21]
                && mutation[10 * 128 + 14] != initial[10 * 128 + 14] && mutation[96 * 128 + 96] == 0xFF271A18,
                "Cicatrices localizadas sin borrar identidad; boca usa UV aparte");
        }
        helper.assertTrue(Arrays.equals(source, before) && rejected(() -> HostSkinAtlas.compose(new int[1], 64, 0)), "Composición acotada sin mutar la imagen del huésped");
        helper.succeed();
    }
    @GameTest(template = "empty")
    public void skinPngDimensionsRejectCompressedOversizedImages(GameTestHelper helper) {
        byte[] png = new byte[33]; byte[] signature = {(byte)137, 80, 78, 71, 13, 10, 26, 10}; System.arraycopy(signature, 0, png, 0, 8);
        png[12] = 'I'; png[13] = 'H'; png[14] = 'D'; png[15] = 'R';
        java.nio.ByteBuffer.wrap(png, 16, 8).putInt(64).putInt(64);
        helper.assertTrue(SkinSources.playerPng(png), "Cabecera de skin moderna");
        java.nio.ByteBuffer.wrap(png, 20, 4).putInt(32); helper.assertTrue(SkinSources.playerPng(png), "Skin antigua 64×32");
        java.nio.ByteBuffer.wrap(png, 16, 4).putInt(16_384); helper.assertTrue(!SkinSources.playerPng(png)
            && !SkinSources.playerPng(new byte[131_073]), "Dimensiones y transferencia acotadas antes del decodificador");
        helper.succeed();
    }
    private static String property() {
        JsonObject skin = new JsonObject(); skin.addProperty("url", TEXTURE); JsonObject textures = new JsonObject(); textures.add("SKIN", skin);
        JsonObject root = new JsonObject(); root.add("textures", textures);
        return Base64.getEncoder().encodeToString(root.toString().getBytes(StandardCharsets.UTF_8));
    }
    private static boolean rejected(Runnable action) { try { action.run(); return false; } catch (RuntimeException expected) { return true; } }
}