199 lines
8.9 KiB
Python
199 lines
8.9 KiB
Python
import hashlib
|
|
import json
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from _helpers import REPO_ROOT
|
|
from app import release_manifest
|
|
from app import version
|
|
|
|
|
|
class PackagingTests(unittest.TestCase):
|
|
def read_text(self, relative_path):
|
|
return (REPO_ROOT / relative_path).read_text(encoding="utf-8")
|
|
|
|
def test_frozen_entrypoint_keeps_program_and_data_paths_separate(self):
|
|
main_py = self.read_text("main.py")
|
|
gui_init = self.read_text("app/gui/__init__.py")
|
|
|
|
self.assertIn('getattr(sys, "frozen", False)', main_py)
|
|
self.assertIn("sys.executable", main_py)
|
|
self.assertNotIn("os.chdir(PROJECT_ROOT)", main_py)
|
|
self.assertIn("appconfig.prepare_data_dir()", gui_init)
|
|
|
|
def test_pyinstaller_spec_uses_onedir_without_local_datas(self):
|
|
spec = self.read_text("cmshopee.spec")
|
|
normalized = "".join(spec.split())
|
|
|
|
self.assertIn("collect_data_files", spec)
|
|
self.assertIn('"app.default_prompts"', spec)
|
|
self.assertIn('includes=["**/*.txt"]', normalized)
|
|
self.assertIn("datas=default_prompt_datas", normalized)
|
|
self.assertIn("COLLECT(", spec)
|
|
self.assertIn('name="cmshopee"', normalized)
|
|
|
|
def test_build_script_blocks_user_data_in_release_output(self):
|
|
script = self.read_text("scripts/build_exe.ps1")
|
|
|
|
for token in (
|
|
"config.json",
|
|
"config\\ai_models.json",
|
|
"cmshopee.db",
|
|
"cmshopee.db-wal",
|
|
"cmshopee.db-shm",
|
|
"db.sqlite",
|
|
"chrome_user_data_dir",
|
|
"images",
|
|
"logs",
|
|
"prompts",
|
|
"title_prompt.txt",
|
|
"data",
|
|
):
|
|
self.assertIn(token, script)
|
|
|
|
self.assertIn('$pythonExe = "py"', script)
|
|
self.assertIn('$pythonArgs = @("-3.10")', script)
|
|
self.assertIn("& $pythonExe @pythonArgs -m PyInstaller --noconfirm --clean", script)
|
|
self.assertIn("cmshopee.spec", script)
|
|
self.assertIn("Decode-Utf8Base64", script)
|
|
|
|
def test_release_version_source_is_single_place(self):
|
|
version_py = self.read_text("app/version.py")
|
|
|
|
self.assertIn('APP_NAME = "蝦皮圈優化助手"', version_py)
|
|
self.assertIn('APP_CODE_NAME = "cmshopee"', version_py)
|
|
self.assertRegex(version.APP_VERSION, r"^\d+\.\d+\.\d+$")
|
|
self.assertEqual(
|
|
f"{version.APP_NAME} v{version.APP_VERSION}",
|
|
version.display_name(),
|
|
)
|
|
|
|
def test_build_script_assembles_versioned_release_package(self):
|
|
script = self.read_text("scripts/build_exe.ps1")
|
|
|
|
self.assertIn('Read-VersionValue -Path $versionFile -Name "APP_VERSION"', script)
|
|
self.assertIn('$pythonExe = "py"', script)
|
|
self.assertIn('$pythonArgs = @("-3.10")', script)
|
|
self.assertIn(
|
|
'Decode-Utf8Base64 "5pyq5om+5YiwIFB5dGhvbiAzLjEw44CC6K+35YWI5a6J6KOFIFB5dGhvbiAzLjEw77yM5bm256Gu6K6kIFdpbmRvd3MgUHl0aG9uIExhdW5jaGVyIOWPr+mAmui/hyBweSAtMy4xMCDosIPnlKjjgII="',
|
|
script,
|
|
)
|
|
self.assertIn('$pythonVersion.StartsWith("3.10.")', script)
|
|
self.assertNotIn('python -m PyInstaller --noconfirm --clean', script)
|
|
self.assertNotIn("3.7.9", script)
|
|
self.assertIn('$releaseRoot = Join-Path $repoRoot "release"', script)
|
|
self.assertIn('$releaseName = "$appName$appVersion"', script)
|
|
self.assertIn('$portableZip = Join-Path $releaseRoot "$releaseName.zip"', script)
|
|
self.assertNotIn('$releaseName = "$appCodeName-$appVersion"', script)
|
|
self.assertNotIn('$portableZip = Join-Path $releaseRoot "$releaseName-portable.zip"', script)
|
|
self.assertIn('version.txt', script)
|
|
self.assertIn('README.txt', script)
|
|
self.assertIn('Compress-Archive', script)
|
|
self.assertIn('Compress-ArchiveWithRetry', script)
|
|
self.assertIn('Start-Sleep -Seconds $DelaySeconds', script)
|
|
self.assertIn('Assert-NoExcludedData -Root $releaseDir', script)
|
|
self.assertIn('-m app.release_manifest manifest', script)
|
|
self.assertIn('-m app.release_manifest metadata', script)
|
|
self.assertIn(
|
|
'$releaseMetadata = Join-Path $releaseRoot "release-metadata${appVersion}.json"',
|
|
script,
|
|
)
|
|
self.assertIn(
|
|
'$legacyReleaseMetadata = Join-Path $releaseRoot "release-metadata.json"',
|
|
script,
|
|
)
|
|
self.assertIn(
|
|
'@($releaseDir, $portableZip, $releaseMetadata, $legacyReleaseMetadata)',
|
|
script,
|
|
)
|
|
self.assertNotIn(
|
|
'$releaseMetadata = Join-Path $releaseRoot "release-metadata.json"',
|
|
script,
|
|
)
|
|
self.assertNotIn('release-metadata*.json', script)
|
|
self.assertIn('cmshopee-updater.spec', script)
|
|
self.assertIn('cmshopee-updater.exe', script)
|
|
|
|
def test_updater_spec_builds_a_windowed_standalone_executable(self):
|
|
spec = self.read_text("cmshopee-updater.spec")
|
|
normalized = "".join(spec.split())
|
|
|
|
self.assertIn('app/updater_entry.py', spec)
|
|
self.assertIn('name="cmshopee-updater"', normalized)
|
|
self.assertIn('console=False', normalized)
|
|
self.assertNotIn("COLLECT(", spec)
|
|
|
|
def make_release_dir(self, root, app_version="1.2.3"):
|
|
release_dir = Path(root) / "release"
|
|
(release_dir / "_internal").mkdir(parents=True)
|
|
(release_dir / "cmshopee.exe").write_bytes(b"exe")
|
|
(release_dir / "_internal" / "runtime.dll").write_bytes(b"runtime")
|
|
(release_dir / "version.txt").write_text(app_version, encoding="ascii")
|
|
(release_dir / "README.txt").write_text("说明", encoding="utf-8")
|
|
return release_dir
|
|
|
|
def test_release_manifest_covers_program_files_with_hashes(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
release_dir = self.make_release_dir(temp_dir)
|
|
output = release_manifest.write_package_manifest(release_dir, "1.2.3")
|
|
manifest = json.loads(output.read_text(encoding="utf-8"))
|
|
|
|
self.assertEqual("cmshopee-portable-v1", manifest["package_format"])
|
|
self.assertEqual("1.2.3", manifest["app_version"])
|
|
self.assertEqual("cmshopee.exe", manifest["entry_point"])
|
|
paths = [item["path"] for item in manifest["files"]]
|
|
self.assertEqual(len(paths), len(set(path.casefold() for path in paths)))
|
|
self.assertNotIn("package-manifest.json", paths)
|
|
self.assertIn("_internal/runtime.dll", paths)
|
|
exe = next(item for item in manifest["files"] if item["path"] == "cmshopee.exe")
|
|
self.assertEqual(3, exe["size_bytes"])
|
|
self.assertEqual(hashlib.sha256(b"exe").hexdigest(), exe["sha256"])
|
|
|
|
def test_release_manifest_rejects_user_data_and_version_mismatch(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
release_dir = self.make_release_dir(temp_dir)
|
|
(release_dir / "data").mkdir()
|
|
with self.assertRaises(release_manifest.ReleaseManifestError):
|
|
release_manifest.build_package_manifest(release_dir, "1.2.3")
|
|
|
|
(release_dir / "data").rmdir()
|
|
with self.assertRaises(release_manifest.ReleaseManifestError):
|
|
release_manifest.build_package_manifest(release_dir, "9.9.9")
|
|
|
|
def test_release_metadata_matches_zip_size_and_hash(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
zip_path = Path(temp_dir) / "蝦皮圈優化助手1.2.3.zip"
|
|
zip_path.write_bytes(b"portable-zip")
|
|
metadata = release_manifest.build_release_metadata(zip_path, "1.2.3")
|
|
release = metadata["release"]
|
|
|
|
self.assertEqual(zip_path.stat().st_size, release["size_bytes"])
|
|
self.assertEqual(
|
|
hashlib.sha256(b"portable-zip").hexdigest(), release["sha256"]
|
|
)
|
|
self.assertEqual("cmshopee-portable-v1", release["package_format"])
|
|
|
|
def test_release_metadata_can_be_written_to_versioned_filename(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
zip_path = Path(temp_dir) / "蝦皮圈優化助手1.2.3.zip"
|
|
zip_path.write_bytes(b"portable-zip")
|
|
output = Path(temp_dir) / "release-metadata1.2.3.json"
|
|
|
|
written = release_manifest.write_release_metadata(zip_path, "1.2.3", output)
|
|
metadata = json.loads(written.read_text(encoding="utf-8"))
|
|
|
|
self.assertEqual(output, written)
|
|
self.assertEqual("release-metadata1.2.3.json", written.name)
|
|
self.assertEqual("1.2.3", metadata["release"]["version"])
|
|
self.assertEqual(zip_path.name, metadata["release"]["download_filename"])
|
|
self.assertEqual(zip_path.stat().st_size, metadata["release"]["size_bytes"])
|
|
self.assertEqual(
|
|
hashlib.sha256(b"portable-zip").hexdigest(),
|
|
metadata["release"]["sha256"],
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|