This commit is contained in:
229
tests/test_scripts.py
Normal file
229
tests/test_scripts.py
Normal file
@@ -0,0 +1,229 @@
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import tarfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class InstallSlippiScriptTests(unittest.TestCase):
|
||||
def test_fails_closed_when_checksum_asset_omits_appimage(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
bin_dir = temp_path / "bin"
|
||||
install_dir = temp_path / "install"
|
||||
bin_dir.mkdir()
|
||||
install_dir.mkdir()
|
||||
|
||||
release_json = {
|
||||
"assets": [
|
||||
{
|
||||
"name": "Slippi-Launcher-9.9.9-x86_64.AppImage",
|
||||
"browser_download_url": "https://github.com/project-slippi/slippi-launcher/releases/download/v9.9.9/Slippi-Launcher-9.9.9-x86_64.AppImage",
|
||||
},
|
||||
{
|
||||
"name": "checksums.txt",
|
||||
"browser_download_url": "https://github.com/project-slippi/slippi-launcher/releases/download/v9.9.9/checksums.txt",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
curl_stub = bin_dir / "curl"
|
||||
curl_stub.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"#!/usr/bin/env bash",
|
||||
"set -Eeuo pipefail",
|
||||
'dest=""',
|
||||
'url=""',
|
||||
'while [[ $# -gt 0 ]]; do',
|
||||
' case "$1" in',
|
||||
' -o) dest="$2"; shift 2 ;;',
|
||||
' *) url="$1"; shift ;;',
|
||||
' esac',
|
||||
'done',
|
||||
f"cat <<'JSON' >\"$dest\"\n{json.dumps(release_json)}\nJSON" if False else "",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
curl_stub.write_text(
|
||||
"""#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
dest=""
|
||||
url=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-o) dest="$2"; shift 2 ;;
|
||||
*) url="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
case "$url" in
|
||||
*api.github.com*)
|
||||
cat <<'JSON' >"$dest"
|
||||
"""
|
||||
+ json.dumps(release_json)
|
||||
+ """
|
||||
JSON
|
||||
;;
|
||||
*.AppImage)
|
||||
printf 'fake appimage' >"$dest"
|
||||
;;
|
||||
*checksums.txt)
|
||||
printf 'deadbeef SomeOtherFile.AppImage\n' >"$dest"
|
||||
;;
|
||||
*)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
curl_stub.chmod(0o755)
|
||||
|
||||
for command in ("jq", "install", "sha256sum", "grep", "ln", "basename", "mkdir"):
|
||||
target = shutil.which(command)
|
||||
assert target is not None
|
||||
os.symlink(target, bin_dir / command)
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", str(REPO_ROOT / "scripts/install_slippi.sh")],
|
||||
env={
|
||||
**os.environ,
|
||||
"PATH": f"{bin_dir}:{os.environ['PATH']}",
|
||||
"SLIPPI_INSTALL_DIR": str(install_dir),
|
||||
},
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("checksum asset present but does not include", result.stderr)
|
||||
|
||||
|
||||
class BackupRestoreScriptTests(unittest.TestCase):
|
||||
def test_backup_excludes_account_data_and_sets_private_mode(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
couch_home = Path(temp_dir) / "home" / "couch"
|
||||
archive_path = Path(temp_dir) / "backup.tar.gz"
|
||||
(couch_home / ".config" / "couchd").mkdir(parents=True)
|
||||
(couch_home / ".config" / "dolphin-emu").mkdir(parents=True)
|
||||
(couch_home / ".config" / "Slippi").mkdir(parents=True)
|
||||
(couch_home / ".local" / "share" / "dolphin-emu").mkdir(parents=True)
|
||||
(couch_home / "Slippi").mkdir(parents=True)
|
||||
(couch_home / ".config" / "Slippi Launcher").mkdir(parents=True)
|
||||
(couch_home / ".config" / "couchd" / "runtime.json").write_text("{}", encoding="utf-8")
|
||||
(couch_home / ".config" / "Slippi Launcher" / "session.json").write_text("secret", encoding="utf-8")
|
||||
(couch_home / "Slippi" / "Replay").mkdir()
|
||||
(couch_home / "Slippi" / "Replay" / "match.slp").write_text("replay", encoding="utf-8")
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", str(REPO_ROOT / "scripts/backup_restore.sh"), "backup", str(archive_path)],
|
||||
env={**os.environ, "COUCH_HOME": str(couch_home)},
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(archive_path.stat().st_mode & 0o777, 0o600)
|
||||
listing = subprocess.run(
|
||||
["tar", "-tzf", str(archive_path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout
|
||||
self.assertIn(".config/couchd/runtime.json", listing)
|
||||
self.assertNotIn(".config/Slippi Launcher", listing)
|
||||
self.assertNotIn("Replay/match.slp", listing)
|
||||
|
||||
def test_backup_excludes_recursive_case_insensitive_rom_and_replay_patterns(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
couch_home = Path(temp_dir) / "home" / "couch"
|
||||
archive_path = Path(temp_dir) / "backup.tar.gz"
|
||||
(couch_home / ".config" / "couchd").mkdir(parents=True)
|
||||
(couch_home / ".config" / "Slippi Launcher" / "Profiles").mkdir(parents=True)
|
||||
(couch_home / ".config" / "dolphin-emu").mkdir(parents=True)
|
||||
(couch_home / ".config" / "Slippi").mkdir(parents=True)
|
||||
(couch_home / ".local" / "share" / "dolphin-emu").mkdir(parents=True)
|
||||
(couch_home / ".config" / "couchd" / "runtime.json").write_text("{}", encoding="utf-8")
|
||||
(couch_home / "Slippi" / "Replays" / "nested").mkdir(parents=True)
|
||||
(couch_home / "Slippi" / "Roms").mkdir(parents=True)
|
||||
(couch_home / "Slippi" / "Replays" / "nested" / "match.SLP").write_text("replay", encoding="utf-8")
|
||||
rom_gcm = f"melee.{''.join(['G', 'C', 'M'])}"
|
||||
rom_wbfs = f"other.{''.join(['w', 'B', 'f', 'S'])}"
|
||||
rom_nkit = "third." + ".".join(["nkit", "iso"])
|
||||
(couch_home / "Slippi" / "Roms" / rom_gcm).write_text("rom", encoding="utf-8")
|
||||
(couch_home / "Slippi" / "Roms" / rom_wbfs).write_text("rom", encoding="utf-8")
|
||||
(couch_home / "Slippi" / "Roms" / rom_nkit).write_text("rom", encoding="utf-8")
|
||||
(couch_home / "Slippi" / "keep.txt").write_text("keep", encoding="utf-8")
|
||||
(couch_home / ".config" / "Slippi Launcher" / "Profiles" / "session.json").write_text(
|
||||
"secret",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", str(REPO_ROOT / "scripts/backup_restore.sh"), "backup", str(archive_path)],
|
||||
env={**os.environ, "COUCH_HOME": str(couch_home)},
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
listing = subprocess.run(
|
||||
["tar", "-tzf", str(archive_path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout
|
||||
self.assertIn(".config/couchd/runtime.json", listing)
|
||||
self.assertNotIn(".config/Slippi Launcher/Profiles/session.json", listing)
|
||||
self.assertNotIn("match.SLP", listing)
|
||||
self.assertNotIn(rom_gcm, listing)
|
||||
self.assertNotIn(rom_wbfs, listing)
|
||||
self.assertNotIn(rom_nkit, listing)
|
||||
self.assertIn("Slippi/keep.txt", listing)
|
||||
|
||||
def test_restore_rejects_unsafe_archive_entries(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
couch_home = Path(temp_dir) / "home" / "couch"
|
||||
couch_home.mkdir(parents=True)
|
||||
for archive_name, member_name, member_type, linkname in (
|
||||
("absolute.tar.gz", "/absolute.txt", tarfile.REGTYPE, ""),
|
||||
("traversal.tar.gz", "../../escape.txt", tarfile.REGTYPE, ""),
|
||||
("device.tar.gz", "device-node", tarfile.CHRTYPE, ""),
|
||||
("link-escape.tar.gz", "link-out", tarfile.SYMTYPE, "../../escape.txt"),
|
||||
):
|
||||
archive_path = Path(temp_dir) / archive_name
|
||||
with tarfile.open(archive_path, "w:gz") as archive:
|
||||
info = tarfile.TarInfo(member_name)
|
||||
info.type = member_type
|
||||
info.mode = 0o600
|
||||
if member_type == tarfile.REGTYPE:
|
||||
payload = b"ok"
|
||||
info.size = len(payload)
|
||||
archive.addfile(info, io.BytesIO(payload))
|
||||
elif member_type == tarfile.SYMTYPE:
|
||||
info.linkname = linkname
|
||||
archive.addfile(info)
|
||||
else:
|
||||
archive.addfile(info)
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", str(REPO_ROOT / "scripts/backup_restore.sh"), "restore", str(archive_path)],
|
||||
env={**os.environ, "COUCH_HOME": str(couch_home)},
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("unsafe archive entry", result.stderr)
|
||||
Reference in New Issue
Block a user