diff --git a/src/specify_cli/extensions/_commands.py b/src/specify_cli/extensions/_commands.py index 6821419b30..6830e35aa9 100644 --- a/src/specify_cli/extensions/_commands.py +++ b/src/specify_cli/extensions/_commands.py @@ -1562,7 +1562,14 @@ def extension_set_priority( raw_priority = metadata.get("priority") # Only skip if the stored value is already a valid int equal to requested priority # This ensures corrupted values (e.g., "high") get repaired even when setting to default (10) - if isinstance(raw_priority, int) and raw_priority == priority: + # A bool is an int in Python (isinstance(True, int) is True), so exclude it explicitly — + # mirroring normalize_priority's bool guard — otherwise a corrupted True/False priority + # equals 1/0 here and is never repaired. + if ( + isinstance(raw_priority, int) + and not isinstance(raw_priority, bool) + and raw_priority == priority + ): console.print(f"[yellow]Extension '{_escape_markup(str(display_name))}' already has priority {priority}[/yellow]") raise typer.Exit(0) diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 4a07a21053..07def0c7ed 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -6270,6 +6270,42 @@ def test_set_priority_same_value_no_change(self, extension_dir, project_dir): plain = strip_ansi(result.output) assert "already has priority 5" in plain + def test_set_priority_repairs_corrupted_bool(self, extension_dir, project_dir): + """A corrupted boolean priority must be repaired, not skipped. + + ``isinstance(True, int)`` is True and ``True == 1`` in Python, so a + stored ``True`` priority would short-circuit the ``already has + priority 1`` skip path and never get rewritten to a real int — + contradicting the comment that promises corrupted values are + repaired. The guard must exclude bools (like normalize_priority). + """ + from typer.testing import CliRunner + from unittest.mock import patch + from specify_cli import app + + runner = CliRunner() + + manager = ExtensionManager(project_dir) + manager.install_from_directory( + extension_dir, "0.1.0", register_commands=False, priority=5 + ) + # Inject a corrupted boolean priority (True == 1). + manager.registry.update("test-ext", {"priority": True}) + + with patch.object(Path, "cwd", return_value=project_dir): + result = runner.invoke(app, ["extension", "set-priority", "test-ext", "1"]) + + assert result.exit_code == 0, result.output + plain = strip_ansi(result.output) + # The corrupted bool must be repaired, not reported as already-set. + assert "already has priority" not in plain + assert "priority changed" in plain + + # The stored value is now a real int, not a bool. + reloaded = ExtensionManager(project_dir).registry.get("test-ext") + assert reloaded["priority"] == 1 + assert not isinstance(reloaded["priority"], bool) + def test_set_priority_invalid_value(self, extension_dir, project_dir): """Test set-priority rejects invalid priority values.""" from typer.testing import CliRunner