diff --git a/tests/extensions/git/test_git_extension.py b/tests/extensions/git/test_git_extension.py index c4f986d177..b3fde0bd4a 100644 --- a/tests/extensions/git/test_git_extension.py +++ b/tests/extensions/git/test_git_extension.py @@ -371,7 +371,7 @@ def test_no_git_graceful_degradation(self, tmp_path: Path): ) 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 245cf9b50c..29ddd51321 100644 --- a/tests/integrations/test_integration_agy.py +++ b/tests/integrations/test_integration_agy.py @@ -131,5 +131,5 @@ def test_hook_note_preserves_indentation(self): ) 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 48af5bd33b..47e75fefd8 100644 --- a/tests/integrations/test_integration_base_markdown.py +++ b/tests/integrations/test_integration_base_markdown.py @@ -269,10 +269,10 @@ def _expected_files(self, script_variant: str) -> list[str]: 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 bed0c66570..5c60f6c788 100644 --- a/tests/integrations/test_integration_base_yaml.py +++ b/tests/integrations/test_integration_base_yaml.py @@ -152,7 +152,7 @@ def test_yaml_is_valid(self, tmp_path): 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 @@ def test_yaml_prompt_excludes_frontmatter(self, tmp_path, monkeypatch): 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 3a6bfe91ec..fd9eada5cc 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 213f8625d8..5d75355a09 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -573,7 +573,9 @@ def test_open_url_attaches_auth_for_matching_host(self, monkeypatch): 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 @@ def test_open_url_no_auth_for_non_matching_host(self, monkeypatch): 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 @@ def test_open_url_no_auth_when_no_config(self, monkeypatch): 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 @@ def test_open_url_falls_through_on_401(self, monkeypatch): 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 @@ def test_config_cached_after_first_load(self, monkeypatch): """_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 @@ def _capture_request(self): 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 @@ def test_gh_token_forwarded_when_configured(self, monkeypatch): 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 2e51ff4974..505f28e6cb 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 7828d4a97e..2ae328732e 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 886eb2d25e..b5863b65fe 100644 --- a/tests/test_extension_skills.py +++ b/tests/test_extension_skills.py @@ -21,7 +21,6 @@ from specify_cli.extensions import ( ExtensionManifest, ExtensionManager, - ExtensionError, ) @@ -241,7 +240,7 @@ def test_skills_created_when_ai_skills_active(self, skills_project, extension_di """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 @@ def test_command_without_frontmatter(self, skills_project, temp_dir): ) 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 @@ def test_gemini_agent_skills(self, project_dir, temp_dir): 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 @@ def test_multiple_extensions_independent_skills(self, skills_project, temp_dir): 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 @@ def test_malformed_frontmatter_handled(self, skills_project, temp_dir): 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 f2e10d8b0f..751e399182 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 ffd7b70323..3f6d8bd2a8 100644 --- a/tests/test_timestamp_branches.py +++ b/tests/test_timestamp_branches.py @@ -923,7 +923,7 @@ def test_dry_run_with_timestamp(self, git_repo: Path): 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 8ccacd75d4..df79e3e018 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 e242a93d25..0360b6cb3a 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,