mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-28 20:32:15 +02:00
133 lines
5.6 KiB
Python
133 lines
5.6 KiB
Python
"""Exercise SDK staging without redistributing the vendor SDK in fixtures."""
|
|
|
|
import hashlib
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
import prepare_discord_sdk as sdk
|
|
|
|
|
|
class PrepareDiscordSdkTest(unittest.TestCase):
|
|
def setUp(self):
|
|
directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(directory.cleanup)
|
|
self.root = Path(directory.name).resolve()
|
|
self.output = self.root / "prepared"
|
|
self.archive = self.root / "sdk.zip"
|
|
self.contents = {
|
|
"lib/release/discord_partner_sdk.aar": b"test Android library",
|
|
"License-Notices.txt": b"test license notices",
|
|
}
|
|
checksums = {name: hashlib.sha256(data).hexdigest()
|
|
for name, data in self.contents.items()}
|
|
patch = mock.patch.object(sdk, "FILES", checksums)
|
|
patch.start()
|
|
self.addCleanup(patch.stop)
|
|
|
|
def make_archive(self, prefix=""):
|
|
with zipfile.ZipFile(self.archive, "w") as zf:
|
|
for name, data in self.contents.items():
|
|
zf.writestr(prefix + name, data)
|
|
zf.writestr("../../outside.txt", b"must not be extracted")
|
|
zf.writestr("lib/release/desktop.dll", b"unused")
|
|
|
|
def assert_prepared(self):
|
|
files = {str(path.relative_to(self.output)): path.read_bytes()
|
|
for path in self.output.rglob("*") if path.is_file()}
|
|
self.assertEqual(files, self.contents)
|
|
|
|
def test_full_and_android_only_archives(self):
|
|
for prefix in ("", "discord_social_sdk/"):
|
|
with self.subTest(prefix=prefix):
|
|
self.make_archive(prefix)
|
|
sdk.prepare(self.output, archive=self.archive)
|
|
self.assert_prepared()
|
|
self.assertFalse((self.root / "outside.txt").exists())
|
|
|
|
def test_source_directory_and_cached_local_build(self):
|
|
source = self.root / "download"
|
|
for name, data in self.contents.items():
|
|
path = source / name
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_bytes(data)
|
|
sdk.prepare(self.output, source_dir=source)
|
|
shutil.rmtree(source)
|
|
self.assertEqual(sdk.prepare(self.output), self.output)
|
|
self.assert_prepared()
|
|
|
|
def test_corrupt_cache_is_not_trusted(self):
|
|
self.make_archive()
|
|
sdk.prepare(self.output, archive=self.archive)
|
|
(self.output / "License-Notices.txt").write_bytes(b"tampered")
|
|
with self.assertRaisesRegex(sdk.SdkError, "checksum mismatch"):
|
|
sdk.prepare(self.output)
|
|
|
|
def test_bad_archive_does_not_replace_verified_sdk(self):
|
|
self.make_archive()
|
|
sdk.prepare(self.output, archive=self.archive)
|
|
with zipfile.ZipFile(self.archive, "w") as zf:
|
|
for name in self.contents:
|
|
zf.writestr(name, b"wrong SDK version")
|
|
with self.assertRaisesRegex(sdk.SdkError, "checksum mismatch"):
|
|
sdk.prepare(self.output, archive=self.archive)
|
|
self.assert_prepared()
|
|
|
|
def test_duplicate_or_missing_required_file_fails(self):
|
|
for duplicate in (False, True):
|
|
self.make_archive()
|
|
if duplicate:
|
|
with zipfile.ZipFile(self.archive, "a") as zf:
|
|
zf.writestr("other/License-Notices.txt", b"duplicate")
|
|
else:
|
|
with zipfile.ZipFile(self.archive, "w") as zf:
|
|
zf.writestr("License-Notices.txt", b"only notices")
|
|
with self.assertRaisesRegex(sdk.SdkError, "exactly one"):
|
|
sdk.prepare(self.output, archive=self.archive)
|
|
self.assertFalse(self.output.exists())
|
|
|
|
def test_missing_configuration_is_actionable(self):
|
|
with self.assertRaisesRegex(sdk.SdkError, "DISCORD_SDK_PASSPHRASE"):
|
|
sdk.prepare(self.output)
|
|
|
|
def test_decryption_failure_does_not_echo_key(self):
|
|
key = "private-test-passphrase"
|
|
with mock.patch.object(sdk.subprocess, "run", return_value=mock.Mock(returncode=2)):
|
|
with self.assertRaises(sdk.SdkError) as error:
|
|
sdk.prepare(self.output, passphrase=key)
|
|
self.assertNotIn(key, str(error.exception))
|
|
self.assertFalse(self.output.exists())
|
|
|
|
def test_missing_gpg_is_actionable(self):
|
|
with mock.patch.object(sdk.subprocess, "run", side_effect=FileNotFoundError):
|
|
with self.assertRaisesRegex(sdk.SdkError, "Install GnuPG"):
|
|
sdk.prepare(self.output, passphrase="test")
|
|
|
|
@unittest.skipUnless(shutil.which("gpg"), "GnuPG is required for encrypted archive test")
|
|
def test_encrypted_archive_roundtrip_and_wrong_key(self):
|
|
self.make_archive()
|
|
encrypted = self.root / "sdk.zip.gpg"
|
|
key = "test-fixture-passphrase"
|
|
gnupg_home = self.root / "gnupg"
|
|
gnupg_home.mkdir(mode=0o700)
|
|
with mock.patch.dict(sdk.os.environ, {"GNUPGHOME": str(gnupg_home)}):
|
|
subprocess.run(
|
|
["gpg", "--batch", "--quiet", "--pinentry-mode", "loopback",
|
|
"--passphrase-fd", "0", "--cipher-algo", "AES256", "--symmetric",
|
|
"--output", str(encrypted), str(self.archive)],
|
|
input=key.encode(), check=True, capture_output=True,
|
|
)
|
|
with mock.patch.object(sdk, "ENCRYPTED_ARCHIVE", encrypted):
|
|
with self.assertRaisesRegex(sdk.SdkError, "decryption failed"):
|
|
sdk.prepare(self.output, passphrase="incorrect")
|
|
sdk.prepare(self.output, passphrase=key)
|
|
self.assert_prepared()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|