From 5a6c075182d40b68e28d0b5d387097870c9930c7 Mon Sep 17 00:00:00 2001 From: AdityaM-IITH Date: Thu, 4 Jun 2026 22:59:31 +0530 Subject: [PATCH 1/9] feat: add topgrade configuration management plugin --- plugins/topgrade/plugin.yaml | 6 ++ plugins/topgrade/src/plugin.py | 130 +++++++++++++++++++++++++ plugins/topgrade/test/test_topgrade.py | 113 +++++++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 plugins/topgrade/plugin.yaml create mode 100644 plugins/topgrade/src/plugin.py create mode 100644 plugins/topgrade/test/test_topgrade.py diff --git a/plugins/topgrade/plugin.yaml b/plugins/topgrade/plugin.yaml new file mode 100644 index 00000000..1a6fc988 --- /dev/null +++ b/plugins/topgrade/plugin.yaml @@ -0,0 +1,6 @@ +name: topgrade +version: 1.0.0 +type: python +main: src/plugin.py +capabilities: + - config_provider diff --git a/plugins/topgrade/src/plugin.py b/plugins/topgrade/src/plugin.py new file mode 100644 index 00000000..3cd59fcd --- /dev/null +++ b/plugins/topgrade/src/plugin.py @@ -0,0 +1,130 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "tomlkit", +# ] +# /// + +import sys +import json +import os +import shutil +import tomlkit + +def get_topgrade_config_path(): + appdata = os.environ.get("APPDATA") + if not appdata: + return None + + # Try %APPDATA%\topgrade.toml + fallback_path = os.path.join(appdata, "topgrade.toml") + # Try %APPDATA%\topgrade\topgrade.toml + main_path = os.path.join(appdata, "topgrade", "topgrade.toml") + + if os.path.exists(main_path): + return main_path + elif os.path.exists(fallback_path): + return fallback_path + + # Default to main path if neither exists + return main_path + +def merge_dict(target, source): + """Recursively merge source dictionary into target tomlkit document/table.""" + for k, v in source.items(): + if isinstance(v, dict): + if k not in target: + target[k] = tomlkit.table() + if isinstance(target[k], dict): + merge_dict(target[k], v) + else: + target[k] = v + else: + target[k] = v + +def handle_check_installed(): + topgrade_path = shutil.which("topgrade") + return { + "success": True, + "changed": False, + "data": { + "installed": topgrade_path is not None + } + } + +def handle_apply(args, context): + settings = args.get("settings", {}) + if not settings: + return {"success": True, "changed": False} + + config_path = get_topgrade_config_path() + if not config_path: + return {"success": False, "changed": False, "error": "APPDATA environment variable not set"} + + dry_run = context.get("dryRun", False) + + if os.path.exists(config_path): + with open(config_path, "r", encoding="utf-8") as f: + try: + doc = tomlkit.load(f) + except Exception as e: + return {"success": False, "changed": False, "error": f"Failed to parse topgrade config: {str(e)}"} + else: + doc = tomlkit.document() + # Create directory if it doesn't exist + os.makedirs(os.path.dirname(config_path), exist_ok=True) + + orig_content = doc.as_string() + + merge_dict(doc, settings) + + new_content = doc.as_string() + changed = (orig_content != new_content) + + if changed and not dry_run: + try: + with open(config_path, "w", encoding="utf-8") as f: + f.write(new_content) + except Exception as e: + return {"success": False, "changed": False, "error": f"Failed to write config: {str(e)}"} + + if changed and dry_run: + sys.stderr.write(f"Would update topgrade config at {config_path}\n") + + return {"success": True, "changed": changed} + +def main(): + raw_input = sys.stdin.read() + if not raw_input: + return + + try: + request = json.loads(raw_input) + except json.JSONDecodeError: + sys.stderr.write("Failed to parse JSON input\n") + return + + cmd = request.get("command") + req_id = request.get("requestId") + args = request.get("args", {}) + context = request.get("context", {}) + + response = { + "requestId": req_id, + "success": False, + "changed": False + } + + if cmd == "check_installed": + res = handle_check_installed() + response.update(res) + elif cmd == "apply": + res = handle_apply(args, context) + response.update(res) + else: + response["error"] = f"Unknown command: {cmd}" + + print(json.dumps(response)) + +if __name__ == "__main__": + main() diff --git a/plugins/topgrade/test/test_topgrade.py b/plugins/topgrade/test/test_topgrade.py new file mode 100644 index 00000000..3b37b4aa --- /dev/null +++ b/plugins/topgrade/test/test_topgrade.py @@ -0,0 +1,113 @@ +import os +import json +import subprocess +import sys +import pytest +import tomlkit + +PLUGIN_SCRIPT = os.path.join(os.path.dirname(__file__), "..", "src", "plugin.py") + +def run_plugin(request_dict): + """Helper to run the plugin script with a given JSON request via uv.""" + input_str = json.dumps(request_dict) + # Use standard python to run since uv might not be perfectly nested here, + # but the script uses inline dependencies so we must use 'uv run' + result = subprocess.run( + ["uv", "run", PLUGIN_SCRIPT], + input=input_str, + text=True, + capture_output=True + ) + return json.loads(result.stdout) if result.stdout else None, result.stderr + +def test_check_installed(): + req = { + "requestId": "123", + "command": "check_installed" + } + resp, stderr = run_plugin(req) + assert resp is not None + assert resp["requestId"] == "123" + assert resp["success"] is True + assert "installed" in resp["data"] + +def test_apply_new_file(monkeypatch, tmp_path): + # Set APPDATA to a temporary directory + monkeypatch.setenv("APPDATA", str(tmp_path)) + + settings = { + "disable": ["pip", "npm"], + "set_title": True, + "git_repos": { + "~/Projects/dotfiles": "main" + } + } + + req = { + "requestId": "456", + "command": "apply", + "args": { + "settings": settings + } + } + + resp, stderr = run_plugin(req) + assert resp is not None + assert resp["requestId"] == "456" + assert resp["success"] is True + assert resp["changed"] is True + + config_file = tmp_path / "topgrade" / "topgrade.toml" + assert config_file.exists() + + with open(config_file, "r", encoding="utf-8") as f: + doc = tomlkit.load(f) + + assert doc["disable"] == ["pip", "npm"] + assert doc["set_title"] is True + assert doc["git_repos"]["~/Projects/dotfiles"] == "main" + +def test_apply_merge_existing(monkeypatch, tmp_path): + monkeypatch.setenv("APPDATA", str(tmp_path)) + + config_dir = tmp_path / "topgrade" + config_dir.mkdir(parents=True, exist_ok=True) + config_file = config_dir / "topgrade.toml" + + initial_content = """ +disable = ["gem"] +display_time = true + +[git_repos] +"~/Projects/old" = "master" +""" + with open(config_file, "w", encoding="utf-8") as f: + f.write(initial_content) + + settings = { + "disable": ["pip", "npm"], + "git_repos": { + "~/Projects/dotfiles": "main" + } + } + + req = { + "requestId": "789", + "command": "apply", + "args": { + "settings": settings + } + } + + resp, stderr = run_plugin(req) + assert resp is not None + assert resp["success"] is True + assert resp["changed"] is True + + with open(config_file, "r", encoding="utf-8") as f: + doc = tomlkit.load(f) + + assert doc["disable"] == ["pip", "npm"] + assert doc["display_time"] is True + assert doc["git_repos"]["~/Projects/old"] == "master" + assert doc["git_repos"]["~/Projects/dotfiles"] == "main" From 6dc726c4f5c9317e0f159e160265962287bd4d03 Mon Sep 17 00:00:00 2001 From: AdityaM-IITH Date: Thu, 4 Jun 2026 23:34:09 +0530 Subject: [PATCH 2/9] fix: resolve ruff lint errors in topgrade plugin --- plugins/topgrade/src/plugin.py | 38 ++++++++++++++------------ plugins/topgrade/test/test_topgrade.py | 31 ++++++++++----------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/plugins/topgrade/src/plugin.py b/plugins/topgrade/src/plugin.py index 3cd59fcd..cff49e55 100644 --- a/plugins/topgrade/src/plugin.py +++ b/plugins/topgrade/src/plugin.py @@ -5,27 +5,29 @@ # ] # /// -import sys import json import os import shutil +import sys + import tomlkit + def get_topgrade_config_path(): appdata = os.environ.get("APPDATA") if not appdata: return None - + # Try %APPDATA%\topgrade.toml fallback_path = os.path.join(appdata, "topgrade.toml") # Try %APPDATA%\topgrade\topgrade.toml main_path = os.path.join(appdata, "topgrade", "topgrade.toml") - + if os.path.exists(main_path): return main_path elif os.path.exists(fallback_path): return fallback_path - + # Default to main path if neither exists return main_path @@ -56,13 +58,13 @@ def handle_apply(args, context): settings = args.get("settings", {}) if not settings: return {"success": True, "changed": False} - + config_path = get_topgrade_config_path() if not config_path: return {"success": False, "changed": False, "error": "APPDATA environment variable not set"} - + dry_run = context.get("dryRun", False) - + if os.path.exists(config_path): with open(config_path, "r", encoding="utf-8") as f: try: @@ -73,48 +75,48 @@ def handle_apply(args, context): doc = tomlkit.document() # Create directory if it doesn't exist os.makedirs(os.path.dirname(config_path), exist_ok=True) - + orig_content = doc.as_string() - + merge_dict(doc, settings) - + new_content = doc.as_string() changed = (orig_content != new_content) - + if changed and not dry_run: try: with open(config_path, "w", encoding="utf-8") as f: f.write(new_content) except Exception as e: return {"success": False, "changed": False, "error": f"Failed to write config: {str(e)}"} - + if changed and dry_run: sys.stderr.write(f"Would update topgrade config at {config_path}\n") - + return {"success": True, "changed": changed} def main(): raw_input = sys.stdin.read() if not raw_input: return - + try: request = json.loads(raw_input) except json.JSONDecodeError: sys.stderr.write("Failed to parse JSON input\n") return - + cmd = request.get("command") req_id = request.get("requestId") args = request.get("args", {}) context = request.get("context", {}) - + response = { "requestId": req_id, "success": False, "changed": False } - + if cmd == "check_installed": res = handle_check_installed() response.update(res) @@ -123,7 +125,7 @@ def main(): response.update(res) else: response["error"] = f"Unknown command: {cmd}" - + print(json.dumps(response)) if __name__ == "__main__": diff --git a/plugins/topgrade/test/test_topgrade.py b/plugins/topgrade/test/test_topgrade.py index 3b37b4aa..6452260f 100644 --- a/plugins/topgrade/test/test_topgrade.py +++ b/plugins/topgrade/test/test_topgrade.py @@ -1,8 +1,7 @@ -import os import json +import os import subprocess -import sys -import pytest + import tomlkit PLUGIN_SCRIPT = os.path.join(os.path.dirname(__file__), "..", "src", "plugin.py") @@ -34,7 +33,7 @@ def test_check_installed(): def test_apply_new_file(monkeypatch, tmp_path): # Set APPDATA to a temporary directory monkeypatch.setenv("APPDATA", str(tmp_path)) - + settings = { "disable": ["pip", "npm"], "set_title": True, @@ -42,7 +41,7 @@ def test_apply_new_file(monkeypatch, tmp_path): "~/Projects/dotfiles": "main" } } - + req = { "requestId": "456", "command": "apply", @@ -50,30 +49,30 @@ def test_apply_new_file(monkeypatch, tmp_path): "settings": settings } } - + resp, stderr = run_plugin(req) assert resp is not None assert resp["requestId"] == "456" assert resp["success"] is True assert resp["changed"] is True - + config_file = tmp_path / "topgrade" / "topgrade.toml" assert config_file.exists() - + with open(config_file, "r", encoding="utf-8") as f: doc = tomlkit.load(f) - + assert doc["disable"] == ["pip", "npm"] assert doc["set_title"] is True assert doc["git_repos"]["~/Projects/dotfiles"] == "main" def test_apply_merge_existing(monkeypatch, tmp_path): monkeypatch.setenv("APPDATA", str(tmp_path)) - + config_dir = tmp_path / "topgrade" config_dir.mkdir(parents=True, exist_ok=True) config_file = config_dir / "topgrade.toml" - + initial_content = """ disable = ["gem"] display_time = true @@ -83,14 +82,14 @@ def test_apply_merge_existing(monkeypatch, tmp_path): """ with open(config_file, "w", encoding="utf-8") as f: f.write(initial_content) - + settings = { "disable": ["pip", "npm"], "git_repos": { "~/Projects/dotfiles": "main" } } - + req = { "requestId": "789", "command": "apply", @@ -98,15 +97,15 @@ def test_apply_merge_existing(monkeypatch, tmp_path): "settings": settings } } - + resp, stderr = run_plugin(req) assert resp is not None assert resp["success"] is True assert resp["changed"] is True - + with open(config_file, "r", encoding="utf-8") as f: doc = tomlkit.load(f) - + assert doc["disable"] == ["pip", "npm"] assert doc["display_time"] is True assert doc["git_repos"]["~/Projects/old"] == "master" From 5febacfd06b48b9ecf0bf43d92c9480b8e613427 Mon Sep 17 00:00:00 2001 From: AdityaM-IITH Date: Thu, 4 Jun 2026 23:39:31 +0530 Subject: [PATCH 3/9] style: run ruff format on plugins --- plugins/everything/test/test_everything.py | 1 + plugins/topgrade/src/plugin.py | 21 ++++----- plugins/topgrade/test/test_topgrade.py | 51 +++++----------------- 3 files changed, 21 insertions(+), 52 deletions(-) diff --git a/plugins/everything/test/test_everything.py b/plugins/everything/test/test_everything.py index 81e5a5ec..397b97bf 100644 --- a/plugins/everything/test/test_everything.py +++ b/plugins/everything/test/test_everything.py @@ -6,6 +6,7 @@ PLUGIN = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src", "plugin.py")) + def run_plugin(payload: dict): process = subprocess.Popen( [sys.executable, PLUGIN], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True diff --git a/plugins/topgrade/src/plugin.py b/plugins/topgrade/src/plugin.py index cff49e55..0987ebbd 100644 --- a/plugins/topgrade/src/plugin.py +++ b/plugins/topgrade/src/plugin.py @@ -31,6 +31,7 @@ def get_topgrade_config_path(): # Default to main path if neither exists return main_path + def merge_dict(target, source): """Recursively merge source dictionary into target tomlkit document/table.""" for k, v in source.items(): @@ -44,15 +45,11 @@ def merge_dict(target, source): else: target[k] = v + def handle_check_installed(): topgrade_path = shutil.which("topgrade") - return { - "success": True, - "changed": False, - "data": { - "installed": topgrade_path is not None - } - } + return {"success": True, "changed": False, "data": {"installed": topgrade_path is not None}} + def handle_apply(args, context): settings = args.get("settings", {}) @@ -81,7 +78,7 @@ def handle_apply(args, context): merge_dict(doc, settings) new_content = doc.as_string() - changed = (orig_content != new_content) + changed = orig_content != new_content if changed and not dry_run: try: @@ -95,6 +92,7 @@ def handle_apply(args, context): return {"success": True, "changed": changed} + def main(): raw_input = sys.stdin.read() if not raw_input: @@ -111,11 +109,7 @@ def main(): args = request.get("args", {}) context = request.get("context", {}) - response = { - "requestId": req_id, - "success": False, - "changed": False - } + response = {"requestId": req_id, "success": False, "changed": False} if cmd == "check_installed": res = handle_check_installed() @@ -128,5 +122,6 @@ def main(): print(json.dumps(response)) + if __name__ == "__main__": main() diff --git a/plugins/topgrade/test/test_topgrade.py b/plugins/topgrade/test/test_topgrade.py index 6452260f..e4d907cb 100644 --- a/plugins/topgrade/test/test_topgrade.py +++ b/plugins/topgrade/test/test_topgrade.py @@ -6,49 +6,32 @@ PLUGIN_SCRIPT = os.path.join(os.path.dirname(__file__), "..", "src", "plugin.py") + def run_plugin(request_dict): """Helper to run the plugin script with a given JSON request via uv.""" input_str = json.dumps(request_dict) # Use standard python to run since uv might not be perfectly nested here, # but the script uses inline dependencies so we must use 'uv run' - result = subprocess.run( - ["uv", "run", PLUGIN_SCRIPT], - input=input_str, - text=True, - capture_output=True - ) + result = subprocess.run(["uv", "run", PLUGIN_SCRIPT], input=input_str, text=True, capture_output=True) return json.loads(result.stdout) if result.stdout else None, result.stderr + def test_check_installed(): - req = { - "requestId": "123", - "command": "check_installed" - } + req = {"requestId": "123", "command": "check_installed"} resp, stderr = run_plugin(req) assert resp is not None assert resp["requestId"] == "123" assert resp["success"] is True assert "installed" in resp["data"] + def test_apply_new_file(monkeypatch, tmp_path): # Set APPDATA to a temporary directory monkeypatch.setenv("APPDATA", str(tmp_path)) - settings = { - "disable": ["pip", "npm"], - "set_title": True, - "git_repos": { - "~/Projects/dotfiles": "main" - } - } - - req = { - "requestId": "456", - "command": "apply", - "args": { - "settings": settings - } - } + settings = {"disable": ["pip", "npm"], "set_title": True, "git_repos": {"~/Projects/dotfiles": "main"}} + + req = {"requestId": "456", "command": "apply", "args": {"settings": settings}} resp, stderr = run_plugin(req) assert resp is not None @@ -66,6 +49,7 @@ def test_apply_new_file(monkeypatch, tmp_path): assert doc["set_title"] is True assert doc["git_repos"]["~/Projects/dotfiles"] == "main" + def test_apply_merge_existing(monkeypatch, tmp_path): monkeypatch.setenv("APPDATA", str(tmp_path)) @@ -83,20 +67,9 @@ def test_apply_merge_existing(monkeypatch, tmp_path): with open(config_file, "w", encoding="utf-8") as f: f.write(initial_content) - settings = { - "disable": ["pip", "npm"], - "git_repos": { - "~/Projects/dotfiles": "main" - } - } - - req = { - "requestId": "789", - "command": "apply", - "args": { - "settings": settings - } - } + settings = {"disable": ["pip", "npm"], "git_repos": {"~/Projects/dotfiles": "main"}} + + req = {"requestId": "789", "command": "apply", "args": {"settings": settings}} resp, stderr = run_plugin(req) assert resp is not None From 3f30006b0fa6572d033ee0e25a4aeb12bed7da37 Mon Sep 17 00:00:00 2001 From: AdityaM-IITH Date: Thu, 4 Jun 2026 23:44:07 +0530 Subject: [PATCH 4/9] revert: undo stray formatter changes to unrelated plugins --- plugins/bat/plugin.yaml | 1 + plugins/everything/src/plugin.py | 55 +++++++++++++++------- plugins/everything/test/test_everything.py | 28 ++++++++--- plugins/go/test/test_go.py | 4 +- plugins/irfanview/plugin.yaml | 10 ++-- plugins/nvm/plugin.yaml | 1 + plugins/nvm/src/plugin.py | 4 +- plugins/nvm/test/test_nvm.py | 17 +++++-- 8 files changed, 84 insertions(+), 36 deletions(-) diff --git a/plugins/bat/plugin.yaml b/plugins/bat/plugin.yaml index cdbe2a5d..34a98e34 100644 --- a/plugins/bat/plugin.yaml +++ b/plugins/bat/plugin.yaml @@ -4,3 +4,4 @@ type: python main: src/plugin.py capabilities: - config_provider + diff --git a/plugins/everything/src/plugin.py b/plugins/everything/src/plugin.py index 29f0aa02..5ae6be64 100644 --- a/plugins/everything/src/plugin.py +++ b/plugins/everything/src/plugin.py @@ -4,14 +4,19 @@ import sys from pathlib import Path + APPDATA = os.environ.get("APPDATA", "") EVERYTHING_DIR = Path(APPDATA) / "Everything" INI_PATH = EVERYTHING_DIR / "Everything.ini" def handle_check_installed(request_id): - return {"requestId": request_id, "success": True, "changed": False, "data": EVERYTHING_DIR.exists()} - + return { + "requestId": request_id, + "success": True, + "changed": False, + "data": EVERYTHING_DIR.exists() + } def load_config(): config = configparser.ConfigParser() @@ -22,7 +27,6 @@ def load_config(): return config - def merge_config(config, settings): changed = False @@ -39,7 +43,6 @@ def merge_config(config, settings): return changed - def handle_apply(args, context, request_id): dry_run = context.get("dryRun", False) settings = args.get("settings", {}) @@ -49,13 +52,19 @@ def handle_apply(args, context, request_id): changed = merge_config(config, settings) if dry_run: - preview = {section: dict(config[section]) for section in config.sections()} + preview = { + section: dict(config[section]) + for section in config.sections() + } return { "requestId": request_id, "success": True, "changed": changed, - "data": {"dryRun": True, "preview": preview}, + "data": { + "dryRun": True, + "preview": preview + } } EVERYTHING_DIR.mkdir(parents=True, exist_ok=True) @@ -63,19 +72,25 @@ def handle_apply(args, context, request_id): with open(INI_PATH, "w", encoding="utf-8") as file: config.write(file) - return {"requestId": request_id, "success": True, "changed": changed, "data": {}} - + return { + "requestId": request_id, + "success": True, + "changed": changed, + "data": {} + } def main(): try: raw = sys.stdin.read().strip() if not raw: - print( - json.dumps( - {"requestId": None, "success": False, "changed": False, "data": None, "error": "empty input"} - ) - ) + print(json.dumps({ + "requestId": None, + "success": False, + "changed": False, + "data": None, + "error": "empty input" + })) return payload = json.loads(raw) @@ -92,13 +107,21 @@ def main(): result = handle_apply(args, context, request_id) else: - result = {"requestId": request_id, "success": False, "error": f"unknown command: {command}"} + result = { + "requestId": request_id, + "success": False, + "error": f"unknown command: {command}" + } print(json.dumps(result), flush=True) except Exception as error: - print(json.dumps({"requestId": None, "success": False, "error": str(error)}), flush=True) - + print(json.dumps({ + "requestId": None, + "success": False, + "error": str(error) + }), flush=True) if __name__ == "__main__": main() + diff --git a/plugins/everything/test/test_everything.py b/plugins/everything/test/test_everything.py index 397b97bf..5fde1989 100644 --- a/plugins/everything/test/test_everything.py +++ b/plugins/everything/test/test_everything.py @@ -6,10 +6,13 @@ PLUGIN = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src", "plugin.py")) - def run_plugin(payload: dict): process = subprocess.Popen( - [sys.executable, PLUGIN], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + [sys.executable, PLUGIN], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True ) stdout, stderr = process.communicate(json.dumps(payload)) @@ -19,9 +22,12 @@ def run_plugin(payload: dict): return stdout.strip() - def test_check_installed(): - payload = {"requestId": "1", "command": "check_installed", "args": {}} + payload = { + "requestId": "1", + "command": "check_installed", + "args": {} + } out = run_plugin(payload) @@ -30,13 +36,20 @@ def test_check_installed(): assert data["success"] is True assert isinstance(data["data"], bool) - def test_apply_dry_run(): payload = { "requestId": "1", "command": "apply", - "context": {"dryRun": True}, - "args": {"settings": {"test_section": {"test_key": "abc123"}}}, + "context": { + "dryRun": True + }, + "args": { + "settings": { + "test_section": { + "test_key": "abc123" + } + } + } } out = run_plugin(payload) @@ -45,3 +58,4 @@ def test_apply_dry_run(): assert data["success"] is True assert data["changed"] is True + diff --git a/plugins/go/test/test_go.py b/plugins/go/test/test_go.py index 3ba585b9..975b1812 100644 --- a/plugins/go/test/test_go.py +++ b/plugins/go/test/test_go.py @@ -112,9 +112,7 @@ def fake_run(cmd, capture_output, check, text): def test_noop_when_values_already_match(self): with patch.object(plugin, "go_executable", return_value="/usr/bin/go"): - with patch.object( - plugin.subprocess, "run", return_value=completed(stdout="https://proxy.golang.org,direct\n") - ): + with patch.object(plugin.subprocess, "run", return_value=completed(stdout="https://proxy.golang.org,direct\n")): result = plugin.apply_config( {"settings": {"GOPROXY": "https://proxy.golang.org,direct"}}, {}, diff --git a/plugins/irfanview/plugin.yaml b/plugins/irfanview/plugin.yaml index 6a41c57f..8c4129e7 100644 --- a/plugins/irfanview/plugin.yaml +++ b/plugins/irfanview/plugin.yaml @@ -1,8 +1,8 @@ -name: 'irfanview' -version: '1.0.0' -type: 'python' -main: 'src/plugin.py' +name: "irfanview" +version: "1.0.0" +type: "python" +main: "src/plugin.py" capabilities: - apply - check_installed -author: 'DotDev262' +author: "DotDev262" diff --git a/plugins/nvm/plugin.yaml b/plugins/nvm/plugin.yaml index 93dc6135..33ba23c5 100644 --- a/plugins/nvm/plugin.yaml +++ b/plugins/nvm/plugin.yaml @@ -5,3 +5,4 @@ main: src/plugin.py capabilities: - config_provider + diff --git a/plugins/nvm/src/plugin.py b/plugins/nvm/src/plugin.py index 59e80b38..3a998a59 100644 --- a/plugins/nvm/src/plugin.py +++ b/plugins/nvm/src/plugin.py @@ -202,7 +202,9 @@ def write_atomic(file_path: str, content: str) -> None: def check_installed(_args: dict, request_id: str) -> dict: installed = ( - os.path.exists(get_settings_path()) or shutil.which("nvm.exe") is not None or shutil.which("nvm") is not None + os.path.exists(get_settings_path()) + or shutil.which("nvm.exe") is not None + or shutil.which("nvm") is not None ) return { diff --git a/plugins/nvm/test/test_nvm.py b/plugins/nvm/test/test_nvm.py index 58785609..5abc0376 100644 --- a/plugins/nvm/test/test_nvm.py +++ b/plugins/nvm/test/test_nvm.py @@ -94,7 +94,10 @@ def test_apply_merges_and_preserves_comments(tmp_path): settings_path = tmp_path / "AppData" / "Roaming" / "nvm" / "settings.txt" settings_path.parent.mkdir(parents=True, exist_ok=True) settings_path.write_text( - "# nvm settings\nroot = C:\\nvm\npath=C:\\node\n; keep this comment\n", + "# nvm settings\n" + "root = C:\\nvm\n" + "path=C:\\node\n" + "; keep this comment\n", encoding="utf-8", ) @@ -124,7 +127,9 @@ def test_apply_preserves_inline_comment_suffix(tmp_path): env = make_env(tmp_path) settings_path = tmp_path / "AppData" / "Roaming" / "nvm" / "settings.txt" settings_path.parent.mkdir(parents=True, exist_ok=True) - settings_path.write_text("root=C:\\nvm # install directory\n", encoding="utf-8") + settings_path.write_text( + "root=C:\\nvm # install directory\n", encoding="utf-8" + ) response = run_plugin( { @@ -138,7 +143,9 @@ def test_apply_preserves_inline_comment_suffix(tmp_path): assert response["success"] is True assert response["changed"] is True - assert settings_path.read_text(encoding="utf-8") == ("root=D:\\tools\\nvm # install directory\n") + assert settings_path.read_text(encoding="utf-8") == ( + "root=D:\\tools\\nvm # install directory\n" + ) def test_apply_dry_run_does_not_write(tmp_path): @@ -188,7 +195,9 @@ def test_apply_creates_missing_file(tmp_path): assert response["changed"] is True assert settings_path.exists() assert settings_path.read_text(encoding="utf-8") == ( - "root=C:\\nvm\npath=C:\\Program Files\\nodejs\nnpm_mirror=https://registry.npmjs.org/\n" + "root=C:\\nvm\n" + "path=C:\\Program Files\\nodejs\n" + "npm_mirror=https://registry.npmjs.org/\n" ) From bc746a7347cc348f93f208e94ceb0ce334a6cfe4 Mon Sep 17 00:00:00 2001 From: AdityaM-IITH Date: Fri, 5 Jun 2026 10:37:08 +0530 Subject: [PATCH 5/9] =?UTF-8?q?fix:=20address=20DotDev262=20review=20?= =?UTF-8?q?=E2=80=94=20stdin/stdout=20error=20handling,=20dryRun=20from=20?= =?UTF-8?q?args,=20remove=20response.update=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/topgrade/src/plugin.py | 185 ++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 61 deletions(-) diff --git a/plugins/topgrade/src/plugin.py b/plugins/topgrade/src/plugin.py index 0987ebbd..d90addbc 100644 --- a/plugins/topgrade/src/plugin.py +++ b/plugins/topgrade/src/plugin.py @@ -13,27 +13,28 @@ import tomlkit +def log(msg): + sys.stderr.write(f"[topgrade-plugin] {msg}\n") + sys.stderr.flush() + + def get_topgrade_config_path(): appdata = os.environ.get("APPDATA") if not appdata: return None - # Try %APPDATA%\topgrade.toml - fallback_path = os.path.join(appdata, "topgrade.toml") - # Try %APPDATA%\topgrade\topgrade.toml main_path = os.path.join(appdata, "topgrade", "topgrade.toml") + fallback_path = os.path.join(appdata, "topgrade.toml") if os.path.exists(main_path): return main_path - elif os.path.exists(fallback_path): + if os.path.exists(fallback_path): return fallback_path - # Default to main path if neither exists return main_path def merge_dict(target, source): - """Recursively merge source dictionary into target tomlkit document/table.""" for k, v in source.items(): if isinstance(v, dict): if k not in target: @@ -46,81 +47,143 @@ def merge_dict(target, source): target[k] = v -def handle_check_installed(): - topgrade_path = shutil.which("topgrade") - return {"success": True, "changed": False, "data": {"installed": topgrade_path is not None}} +def check_installed(args, request_id): + installed = shutil.which("topgrade") is not None + return { + "requestId": request_id, + "success": True, + "changed": False, + "data": installed, + } -def handle_apply(args, context): +def apply_config(args, request_id): + dry_run = args.get("dryRun", False) settings = args.get("settings", {}) - if not settings: - return {"success": True, "changed": False} + + if not isinstance(settings, dict): + return { + "requestId": request_id, + "success": False, + "changed": False, + "error": "settings must be an object", + "data": None, + } config_path = get_topgrade_config_path() if not config_path: - return {"success": False, "changed": False, "error": "APPDATA environment variable not set"} - - dry_run = context.get("dryRun", False) + return { + "requestId": request_id, + "success": False, + "changed": False, + "error": "APPDATA environment variable not set", + "data": None, + } - if os.path.exists(config_path): - with open(config_path, "r", encoding="utf-8") as f: - try: + try: + if os.path.exists(config_path): + with open(config_path, "r", encoding="utf-8") as f: doc = tomlkit.load(f) - except Exception as e: - return {"success": False, "changed": False, "error": f"Failed to parse topgrade config: {str(e)}"} - else: - doc = tomlkit.document() - # Create directory if it doesn't exist - os.makedirs(os.path.dirname(config_path), exist_ok=True) - - orig_content = doc.as_string() - - merge_dict(doc, settings) - - new_content = doc.as_string() - changed = orig_content != new_content - - if changed and not dry_run: - try: - with open(config_path, "w", encoding="utf-8") as f: - f.write(new_content) - except Exception as e: - return {"success": False, "changed": False, "error": f"Failed to write config: {str(e)}"} - - if changed and dry_run: - sys.stderr.write(f"Would update topgrade config at {config_path}\n") - - return {"success": True, "changed": changed} + else: + doc = tomlkit.document() + os.makedirs(os.path.dirname(config_path), exist_ok=True) + + orig_content = doc.as_string() + merge_dict(doc, settings) + new_content = doc.as_string() + changed = orig_content != new_content + + if not changed: + return { + "requestId": request_id, + "success": True, + "changed": False, + "data": None, + } + + if dry_run: + log(f"Would update {config_path}") + return { + "requestId": request_id, + "success": True, + "changed": True, + "data": {"path": config_path, "settings": settings}, + } + + with open(config_path, "w", encoding="utf-8") as f: + f.write(new_content) + + log(f"Updated topgrade config: {config_path}") + return { + "requestId": request_id, + "success": True, + "changed": True, + "data": {"path": config_path}, + } + + except Exception as e: + log(f"Failed to apply config: {e}") + return { + "requestId": request_id, + "success": False, + "changed": False, + "error": str(e), + "data": None, + } def main(): - raw_input = sys.stdin.read() - if not raw_input: + input_data = sys.stdin.read() + + if not input_data: + response = { + "requestId": "unknown", + "success": False, + "changed": False, + "error": "No input received", + "data": None, + } + sys.stdout.write(json.dumps(response) + "\n") + sys.stdout.flush() return try: - request = json.loads(raw_input) - except json.JSONDecodeError: - sys.stderr.write("Failed to parse JSON input\n") + request = json.loads(input_data) + except Exception as e: + response = { + "requestId": "unknown", + "success": False, + "changed": False, + "error": f"Failed to parse JSON request: {str(e)}", + "data": None, + } + sys.stdout.write(json.dumps(response) + "\n") + sys.stdout.flush() return - cmd = request.get("command") - req_id = request.get("requestId") + request_id = request.get("requestId") or "unknown" + command = request.get("command") args = request.get("args", {}) - context = request.get("context", {}) - response = {"requestId": req_id, "success": False, "changed": False} + response = { + "requestId": request_id, + "success": False, + "changed": False, + "data": None, + } - if cmd == "check_installed": - res = handle_check_installed() - response.update(res) - elif cmd == "apply": - res = handle_apply(args, context) - response.update(res) - else: - response["error"] = f"Unknown command: {cmd}" + try: + if command == "check_installed": + response = check_installed(args, request_id) + elif command == "apply": + response = apply_config(args, request_id) + else: + response["error"] = f"Unknown command: {command}" + except Exception as fatal_err: + response["error"] = f"Internal error: {str(fatal_err)}" - print(json.dumps(response)) + sys.stdout.write(json.dumps(response) + "\n") + sys.stdout.flush() if __name__ == "__main__": From ef860dc644b4599bc9a2a34ae11d15b9a3abe1bc Mon Sep 17 00:00:00 2001 From: AdityaM-IITH Date: Sat, 13 Jun 2026 21:00:28 +0530 Subject: [PATCH 6/9] fix: resolve ruff lint errors in everything plugin --- plugins/everything/src/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/everything/src/plugin.py b/plugins/everything/src/plugin.py index 5ae6be64..7b9811e1 100644 --- a/plugins/everything/src/plugin.py +++ b/plugins/everything/src/plugin.py @@ -4,7 +4,6 @@ import sys from pathlib import Path - APPDATA = os.environ.get("APPDATA", "") EVERYTHING_DIR = Path(APPDATA) / "Everything" INI_PATH = EVERYTHING_DIR / "Everything.ini" From f5e55be1f06e136996b5466ab2fdd0d290cb81b5 Mon Sep 17 00:00:00 2001 From: AdityaM-IITH Date: Sat, 13 Jun 2026 21:04:40 +0530 Subject: [PATCH 7/9] style: run ruff format on plugins --- plugins/everything/src/plugin.py | 54 +++++++--------------- plugins/everything/test/test_everything.py | 27 +++-------- plugins/go/test/test_go.py | 4 +- plugins/nvm/src/plugin.py | 4 +- plugins/nvm/test/test_nvm.py | 17 ++----- 5 files changed, 30 insertions(+), 76 deletions(-) diff --git a/plugins/everything/src/plugin.py b/plugins/everything/src/plugin.py index 7b9811e1..29f0aa02 100644 --- a/plugins/everything/src/plugin.py +++ b/plugins/everything/src/plugin.py @@ -10,12 +10,8 @@ def handle_check_installed(request_id): - return { - "requestId": request_id, - "success": True, - "changed": False, - "data": EVERYTHING_DIR.exists() - } + return {"requestId": request_id, "success": True, "changed": False, "data": EVERYTHING_DIR.exists()} + def load_config(): config = configparser.ConfigParser() @@ -26,6 +22,7 @@ def load_config(): return config + def merge_config(config, settings): changed = False @@ -42,6 +39,7 @@ def merge_config(config, settings): return changed + def handle_apply(args, context, request_id): dry_run = context.get("dryRun", False) settings = args.get("settings", {}) @@ -51,19 +49,13 @@ def handle_apply(args, context, request_id): changed = merge_config(config, settings) if dry_run: - preview = { - section: dict(config[section]) - for section in config.sections() - } + preview = {section: dict(config[section]) for section in config.sections()} return { "requestId": request_id, "success": True, "changed": changed, - "data": { - "dryRun": True, - "preview": preview - } + "data": {"dryRun": True, "preview": preview}, } EVERYTHING_DIR.mkdir(parents=True, exist_ok=True) @@ -71,25 +63,19 @@ def handle_apply(args, context, request_id): with open(INI_PATH, "w", encoding="utf-8") as file: config.write(file) - return { - "requestId": request_id, - "success": True, - "changed": changed, - "data": {} - } + return {"requestId": request_id, "success": True, "changed": changed, "data": {}} + def main(): try: raw = sys.stdin.read().strip() if not raw: - print(json.dumps({ - "requestId": None, - "success": False, - "changed": False, - "data": None, - "error": "empty input" - })) + print( + json.dumps( + {"requestId": None, "success": False, "changed": False, "data": None, "error": "empty input"} + ) + ) return payload = json.loads(raw) @@ -106,21 +92,13 @@ def main(): result = handle_apply(args, context, request_id) else: - result = { - "requestId": request_id, - "success": False, - "error": f"unknown command: {command}" - } + result = {"requestId": request_id, "success": False, "error": f"unknown command: {command}"} print(json.dumps(result), flush=True) except Exception as error: - print(json.dumps({ - "requestId": None, - "success": False, - "error": str(error) - }), flush=True) + print(json.dumps({"requestId": None, "success": False, "error": str(error)}), flush=True) + if __name__ == "__main__": main() - diff --git a/plugins/everything/test/test_everything.py b/plugins/everything/test/test_everything.py index 5fde1989..81e5a5ec 100644 --- a/plugins/everything/test/test_everything.py +++ b/plugins/everything/test/test_everything.py @@ -8,11 +8,7 @@ def run_plugin(payload: dict): process = subprocess.Popen( - [sys.executable, PLUGIN], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True + [sys.executable, PLUGIN], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) stdout, stderr = process.communicate(json.dumps(payload)) @@ -22,12 +18,9 @@ def run_plugin(payload: dict): return stdout.strip() + def test_check_installed(): - payload = { - "requestId": "1", - "command": "check_installed", - "args": {} - } + payload = {"requestId": "1", "command": "check_installed", "args": {}} out = run_plugin(payload) @@ -36,20 +29,13 @@ def test_check_installed(): assert data["success"] is True assert isinstance(data["data"], bool) + def test_apply_dry_run(): payload = { "requestId": "1", "command": "apply", - "context": { - "dryRun": True - }, - "args": { - "settings": { - "test_section": { - "test_key": "abc123" - } - } - } + "context": {"dryRun": True}, + "args": {"settings": {"test_section": {"test_key": "abc123"}}}, } out = run_plugin(payload) @@ -58,4 +44,3 @@ def test_apply_dry_run(): assert data["success"] is True assert data["changed"] is True - diff --git a/plugins/go/test/test_go.py b/plugins/go/test/test_go.py index 975b1812..3ba585b9 100644 --- a/plugins/go/test/test_go.py +++ b/plugins/go/test/test_go.py @@ -112,7 +112,9 @@ def fake_run(cmd, capture_output, check, text): def test_noop_when_values_already_match(self): with patch.object(plugin, "go_executable", return_value="/usr/bin/go"): - with patch.object(plugin.subprocess, "run", return_value=completed(stdout="https://proxy.golang.org,direct\n")): + with patch.object( + plugin.subprocess, "run", return_value=completed(stdout="https://proxy.golang.org,direct\n") + ): result = plugin.apply_config( {"settings": {"GOPROXY": "https://proxy.golang.org,direct"}}, {}, diff --git a/plugins/nvm/src/plugin.py b/plugins/nvm/src/plugin.py index 3a998a59..59e80b38 100644 --- a/plugins/nvm/src/plugin.py +++ b/plugins/nvm/src/plugin.py @@ -202,9 +202,7 @@ def write_atomic(file_path: str, content: str) -> None: def check_installed(_args: dict, request_id: str) -> dict: installed = ( - os.path.exists(get_settings_path()) - or shutil.which("nvm.exe") is not None - or shutil.which("nvm") is not None + os.path.exists(get_settings_path()) or shutil.which("nvm.exe") is not None or shutil.which("nvm") is not None ) return { diff --git a/plugins/nvm/test/test_nvm.py b/plugins/nvm/test/test_nvm.py index 5abc0376..58785609 100644 --- a/plugins/nvm/test/test_nvm.py +++ b/plugins/nvm/test/test_nvm.py @@ -94,10 +94,7 @@ def test_apply_merges_and_preserves_comments(tmp_path): settings_path = tmp_path / "AppData" / "Roaming" / "nvm" / "settings.txt" settings_path.parent.mkdir(parents=True, exist_ok=True) settings_path.write_text( - "# nvm settings\n" - "root = C:\\nvm\n" - "path=C:\\node\n" - "; keep this comment\n", + "# nvm settings\nroot = C:\\nvm\npath=C:\\node\n; keep this comment\n", encoding="utf-8", ) @@ -127,9 +124,7 @@ def test_apply_preserves_inline_comment_suffix(tmp_path): env = make_env(tmp_path) settings_path = tmp_path / "AppData" / "Roaming" / "nvm" / "settings.txt" settings_path.parent.mkdir(parents=True, exist_ok=True) - settings_path.write_text( - "root=C:\\nvm # install directory\n", encoding="utf-8" - ) + settings_path.write_text("root=C:\\nvm # install directory\n", encoding="utf-8") response = run_plugin( { @@ -143,9 +138,7 @@ def test_apply_preserves_inline_comment_suffix(tmp_path): assert response["success"] is True assert response["changed"] is True - assert settings_path.read_text(encoding="utf-8") == ( - "root=D:\\tools\\nvm # install directory\n" - ) + assert settings_path.read_text(encoding="utf-8") == ("root=D:\\tools\\nvm # install directory\n") def test_apply_dry_run_does_not_write(tmp_path): @@ -195,9 +188,7 @@ def test_apply_creates_missing_file(tmp_path): assert response["changed"] is True assert settings_path.exists() assert settings_path.read_text(encoding="utf-8") == ( - "root=C:\\nvm\n" - "path=C:\\Program Files\\nodejs\n" - "npm_mirror=https://registry.npmjs.org/\n" + "root=C:\\nvm\npath=C:\\Program Files\\nodejs\nnpm_mirror=https://registry.npmjs.org/\n" ) From e73d4dd41ec218187b5646c2350ee108231f8d60 Mon Sep 17 00:00:00 2001 From: AdityaM-IITH Date: Sat, 13 Jun 2026 21:07:33 +0530 Subject: [PATCH 8/9] style: run prettier on plugins --- plugins/bat/plugin.yaml | 1 - plugins/irfanview/plugin.yaml | 10 +++++----- plugins/nvm/plugin.yaml | 1 - 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/bat/plugin.yaml b/plugins/bat/plugin.yaml index 34a98e34..cdbe2a5d 100644 --- a/plugins/bat/plugin.yaml +++ b/plugins/bat/plugin.yaml @@ -4,4 +4,3 @@ type: python main: src/plugin.py capabilities: - config_provider - diff --git a/plugins/irfanview/plugin.yaml b/plugins/irfanview/plugin.yaml index 8c4129e7..6a41c57f 100644 --- a/plugins/irfanview/plugin.yaml +++ b/plugins/irfanview/plugin.yaml @@ -1,8 +1,8 @@ -name: "irfanview" -version: "1.0.0" -type: "python" -main: "src/plugin.py" +name: 'irfanview' +version: '1.0.0' +type: 'python' +main: 'src/plugin.py' capabilities: - apply - check_installed -author: "DotDev262" +author: 'DotDev262' diff --git a/plugins/nvm/plugin.yaml b/plugins/nvm/plugin.yaml index 33ba23c5..93dc6135 100644 --- a/plugins/nvm/plugin.yaml +++ b/plugins/nvm/plugin.yaml @@ -5,4 +5,3 @@ main: src/plugin.py capabilities: - config_provider - From c8f97268a6fd342136e5ba0eaadc7ac19395343b Mon Sep 17 00:00:00 2001 From: AdityaM-IITH Date: Tue, 23 Jun 2026 21:46:45 +0530 Subject: [PATCH 9/9] fix: fully address protocol issues and non-atomic writes --- plugins/topgrade/src/plugin.py | 106 ++++++++----------------- plugins/topgrade/test/test_topgrade.py | 9 +-- 2 files changed, 34 insertions(+), 81 deletions(-) diff --git a/plugins/topgrade/src/plugin.py b/plugins/topgrade/src/plugin.py index d90addbc..ed168789 100644 --- a/plugins/topgrade/src/plugin.py +++ b/plugins/topgrade/src/plugin.py @@ -49,12 +49,7 @@ def merge_dict(target, source): def check_installed(args, request_id): installed = shutil.which("topgrade") is not None - return { - "requestId": request_id, - "success": True, - "changed": False, - "data": installed, - } + return installed def apply_config(args, request_id): @@ -62,23 +57,11 @@ def apply_config(args, request_id): settings = args.get("settings", {}) if not isinstance(settings, dict): - return { - "requestId": request_id, - "success": False, - "changed": False, - "error": "settings must be an object", - "data": None, - } + raise ValueError("settings must be an object") config_path = get_topgrade_config_path() if not config_path: - return { - "requestId": request_id, - "success": False, - "changed": False, - "error": "APPDATA environment variable not set", - "data": None, - } + raise ValueError("APPDATA environment variable not set") try: if os.path.exists(config_path): @@ -93,56 +76,35 @@ def apply_config(args, request_id): new_content = doc.as_string() changed = orig_content != new_content - if not changed: - return { - "requestId": request_id, - "success": True, - "changed": False, - "data": None, - } - - if dry_run: - log(f"Would update {config_path}") - return { - "requestId": request_id, - "success": True, - "changed": True, - "data": {"path": config_path, "settings": settings}, - } - - with open(config_path, "w", encoding="utf-8") as f: - f.write(new_content) + if not changed or dry_run: + if dry_run: + log(f"Would update {config_path}") + return {"changed": changed} + + import tempfile + + fd, temp_path = tempfile.mkstemp(dir=os.path.dirname(config_path), text=True) + try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + f.write(new_content) + os.replace(temp_path, config_path) + except Exception as e: + os.remove(temp_path) + raise e log(f"Updated topgrade config: {config_path}") - return { - "requestId": request_id, - "success": True, - "changed": True, - "data": {"path": config_path}, - } + return {"changed": True} except Exception as e: log(f"Failed to apply config: {e}") - return { - "requestId": request_id, - "success": False, - "changed": False, - "error": str(e), - "data": None, - } + raise e def main(): input_data = sys.stdin.read() if not input_data: - response = { - "requestId": "unknown", - "success": False, - "changed": False, - "error": "No input received", - "data": None, - } + response = {"requestId": "unknown", "error": "No input received"} sys.stdout.write(json.dumps(response) + "\n") sys.stdout.flush() return @@ -150,33 +112,27 @@ def main(): try: request = json.loads(input_data) except Exception as e: - response = { - "requestId": "unknown", - "success": False, - "changed": False, - "error": f"Failed to parse JSON request: {str(e)}", - "data": None, - } + response = {"requestId": "unknown", "error": f"Failed to parse JSON request: {str(e)}"} sys.stdout.write(json.dumps(response) + "\n") sys.stdout.flush() return - request_id = request.get("requestId") or "unknown" + request_id = request.get("requestId") + if request_id is None: + request_id = "unknown" + command = request.get("command") args = request.get("args", {}) - response = { - "requestId": request_id, - "success": False, - "changed": False, - "data": None, - } + response = {"requestId": request_id} try: if command == "check_installed": - response = check_installed(args, request_id) + installed = check_installed(args, request_id) + response["installed"] = installed elif command == "apply": - response = apply_config(args, request_id) + res = apply_config(args, request_id) + response.update(res) else: response["error"] = f"Unknown command: {command}" except Exception as fatal_err: diff --git a/plugins/topgrade/test/test_topgrade.py b/plugins/topgrade/test/test_topgrade.py index e4d907cb..619f4e78 100644 --- a/plugins/topgrade/test/test_topgrade.py +++ b/plugins/topgrade/test/test_topgrade.py @@ -21,8 +21,7 @@ def test_check_installed(): resp, stderr = run_plugin(req) assert resp is not None assert resp["requestId"] == "123" - assert resp["success"] is True - assert "installed" in resp["data"] + assert isinstance(resp["installed"], bool) def test_apply_new_file(monkeypatch, tmp_path): @@ -36,8 +35,7 @@ def test_apply_new_file(monkeypatch, tmp_path): resp, stderr = run_plugin(req) assert resp is not None assert resp["requestId"] == "456" - assert resp["success"] is True - assert resp["changed"] is True + assert resp.get("changed") is True config_file = tmp_path / "topgrade" / "topgrade.toml" assert config_file.exists() @@ -73,8 +71,7 @@ def test_apply_merge_existing(monkeypatch, tmp_path): resp, stderr = run_plugin(req) assert resp is not None - assert resp["success"] is True - assert resp["changed"] is True + assert resp.get("changed") is True with open(config_file, "r", encoding="utf-8") as f: doc = tomlkit.load(f)