diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index acee764..c479aa9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -157,13 +157,9 @@ jobs: echo "=== Release assets ===" ls -la release-assets/ test -f "release-assets/PDFReader-by-Sparsh-Windows.zip" || { echo "Missing Windows ZIP"; exit 1; } + test -f "release-assets/PDFReader-by-Sparsh-Setup.exe" || { echo "Missing Windows Setup.exe"; exit 1; } test -f "release-assets/PDFReader-by-Sparsh-macOS-Apple-Silicon.zip" || { echo "Missing macOS Apple Silicon ZIP"; exit 1; } test -f "release-assets/PDFReader-by-Sparsh-macOS-Intel.zip" || { echo "Missing macOS Intel ZIP"; exit 1; } - if test -f "release-assets/PDFReader-by-Sparsh-Setup.exe"; then - echo "Windows Setup.exe: present" - else - echo "Windows Setup.exe: not built (Inno Setup may be unavailable)" - fi echo "All required assets verified" - name: Generate release notes @@ -172,14 +168,14 @@ jobs: Release ${GITHUB_REF_NAME}. ### Assets for Windows - - **\`PDFReader-by-Sparsh-Setup.exe\`** — ✅ **Recommended for normal use.** Inno Setup installer with PDF file association, Start Menu shortcut, desktop shortcut, and Add/Remove Programs entry. Requires admin rights. - - \`PDFReader-by-Sparsh-Windows.zip\` — Updater package only. Do not download unless you are upgrading from v1.0.0/v1.0.1 (see SUPPORT.md recovery guide) or need a portable copy. + - **\`PDFReader-by-Sparsh-Setup.exe\`** — ✅ **Recommended for normal use and in-app updates.** Inno Setup installer with PDF file association, Start Menu shortcut, desktop shortcut, and Add/Remove Programs entry. Requires admin rights. + - \`PDFReader-by-Sparsh-Windows.zip\` — Portable/manual recovery package. Use only if you need a portable copy or are following SUPPORT.md recovery steps. ### Assets for macOS - \`PDFReader-by-Sparsh-macOS-Apple-Silicon.zip\` — macOS Apple Silicon app bundle. - \`PDFReader-by-Sparsh-macOS-Intel.zip\` — macOS Intel app bundle. - Packaged builds include the tag-injected app version and canonical updater asset names. + Packaged builds include the tag-injected app version and canonical release asset names. NOTE_EOF - name: Publish release diff --git a/CHANGELOG.md b/CHANGELOG.md index c03d202..0a75617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.1.10 — Installer-Based Windows Updater — 2026-06-17 + +- **Version:** Bumped `__version__` to `1.1.10-dev`. +- **Windows updater uses Setup.exe** — in-app updates now select `PDFReader-by-Sparsh-Setup.exe` on Windows instead of the ZIP package. +- **Program Files update fix** — Windows updates are applied by Inno Setup so UAC elevation, app closing, file replacement, shortcuts, and file associations are handled by the installer rather than a hand-written `xcopy` batch script. +- **Portable ZIP preserved** — `PDFReader-by-Sparsh-Windows.zip` remains available for portable/manual recovery use, but it is no longer the normal Windows in-app update path. +- **Release workflow hardening** — `PDFReader-by-Sparsh-Setup.exe` is now a required release asset. The release fails if the installer is missing. +- **Updater diagnostics** — installer launch and exit/failure details are written to `%TEMP%\PDFReader-Updates\updater-debug.log`. +- **Regression coverage** — updater asset selection, update method routing, release asset consistency, and the standalone asset-flow script now expect the installer-first Windows path. + ## v1.1.1 — Stability and UX Hardening — 2026-06-16 - **Version:** Bumped `__version__` to `1.1.1-dev`. diff --git a/README.md b/README.md index bfa10f6..815d92d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ PDFReader by Sparsh is a **stable, local-first desktop PDF utility** built with The app is intentionally local-first: PDFs are opened, rendered, searched, merged, split, annotated, and compressed on your computer — no uploads, no accounts, no telemetry. -**v1.1.1** is the current stable release for Windows. macOS builds are published for source-build testing but are not stable — the primary target is Windows. See the [changelog](CHANGELOG.md) and [roadmap](ROADMAP.md) for what's new and what's next. +**v1.1.10** is the current stable release for Windows. macOS builds are published for source-build testing but are not stable — the primary target is Windows. See the [changelog](CHANGELOG.md) and [roadmap](ROADMAP.md) for what's new and what's next. ## Download @@ -51,7 +51,7 @@ Get the latest builds from the [Releases page](https://github.com/sparshsam/pdfr | Platform | Recommended Download | Alternative | Notes | |---|---|---|---|---| -| Windows | `PDFReader-by-Sparsh-Setup.exe` | `PDFReader-by-Sparsh-Windows.zip` | **Stable and tested.** Use Setup.exe for normal installation (requires admin — see [Windows installer notes](SUPPORT.md#windows-installer)). ZIP remains for updater/portable/manual use. | +| Windows | `PDFReader-by-Sparsh-Setup.exe` | `PDFReader-by-Sparsh-Windows.zip` | **Stable and tested.** Use Setup.exe for normal installation and in-app updates (requires admin — see [Windows installer notes](SUPPORT.md#windows-installer)). ZIP remains for portable/manual recovery use. | | macOS | — | — | **Not currently stable.** macOS builds are published for source-build testing only. The app may exhibit UI issues, missing features, or crashes. Run from source for the best macOS experience (see [Build From Source](#build-from-source)). | Windows may show a SmartScreen warning because community builds are not code-signed. macOS may show a Gatekeeper warning because the Mac builds are not Apple-notarized. Only run software from sources you trust. @@ -80,7 +80,7 @@ Packaged builds check the latest GitHub Release for updates. Source builds are i | Desktop integration | Windows installer with `.pdf` file association, Start Menu, and desktop shortcut | | Dark mode | System-aware dark theme (Catppuccin Mocha) with Auto/Light/Dark toggle via View → Theme | | Recent files | Quick access to the last 10 opened PDFs via File → Open Recent | -| Auto-update | Packaged builds check GitHub Releases and update from canonical release ZIP assets | +| Auto-update | Packaged builds check GitHub Releases and Windows installs update through the canonical Setup.exe asset | | Release engineering | Tag-driven GitHub Release publishing, PyInstaller packaging, Windows/macOS GitHub Actions builds, Inno Setup installer, self-update mechanism with diagnostic logging | ## Screenshots @@ -189,11 +189,16 @@ https://api.github.com/repos/sparshsam/pdfreader-by-sparsh/releases/latest It expects these exact asset names on the latest GitHub Release: ```text +PDFReader-by-Sparsh-Setup.exe PDFReader-by-Sparsh-Windows.zip PDFReader-by-Sparsh-macOS-Apple-Silicon.zip PDFReader-by-Sparsh-macOS-Intel.zip ``` +On Windows, in-app updates use `PDFReader-by-Sparsh-Setup.exe` so the installer +handles UAC elevation and replacement under `C:\Program Files`. The ZIP is kept +for portable use and manual recovery. + See [RELEASE.md](RELEASE.md) for release instructions, version injection, updater discovery, and validation. ## Use as Default PDF App @@ -273,7 +278,7 @@ sudo pacman -S tesseract tesseract-data-eng - [x] README features table synced with code - [x] README tech stack expanded -### ✓ v1.1.1 — Stability and UX Hardening (Current Stable — Windows) +### ✓ v1.1.1 — Stability and UX Hardening - [x] Open file — single picker, no cascading fallbacks, re-entrant guard - [x] New Tab — creates blank tab without file dialog @@ -283,6 +288,13 @@ sudo pacman -S tesseract tesseract-data-eng - [x] Windows publisher docs — "Unknown Publisher" explained - [x] 9 new regression tests (28 total, all passing) +### ✓ v1.1.10 — Installer-Based Windows Updater (Current Stable — Windows) + +- [x] Windows in-app updates use `PDFReader-by-Sparsh-Setup.exe` +- [x] Inno Setup handles UAC elevation and Program Files replacement +- [x] Portable ZIP remains available for manual recovery +- [x] Release workflow requires the Windows installer asset + ### Near-Term Items in active or planned development. diff --git a/RELEASE.md b/RELEASE.md index cb214cf..f0627d1 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -20,14 +20,18 @@ https://api.github.com/repos/sparshsam/pdfreader-by-sparsh/releases/latest The release workflow must attach these assets: ```text -PDFReader-by-Sparsh-Windows.zip (updater package — do not download unless instructed) -PDFReader-by-Sparsh-Setup.exe (Windows installer — recommended for normal use) +PDFReader-by-Sparsh-Setup.exe (Windows installer and in-app updater package) +PDFReader-by-Sparsh-Windows.zip (portable/manual recovery package) PDFReader-by-Sparsh-macOS-Apple-Silicon.zip PDFReader-by-Sparsh-macOS-Intel.zip ``` -The updater uses only `PDFReader-by-Sparsh-Windows.zip`. The `-Setup.exe` is the recommended download for normal Windows users. -Do not rename or remove the canonical ZIP assets without updating `main.py`. +The Windows updater uses `PDFReader-by-Sparsh-Setup.exe` so Inno Setup owns +elevation and replacement under `C:\Program Files`. The ZIP remains available +for portable/manual recovery use, but it is not the normal Windows in-app update +path. + +Do not rename or remove the canonical assets without updating `main.py`. ## How to Cut a Release @@ -43,7 +47,7 @@ Do not rename or remove the canonical ZIP assets without updating `main.py`. 5. GitHub Actions runs `.github/workflows/release.yml`. 6. The workflow builds Windows, macOS Apple Silicon, and macOS Intel packages. -7. The workflow creates the GitHub Release and attaches the canonical ZIP assets. +7. The workflow creates the GitHub Release and attaches the canonical release assets. ## Auto-Update Discovery @@ -53,6 +57,8 @@ The app's updater: 2. Reads `tag_name`. 3. Compares `tag_name` against the packaged app's injected `__version__`. 4. Selects the platform asset by exact canonical filename. + - Windows: `PDFReader-by-Sparsh-Setup.exe` + - macOS: the matching architecture ZIP 5. Downloads and applies the package for supported packaged builds. Source builds usually run with a `-dev` version and are not the primary auto-update target. Developers should update source builds with `git pull` and rebuild locally. @@ -63,13 +69,14 @@ After publishing a tag: - [ ] The release workflow completed successfully. - [ ] The GitHub Release exists for the pushed tag. -- [ ] The release contains `PDFReader-by-Sparsh-Windows.zip` (updater). +- [ ] The release contains `PDFReader-by-Sparsh-Setup.exe` (Windows updater). +- [ ] The release contains `PDFReader-by-Sparsh-Windows.zip` (portable/recovery). - [ ] The release contains `PDFReader-by-Sparsh-macOS-Apple-Silicon.zip`. - [ ] The release contains `PDFReader-by-Sparsh-macOS-Intel.zip`. - [ ] Downloaded packaged builds show the tag-injected version in **Help > About**. -- [ ] `releases/latest` returns the new tag and all assets (including Setup.exe). +- [ ] `releases/latest` returns the new tag and all canonical assets. - [ ] An older packaged build detects the newer version. -- [ ] The updater selects the correct asset for Windows. +- [ ] The updater selects `PDFReader-by-Sparsh-Setup.exe` for Windows. - [ ] The updater selects the Apple Silicon asset on arm64 macOS. - [ ] The updater selects the Intel asset on Intel macOS. @@ -108,7 +115,7 @@ Resolution: The updater now binds immutable metadata directly to the `QNetworkReply` with `asset_name` and `latest_tag` properties. The download-finished handler reads -those reply properties, saves Windows updates only as -`PDFReader-by-Sparsh-Windows.zip`, and fails loudly if metadata is missing. -Windows ZIP updates are routed only when the canonical Windows asset name is -present, preventing silent manual-install fallbacks. +those reply properties, saves Windows installer updates as +`PDFReader-by-Sparsh-Setup.exe`, and fails loudly if metadata is missing. +Windows installer updates are routed only when the canonical Windows installer +asset name is present, preventing silent manual-install fallbacks. diff --git a/main.py b/main.py index aeb273b..3ec3b82 100644 --- a/main.py +++ b/main.py @@ -52,9 +52,11 @@ ) -__version__ = "1.1.2-dev" +__version__ = "1.1.10-dev" GITHUB_REPO = "sparshsam/pdfreader-by-sparsh" -WINDOWS_UPDATE_ASSET = "PDFReader-by-Sparsh-Windows.zip" +WINDOWS_INSTALLER_ASSET = "PDFReader-by-Sparsh-Setup.exe" +WINDOWS_PORTABLE_ASSET = "PDFReader-by-Sparsh-Windows.zip" +WINDOWS_UPDATE_ASSET = WINDOWS_INSTALLER_ASSET MACOS_APPLE_SILICON_UPDATE_ASSET = "PDFReader-by-Sparsh-macOS-Apple-Silicon.zip" MACOS_INTEL_UPDATE_ASSET = "PDFReader-by-Sparsh-macOS-Intel.zip" IPC_SERVER_NAME = "PDFReaderBySparsh-IPC" @@ -3089,7 +3091,9 @@ def _show_update_failed_diagnostic(self, log_content: str): @staticmethod def _select_update_apply_method(system, asset_name, dest): suffix = Path(dest).suffix.lower() - if system == "Windows" and asset_name == WINDOWS_UPDATE_ASSET and suffix == ".zip": + if system == "Windows" and asset_name == WINDOWS_INSTALLER_ASSET and suffix == ".exe": + return "windows_installer", "" + if system == "Windows" and asset_name == WINDOWS_PORTABLE_ASSET and suffix == ".zip": return "windows_zip", "" if system == "Darwin" and suffix == ".zip": return "macos_zip", "" @@ -3210,7 +3214,7 @@ def _get_platform_asset(self, assets): assets_by_name = {a.get("name", ""): a for a in assets} system = platform.system() if system == "Windows": - asset = assets_by_name.get(WINDOWS_UPDATE_ASSET) + asset = assets_by_name.get(WINDOWS_INSTALLER_ASSET) if asset: return asset["browser_download_url"], asset["name"] elif system == "Darwin": @@ -3328,9 +3332,11 @@ def _start_download(self, asset_url, asset_name, latest_tag): validation_errors.append("missing asset name") if not latest_tag: validation_errors.append("missing release tag") - if system == "Windows" and asset_name != WINDOWS_UPDATE_ASSET: + if system == "Windows" and asset_name not in (WINDOWS_INSTALLER_ASSET, WINDOWS_PORTABLE_ASSET): validation_errors.append( - f"Windows updater expected {WINDOWS_UPDATE_ASSET}, got {asset_name or ''}" + "Windows updater expected " + f"{WINDOWS_INSTALLER_ASSET} or {WINDOWS_PORTABLE_ASSET}, " + f"got {asset_name or ''}" ) if validation_errors: message = "Cannot start update download:\n\n" + "\n".join(validation_errors) @@ -3468,7 +3474,9 @@ def _apply_update(self, dest: Path, latest_tag: str, asset_name: str): method, diagnostic = self._select_update_apply_method(system, asset_name, dest) self._log_update(f"selected_apply_method={method or 'unsupported'}") - if method == "windows_zip": + if method == "windows_installer": + self._apply_update_windows_installer(dest, latest_tag) + elif method == "windows_zip": self._apply_update_windows_zip(dest, latest_tag) elif method == "macos_zip": self._apply_update_macos(dest, latest_tag) @@ -3477,6 +3485,80 @@ def _apply_update(self, dest: Path, latest_tag: str, asset_name: str): QMessageBox.critical(self, "Update Error", diagnostic) return + @staticmethod + def _powershell_single_quote(value): + return str(value).replace("'", "''") + + def _apply_update_windows_installer(self, dest, tag): + """Update Windows installs through Inno Setup instead of in-place ZIP copy.""" + current_exe = Path(sys.executable) + installer = Path(dest) + log_path = self._updater_log_path() + + ps_command = ( + f"$installer = '{self._powershell_single_quote(installer)}'; " + f"$exe = '{self._powershell_single_quote(current_exe)}'; " + "$args = @('/SP-', '/SILENT', '/SUPPRESSMSGBOXES', " + "'/CLOSEAPPLICATIONS', '/RESTARTAPPLICATIONS', '/NORESTART'); " + f"Add-Content -LiteralPath '{self._powershell_single_quote(log_path)}' " + f"-Value '[installer] starting {tag}'; " + "try { " + "$p = Start-Process -FilePath $installer -ArgumentList $args -Verb RunAs -Wait -PassThru; " + f"Add-Content -LiteralPath '{self._powershell_single_quote(log_path)}' " + "-Value \"[installer] exit_code=$($p.ExitCode)\"; " + "if ((Test-Path -LiteralPath $exe) -and ($p.ExitCode -eq 0)) { " + "Start-Process -FilePath $exe " + "} " + "} catch { " + f"Add-Content -LiteralPath '{self._powershell_single_quote(log_path)}' " + "-Value \"[installer] failed=$($_.Exception.Message)\"; " + "exit 1 " + "}" + ) + + try: + self._log_update(f"success=launching Windows installer updater: {installer}") + subprocess.Popen( # nosec B603, B607 — Windows self-update + [ + "powershell.exe", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + ps_command, + ], + cwd=str(installer.parent), + ) + except Exception as exc: + self._log_update(f"failure=could not launch installer updater: {exc}") + QMessageBox.critical( + self, "Update Error", + "

