From 090b10c079a19e64029ffbbfb37be749f4ca5626 Mon Sep 17 00:00:00 2001
From: Test Improver <test-improver@github.com>
Date: Thu, 19 Mar 2026 01:15:57 +0000
Subject: [PATCH 1/2] =?UTF-8?q?test:=20add=20unit=20tests=20for=20config?=
 =?UTF-8?q?=20command=20(22%=20=E2=86=92=20100%)=20+=20fix=20missing=20aut?=
 =?UTF-8?q?o-integrate=20functions?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Add 23 unit tests covering all three config subcommands:
  - config (show): outside project, inside project, with compilation settings, Rich fallback
  - config set: enable/disable auto-integrate (all valid aliases), invalid value, unknown key
  - config get: by key, all keys, unknown key, formatting of auto_integrate key
- Fix bug: add missing get_auto_integrate() and set_auto_integrate() to apm_cli/config.py;
  these functions were referenced by commands/config.py but did not exist, causing
  an ImportError at runtime for 'apm config set/get auto-integrate'
- commands/config.py coverage: 22% → 100%

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 src/apm_cli/config.py             |  35 +++-
 tests/unit/test_config_command.py | 297 ++++++++++++++++++++++++++++++
 2 files changed, 323 insertions(+), 9 deletions(-)
 create mode 100644 tests/unit/test_config_command.py

diff --git a/src/apm_cli/config.py b/src/apm_cli/config.py
index 1415c4de4..0d4532215 100644
--- a/src/apm_cli/config.py
+++ b/src/apm_cli/config.py
@@ -1,10 +1,9 @@
 """Configuration management for APM."""
 
-import os
 import json
+import os
 from typing import Optional
 
-
 CONFIG_DIR = os.path.expanduser("~/.apm")
 CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
 
@@ -15,7 +14,7 @@ def ensure_config_exists():
     """Ensure the configuration directory and file exist."""
     if not os.path.exists(CONFIG_DIR):
         os.makedirs(CONFIG_DIR)
-        
+
     if not os.path.exists(CONFIG_FILE):
         with open(CONFIG_FILE, "w") as f:
             json.dump({"default_client": "vscode"}, f)
@@ -23,9 +22,9 @@ def ensure_config_exists():
 
 def get_config():
     """Get the current configuration.
-    
+
     Results are cached for the lifetime of the process.
-    
+
     Returns:
         dict: Current configuration.
     """
@@ -46,14 +45,14 @@ def _invalidate_config_cache():
 
 def update_config(updates):
     """Update the configuration with new values.
-    
+
     Args:
         updates (dict): Dictionary of configuration values to update.
     """
     _invalidate_config_cache()
     config = get_config()
     config.update(updates)
-    
+
     with open(CONFIG_FILE, "w") as f:
         json.dump(config, f, indent=2)
     _invalidate_config_cache()
@@ -61,7 +60,7 @@ def update_config(updates):
 
 def get_default_client():
     """Get the default MCP client.
-    
+
     Returns:
         str: Default MCP client type.
     """
@@ -70,8 +69,26 @@ def get_default_client():
 
 def set_default_client(client_type):
     """Set the default MCP client.
-    
+
     Args:
         client_type (str): Type of client to set as default.
     """
     update_config({"default_client": client_type})
