Files
kernalix7--winpodx/tests/test_reverse_open_discovery.py
wehub-resource-sync 3e779be6f3
CI / lint (push) Failing after 13m4s
CI / test (3.11, ubuntu-latest) (push) Failing after 2m4s
CI / test (3.13, ubuntu-latest) (push) Successful in 13m30s
CI / test (3.14, ubuntu-latest) (push) Successful in 17m21s
CI / test (3.12, ubuntu-latest) (push) Successful in 17m55s
CI / discover-apps-ps (push) Successful in 1m56s
CI / test (3.9, ubuntu-latest) (push) Successful in 13m17s
CI / test (3.10, ubuntu-latest) (push) Successful in 26m21s
CI / audit (push) Successful in 13m38s
Deploy site / deploy (push) Has been cancelled
CI / test (3.14, ubuntu-24.04-arm) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:37 +08:00

327 lines
11 KiB
Python

# SPDX-License-Identifier: MIT
"""Tests for ``winpodx.reverse_open.discovery``.
The XDG_* env vars are isolated per-test by ``conftest._isolate_xdg_and_home``,
so each test that writes ``.desktop`` files into ``XDG_DATA_HOME/applications``
or ``XDG_DATA_DIRS/<dir>/applications`` gets a clean tree.
"""
from __future__ import annotations
import os
from pathlib import Path
import pytest
from winpodx.reverse_open.discovery import (
LinuxApp,
_exec_is_safe,
_is_displayed,
_strip_field_codes,
discover_apps,
slug_for_desktop,
)
@pytest.fixture(autouse=True)
def _isolate_xdg_data_dirs(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
"""Override XDG_DATA_DIRS so discover_apps doesn't see /usr/share.
The conftest's autouse fixture only redirects XDG_DATA_HOME plus
the other ``XDG_*_HOME`` vars; the system path list
``XDG_DATA_DIRS`` (which defaults to ``/usr/local/share:/usr/share``
when unset) still pointed at the real host, so every
discover_apps() call returned the test app PLUS every legit app
installed on the developer's machine. Point both system paths at
an empty dir per-test.
"""
empty = tmp_path / "_empty_xdg_dirs"
empty.mkdir()
monkeypatch.setenv("XDG_DATA_DIRS", str(empty))
# XDG_CURRENT_DESKTOP is read by _current_desktops(); clear it so
# OnlyShowIn / NotShowIn filters don't accidentally fire.
monkeypatch.delenv("XDG_CURRENT_DESKTOP", raising=False)
def _xdg_apps_dir() -> Path:
base = Path(os.environ["XDG_DATA_HOME"]) / "applications"
base.mkdir(parents=True, exist_ok=True)
return base
def _write_desktop(name: str, body: str) -> Path:
path = _xdg_apps_dir() / name
path.write_text(body, encoding="utf-8")
return path
# --- slug derivation ---------------------------------------------------------
def test_slug_for_desktop_simple() -> None:
assert slug_for_desktop(Path("/x/kate.desktop")) == "kate"
def test_slug_for_desktop_dotted_reverse_dns() -> None:
assert slug_for_desktop(Path("/usr/share/applications/org.kde.kate.desktop")) == "org-kde-kate"
def test_slug_for_desktop_uppercase_and_punctuation_stripped() -> None:
assert slug_for_desktop(Path("/x/Some_Weird+name.desktop")) == "someweirdname"
# --- field code stripping ----------------------------------------------------
def test_strip_field_codes_keeps_f_u_and_drops_others() -> None:
argv = ["kate", "%c", "%f", "%k", "%i", "%U", "--flag"]
assert _strip_field_codes(argv) == ["kate", "%f", "%U", "--flag"]
def test_strip_field_codes_preserves_args_starting_with_percent_substring() -> None:
# Only WHOLE-token field codes are stripped. A token like "%foo"
# isn't a field code per the spec, so it survives. (Discovery
# never substitutes anything but the four spec codes, so this is
# the right semantic.)
argv = ["weird-app", "%foo", "%f"]
assert _strip_field_codes(argv) == ["weird-app", "%foo", "%f"]
# --- Hidden / NoDisplay / OnlyShowIn / NotShowIn -----------------------------
def test_is_displayed_hidden_excludes() -> None:
assert _is_displayed({"Hidden": "true"}, frozenset()) is False
def test_is_displayed_nodisplay_excludes() -> None:
assert _is_displayed({"NoDisplay": "true"}, frozenset()) is False
def test_is_displayed_only_show_in_matches() -> None:
entry = {"OnlyShowIn": "KDE;GNOME"}
assert _is_displayed(entry, frozenset({"KDE"})) is True
assert _is_displayed(entry, frozenset({"XFCE"})) is False
def test_is_displayed_not_show_in_excludes() -> None:
entry = {"NotShowIn": "KDE"}
assert _is_displayed(entry, frozenset({"KDE"})) is False
assert _is_displayed(entry, frozenset({"GNOME"})) is True
def test_is_displayed_no_filter_when_desktops_empty() -> None:
# XDG_CURRENT_DESKTOP unset: treat all OnlyShowIn / NotShowIn as
# no-ops (match gnome-shell's behaviour).
entry = {"OnlyShowIn": "KDE", "NotShowIn": "GNOME"}
assert _is_displayed(entry, frozenset()) is True
# --- Exec sanitisation -------------------------------------------------------
def test_exec_is_safe_rejects_shell_metacharacters() -> None:
assert _exec_is_safe("kate %f && rm -rf /", ["kate", "%f", "&&", "rm"]) is False
assert _exec_is_safe("kate $(whoami) %f", ["kate", "$(whoami)", "%f"]) is False
assert _exec_is_safe("kate %f | tee log", ["kate", "%f", "|", "tee"]) is False
def test_exec_is_safe_rejects_wrapper_prefixes() -> None:
assert _exec_is_safe("wine notepad.exe", ["wine", "notepad.exe"]) is False
assert _exec_is_safe("winapps run kate", ["winapps", "run", "kate"]) is False
assert _exec_is_safe("winpodx app run kate", ["winpodx", "app", "run"]) is False
def test_exec_is_safe_accepts_clean_invocation() -> None:
assert _exec_is_safe("/usr/bin/kate %f", ["/usr/bin/kate", "%f"]) is True
# --- discover_apps integration ------------------------------------------------
def _kate_entry(extra: str = "") -> str:
return (
"[Desktop Entry]\n"
"Type=Application\n"
"Name=Kate\n"
"Comment=Advanced text editor\n"
"Exec=/usr/bin/kate %F\n"
"Icon=kate\n"
"MimeType=text/plain;text/xml;\n"
f"{extra}"
)
def test_discover_apps_returns_basic_app() -> None:
_write_desktop("org.kde.kate.desktop", _kate_entry())
apps = discover_apps()
assert len(apps) == 1
app = apps[0]
assert isinstance(app, LinuxApp)
assert app.slug == "org-kde-kate"
assert app.name == "Kate"
assert app.exec_argv == ["/usr/bin/kate", "%F"]
assert app.icon_name == "kate"
assert app.mime_types == ["text/plain", "text/xml"]
def test_discover_apps_skips_entries_without_mime() -> None:
_write_desktop(
"settings.desktop",
"[Desktop Entry]\nType=Application\nName=Settings\nExec=settings\n",
)
assert discover_apps() == []
def test_discover_apps_drops_records_reason() -> None:
# #594: the `drops` out-param records every scanned .desktop that wasn't
# returned, with a human reason — the data behind `host-open refresh -v`.
p = _write_desktop(
"settings.desktop",
"[Desktop Entry]\nType=Application\nName=Settings\nExec=settings\n",
)
drops: list = []
apps = discover_apps(drops=drops)
assert apps == []
assert (p, "no MimeType= (handles no file type / URL scheme)") in drops
def test_discover_apps_drops_empty_when_app_kept() -> None:
_write_desktop("org.kde.kate.desktop", _kate_entry())
drops: list = []
apps = discover_apps(drops=drops)
assert len(apps) == 1
assert drops == []
def test_discover_apps_skips_nodisplay_by_default() -> None:
_write_desktop("kate.desktop", _kate_entry("NoDisplay=true\n"))
assert discover_apps() == []
def test_discover_apps_include_nodisplay_opts_in() -> None:
_write_desktop("kate.desktop", _kate_entry("NoDisplay=true\n"))
apps = discover_apps(include_nodisplay=True)
assert [a.slug for a in apps] == ["kate"]
def test_discover_apps_skips_hidden_even_with_include_nodisplay() -> None:
_write_desktop("kate.desktop", _kate_entry("Hidden=true\n"))
assert discover_apps(include_nodisplay=True) == []
def test_discover_apps_rejects_shell_exec() -> None:
_write_desktop(
"bad.desktop",
"[Desktop Entry]\nType=Application\nName=Bad\n"
"Exec=rm -rf %f && echo gotcha\n"
"MimeType=text/plain;\n",
)
assert discover_apps() == []
def test_discover_apps_excludes_wine_wrapper() -> None:
_write_desktop(
"wine-notepad.desktop",
"[Desktop Entry]\nType=Application\nName=Notepad\n"
"Exec=wine notepad.exe %f\n"
"MimeType=text/plain;\n",
)
assert discover_apps() == []
def test_discover_apps_excludes_winpodx_self_generated_by_name_prefix() -> None:
_write_desktop(
"winpodx-windows-notepad.desktop",
"[Desktop Entry]\nType=Application\nName=Windows: Notepad\n"
"Exec=/usr/bin/winpodx-run app run notepad\n"
"MimeType=text/plain;\n",
)
assert discover_apps() == []
def test_discover_apps_basename_shadowing(monkeypatch: pytest.MonkeyPatch) -> None:
# XDG_DATA_HOME/applications wins over XDG_DATA_DIRS entries by
# basename — matches xdg-open semantics.
_write_desktop("kate.desktop", _kate_entry("Name=Kate-User\n"))
system_dir = Path(os.environ["XDG_DATA_HOME"]).parent / "system" / "applications"
system_dir.mkdir(parents=True)
(system_dir / "kate.desktop").write_text(_kate_entry("Name=Kate-System\n"), encoding="utf-8")
monkeypatch.setenv("XDG_DATA_DIRS", str(system_dir.parent))
apps = discover_apps()
assert len(apps) == 1
assert apps[0].name == "Kate-User"
def test_discover_apps_default_handlers_marked(monkeypatch: pytest.MonkeyPatch) -> None:
_write_desktop("org.kde.kate.desktop", _kate_entry())
config_home = Path(os.environ["XDG_CONFIG_HOME"])
(config_home / "mimeapps.list").write_text(
"[Default Applications]\ntext/plain=org.kde.kate.desktop;\n",
encoding="utf-8",
)
apps = discover_apps()
assert apps[0].is_default_for == ["text/plain"]
def test_discover_apps_picks_up_system_level_kde_mimeapps_list(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""Defaults set in a system-wide ``<desktop>-mimeapps.list``
(e.g. ``/usr/share/applications/kde-mimeapps.list``) must be
honoured. This is where distro packaging puts per-DE defaults —
KDE Plasma installs typically ship a kde-mimeapps.list with
``text/plain=org.kde.kate.desktop`` that the user inherits without
any per-user mimeapps.list.
Regression guard for the v0.4.6 smoke finding where only 6 of 95
discovered apps had ``is_default_for`` populated because the
candidate walk stopped at $XDG_CONFIG_HOME / $XDG_DATA_HOME.
"""
_write_desktop("org.kde.kate.desktop", _kate_entry())
# Stand up a fake XDG_DATA_DIRS entry containing a system-wide
# kde-mimeapps.list with the default. We DO NOT write any
# user-level mimeapps.list — that's the point: the default lives
# purely in the system file and discovery must still find it.
sys_share = tmp_path / "share"
sys_apps = sys_share / "applications"
sys_apps.mkdir(parents=True)
(sys_apps / "kde-mimeapps.list").write_text(
"[Default Applications]\ntext/plain=org.kde.kate.desktop\n",
encoding="utf-8",
)
monkeypatch.setenv("XDG_DATA_DIRS", str(sys_share))
monkeypatch.setenv("XDG_CURRENT_DESKTOP", "KDE")
apps = discover_apps()
kate = next((a for a in apps if a.slug == "org-kde-kate"), None)
assert kate is not None
assert kate.is_default_for == ["text/plain"]
def test_discover_apps_extra_dirs_after_xdg() -> None:
_write_desktop("kate.desktop", _kate_entry())
extra = Path(os.environ["XDG_DATA_HOME"]).parent / "extra" / "apps"
extra.mkdir(parents=True)
(extra / "gimp.desktop").write_text(
"[Desktop Entry]\nType=Application\nName=GIMP\n"
"Exec=/usr/bin/gimp %F\nIcon=gimp\nMimeType=image/png;\n",
encoding="utf-8",
)
apps = discover_apps(extra_dirs=[extra])
slugs = sorted(a.slug for a in apps)
assert slugs == ["gimp", "kate"]
def test_discover_apps_rejects_unbalanced_quotes_in_exec() -> None:
_write_desktop(
"broken.desktop",
"[Desktop Entry]\nType=Application\nName=Broken\n"
'Exec=editor "unbalanced %f\n'
"MimeType=text/plain;\n",
)
assert discover_apps() == []