diff --git a/plugins/decky-vault/main.py b/plugins/decky-vault/main.py index 4178f88..75112ea 100644 --- a/plugins/decky-vault/main.py +++ b/plugins/decky-vault/main.py @@ -308,7 +308,7 @@ exec mangohud "$@" import time candidates = [] now = time.time() - for pattern in ["/tmp/*MangoHud*", "/tmp/*.csv", "/tmp/*.log"]: + for pattern in ["/tmp/*MangoHud*"]: for f in glob.glob(pattern): if os.path.isdir(f): continue diff --git a/plugins/decky-vault/src/index.tsx b/plugins/decky-vault/src/index.tsx index cba24af..1b3613a 100644 --- a/plugins/decky-vault/src/index.tsx +++ b/plugins/decky-vault/src/index.tsx @@ -70,6 +70,7 @@ function Content() { const logResult = await readAndParseMangohudLog(logPath.path ?? undefined) if (logResult.error) { setError(logResult.error) + setLastLogPath(null) return } setLastLogPath(logPath.path ?? null) diff --git a/plugins/decky-vault/tests/test_clear_log.py b/plugins/decky-vault/tests/test_clear_log.py index 30ea80a..e703178 100644 --- a/plugins/decky-vault/tests/test_clear_log.py +++ b/plugins/decky-vault/tests/test_clear_log.py @@ -1,9 +1,18 @@ """Tests for safe MangoHud log clearing (Bug 1 fix).""" import os +import shutil +import sys import tempfile import time +import types import pytest +# Note: the helpers above (_safe_clear_mangohud_logs, _validate_log_path) are +# intentional mirrors of Plugin.clear_mangohud_log / delete_log_file so tests can +# run without the decky module. The test below (test_delete_log_file_real_plugin) +# exercises the real Plugin.delete_log_file to guard against the mirrors drifting +# from the implementation. + def _touch(path, mtime_age=10): """Create a file at path, optionally backdated mtime by mtime_age seconds.""" @@ -84,4 +93,64 @@ def test_delete_log_file_rejects_outside_tmp(): def test_delete_log_file_rejects_non_mangohud(): with tempfile.TemporaryDirectory() as tmp: assert _validate_log_path(os.path.join(tmp, "system.log"), tmp) is False - assert _validate_log_path(os.path.join(tmp, "MangoHud-1.csv"), tmp) is True \ No newline at end of file + assert _validate_log_path(os.path.join(tmp, "MangoHud-1.csv"), tmp) is True + + +@pytest.mark.asyncio +async def test_delete_log_file_real_plugin(): + """Exercise the real Plugin.delete_log_file path validation. + + Unlike the mirror tests above, this imports main.Plugin and calls the + actual method. A real /tmp subdirectory is used because delete_log_file + hardcodes the "/tmp/" prefix check, and tempfile.TemporaryDirectory() on + macOS resolves under /var/folders (not /tmp). + """ + # Mock the decky module so main.py imports cleanly even when the real + # decky package (only present on the Deck) is unavailable. + sys.modules.pop("main", None) + if "decky" not in sys.modules: + mock_decky = types.ModuleType("decky") + mock_logger = types.ModuleType("decky.logger") + mock_logger.info = lambda *a, **kw: None + mock_logger.error = lambda *a, **kw: None + mock_decky.logger = mock_logger + mock_decky.DECKY_PLUGIN_NAME = "test" + mock_decky.DECKY_PLUGIN_SETTINGS_DIR = "/tmp/decky-test" + sys.modules["decky"] = mock_decky + + from main import Plugin + plugin = Plugin() + + tmp = "/tmp/deckyvault_test_real_plugin_%d" % os.getpid() + shutil.rmtree(tmp, ignore_errors=True) + os.makedirs(tmp) + try: + # Outside /tmp → rejected with the "outside /tmp" reason. + r = await plugin.delete_log_file("/etc/passwd") + assert r["success"] is False + assert "outside /tmp" in r["error"] + + # Under /tmp but basename has no "MangoHud" → rejected as non-MangoHud. + r = await plugin.delete_log_file(os.path.join(tmp, "system.log")) + assert r["success"] is False + assert "non-MangoHud" in r["error"] + + # Valid MangoHud log under /tmp → deleted. + mangohud_path = os.path.join(tmp, "MangoHud-test.csv") + _touch(mangohud_path, mtime_age=10) + r = await plugin.delete_log_file(mangohud_path) + assert r["success"] is True + assert r["deleted"] is True + assert not os.path.exists(mangohud_path) + + # Same path again → already gone, still success but deleted is False. + r = await plugin.delete_log_file(mangohud_path) + assert r["success"] is True + assert r["deleted"] is False + + # Empty path → rejected. + r = await plugin.delete_log_file("") + assert r["success"] is False + finally: + shutil.rmtree(tmp, ignore_errors=True) + sys.modules.pop("main", None) \ No newline at end of file