import hashlib from pathlib import Path import pytest from tools.beta_release_lib import validate_complete_mapping def test_manifest_rejects_an_unclassified_or_missing_mod() -> None: with pytest.raises(ValueError, match="missing=.*alpha.jar.*unknown=.*beta.jar"): validate_complete_mapping( {"alpha.jar", "beta.jar"}, {"beta.jar": "UNCLASSIFIED"}, ) def test_manifest_accepts_only_known_sides_and_exact_names() -> None: validate_complete_mapping( {"alpha.jar", "beta.jar"}, {"alpha.jar": "client", "beta.jar": "server"}, ) with pytest.raises(ValueError, match="extra=.*gamma.jar"): validate_complete_mapping( {"alpha.jar"}, {"alpha.jar": "both", "gamma.jar": "client"}, ) def test_write_keybind_options_keeps_only_version_and_key_lines(tmp_path: Path) -> None: from tools.beta_release_lib import write_keybind_options source = tmp_path / "source-options.txt" destination = tmp_path / "options.txt" source.write_text( "version:3465\nrenderDistance:32\nkey_key.tacz.reload.desc:key.keyboard.r\n" "fov:0.0\nkey_key.forward:key.keyboard.w\n", encoding="utf-8", ) write_keybind_options(source, destination) assert destination.read_text(encoding="utf-8") == ( "version:3465\nkey_key.tacz.reload.desc:key.keyboard.r\nkey_key.forward:key.keyboard.w\n" ) def test_copy_classified_tree_never_copies_wrong_side_or_world(tmp_path: Path) -> None: from tools.beta_release_lib import copy_classified_tree source = tmp_path / "source" (source / "minecraft/mods").mkdir(parents=True) (source / "minecraft/saves/world").mkdir(parents=True) (source / "minecraft/mods/client.jar").write_bytes(b"client") (source / "minecraft/mods/both.jar").write_bytes(b"both") (source / "minecraft/mods/server.jar").write_bytes(b"server") (source / "minecraft/saves/world/level.dat").write_bytes(b"world") destination = tmp_path / "client" copy_classified_tree( source, destination, "client", {"client.jar": "client", "both.jar": "both", "server.jar": "server"}, [], ) assert (destination / "minecraft/mods/client.jar").is_file() assert (destination / "minecraft/mods/both.jar").is_file() assert not (destination / "minecraft/mods/server.jar").exists() assert not (destination / "minecraft/saves").exists() def test_validate_client_stage_requires_prism_root_and_rejects_world(tmp_path: Path) -> None: from tools.beta_release_lib import validate_client_stage stage = tmp_path / "client-stage" (stage / "minecraft/saves/world").mkdir(parents=True) (stage / "minecraft/saves/world/level.dat").write_bytes(b"world") (stage / "minecraft/mods").mkdir() (stage / "minecraft/options.txt").write_text("version:3465\n", encoding="utf-8") (stage / "instance.cfg").write_text("InstanceType=OneSix\n", encoding="utf-8") (stage / "mmc-pack.json").write_text("{}\n", encoding="utf-8") with pytest.raises(ValueError, match="forbidden client path.*minecraft/saves"): validate_client_stage(stage, {}) def test_sanitize_client_stage_removes_server_credentials_and_personal_cache( tmp_path: Path, ) -> None: from tools.beta_release_lib import sanitize_client_stage stage = tmp_path / "client-stage" credentials = stage / "minecraft/config/Discord-Integration.toml" cache = stage / "minecraft/config/voicechat/username-cache.json" shader_selection = stage / "minecraft/config/oculus.properties" skin_provider = stage / "minecraft/config/skinrestorer/config.json" credentials.parent.mkdir(parents=True) cache.parent.mkdir(parents=True) skin_provider.parent.mkdir(parents=True) credentials.write_text("botToken=do-not-publish\n", encoding="utf-8") cache.write_text('{"player":"do-not-publish"}\n', encoding="utf-8") shader_selection.write_text("shaderPack=local-only\n", encoding="utf-8") skin_provider.write_text('{"apiKey":"do-not-publish"}\n', encoding="utf-8") sanitize_client_stage(stage) assert not credentials.exists() assert not cache.exists() assert not shader_selection.exists() assert not skin_provider.exists() def test_validate_server_stage_rejects_client_jar_and_requires_target_world(tmp_path: Path) -> None: from tools.beta_release_lib import validate_server_stage stage = tmp_path / "server-stage" (stage / "mods").mkdir(parents=True) (stage / "mods/client.jar").write_bytes(b"client") (stage / "eula.txt").write_text("eula=false\n", encoding="utf-8") (stage / "server.properties").write_text("level-name=world\n", encoding="utf-8") with pytest.raises(ValueError, match="server JAR mismatch|missing target world"): validate_server_stage(stage, {"client.jar": "client"}, "world") def test_forge_request_declares_a_user_agent() -> None: from tools.build_beta_release import forge_request request = forge_request("https://example.invalid/forge-installer.jar") assert request.get_header("User-agent") == "EscapeFromFuncraftBetaBuilder/1.0" def test_forge_install_env_prepends_compatibility_library_path( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: from tools.build_beta_release import forge_install_env monkeypatch.setenv("LD_LIBRARY_PATH", "/system/lib") env = forge_install_env(tmp_path / "legacy-zlib") assert env is not None assert env["LD_LIBRARY_PATH"] == f"{tmp_path / 'legacy-zlib'}:/system/lib" def test_sha256_file_streams_without_reading_the_archive_at_once( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: from tools.beta_release_lib import sha256_file archive = tmp_path / "large-archive.bin" payload = b"release-data" * 100_000 archive.write_bytes(payload) def fail_read_bytes(_: Path) -> bytes: raise AssertionError("release archives must not be read into memory at once") monkeypatch.setattr(Path, "read_bytes", fail_read_bytes) assert sha256_file(archive) == hashlib.sha256(payload).hexdigest() def test_source_mod_names_includes_only_mod_jars(tmp_path: Path) -> None: from tools.build_beta_release import source_mod_names mods = tmp_path / "minecraft/mods" mods.mkdir(parents=True) (mods / "required.jar").write_bytes(b"jar") (mods / "notes.txt").write_text("ignore", encoding="utf-8") assert source_mod_names(tmp_path) == {"required.jar"}