diff --git a/tests/extensions/git/test_git_extension.py b/tests/extensions/git/test_git_extension.py index c4f986d17..b3fde0bd4 100644 --- a/tests/extensions/git/test_git_extension.py +++ b/tests/extensions/git/test_git_extension.py @@ -371,7 +371,7 @@ class TestCreateFeaturePowerShell: ) assert result.returncode == 0, result.stderr # pwsh may prefix warnings to stdout; find the JSON line - json_line = [l for l in result.stdout.splitlines() if l.strip().startswith("{")] + json_line = [ln for ln in result.stdout.splitlines() if ln.strip().startswith("{")] assert json_line, f"No JSON in output: {result.stdout}" data = json.loads(json_line[-1]) assert "BRANCH_NAME" in data diff --git a/tests/integrations/test_integration_agy.py b/tests/integrations/test_integration_agy.py index 245cf9b50..29ddd5132 100644 --- a/tests/integrations/test_integration_agy.py +++ b/tests/integrations/test_integration_agy.py @@ -131,5 +131,5 @@ class TestAgyHookCommandNote: ) result = AgyIntegration._inject_hook_command_note(content) lines = result.splitlines() - note_line = [l for l in lines if "replace dots" in l][0] + note_line = [ln for ln in lines if "replace dots" in ln][0] assert note_line.startswith(" "), "Note should preserve indentation" diff --git a/tests/integrations/test_integration_base_markdown.py b/tests/integrations/test_integration_base_markdown.py index 48af5bd33..47e75fefd 100644 --- a/tests/integrations/test_integration_base_markdown.py +++ b/tests/integrations/test_integration_base_markdown.py @@ -269,10 +269,10 @@ class MarkdownIntegrationTests: files.append(f"{cmd_dir}/speckit.{stem}.md") # Framework files - files.append(f".specify/integration.json") - files.append(f".specify/init-options.json") + files.append(".specify/integration.json") + files.append(".specify/init-options.json") files.append(f".specify/integrations/{self.KEY}.manifest.json") - files.append(f".specify/integrations/speckit.manifest.json") + files.append(".specify/integrations/speckit.manifest.json") if script_variant == "sh": for name in ["check-prerequisites.sh", "common.sh", "create-new-feature.sh", diff --git a/tests/integrations/test_integration_base_yaml.py b/tests/integrations/test_integration_base_yaml.py index bed0c6657..5c60f6c78 100644 --- a/tests/integrations/test_integration_base_yaml.py +++ b/tests/integrations/test_integration_base_yaml.py @@ -152,7 +152,7 @@ class YamlIntegrationTests: content = f.read_text(encoding="utf-8") # Strip trailing source comment before parsing lines = content.split("\n") - yaml_lines = [l for l in lines if not l.startswith("# Source:")] + yaml_lines = [ln for ln in lines if not ln.startswith("# Source:")] try: parsed = yaml.safe_load("\n".join(yaml_lines)) except Exception as exc: @@ -183,7 +183,7 @@ class YamlIntegrationTests: content = cmd_files[0].read_text(encoding="utf-8") # Strip source comment for parsing lines = content.split("\n") - yaml_lines = [l for l in lines if not l.startswith("# Source:")] + yaml_lines = [ln for ln in lines if not ln.startswith("# Source:")] parsed = yaml.safe_load("\n".join(yaml_lines)) assert "description:" not in parsed["prompt"] diff --git a/tests/integrations/test_integration_subcommand.py b/tests/integrations/test_integration_subcommand.py index 3a6bfe91e..fd9eada5c 100644 --- a/tests/integrations/test_integration_subcommand.py +++ b/tests/integrations/test_integration_subcommand.py @@ -3,7 +3,6 @@ import json import os -import pytest from typer.testing import CliRunner from specify_cli import app diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 213f8625d..5d75355a0 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -573,7 +573,9 @@ class TestAuthenticatedHttp: mock_opener = MagicMock() def fake_open(req, timeout=None): captured["req"] = req - resp = MagicMock(); resp.__enter__ = lambda s: s; resp.__exit__ = MagicMock(return_value=False) + resp = MagicMock() + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) return resp mock_opener.open.side_effect = fake_open with patch("specify_cli.authentication.http.urllib.request.build_opener", return_value=mock_opener): @@ -588,7 +590,9 @@ class TestAuthenticatedHttp: captured = {} def fake_urlopen(req, timeout=None): captured["req"] = req - resp = MagicMock(); resp.__enter__ = lambda s: s; resp.__exit__ = MagicMock(return_value=False) + resp = MagicMock() + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) return resp with patch("specify_cli.authentication.http.urllib.request.urlopen", side_effect=fake_urlopen): open_url("https://example.com/file.json") @@ -601,7 +605,9 @@ class TestAuthenticatedHttp: captured = {} def fake_urlopen(req, timeout=None): captured["req"] = req - resp = MagicMock(); resp.__enter__ = lambda s: s; resp.__exit__ = MagicMock(return_value=False) + resp = MagicMock() + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) return resp with patch("specify_cli.authentication.http.urllib.request.urlopen", side_effect=fake_urlopen): open_url("https://github.com/org/repo") @@ -615,12 +621,16 @@ class TestAuthenticatedHttp: self._set_config(monkeypatch, [_github_entry()]) call_count = 0 def fake_side_effect(req, timeout=None): - nonlocal call_count; call_count += 1 + nonlocal call_count + call_count += 1 if call_count == 1: raise urllib.error.HTTPError("url", 401, "Unauthorized", {}, None) - resp = MagicMock(); resp.__enter__ = lambda s: s; resp.__exit__ = MagicMock(return_value=False) + resp = MagicMock() + resp.__enter__ = lambda s: s + resp.__exit__ = MagicMock(return_value=False) return resp - mock_opener = MagicMock(); mock_opener.open.side_effect = fake_side_effect + mock_opener = MagicMock() + mock_opener.open.side_effect = fake_side_effect with patch("specify_cli.authentication.http.urllib.request.build_opener", return_value=mock_opener), \ patch("specify_cli.authentication.http.urllib.request.urlopen", side_effect=fake_side_effect): open_url("https://github.com/org/repo") @@ -692,7 +702,6 @@ class TestLoadConfigCaching: """_load_config() should call load_auth_config only once per process.""" from unittest.mock import patch from specify_cli.authentication import http as _mod - from specify_cli.authentication.config import AuthConfigEntry # Allow the real load path (no override) monkeypatch.setattr(_mod, "_config_override", None) monkeypatch.setattr(_mod, "_config_cache", None) @@ -825,8 +834,11 @@ class TestFetchLatestReleaseTagDelegation: def side_effect(req, timeout=None): captured["request"] = req body = _json.dumps({"tag_name": "v9.9.9"}).encode() - resp = MagicMock(); resp.read.return_value = body - cm = MagicMock(); cm.__enter__.return_value = resp; cm.__exit__.return_value = False + resp = MagicMock() + resp.read.return_value = body + cm = MagicMock() + cm.__enter__.return_value = resp + cm.__exit__.return_value = False return cm return captured, side_effect @@ -836,7 +848,8 @@ class TestFetchLatestReleaseTagDelegation: monkeypatch.setenv("GH_TOKEN", "forwarded-sentinel") self._set_config(monkeypatch, [_github_entry()]) captured, side_effect = self._capture_request() - mock_opener = MagicMock(); mock_opener.open.side_effect = side_effect + mock_opener = MagicMock() + mock_opener.open.side_effect = side_effect with patch("specify_cli.authentication.http.urllib.request.build_opener", return_value=mock_opener): _fetch_latest_release_tag() assert captured["request"].get_header("Authorization") == "Bearer forwarded-sentinel" diff --git a/tests/test_commands_package.py b/tests/test_commands_package.py index 2e51ff497..505f28e6c 100644 --- a/tests/test_commands_package.py +++ b/tests/test_commands_package.py @@ -29,7 +29,7 @@ def test_agent_config_importable(): def test_agent_config_re_exported_from_init(): - from specify_cli import AGENT_CONFIG, AI_ASSISTANT_ALIASES, AI_ASSISTANT_HELP, SCRIPT_TYPE_CHOICES + from specify_cli import AGENT_CONFIG, SCRIPT_TYPE_CHOICES assert isinstance(AGENT_CONFIG, dict) assert "sh" in SCRIPT_TYPE_CHOICES diff --git a/tests/test_console_imports.py b/tests/test_console_imports.py index 7828d4a97..2ae328732 100644 --- a/tests/test_console_imports.py +++ b/tests/test_console_imports.py @@ -2,12 +2,7 @@ from specify_cli import ( console, StepTracker, - get_key, select_with_arrows, - BannerGroup, - show_banner, - BANNER, - TAGLINE, ) diff --git a/tests/test_extension_skills.py b/tests/test_extension_skills.py index 886eb2d25..b5863b65f 100644 --- a/tests/test_extension_skills.py +++ b/tests/test_extension_skills.py @@ -21,7 +21,6 @@ from pathlib import Path from specify_cli.extensions import ( ExtensionManifest, ExtensionManager, - ExtensionError, ) @@ -241,7 +240,7 @@ class TestExtensionSkillRegistration: """Skills should be created when ai_skills is enabled.""" project_dir, skills_dir = skills_project manager = ExtensionManager(project_dir) - manifest = manager.install_from_directory( + manager.install_from_directory( extension_dir, "0.1.0", register_commands=False ) @@ -784,7 +783,7 @@ class TestExtensionSkillEdgeCases: ) manager = ExtensionManager(project_dir) - manifest = manager.install_from_directory( + manager.install_from_directory( ext_dir, "0.1.0", register_commands=False ) @@ -803,7 +802,7 @@ class TestExtensionSkillEdgeCases: ext_dir = _create_extension_dir(temp_dir, ext_id="test-ext") manager = ExtensionManager(project_dir) - manifest = manager.install_from_directory( + manager.install_from_directory( ext_dir, "0.1.0", register_commands=False ) @@ -819,10 +818,10 @@ class TestExtensionSkillEdgeCases: ext_dir_b = _create_extension_dir(temp_dir, ext_id="ext-b") manager = ExtensionManager(project_dir) - manifest_a = manager.install_from_directory( + manager.install_from_directory( ext_dir_a, "0.1.0", register_commands=False ) - manifest_b = manager.install_from_directory( + manager.install_from_directory( ext_dir_b, "0.1.0", register_commands=False ) @@ -880,7 +879,7 @@ class TestExtensionSkillEdgeCases: manager = ExtensionManager(project_dir) # Should not raise - manifest = manager.install_from_directory( + manager.install_from_directory( ext_dir, "0.1.0", register_commands=False ) diff --git a/tests/test_setup_tasks.py b/tests/test_setup_tasks.py index f2e10d8b0..751e39918 100644 --- a/tests/test_setup_tasks.py +++ b/tests/test_setup_tasks.py @@ -123,7 +123,7 @@ def test_setup_tasks_bash_core_template_resolved(tasks_repo: Path) -> None: setup-tasks.sh --json should exit 0 and return an absolute, existing TASKS_TEMPLATE path pointing to the core template. """ - feat = _minimal_feature(tasks_repo) + _minimal_feature(tasks_repo) script = tasks_repo / ".specify" / "scripts" / "bash" / "setup-tasks.sh" result = subprocess.run( @@ -150,7 +150,7 @@ def test_setup_tasks_bash_override_wins(tasks_repo: Path) -> None: When an override exists at .specify/templates/overrides/tasks-template.md, setup-tasks.sh --json must return the override path, not the core path. """ - feat = _minimal_feature(tasks_repo) + _minimal_feature(tasks_repo) # Create the override overrides_dir = tasks_repo / ".specify" / "templates" / "overrides" @@ -187,7 +187,7 @@ def test_setup_tasks_bash_extension_wins_over_core(tasks_repo: Path) -> None: When an extension template exists, setup-tasks.sh --json must resolve tasks-template.md from the extension before falling back to the core path. """ - feat = _minimal_feature(tasks_repo) + _minimal_feature(tasks_repo) # FIX: real extension layout is .specify/extensions//templates/.md extension_dir = ( @@ -225,7 +225,7 @@ def test_setup_tasks_bash_preset_wins_over_extension(tasks_repo: Path) -> None: When both preset and extension templates exist, setup-tasks.sh --json must resolve the preset path because presets outrank extensions. """ - feat = _minimal_feature(tasks_repo) + _minimal_feature(tasks_repo) # FIX: real extension layout is .specify/extensions//templates/.md extension_dir = ( @@ -269,7 +269,7 @@ def test_setup_tasks_bash_preset_priority_order(tasks_repo: Path) -> None: When two presets both provide tasks-template.md, the one listed first in .specify/presets/.registry wins. """ - feat = _minimal_feature(tasks_repo) + _minimal_feature(tasks_repo) # resolve_template reads .specify/presets/.registry as a JSON object with a # "presets" map where each entry has a numeric "priority" (lower = higher @@ -329,7 +329,7 @@ def test_setup_tasks_bash_missing_template_errors(tasks_repo: Path) -> None: When tasks-template.md is absent from all locations, setup-tasks.sh must exit non-zero and print a helpful ERROR message to stderr. """ - feat = _minimal_feature(tasks_repo) + _minimal_feature(tasks_repo) # Remove the core template so no template exists anywhere core = tasks_repo / ".specify" / "templates" / "tasks-template.md" @@ -429,7 +429,7 @@ def test_setup_tasks_ps_core_template_resolved(tasks_repo: Path) -> None: setup-tasks.ps1 -Json should exit 0 and return an absolute, existing TASKS_TEMPLATE path. """ - feat = _minimal_feature(tasks_repo) + _minimal_feature(tasks_repo) script = tasks_repo / ".specify" / "scripts" / "powershell" / "setup-tasks.ps1" exe = "pwsh" if HAS_PWSH else _POWERSHELL @@ -457,7 +457,7 @@ def test_setup_tasks_ps_override_wins(tasks_repo: Path) -> None: When an override exists at .specify/templates/overrides/tasks-template.md, setup-tasks.ps1 -Json must return the override path, not the core path. """ - feat = _minimal_feature(tasks_repo) + _minimal_feature(tasks_repo) overrides_dir = tasks_repo / ".specify" / "templates" / "overrides" overrides_dir.mkdir(parents=True, exist_ok=True) @@ -493,7 +493,7 @@ def test_setup_tasks_ps_missing_template_errors(tasks_repo: Path) -> None: When tasks-template.md is absent from all locations, setup-tasks.ps1 must exit non-zero and write a helpful error to stderr. """ - feat = _minimal_feature(tasks_repo) + _minimal_feature(tasks_repo) core = tasks_repo / ".specify" / "templates" / "tasks-template.md" core.unlink() diff --git a/tests/test_timestamp_branches.py b/tests/test_timestamp_branches.py index ffd7b7032..3f6d8bd2a 100644 --- a/tests/test_timestamp_branches.py +++ b/tests/test_timestamp_branches.py @@ -923,7 +923,7 @@ class TestDryRun: assert re.match(r"^\d{8}-\d{6}-ts-feat$", branch), f"unexpected: {branch}" # Verify no side effects branches = subprocess.run( - ["git", "branch", "--list", f"*ts-feat*"], + ["git", "branch", "--list", "*ts-feat*"], cwd=git_repo, capture_output=True, text=True, diff --git a/tests/test_utils_assets_imports.py b/tests/test_utils_assets_imports.py index 8ccacd75d..df79e3e01 100644 --- a/tests/test_utils_assets_imports.py +++ b/tests/test_utils_assets_imports.py @@ -1,7 +1,6 @@ """Regression guard: utility and asset symbols importable from specify_cli.""" from specify_cli import ( - run_command, check_tool, is_git_repo, init_git_repo, - handle_vscode_settings, merge_json_files, + check_tool, is_git_repo, merge_json_files, get_speckit_version, CLAUDE_LOCAL_PATH, CLAUDE_NPM_LOCAL_PATH, ) diff --git a/tests/test_version_imports.py b/tests/test_version_imports.py index e242a93d2..0360b6cb3 100644 --- a/tests/test_version_imports.py +++ b/tests/test_version_imports.py @@ -23,7 +23,6 @@ def test_version_symbols_available_from_star_import(): def test_version_module_symbols_directly_importable(): from specify_cli._version import ( - GITHUB_API_LATEST, _fetch_latest_release_tag, _get_installed_version, _is_newer,