+
+
+def get_auto_integrate() -> bool:
+    """Get the auto-integrate setting.
+
+    Returns:
+        bool: Whether auto-integration is enabled (default: True).
+    """
+    return get_config().get("auto_integrate", True)
+
+
+def set_auto_integrate(enabled: bool) -> None:
+    """Set the auto-integrate setting.
+
+    Args:
+        enabled: Whether to enable auto-integration.
+    """
+    update_config({"auto_integrate": enabled})
diff --git a/tests/unit/test_config_command.py b/tests/unit/test_config_command.py
new file mode 100644
index 000000000..259956275
--- /dev/null
+++ b/tests/unit/test_config_command.py
@@ -0,0 +1,297 @@
+"""Tests for the apm config command."""
+
+import os
+import sys
+import tempfile
+from pathlib import Path
+from unittest.mock import MagicMock, patch
+
+import pytest
+from click.testing import CliRunner
+
+from apm_cli.commands.config import config
+
+
+class TestConfigShow:
+    """Tests for `apm config` (show current configuration)."""
+
+    def setup_method(self):
+        self.runner = CliRunner()
+        self.original_dir = os.getcwd()
+
+    def teardown_method(self):
+        try:
+            os.chdir(self.original_dir)
+        except (FileNotFoundError, OSError):
+            pass
+
+    def test_config_show_outside_project(self):
+        """Show config when not in an APM project directory."""
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            os.chdir(tmp_dir)
+            with patch("apm_cli.commands.config.get_version", return_value="1.2.3"):
+                result = self.runner.invoke(config, [])
+        assert result.exit_code == 0
+
+    def test_config_show_inside_project(self):
+        """Show config when apm.yml is present."""
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            os.chdir(tmp_dir)
+            apm_yml = Path(tmp_dir) / "apm.yml"
+            apm_yml.write_text("name: myproject\nversion: '0.1'\n")
+            with (
+                patch("apm_cli.commands.config.get_version", return_value="1.2.3"),
+                patch(
+                    "apm_cli.commands.config._load_apm_config",
+                    return_value={
+                        "name": "myproject",
+                        "version": "0.1",
+                        "entrypoint": "main.md",
+                    },
+                ),
+            ):
+                result = self.runner.invoke(config, [])
+        assert result.exit_code == 0
+
+    def test_config_show_inside_project_with_compilation(self):
+        """Show config when apm.yml has compilation settings."""
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            os.chdir(tmp_dir)
+            apm_yml = Path(tmp_dir) / "apm.yml"
+            apm_yml.write_text("name: myproject\ncompilation:\n  output: AGENTS.md\n")
+            apm_config = {
+                "name": "myproject",
+                "version": "0.1",
+                "compilation": {
+                    "output": "AGENTS.md",
+                    "chatmode": "copilot",
+                    "resolve_links": False,
+                },
+                "dependencies": {"mcp": ["server1", "server2"]},
+            }
+            with (
+                patch("apm_cli.commands.config.get_version", return_value="1.2.3"),
+                patch(
+                    "apm_cli.commands.config._load_apm_config", return_value=apm_config
+                ),
+            ):
+                result = self.runner.invoke(config, [])
+        assert result.exit_code == 0
+
+    def test_config_show_rich_import_error_fallback(self):
+        """Fallback plain-text display when Rich (rich.table.Table) is unavailable."""
+        import rich.table
+
+        mock_table_cls = MagicMock(side_effect=ImportError("no rich"))
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            os.chdir(tmp_dir)
+            with (
+                patch("apm_cli.commands.config.get_version", return_value="0.9.0"),
+                patch.object(rich.table, "Table", side_effect=ImportError("no rich")),
+            ):
+                result = self.runner.invoke(config, [])
+        assert result.exit_code == 0
+
+    def test_config_show_fallback_inside_project(self):
+        """Fallback display inside a project directory when console/table unavailable."""
+        import rich.table
+
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            os.chdir(tmp_dir)
+            apm_yml = Path(tmp_dir) / "apm.yml"
+            apm_yml.write_text("name: proj\n")
+            apm_config = {
+                "name": "proj",
+                "version": "1.0",
+                "entrypoint": None,
+                "dependencies": {"mcp": []},
+            }
+            with (
+                patch("apm_cli.commands.config.get_version", return_value="0.9.0"),
+                patch(
+                    "apm_cli.commands.config._load_apm_config", return_value=apm_config
+                ),
+                patch.object(rich.table, "Table", side_effect=ImportError("no rich")),
+            ):
+                result = self.runner.invoke(config, [])
+        assert result.exit_code == 0
+
+
+class TestConfigSet:
+    """Tests for `apm config set <key> <value>`."""
+
+    def setup_method(self):
+        self.runner = CliRunner()
+
+    def test_set_auto_integrate_true(self):
+        """Enable auto-integration."""
+        with (
+            patch("apm_cli.config.set_auto_integrate") as mock_set,
+            patch("apm_cli.commands.config._rich_success"),
+        ):
+            result = self.runner.invoke(config, ["set", "auto-integrate", "true"])
+        assert result.exit_code == 0
+        mock_set.assert_called_once_with(True)
+
+    def test_set_auto_integrate_yes(self):
+        """Enable auto-integration with 'yes' alias."""
+        with (
+            patch("apm_cli.config.set_auto_integrate") as mock_set,
+            patch("apm_cli.commands.config._rich_success"),
+        ):
+            result = self.runner.invoke(config, ["set", "auto-integrate", "yes"])
+        assert result.exit_code == 0
+        mock_set.assert_called_once_with(True)
+
+    def test_set_auto_integrate_one(self):
+        """Enable auto-integration with '1' alias."""
+        with (
+            patch("apm_cli.config.set_auto_integrate") as mock_set,
+            patch("apm_cli.commands.config._rich_success"),
+        ):
+            result = self.runner.invoke(config, ["set", "auto-integrate", "1"])
+        assert result.exit_code == 0
+        mock_set.assert_called_once_with(True)
+
+    def test_set_auto_integrate_false(self):
+        """Disable auto-integration."""
+        with (
+            patch("apm_cli.config.set_auto_integrate") as mock_set,
+            patch("apm_cli.commands.config._rich_success"),
+        ):
+            result = self.runner.invoke(config, ["set", "auto-integrate", "false"])
+        assert result.exit_code == 0
+        mock_set.assert_called_once_with(False)
+
+    def test_set_auto_integrate_no(self):
+        """Disable auto-integration with 'no' alias."""
+        with (
+            patch("apm_cli.config.set_auto_integrate") as mock_set,
+            patch("apm_cli.commands.config._rich_success"),
+        ):
+            result = self.runner.invoke(config, ["set", "auto-integrate", "no"])
+        assert result.exit_code == 0
+        mock_set.assert_called_once_with(False)
+
+    def test_set_auto_integrate_zero(self):
+        """Disable auto-integration with '0' alias."""
+        with (
+            patch("apm_cli.config.set_auto_integrate") as mock_set,
+            patch("apm_cli.commands.config._rich_success"),
+        ):
+            result = self.runner.invoke(config, ["set", "auto-integrate", "0"])
+        assert result.exit_code == 0
+        mock_set.assert_called_once_with(False)
+
+    def test_set_auto_integrate_invalid_value(self):
+        """Reject an invalid value for auto-integrate."""
+        with patch("apm_cli.commands.config._rich_error"):
+            result = self.runner.invoke(config, ["set", "auto-integrate", "maybe"])
+        assert result.exit_code == 1
+
+    def test_set_unknown_key(self):
+        """Reject an unknown configuration key."""
+        with (
+            patch("apm_cli.commands.config._rich_error"),
+            patch("apm_cli.commands.config._rich_info"),
+        ):
+            result = self.runner.invoke(config, ["set", "nonexistent", "value"])
+        assert result.exit_code == 1
+
+    def test_set_auto_integrate_case_insensitive(self):
+        """Value comparison is case-insensitive."""
+        with (
+            patch("apm_cli.config.set_auto_integrate") as mock_set,
+            patch("apm_cli.commands.config._rich_success"),
+        ):
+            result = self.runner.invoke(config, ["set", "auto-integrate", "TRUE"])
+        assert result.exit_code == 0
+        mock_set.assert_called_once_with(True)
+
+
+class TestConfigGet:
+    """Tests for `apm config get [key]`."""
+
+    def setup_method(self):
+        self.runner = CliRunner()
+
+    def test_get_auto_integrate(self):
+        """Get the auto-integrate setting."""
+        with patch("apm_cli.config.get_auto_integrate", return_value=True):
+            result = self.runner.invoke(config, ["get", "auto-integrate"])
+        assert result.exit_code == 0
+        assert "auto-integrate: True" in result.output
+
+    def test_get_auto_integrate_disabled(self):
+        """Get auto-integrate when disabled."""
+        with patch("apm_cli.config.get_auto_integrate", return_value=False):
+            result = self.runner.invoke(config, ["get", "auto-integrate"])
+        assert result.exit_code == 0
+        assert "auto-integrate: False" in result.output
+
+    def test_get_unknown_key(self):
+        """Reject an unknown key."""
+        with (
+            patch("apm_cli.commands.config._rich_error"),
+            patch("apm_cli.commands.config._rich_info"),
+        ):
+            result = self.runner.invoke(config, ["get", "nonexistent"])
+        assert result.exit_code == 1
+
+    def test_get_all_config(self):
+        """Show all config when no key is provided."""
+        fake_config = {"auto_integrate": True, "default_client": "vscode"}
+        with (
+            patch("apm_cli.config.get_config", return_value=fake_config),
+            patch("apm_cli.commands.config._rich_info"),
+        ):
+            result = self.runner.invoke(config, ["get"])
+        assert result.exit_code == 0
+        assert "auto-integrate: True" in result.output
+
+    def test_get_all_config_unknown_key_passthrough(self):
+        """Unknown config keys are shown as-is."""
+        fake_config = {"some_other_key": "value"}
+        with (
+            patch("apm_cli.config.get_config", return_value=fake_config),
+            patch("apm_cli.commands.config._rich_info"),
+        ):
+            result = self.runner.invoke(config, ["get"])
+        assert result.exit_code == 0
+        assert "some_other_key: value" in result.output
+
+
+class TestAutoIntegrateFunctions:
+    """Tests for get_auto_integrate and set_auto_integrate in apm_cli.config."""
+
+    def test_get_auto_integrate_default(self):
+        """Default value is True when not set."""
+        import apm_cli.config as cfg_module
+
+        with patch.object(cfg_module, "get_config", return_value={}):
+            assert cfg_module.get_auto_integrate() is True
+
+    def test_get_auto_integrate_false(self):
+        """Returns False when set to False."""
+        import apm_cli.config as cfg_module
+
+        with patch.object(
+            cfg_module, "get_config", return_value={"auto_integrate": False}
+        ):
+            assert cfg_module.get_auto_integrate() is False
+
+    def test_set_auto_integrate_calls_update_config(self):
+        """set_auto_integrate delegates to update_config."""
+        import apm_cli.config as cfg_module
+
+        with patch.object(cfg_module, "update_config") as mock_update:
+            cfg_module.set_auto_integrate(True)
+            mock_update.assert_called_once_with({"auto_integrate": True})
+
+    def test_set_auto_integrate_false_calls_update_config(self):
+        """set_auto_integrate(False) passes False to update_config."""
+        import apm_cli.config as cfg_module
+
+        with patch.object(cfg_module, "update_config") as mock_update:
+            cfg_module.set_auto_integrate(False)
+            mock_update.assert_called_once_with({"auto_integrate": False})

