From 728a708468df3016828427fd6f4246217eec1469 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Sun, 26 Apr 2026 01:17:55 -0700 Subject: [PATCH 1/2] feat(drone): test(drone): cover all untested functions per TDPLAN-0003 Co-Authored-By: @drone --- src/aipass/drone/tests/test_commands.py | 42 +++ src/aipass/drone/tests/test_json_handler.py | 82 +++++ .../drone/tests/test_module_registry.py | 323 +++++++++++++++++- 3 files changed, 446 insertions(+), 1 deletion(-) diff --git a/src/aipass/drone/tests/test_commands.py b/src/aipass/drone/tests/test_commands.py index 4c278b87..6f639e30 100644 --- a/src/aipass/drone/tests/test_commands.py +++ b/src/aipass/drone/tests/test_commands.py @@ -430,3 +430,45 @@ class TestModuleOrchestrator: result = handle_command("unknown_sub", ["arg"]) assert result is False + + +# =================================================================== +# 8. list_all module function +# =================================================================== + + +class TestListAll: + """Tests for the list_all() module-level function.""" + + def test_list_all_empty(self, isolated_registry: Path) -> None: + """list_all returns empty list when no commands registered.""" + from aipass.drone.apps.modules.commands import list_all + + result = list_all() + assert result == [] + + def test_list_all_returns_sorted(self, isolated_registry: Path) -> None: + """list_all returns commands sorted by name.""" + ops.add_command("zebra", "@t", "c1") + ops.add_command("alpha", "@t", "c2") + ops.add_command("middle", "@t", "c3") + + from aipass.drone.apps.modules.commands import list_all + + result = list_all() + names = [c["name"] for c in result] + assert names == ["alpha", "middle", "zebra"] + + def test_list_all_returns_all_fields(self, isolated_registry: Path) -> None: + """list_all returns full command dicts with expected keys.""" + ops.add_command("audit", "@seedgo", "audit", ["aipass"], "Run audit", "seedgo") + + from aipass.drone.apps.modules.commands import list_all + + result = list_all() + assert len(result) == 1 + cmd = result[0] + assert cmd["name"] == "audit" + assert cmd["target"] == "@seedgo" + assert cmd["command"] == "audit" + assert cmd["args"] == ["aipass"] diff --git a/src/aipass/drone/tests/test_json_handler.py b/src/aipass/drone/tests/test_json_handler.py index 2f135664..1ab05024 100644 --- a/src/aipass/drone/tests/test_json_handler.py +++ b/src/aipass/drone/tests/test_json_handler.py @@ -684,3 +684,85 @@ def test_log_operation_survives_empty_log_file(tmp_path: Path) -> None: # JH-04 data = json.loads(log_path.read_text(encoding="utf-8")) assert len(data) == 1, "Should have exactly one log entry after recovery" assert data[0]["operation"] == "test_op" + + +# =========================================================================== +# 9. increment_counter +# =========================================================================== + + +class TestIncrementCounter: + """Tests for increment_counter().""" + + def test_increment_creates_counter(self, tmp_path: Path) -> None: + """Incrementing a non-existent counter creates it at the given amount.""" + json_handler.ensure_module_jsons("incr_test") + result = json_handler.increment_counter("incr_test", "hits") + assert result is True + data = json_handler.load_json("incr_test", "data") + assert data["hits"] == 1 + + def test_increment_adds_to_existing(self, tmp_path: Path) -> None: + """Incrementing an existing counter adds to its current value.""" + json_handler.ensure_module_jsons("incr_test2") + json_handler.increment_counter("incr_test2", "hits") + json_handler.increment_counter("incr_test2", "hits") + json_handler.increment_counter("incr_test2", "hits", amount=5) + data = json_handler.load_json("incr_test2", "data") + assert data["hits"] == 7 + + def test_increment_custom_amount(self, tmp_path: Path) -> None: + """Custom amount parameter is respected.""" + json_handler.ensure_module_jsons("incr_test3") + result = json_handler.increment_counter("incr_test3", "visits", amount=42) + assert result is True + data = json_handler.load_json("incr_test3", "data") + assert data["visits"] == 42 + + def test_increment_returns_false_on_load_failure(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Returns False when data file cannot be loaded.""" + json_handler.ensure_module_jsons("incr_fail") + monkeypatch.setattr(json_handler, "load_json", lambda *a, **kw: None) + result = json_handler.increment_counter("incr_fail", "hits") + assert result is False + + +# =========================================================================== +# 10. update_data_metrics +# =========================================================================== + + +class TestUpdateDataMetrics: + """Tests for update_data_metrics().""" + + def test_update_single_metric(self, tmp_path: Path) -> None: + """Updating a single metric writes it to the data file.""" + json_handler.ensure_module_jsons("metric_test") + result = json_handler.update_data_metrics("metric_test", uptime=99.5) + assert result is True + data = json_handler.load_json("metric_test", "data") + assert data["uptime"] == 99.5 + + def test_update_multiple_metrics(self, tmp_path: Path) -> None: + """Multiple keyword arguments are all written.""" + json_handler.ensure_module_jsons("metric_test2") + json_handler.update_data_metrics("metric_test2", cpu=80, memory=60, disk=45) + data = json_handler.load_json("metric_test2", "data") + assert data["cpu"] == 80 + assert data["memory"] == 60 + assert data["disk"] == 45 + + def test_update_overwrites_existing(self, tmp_path: Path) -> None: + """Existing keys are overwritten by new values.""" + json_handler.ensure_module_jsons("metric_test3") + json_handler.update_data_metrics("metric_test3", score=10) + json_handler.update_data_metrics("metric_test3", score=20) + data = json_handler.load_json("metric_test3", "data") + assert data["score"] == 20 + + def test_update_returns_false_on_load_failure(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Returns False when data file cannot be loaded.""" + json_handler.ensure_module_jsons("metric_fail") + monkeypatch.setattr(json_handler, "load_json", lambda *a, **kw: None) + result = json_handler.update_data_metrics("metric_fail", x=1) + assert result is False diff --git a/src/aipass/drone/tests/test_module_registry.py b/src/aipass/drone/tests/test_module_registry.py index c4f691a9..da7fd934 100644 --- a/src/aipass/drone/tests/test_module_registry.py +++ b/src/aipass/drone/tests/test_module_registry.py @@ -2,13 +2,14 @@ from __future__ import annotations -from unittest.mock import patch +from unittest.mock import MagicMock, patch # --------------------------------------------------------------------------- # Module path prefix for patching # --------------------------------------------------------------------------- _MOD = "aipass.drone.apps.modules.module_registry" +_HANDLER = "aipass.drone.apps.handlers.module_registry_handler" # --------------------------------------------------------------------------- @@ -396,3 +397,323 @@ class TestHandleCommandUnknown: mock_jh.log_operation.assert_called_once_with( "handle_command", {"module": "module_registry", "command": "bogus"} ) + + +# =========================================================================== +# 7. refresh_external_modules (handler-level) +# =========================================================================== + + +class TestRefreshExternalModules: + """refresh_external_modules() reloads from routing_config.json.""" + + def test_refresh_reloads_config(self) -> None: + """After refresh, _EXTERNAL_MODULES reflects current config.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original = dict(mrh._EXTERNAL_MODULES) + with patch.object(mrh, "_load_external_modules", return_value={}): + mrh.refresh_external_modules() + assert mrh._EXTERNAL_MODULES == {} + mrh._EXTERNAL_MODULES = original + + def test_refresh_picks_up_new_module(self) -> None: + """A module added to config appears after refresh.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + from aipass.drone.apps.handlers.module_registry_handler import ( + _ExternalModuleConfig, + ) + + original = dict(mrh._EXTERNAL_MODULES) + fake = {"newmod": _ExternalModuleConfig("newmod", "some.entry", "New", "1.0")} + with patch.object(mrh, "_load_external_modules", return_value=fake): + mrh.refresh_external_modules() + assert "newmod" in mrh._EXTERNAL_MODULES + mrh._EXTERNAL_MODULES = original + + +# =========================================================================== +# 8. route_module_command (handler-level) +# =========================================================================== + + +class TestRouteModuleCommand: + """route_module_command() routes to external or internal modules.""" + + def test_routes_external_module_via_capture(self) -> None: + """External modules route through capture_main.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + from aipass.drone.apps.handlers.module_registry_handler import ( + _ExternalModuleConfig, + ) + + original_ext = dict(mrh._EXTERNAL_MODULES) + mrh._EXTERNAL_MODULES["testext"] = _ExternalModuleConfig("testext", "fake.entry", "Test external", "1.0") + try: + with ( + patch( + f"{_HANDLER}.capture_main", + return_value={ + "stdout": "ok", + "stderr": "", + "exit_code": 0, + }, + ) as mock_cap, + patch(f"{_HANDLER}.json_handler"), + ): + result = mrh.route_module_command("testext", "run", ["--flag"]) + assert result["stdout"] == "ok" + assert result["exit_code"] == 0 + mock_cap.assert_called_once_with("fake.entry", "testext", "run", ["--flag"]) + finally: + mrh._EXTERNAL_MODULES = original_ext + + def test_routes_internal_module_via_import(self) -> None: + """Internal modules route through importlib + handle_command.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original_int = dict(mrh._INTERNAL_MODULES) + mrh._INTERNAL_MODULES["fakeint"] = "fake.internal.mod" + try: + mock_mod = MagicMock() + mock_mod.handle_command.return_value = { + "stdout": "done", + "stderr": "", + "exit_code": 0, + } + with ( + patch( + f"{_HANDLER}.importlib.import_module", + return_value=mock_mod, + ), + patch(f"{_HANDLER}.json_handler"), + ): + result = mrh.route_module_command("fakeint", "status") + assert result["stdout"] == "done" + mock_mod.handle_command.assert_called_once_with("status", None) + finally: + mrh._INTERNAL_MODULES = original_int + + def test_internal_bool_true_converted_to_dict(self) -> None: + """Internal module returning True converts to dict with exit_code 0.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original_int = dict(mrh._INTERNAL_MODULES) + mrh._INTERNAL_MODULES["boolmod"] = "fake.bool.mod" + try: + mock_mod = MagicMock() + mock_mod.handle_command.return_value = True + with ( + patch( + f"{_HANDLER}.importlib.import_module", + return_value=mock_mod, + ), + patch(f"{_HANDLER}.json_handler"), + ): + result = mrh.route_module_command("boolmod", "check") + assert result["exit_code"] == 0 + finally: + mrh._INTERNAL_MODULES = original_int + + def test_internal_bool_false_converted_to_exit_code_1(self) -> None: + """Internal module returning False gets exit_code 1.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original_int = dict(mrh._INTERNAL_MODULES) + mrh._INTERNAL_MODULES["failmod"] = "fake.fail.mod" + try: + mock_mod = MagicMock() + mock_mod.handle_command.return_value = False + with ( + patch( + f"{_HANDLER}.importlib.import_module", + return_value=mock_mod, + ), + patch(f"{_HANDLER}.json_handler"), + ): + result = mrh.route_module_command("failmod", "broken") + assert result["exit_code"] == 1 + finally: + mrh._INTERNAL_MODULES = original_int + + def test_logs_operation_for_external(self) -> None: + """External module routing logs via json_handler.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + from aipass.drone.apps.handlers.module_registry_handler import ( + _ExternalModuleConfig, + ) + + original_ext = dict(mrh._EXTERNAL_MODULES) + mrh._EXTERNAL_MODULES["logext"] = _ExternalModuleConfig("logext", "fake.entry", "Log test", "1.0") + try: + with ( + patch( + f"{_HANDLER}.capture_main", + return_value={ + "stdout": "", + "stderr": "", + "exit_code": 0, + }, + ), + patch(f"{_HANDLER}.json_handler") as mock_jh, + ): + mrh.route_module_command("logext", "ping") + mock_jh.log_operation.assert_called_once_with( + "route_module_command", {"module": "logext", "command": "ping"} + ) + finally: + mrh._EXTERNAL_MODULES = original_ext + + +# =========================================================================== +# 9. get_module_help (handler-level) +# =========================================================================== + + +class TestGetModuleHelp: + """get_module_help() retrieves help text from modules.""" + + def test_external_module_help_no_command(self) -> None: + """External module help without command captures --help output.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + from aipass.drone.apps.handlers.module_registry_handler import ( + _ExternalModuleConfig, + ) + + original_ext = dict(mrh._EXTERNAL_MODULES) + mrh._EXTERNAL_MODULES["helpext"] = _ExternalModuleConfig("helpext", "fake.entry", "Help test", "1.0") + try: + with patch( + f"{_HANDLER}.capture_main", + return_value={ + "stdout": "Usage: helpext", + "stderr": "", + }, + ) as mock_cap: + result = mrh.get_module_help("helpext") + assert result == "Usage: helpext" + mock_cap.assert_called_once_with("fake.entry", "helpext", "--help") + finally: + mrh._EXTERNAL_MODULES = original_ext + + def test_external_module_help_with_command(self) -> None: + """External module help with command passes command + --help.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + from aipass.drone.apps.handlers.module_registry_handler import ( + _ExternalModuleConfig, + ) + + original_ext = dict(mrh._EXTERNAL_MODULES) + mrh._EXTERNAL_MODULES["helpext2"] = _ExternalModuleConfig("helpext2", "fake.entry", "Help test", "1.0") + try: + with patch( + f"{_HANDLER}.capture_main", + return_value={"stdout": "Sub help", "stderr": ""}, + ) as mock_cap: + result = mrh.get_module_help("helpext2", "subcmd") + assert result == "Sub help" + mock_cap.assert_called_once_with("fake.entry", "helpext2", "subcmd", ["--help"]) + finally: + mrh._EXTERNAL_MODULES = original_ext + + def test_internal_module_help(self) -> None: + """Internal module help calls get_help() on the module.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original_int = dict(mrh._INTERNAL_MODULES) + mrh._INTERNAL_MODULES["helpint"] = "fake.help.mod" + try: + mock_mod = MagicMock() + mock_mod.get_help.return_value = "Internal help text" + with patch( + f"{_HANDLER}.importlib.import_module", + return_value=mock_mod, + ): + result = mrh.get_module_help("helpint", "status") + assert result == "Internal help text" + mock_mod.get_help.assert_called_once_with("status") + finally: + mrh._INTERNAL_MODULES = original_int + + def test_internal_module_without_get_help(self) -> None: + """Internal module without get_help() returns empty string.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original_int = dict(mrh._INTERNAL_MODULES) + mrh._INTERNAL_MODULES["nohelp"] = "fake.nohelp.mod" + try: + mock_mod = MagicMock(spec=[]) + with patch( + f"{_HANDLER}.importlib.import_module", + return_value=mock_mod, + ): + result = mrh.get_module_help("nohelp") + assert result == "" + finally: + mrh._INTERNAL_MODULES = original_int + + def test_unknown_module_returns_empty(self) -> None: + """Unknown module name returns empty string.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + result = mrh.get_module_help("nonexistent_mod_xyz") + assert result == "" + + def test_import_error_returns_empty(self) -> None: + """ImportError during internal module load returns empty string.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original_int = dict(mrh._INTERNAL_MODULES) + mrh._INTERNAL_MODULES["broken"] = "fake.broken.mod" + try: + with patch( + f"{_HANDLER}.importlib.import_module", + side_effect=ImportError("nope"), + ): + result = mrh.get_module_help("broken") + assert result == "" + finally: + mrh._INTERNAL_MODULES = original_int + + +# =========================================================================== +# 10. register_module (handler-level) +# =========================================================================== + + +class TestRegisterModule: + """register_module() adds internal modules dynamically.""" + + def test_register_adds_to_internal(self) -> None: + """register_module makes the module available.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original_int = dict(mrh._INTERNAL_MODULES) + try: + mrh.register_module("dynamic", "aipass.dynamic.mod") + assert mrh._INTERNAL_MODULES["dynamic"] == "aipass.dynamic.mod" + finally: + mrh._INTERNAL_MODULES = original_int + + def test_register_overwrites_existing(self) -> None: + """Registering an existing name overwrites the adapter path.""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original_int = dict(mrh._INTERNAL_MODULES) + try: + mrh.register_module("overwrite", "path.v1") + mrh.register_module("overwrite", "path.v2") + assert mrh._INTERNAL_MODULES["overwrite"] == "path.v2" + finally: + mrh._INTERNAL_MODULES = original_int + + def test_registered_module_appears_in_list(self) -> None: + """A dynamically registered module appears in list_modules().""" + import aipass.drone.apps.handlers.module_registry_handler as mrh + + original_int = dict(mrh._INTERNAL_MODULES) + try: + mrh.register_module("newdyn", "aipass.newdyn.mod") + assert "newdyn" in mrh.list_modules() + finally: + mrh._INTERNAL_MODULES = original_int From c09ff6debd3c4e5fd3e20616d021af2022c498c3 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Sun, 26 Apr 2026 01:18:15 -0700 Subject: [PATCH 2/2] feat(system): test(drone): cover all untested functions per TDPLAN-0003 Co-Authored-By: @devpulse --- .../spawn/templates/builder/.spawn/.template_registry.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aipass/spawn/templates/builder/.spawn/.template_registry.json b/src/aipass/spawn/templates/builder/.spawn/.template_registry.json index 772ec020..fec2feb7 100644 --- a/src/aipass/spawn/templates/builder/.spawn/.template_registry.json +++ b/src/aipass/spawn/templates/builder/.spawn/.template_registry.json @@ -155,7 +155,7 @@ "content_hash": "a4cf0a8e3b4f", "has_branch_placeholder": false }, - "f026": { + "f015": { "path": "apps/modules/__init__.py", "name": "__init__.py", "content_hash": "e3b0c44298fc", @@ -263,7 +263,7 @@ "content_hash": "28e9ae373563", "has_branch_placeholder": false }, - "f015": { + "f026": { "path": "apps/plugins/__init__.py", "name": "__init__.py", "content_hash": "e3b0c44298fc",