118 lines
4.7 KiB
Python
118 lines
4.7 KiB
Python
"""App Installer checker output contracts and native WinRT dependency coverage.
|
|
|
|
Stubbed subprocess checks cover OS outcomes without a packaged process.
|
|
The native Windows test checks the URI and async projection types.
|
|
|
|
Root causes pinned here (audit C08):
|
|
- ``Package.current`` is a PROPERTY, not a callable.
|
|
- ``PackageManager.check_package_update_availability_async`` does not exist;
|
|
the method lives on the Package instance.
|
|
- A not-packaged process is identifiable by the package-identity HRESULT
|
|
(0x80073D54); ANY other failure must surface as unknown, never as
|
|
"no update".
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
HERMES_PYTHON = sys.executable
|
|
SCRIPT = (
|
|
Path(__file__).resolve().parents[2]
|
|
/ "apps/desktop/scripts/check-appinstaller-update.py"
|
|
)
|
|
|
|
@pytest.mark.parametrize("state,code,available", [
|
|
("not-packaged", 0, False), ("AVAILABLE", 2, True), ("REQUIRED", 2, True),
|
|
("NO_UPDATES", 0, False), ("UNKNOWN", 1, None), ("ERROR", 1, None),
|
|
("no-source", 1, None), ("check-error", 1, None), ("identity-error", 1, None),
|
|
("missing-projection", 1, None), ("missing-current", 1, None),
|
|
])
|
|
def test_projection_outcomes(monkeypatch, capsys, state, code, available):
|
|
import enum
|
|
import runpy
|
|
from types import SimpleNamespace
|
|
|
|
main = runpy.run_path(str(SCRIPT))["main"]
|
|
availability = enum.IntEnum("Availability", "UNKNOWN NO_UPDATES AVAILABLE REQUIRED ERROR", start=0)
|
|
class PackageInstance:
|
|
def get_app_installer_info(self):
|
|
return None if state == "no-source" else SimpleNamespace(
|
|
uri=SimpleNamespace(absolute_uri="https://registered.example/updates.appinstaller"))
|
|
|
|
def check_update_availability_async(self):
|
|
def get():
|
|
if state == "check-error":
|
|
raise RuntimeError("rpc boom")
|
|
return SimpleNamespace(availability=availability[state])
|
|
return SimpleNamespace(get=get)
|
|
|
|
class PackageMeta(type):
|
|
@property
|
|
def current(cls):
|
|
if state in ("not-packaged", "identity-error"):
|
|
error = OSError("identity failure")
|
|
error.winerror = -2147009196 if state == "not-packaged" else -2147024891
|
|
raise error
|
|
return PackageInstance()
|
|
|
|
class Package(metaclass=PackageMeta):
|
|
pass
|
|
|
|
def load():
|
|
if state == "missing-projection":
|
|
raise ImportError("missing winrt")
|
|
return (object if state == "missing-current" else Package), availability
|
|
|
|
monkeypatch.setitem(main.__globals__, "_load_projection", load)
|
|
assert main() == code
|
|
payload = json.loads(capsys.readouterr().out)
|
|
assert payload["available"] is available
|
|
if state == "not-packaged":
|
|
assert payload == {"available": False, "reason": "not-packaged"}
|
|
elif state in ("AVAILABLE", "REQUIRED", "NO_UPDATES"):
|
|
assert payload["availability"] == state
|
|
assert payload["source_uri"] == "https://registered.example/updates.appinstaller"
|
|
else:
|
|
assert payload["error"]
|
|
if state == "no-source":
|
|
assert payload["reason"] == "no-app-installer-source"
|
|
assert ".appinstaller" in payload["error"]
|
|
|
|
|
|
def test_script_import_failure_json_and_exit(tmp_path):
|
|
# A real subprocess executes __main__ and imports the absent projection.
|
|
(tmp_path / "winrt.py").write_text("raise ImportError('fixture unavailable')", encoding="utf-8")
|
|
child = subprocess.run([HERMES_PYTHON, str(SCRIPT)], capture_output=True,
|
|
text=True, timeout=30, env={**os.environ, "PYTHONPATH": str(tmp_path)})
|
|
assert child.returncode == 1, child.stderr
|
|
payload = json.loads(child.stdout)
|
|
assert payload["available"] is None
|
|
assert "fixture unavailable" in payload["error"]
|
|
|
|
|
|
@pytest.mark.platforms("windows")
|
|
def test_installed_winrt_projects_checker_uri_and_async_types(tmp_path):
|
|
# A dev process has no package identity, so exercise the types that the
|
|
# packaged update call projects only after Package.current succeeds.
|
|
probe = subprocess.run(
|
|
[HERMES_PYTHON, "-I", "-c",
|
|
"import runpy; "
|
|
"from winrt.windows.foundation import IAsyncOperation, Uri; "
|
|
"uri = Uri('https://example.invalid/updates.appinstaller'); "
|
|
"assert uri.absolute_uri == 'https://example.invalid/updates.appinstaller'; "
|
|
"assert callable(IAsyncOperation.get); "
|
|
f"runpy.run_path({str(SCRIPT)!r})['_load_projection'](); "
|
|
"print('WINRT_CHECKER_TYPES_OK')"],
|
|
cwd=tmp_path, capture_output=True, text=True, timeout=30,
|
|
)
|
|
assert probe.returncode == 0, probe.stderr
|
|
assert probe.stdout.strip() == "WINRT_CHECKER_TYPES_OK"
|