Something went wrong. Try again.
Steam Deck Doom modding setup automation for Doom Runner, UZDoom, Brutal Doom, and Project Brutality.
brutal-doom moddb python doom-runner linux-gaming project-brutality uzdoom doom steam-deck
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304from __future__ import annotations
import argparseimport ioimport jsonimport loggingfrom pathlib import Pathimport tarfileimport tempfileimport unittestimport urllib.errorfrom email.message import Messagefrom unittest.mock import patch
from doomdeck.application.release_assets import fetch_linux_release_asset, fetch_windows_release_assetfrom doomdeck.cli import build_arg_parser, main, restore, select_project_brutality_downloadfrom doomdeck.domain.models import DoomDeckError
class CLIBehaviorTests(unittest.TestCase): def test_install_parser_accepts_stable_brutal_doom_channel(self) -> None: args = build_arg_parser().parse_args(["install", "--brutal-doom-channel", "stable"])
self.assertEqual(args.command, "install") self.assertEqual(args.brutal_doom_channel, "stable") self.assertFalse(args.skip_downloads) self.assertEqual(args.proton_compat_tool, "proton_10") self.assertTrue(callable(args.func))
def test_install_parser_accepts_proton_compat_tool_override(self) -> None: args = build_arg_parser().parse_args(["install", "--proton-compat-tool", "proton_experimental"])
self.assertEqual(args.proton_compat_tool, "proton_experimental")
def test_install_parser_rejects_moddb_wad_url_option(self) -> None: with self.assertRaises(SystemExit): build_arg_parser().parse_args( [ "install", "--moddb-wad-url", "https://www.moddb.com/games/doom/addons/doom-the-way-id-did-v11", ] )
def test_install_wads_parser_accepts_positional_moddb_wad_urls(self) -> None: args = build_arg_parser().parse_args( [ "install-wads", "https://www.moddb.com/games/doom/addons/doom-the-way-id-did-v11", "https://www.moddb.com/games/doom-ii/addons/doom-2-the-way-id-did", ] )
self.assertEqual(args.command, "install-wads") self.assertEqual( args.moddb_wad_urls, [ "https://www.moddb.com/games/doom/addons/doom-the-way-id-did-v11", "https://www.moddb.com/games/doom-ii/addons/doom-2-the-way-id-did", ], ) self.assertTrue(callable(args.func))
def test_install_wads_command_runs_wad_installer_without_full_install_discovery(self) -> None: urls = ["https://www.moddb.com/games/doom/addons/doom-the-way-id-did-v11"] with tempfile.TemporaryDirectory() as temp_dir: root = Path(temp_dir).resolve() / "Doom" expected = root / "pwads" / "DTWID.WAD"
def fake_install_moddb_wad_urls( page_urls, downloads_dir, pwads_dir, backups_dir, dry_run, logger, force_download=False, user_agent="", ): self.assertEqual(page_urls, urls) self.assertEqual(downloads_dir, root / "downloads") self.assertEqual(pwads_dir, root / "pwads") self.assertEqual(backups_dir, root / "backups") self.assertFalse(dry_run) self.assertFalse(force_download) self.assertTrue(user_agent) return [expected]
with ( patch("doomdeck.cli.install_moddb_wad_urls", side_effect=fake_install_moddb_wad_urls), patch("doomdeck.cli.discover_steam") as discover_steam, ): result = main(["install-wads", "--root", str(root), urls[0]])
self.assertEqual(result, 0) discover_steam.assert_not_called() self.assertTrue((root / "downloads").is_dir()) self.assertTrue((root / "pwads").is_dir()) self.assertTrue((root / "backups").is_dir()) content_groups = json.loads((root / "configs" / "doomrunner" / "content-groups.json").read_text(encoding="utf-8")) self.assertEqual(content_groups["schema"], "doom-deck-setup/content-groups/v1")
def test_parser_exposes_maintenance_commands(self) -> None: parser = build_arg_parser()
for command in ["validate", "backup", "clean", "restore", "self-update"]: with self.subTest(command=command): argv = [command, "archive.tar.gz"] if command == "restore" else [command] args = parser.parse_args(argv) self.assertEqual(args.command, command) self.assertTrue(callable(args.func))
def test_top_level_help_mentions_install_wads_command(self) -> None: self.assertIn("install-wads", build_arg_parser().format_help())
def test_restore_rejects_wrong_backup_root_before_moving_existing_install(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: root = Path(temp_dir) / "Doom" root.mkdir() marker = root / "existing.txt" marker.write_text("keep", encoding="utf-8") archive_path = Path(temp_dir) / "wrong-root.tar.gz" with tarfile.open(archive_path, "w:gz") as archive: payload = b"wrong" member = tarfile.TarInfo("OtherRoot/file.txt") member.size = len(payload) archive.addfile(member, io.BytesIO(payload))
args = argparse.Namespace(root=str(root), backup_archive=str(archive_path), verbose=False, dry_run=False)
with ( patch("doomdeck.cli.configure_logging", return_value=logging.getLogger("test")), patch("doomdeck.cli.print_plan"), self.assertRaisesRegex(DoomDeckError, "Unexpected backup archive root"), ): restore(args)
self.assertTrue(root.is_dir()) self.assertEqual(marker.read_text(encoding="utf-8"), "keep") self.assertEqual(list(root.parent.glob("Doom.pre-restore-*")), [])
def test_self_update_parser_accepts_check_and_ref_options(self) -> None: args = build_arg_parser().parse_args( [ "self-update", "--check", "--ref", "main", "--install-dir", "/tmp/doomdeck/source", ] )
self.assertEqual(args.command, "self-update") self.assertTrue(args.check) self.assertEqual(args.ref, "main") self.assertEqual(args.install_dir, "/tmp/doomdeck/source")
def test_self_update_check_does_not_download_archive(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: install_dir = Path(temp_dir) / "source" (install_dir / "src" / "doomdeck").mkdir(parents=True) (install_dir / "pyproject.toml").write_text("[project]\nname = 'doomdeck'\n", encoding="utf-8")
with patch("doomdeck.cli.download_url") as download: result = main( [ "self-update", "--check", "--install-dir", str(install_dir), "--archive-url", "https://example.test/doomdeck.tar.gz", ] )
self.assertEqual(result, 0) download.assert_not_called()
def test_release_asset_selection_rejects_assets_missing_download_urls(self) -> None: release = { "tag_name": "v1.2.3", "assets": [ { "name": "uzdoom-linux-x86_64.AppImage", "size": 1234, } ], }
with patch("doomdeck.application.release_assets.request_github_json", return_value=release): with self.assertRaisesRegex(DoomDeckError, "assets\\.0\\.browser_download_url"): fetch_linux_release_asset("ZDoom/UZDoom", False, logging.getLogger("test"), "test-agent")
def test_release_asset_selection_rejects_non_list_assets(self) -> None: release = { "tag_name": "v1.2.3", "assets": { "name": "uzdoom-linux-x86_64.AppImage", "browser_download_url": "https://example.test/uzdoom.AppImage", }, }
with patch("doomdeck.application.release_assets.request_github_json", return_value=release): with self.assertRaisesRegex(DoomDeckError, "assets"): fetch_linux_release_asset("ZDoom/UZDoom", False, logging.getLogger("test"), "test-agent")
def test_windows_release_asset_selection_prefers_x86_64_zip_over_appimage_and_legacy(self) -> None: release = { "tag_name": "v1.2.3", "assets": [ { "name": "DoomRunner-1.9.2-Windows-legacy_i386-static_exe.zip", "browser_download_url": "https://example.test/doomrunner-i386.zip", "size": 1234, }, { "name": "DoomRunner-1.9.2-Linux-x86_64.AppImage", "browser_download_url": "https://example.test/doomrunner.AppImage", "size": 1234, }, { "name": "DoomRunner-1.9.2-Windows-recent_x86_64-static_exe.zip", "browser_download_url": "https://example.test/doomrunner-x86_64.zip", "size": 1234, }, ], }
with patch("doomdeck.application.release_assets.request_github_json", return_value=release): asset = fetch_windows_release_asset("Youda008/DoomRunner", logging.getLogger("test"), "test-agent")
self.assertEqual(asset.name, "DoomRunner-1.9.2-Windows-recent_x86_64-static_exe.zip")
def test_windows_release_asset_selection_finds_uzdoom_windows_zip(self) -> None: release = { "tag_name": "4.14.3", "assets": [ { "name": "Linux-UZDoom-4.14.3.AppImage", "browser_download_url": "https://example.test/uzdoom.AppImage", "size": 1234, }, { "name": "Windows-UZDoom-4.14.3.zip", "browser_download_url": "https://example.test/uzdoom.zip", "size": 1234, }, ], }
with patch("doomdeck.application.release_assets.request_github_json", return_value=release): asset = fetch_windows_release_asset("UZDoom/UZDoom", logging.getLogger("test"), "test-agent")
self.assertEqual(asset.name, "Windows-UZDoom-4.14.3.zip")
def test_project_brutality_release_404_resolves_commit_specific_fallback(self) -> None: not_found = urllib.error.HTTPError( "https://api.github.com/repos/pa1nki113r/Project_Brutality/releases/latest", 404, "Not Found", Message(), io.BytesIO(), ) commit = "a" * 40
with patch( "doomdeck.cli.request_github_json", side_effect=[not_found, {"default_branch": "main"}, {"sha": commit}], ): asset = select_project_brutality_download(logging.getLogger("test"))
self.assertEqual( asset.url, f"https://api.github.com/repos/pa1nki113r/Project_Brutality/zipball/{commit}", ) self.assertIn(commit, asset.name)
def test_project_brutality_cache_names_distinguish_full_commit_shas(self) -> None: prefix = "a" * 12 commits = [prefix + "1" * 28, prefix + "2" * 28] names = [] for commit in commits: not_found = urllib.error.HTTPError( "https://api.github.com/repos/pa1nki113r/Project_Brutality/releases/latest", 404, "Not Found", Message(), io.BytesIO(), ) with patch( "doomdeck.cli.request_github_json", side_effect=[not_found, {"default_branch": "main"}, {"sha": commit}], ): names.append(select_project_brutality_download(logging.getLogger("test")).name)
self.assertNotEqual(names[0], names[1]) self.assertIn(commits[0], names[0]) self.assertIn(commits[1], names[1])
if __name__ == "__main__": unittest.main()