Update Could Not Start

" + "

PDFReader was unable to launch the installer.

" + "
" + "

What happened:
" + f"{exc}

" + "

What you can do:
" + "Download the latest Setup.exe manually from the GitHub releases page " + "and run it as Administrator.

" + "
" + f"

Installer: {installer}

", + ) + return + + QMessageBox.information( + self, + "Update Starting", + "

Update Download Complete

" + "

PDFReader will now close and run the installer.

" + "

It will reopen automatically after the installer completes.

" + "
" + "

" + "If you see a UAC (User Account Control) prompt, click Yes " + "to allow the update to complete.

", + ) + QTimer.singleShot(500, self.close) + # ------------------------------------------------------------------ def _apply_update_windows_zip(self, dest, tag): """Replace the running app via ZIP extract + batch updater (onedir mode). diff --git a/tests/test_reliability.py b/tests/test_reliability.py index 97ddb8e..33108d8 100644 --- a/tests/test_reliability.py +++ b/tests/test_reliability.py @@ -62,7 +62,9 @@ def test_version_format(self): def test_update_asset_names_are_canonical(self): import main as m - assert m.WINDOWS_UPDATE_ASSET == "PDFReader-by-Sparsh-Windows.zip" + assert m.WINDOWS_INSTALLER_ASSET == "PDFReader-by-Sparsh-Setup.exe" + assert m.WINDOWS_PORTABLE_ASSET == "PDFReader-by-Sparsh-Windows.zip" + assert m.WINDOWS_UPDATE_ASSET == m.WINDOWS_INSTALLER_ASSET assert m.MACOS_APPLE_SILICON_UPDATE_ASSET == "PDFReader-by-Sparsh-macOS-Apple-Silicon.zip" assert m.MACOS_INTEL_UPDATE_ASSET == "PDFReader-by-Sparsh-macOS-Intel.zip" @@ -77,19 +79,23 @@ def test_github_repo_is_correct(self): class TestReleaseAssetConsistency: - def test_setup_exe_not_mistaken_for_updater_asset(self): - """The Setup.exe should not match any canonical update asset name.""" + def test_setup_exe_is_windows_updater_asset(self): + """Windows updates should use the installer, not in-place ZIP overwrite.""" import main as m setup_name = "PDFReader-by-Sparsh-Setup.exe" - windows_asset = m.WINDOWS_UPDATE_ASSET - assert setup_name != windows_asset - # The updater should NOT select Setup.exe - assert m.WINDOWS_UPDATE_ASSET == "PDFReader-by-Sparsh-Windows.zip" + assert m.WINDOWS_INSTALLER_ASSET == setup_name + assert m.WINDOWS_UPDATE_ASSET == setup_name + assert m.WINDOWS_PORTABLE_ASSET == "PDFReader-by-Sparsh-Windows.zip" def test_asset_names_are_distinct(self): import main as m - names = {m.WINDOWS_UPDATE_ASSET, m.MACOS_APPLE_SILICON_UPDATE_ASSET, m.MACOS_INTEL_UPDATE_ASSET} - assert len(names) == 3, "Update asset names must be distinct" + names = { + m.WINDOWS_INSTALLER_ASSET, + m.WINDOWS_PORTABLE_ASSET, + m.MACOS_APPLE_SILICON_UPDATE_ASSET, + m.MACOS_INTEL_UPDATE_ASSET, + } + assert len(names) == 4, "Release asset names must be distinct" # --------------------------------------------------------------------------- @@ -270,4 +276,3 @@ def test_open_pdf_cancelled_message_is_clean(self): assert "no file selected (cancelled)" in src # Verify old cascading fallback messages are removed assert "_pick_file_tkinter()" not in src.split("open_pdf: no file selected")[0].rsplit("def open_pdf")[-1] - diff --git a/tests/test_updater.py b/tests/test_updater.py index 1a7dc4b..911f839 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -212,6 +212,7 @@ def _make_assets(self, filenames): def test_windows_selects_correct_asset(self, updater): assets = self._make_assets([ + "PDFReader-by-Sparsh-Setup.exe", "PDFReader-by-Sparsh-Windows.zip", "PDFReader-by-Sparsh-macOS-Apple-Silicon.zip", "PDFReader-by-Sparsh-macOS-Intel.zip", @@ -221,8 +222,8 @@ def test_windows_selects_correct_asset(self, updater): platform.system = lambda: "Windows" try: url, name = updater._get_platform_asset(assets) - assert url == "https://example.com/PDFReader-by-Sparsh-Windows.zip" - assert name == "PDFReader-by-Sparsh-Windows.zip" + assert url == "https://example.com/PDFReader-by-Sparsh-Setup.exe" + assert name == "PDFReader-by-Sparsh-Setup.exe" finally: platform.system = original @@ -317,6 +318,14 @@ def test_windows_zip_method(self, updater, tmpzip): assert method == "windows_zip" assert diagnostic == "" + def test_windows_installer_method(self, updater, tmp_path): + installer = str(tmp_path / "PDFReader-by-Sparsh-Setup.exe") + method, diagnostic = updater._select_update_apply_method( + "Windows", "PDFReader-by-Sparsh-Setup.exe", installer, + ) + assert method == "windows_installer" + assert diagnostic == "" + def test_macos_zip_method(self, updater, tmpzip): method, diagnostic = updater._select_update_apply_method( "Darwin", "PDFReader-by-Sparsh-macOS-Apple-Silicon.zip", tmpzip, diff --git a/tools/test_updater_asset_flow.py b/tools/test_updater_asset_flow.py index b0ea316..dc71bc4 100644 --- a/tools/test_updater_asset_flow.py +++ b/tools/test_updater_asset_flow.py @@ -12,6 +12,8 @@ from main import ( # noqa: E402 MACOS_APPLE_SILICON_UPDATE_ASSET, MACOS_INTEL_UPDATE_ASSET, + WINDOWS_INSTALLER_ASSET, + WINDOWS_PORTABLE_ASSET, WINDOWS_UPDATE_ASSET, PdfReaderWindow, ) @@ -33,6 +35,10 @@ def canonical_assets(): "name": WINDOWS_UPDATE_ASSET, "browser_download_url": f"https://example.test/{WINDOWS_UPDATE_ASSET}", }, + { + "name": WINDOWS_PORTABLE_ASSET, + "browser_download_url": f"https://example.test/{WINDOWS_PORTABLE_ASSET}", + }, { "name": MACOS_APPLE_SILICON_UPDATE_ASSET, "browser_download_url": f"https://example.test/{MACOS_APPLE_SILICON_UPDATE_ASSET}", @@ -73,8 +79,19 @@ def test_windows_download_filename_and_route(): method, diagnostic = PdfReaderWindow._select_update_apply_method( "Windows", WINDOWS_UPDATE_ASSET, dest ) - assert_equal(method, "windows_zip", "Windows ZIP route") - assert_equal(diagnostic, "", "Windows ZIP diagnostic") + assert_equal(method, "windows_installer", "Windows installer route") + assert_equal(diagnostic, "", "Windows installer diagnostic") + + +def test_windows_portable_zip_route_remains_explicit(): + temp_dir = Path("C:/Temp/PDFReader-Updates") + dest = temp_dir / WINDOWS_PORTABLE_ASSET + + method, diagnostic = PdfReaderWindow._select_update_apply_method( + "Windows", WINDOWS_PORTABLE_ASSET, dest + ) + assert_equal(method, "windows_zip", "Windows portable ZIP route") + assert_equal(diagnostic, "", "Windows portable ZIP diagnostic") def test_missing_metadata_fails_loudly(): @@ -108,7 +125,8 @@ def test_windows_wrong_asset_does_not_route_to_zip_installer(): "html_url": "https://github.com/sparshsam/pdfreader-by-sparsh/releases/tag/" + tag, "body": "Test release notes.", "assets": [ - {"name": "PDFReader-by-Sparsh-Windows.zip", "browser_download_url": "https://example.test/pkg.zip"}, + {"name": WINDOWS_INSTALLER_ASSET, "browser_download_url": "https://example.test/setup.exe"}, + {"name": WINDOWS_PORTABLE_ASSET, "browser_download_url": "https://example.test/pkg.zip"}, {"name": "PDFReader-by-Sparsh-macOS-Apple-Silicon.zip", "browser_download_url": "https://example.test/mac-arm.zip"}, {"name": "PDFReader-by-Sparsh-macOS-Intel.zip", "browser_download_url": "https://example.test/mac-intel.zip"}, ], @@ -196,6 +214,7 @@ def test_classify_unparseable_tag(): if __name__ == "__main__": test_platform_asset_selection() test_windows_download_filename_and_route() + test_windows_portable_zip_route_remains_explicit() test_missing_metadata_fails_loudly() test_windows_wrong_asset_does_not_route_to_zip_installer() @@ -211,4 +230,3 @@ def test_classify_unparseable_tag(): test_classify_missing_tag() test_classify_unparseable_tag() print("All regression checks passed (both asset flow and update check classification).") -