Files
hermes-agent/tests/desktop/test_appinstaller_check_script.py

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"