From 630835e44a2dbc602d993b09c68fb8883f3a41a0 Mon Sep 17 00:00:00 2001
From: danielmeppiel <dmeppiel@microsoft.com>
Date: Sun, 22 Mar 2026 17:24:57 +0100
Subject: [PATCH 2/2] fix(tests): update config command test mocks for
 CommandLogger pattern

The auth overhaul (#393) replaced _rich_success/_rich_error/_rich_info
with CommandLogger methods. Tests now work without mocking these
removed functions since CliRunner captures output naturally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 tests/unit/test_config_command.py | 60 +++++++------------------------
 1 file changed, 12 insertions(+), 48 deletions(-)

diff --git a/tests/unit/test_config_command.py b/tests/unit/test_config_command.py
index 259956275..3801becbd 100644
--- a/tests/unit/test_config_command.py
+++ b/tests/unit/test_config_command.py
@@ -125,85 +125,59 @@ def setup_method(self):
 
     def test_set_auto_integrate_true(self):
         """Enable auto-integration."""
-        with (
-            patch("apm_cli.config.set_auto_integrate") as mock_set,
-            patch("apm_cli.commands.config._rich_success"),
-        ):
+        with patch("apm_cli.config.set_auto_integrate") as mock_set:
             result = self.runner.invoke(config, ["set", "auto-integrate", "true"])
         assert result.exit_code == 0
         mock_set.assert_called_once_with(True)
 
     def test_set_auto_integrate_yes(self):
         """Enable auto-integration with 'yes' alias."""
-        with (
-            patch("apm_cli.config.set_auto_integrate") as mock_set,
-            patch("apm_cli.commands.config._rich_success"),
-        ):
+        with patch("apm_cli.config.set_auto_integrate") as mock_set:
             result = self.runner.invoke(config, ["set", "auto-integrate", "yes"])
         assert result.exit_code == 0
         mock_set.assert_called_once_with(True)
 
     def test_set_auto_integrate_one(self):
         """Enable auto-integration with '1' alias."""
-        with (
-            patch("apm_cli.config.set_auto_integrate") as mock_set,
-            patch("apm_cli.commands.config._rich_success"),
-        ):
+        with patch("apm_cli.config.set_auto_integrate") as mock_set:
             result = self.runner.invoke(config, ["set", "auto-integrate", "1"])
         assert result.exit_code == 0
         mock_set.assert_called_once_with(True)
 
     def test_set_auto_integrate_false(self):
         """Disable auto-integration."""
-        with (
-            patch("apm_cli.config.set_auto_integrate") as mock_set,
-            patch("apm_cli.commands.config._rich_success"),
-        ):
+        with patch("apm_cli.config.set_auto_integrate") as mock_set:
             result = self.runner.invoke(config, ["set", "auto-integrate", "false"])
         assert result.exit_code == 0
         mock_set.assert_called_once_with(False)
 
     def test_set_auto_integrate_no(self):
         """Disable auto-integration with 'no' alias."""
-        with (
-            patch("apm_cli.config.set_auto_integrate") as mock_set,
-            patch("apm_cli.commands.config._rich_success"),
-        ):
+        with patch("apm_cli.config.set_auto_integrate") as mock_set:
             result = self.runner.invoke(config, ["set", "auto-integrate", "no"])
         assert result.exit_code == 0
         mock_set.assert_called_once_with(False)
 
     def test_set_auto_integrate_zero(self):
         """Disable auto-integration with '0' alias."""
-        with (
-            patch("apm_cli.config.set_auto_integrate") as mock_set,
-            patch("apm_cli.commands.config._rich_success"),
-        ):
+        with patch("apm_cli.config.set_auto_integrate") as mock_set:
             result = self.runner.invoke(config, ["set", "auto-integrate", "0"])
         assert result.exit_code == 0
         mock_set.assert_called_once_with(False)
 
     def test_set_auto_integrate_invalid_value(self):
         """Reject an invalid value for auto-integrate."""
-        with patch("apm_cli.commands.config._rich_error"):
-            result = self.runner.invoke(config, ["set", "auto-integrate", "maybe"])
+        result = self.runner.invoke(config, ["set", "auto-integrate", "maybe"])
         assert result.exit_code == 1
 
     def test_set_unknown_key(self):
         """Reject an unknown configuration key."""
-        with (
-            patch("apm_cli.commands.config._rich_error"),
-            patch("apm_cli.commands.config._rich_info"),
-        ):
-            result = self.runner.invoke(config, ["set", "nonexistent", "value"])
+        result = self.runner.invoke(config, ["set", "nonexistent", "value"])
         assert result.exit_code == 1
 
     def test_set_auto_integrate_case_insensitive(self):
         """Value comparison is case-insensitive."""
-        with (
-            patch("apm_cli.config.set_auto_integrate") as mock_set,
-            patch("apm_cli.commands.config._rich_success"),
-        ):
+        with patch("apm_cli.config.set_auto_integrate") as mock_set:
             result = self.runner.invoke(config, ["set", "auto-integrate", "TRUE"])
         assert result.exit_code == 0
         mock_set.assert_called_once_with(True)
@@ -231,20 +205,13 @@ def test_get_auto_integrate_disabled(self):
 
     def test_get_unknown_key(self):
         """Reject an unknown key."""
-        with (
-            patch("apm_cli.commands.config._rich_error"),
-            patch("apm_cli.commands.config._rich_info"),
-        ):
-            result = self.runner.invoke(config, ["get", "nonexistent"])
+        result = self.runner.invoke(config, ["get", "nonexistent"])
         assert result.exit_code == 1
 
     def test_get_all_config(self):
         """Show all config when no key is provided."""
         fake_config = {"auto_integrate": True, "default_client": "vscode"}
-        with (
-            patch("apm_cli.config.get_config", return_value=fake_config),
-            patch("apm_cli.commands.config._rich_info"),
-        ):
+        with patch("apm_cli.config.get_config", return_value=fake_config):
             result = self.runner.invoke(config, ["get"])
         assert result.exit_code == 0
         assert "auto-integrate: True" in result.output
@@ -252,10 +219,7 @@ def test_get_all_config(self):
     def test_get_all_config_unknown_key_passthrough(self):
         """Unknown config keys are shown as-is."""
         fake_config = {"some_other_key": "value"}
-        with (
-            patch("apm_cli.config.get_config", return_value=fake_config),
-            patch("apm_cli.commands.config._rich_info"),
-        ):
+        with patch("apm_cli.config.get_config", return_value=fake_config):
             result = self.runner.invoke(config, ["get"])
         assert result.exit_code == 0
         assert "some_other_key: value" in result.output
