fix(extensions): set-priority repairs corrupted boolean priority (#3268)

The set-priority skip guard 'isinstance(raw_priority, int) and
raw_priority == priority' treats a stored boolean as a match because
isinstance(True, int) is True and True == 1 (False == 0). So a corrupted
boolean priority short-circuits to 'already has priority N' and is never
rewritten to a real int — contradicting the adjacent comment that
promises corrupted values get repaired. Exclude bools explicitly,
mirroring normalize_priority's own bool guard.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ali jawwad
2026-07-13 19:41:47 +05:00
committed by GitHub
parent f6f3540409
commit 903d707d21
2 changed files with 44 additions and 1 deletions

View File

@@ -1566,7 +1566,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)

View File

@@ -6424,6 +6424,42 @@ class TestExtensionPriorityCLI:
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