From e17ba3e2cba4235c5843f4cfd5e6a293bd875bb7 Mon Sep 17 00:00:00 2001 From: Gurney Buchanan Date: Tue, 21 Apr 2026 08:47:58 -0400 Subject: [PATCH 1/2] Fix false positives in DependencyPinningAssessor for multi-module, Terraform, and empty repos - Add recursive lock file search for multi-module repos (Go workspaces, monorepos) - Add .terraform.lock.hcl to strict lock files and score versions.tf constraints - Return not_applicable for repos with no dependency manifests - Exclude vendor/, node_modules/, .venv/ from recursive search - Add 8 new test cases covering all three false positive categories --- src/agentready/assessors/stub_assessors.py | 184 ++++++++++++++- tests/unit/test_assessors_stub.py | 257 ++++++++++++++++++++- 2 files changed, 424 insertions(+), 17 deletions(-) diff --git a/src/agentready/assessors/stub_assessors.py b/src/agentready/assessors/stub_assessors.py index e6661bba..f6061d02 100644 --- a/src/agentready/assessors/stub_assessors.py +++ b/src/agentready/assessors/stub_assessors.py @@ -43,6 +43,121 @@ def attribute(self) -> Attribute: default_weight=0.05, ) + # Directories to exclude from recursive lock file searches + _EXCLUDED_DIRS = { + "vendor", "node_modules", ".venv", "venv", + "__pycache__", ".git", ".terraform", + } + + # Package manifests that indicate a repo actually uses dependencies + _DEPENDENCY_MANIFESTS = [ + "go.mod", "package.json", "pyproject.toml", "setup.py", + "setup.cfg", "Gemfile", "Cargo.toml", "requirements.txt", + "Pipfile", "pom.xml", "build.gradle", "build.gradle.kts", + "composer.json", "mix.exs", "pubspec.yaml", + "Dockerfile", "Containerfile", + ] + + def _rglob_filtered(self, root: Path, filename: str) -> list[Path]: + """Recursively search for filename, excluding common non-source dirs.""" + matches = [] + for match in root.rglob(filename): + if not any(part in self._EXCLUDED_DIRS for part in match.parts): + matches.append(match) + return matches + + def _has_any_dependency_manifest(self, repository: Repository) -> bool: + """Check whether the repo uses any package manager at all.""" + for manifest in self._DEPENDENCY_MANIFESTS: + if (repository.path / manifest).exists(): + return True + if self._rglob_filtered(repository.path, manifest): + return True + return False + + def _score_terraform_constraints(self, repository: Repository) -> Finding | None: + """Score Terraform repos based on versions.tf provider constraints.""" + import re + + tf_version_files = self._rglob_filtered(repository.path, "versions.tf") + tf_lock_files = self._rglob_filtered( + repository.path, ".terraform.lock.hcl" + ) + + if not tf_version_files and not tf_lock_files: + return None + + if tf_lock_files: + rel_paths = [ + str(p.relative_to(repository.path)) for p in tf_lock_files + ] + return Finding( + attribute=self.attribute, + status="pass", + score=100.0, + measured_value=", ".join(rel_paths), + threshold="lock file with pinned versions", + evidence=[ + f"Found Terraform lock file(s): {', '.join(rel_paths)}" + ], + remediation=None, + error_message=None, + ) + + pinned = 0 + ranged = 0 + for vf in tf_version_files: + try: + content = vf.read_text() + for m in re.finditer( + r'version\s*=\s*"([^"]+)"', content + ): + ver = m.group(1) + if any(op in ver for op in [">", "<", "~", "!="]): + ranged += 1 + else: + pinned += 1 + except OSError: + pass + + total = pinned + ranged + if total == 0: + return None + + score = (pinned / total) * 100 if pinned > 0 else 35.0 + score = max(score, 35.0) + status = "pass" if score >= 75 else "fail" + + return Finding( + attribute=self.attribute, + status=status, + score=score, + measured_value=f"{len(tf_version_files)} versions.tf files", + threshold="lock file with pinned versions", + evidence=[ + f"Found {len(tf_version_files)} versions.tf file(s) " + f"with provider constraints", + f"{pinned} exact pins, {ranged} range constraints", + ], + remediation=Remediation( + summary="Pin Terraform providers to exact versions", + steps=[ + "Run 'terraform providers lock' to generate " + ".terraform.lock.hcl", + "Or pin exact versions in versions.tf " + '(e.g., version = "6.0.0" not ">= 6.0")', + ], + tools=["terraform"], + commands=[ + "terraform providers lock " + "-platform=linux_amd64", + ], + examples=[], + citations=[], + ) if status == "fail" else None, + error_message=None, + ) + def assess(self, repository: Repository) -> Finding: """Check for dependency lock files and validate version pinning quality.""" # Language-specific lock files (auto-managed, always have exact versions) @@ -57,22 +172,58 @@ def assess(self, repository: Repository) -> Finding: "Gemfile.lock", # Ruby "go.sum", # Go "pdm.lock", # pdm + ".terraform.lock.hcl", # Terraform ] # Manual lock files (need validation) manual_lock_files = ["requirements.txt"] # Python pip - found_strict = [f for f in strict_lock_files if (repository.path / f).exists()] - found_manual = [f for f in manual_lock_files if (repository.path / f).exists()] + # 1. Check root-level lock files + found_strict = [ + f for f in strict_lock_files + if (repository.path / f).exists() + ] + found_manual = [ + f for f in manual_lock_files + if (repository.path / f).exists() + ] + + # 2. Recursive fallback for multi-module repos (e.g. Go workspaces, + # monorepos with per-package lock files) + if not found_strict: + for f in strict_lock_files: + matches = self._rglob_filtered(repository.path, f) + if matches: + found_strict.extend( + str(m.relative_to(repository.path)) + for m in matches + ) - # Check subdirectories for Go monorepos (go.sum in module dirs) - if "go.sum" not in found_strict: - for gosum in repository.path.rglob("go.sum"): - if "vendor" in gosum.parts: - continue - found_strict.append(str(gosum.relative_to(repository.path))) + if not found_manual and not found_strict: + for f in manual_lock_files: + matches = self._rglob_filtered(repository.path, f) + if matches: + found_manual.extend( + str(m.relative_to(repository.path)) + for m in matches + ) + # 3. If nothing found, check for special ecosystems and edge cases if not found_strict and not found_manual: + # 3a. Terraform repos: score based on versions.tf constraints + tf_finding = self._score_terraform_constraints(repository) + if tf_finding is not None: + return tf_finding + + # 3b. Repos with no dependency manifests at all → not applicable + if not self._has_any_dependency_manifest(repository): + return Finding.not_applicable( + self.attribute, + reason="No dependency manifests found " + "(no package manager in use)", + ) + + # 3c. Has manifests but no lock files → fail return Finding( attribute=self.attribute, status="fail", @@ -83,15 +234,24 @@ def assess(self, repository: Repository) -> Finding: remediation=Remediation( summary="Add lock file for dependency reproducibility", steps=[ - "For npm: run 'npm install' (generates package-lock.json)", - "For Python: use 'pip freeze > requirements.txt' or poetry", - "For Ruby: run 'bundle install' (generates Gemfile.lock)", + "For Go: run 'go mod tidy' (generates go.sum)", + "For npm: run 'npm install' " + "(generates package-lock.json)", + "For Python: use 'pip freeze > requirements.txt'" + " or poetry", + "For Ruby: run 'bundle install' " + "(generates Gemfile.lock)", + "For Terraform: run 'terraform providers lock'" + " (generates .terraform.lock.hcl)", ], - tools=["npm", "pip", "poetry", "bundler"], + tools=["go", "npm", "pip", "poetry", "bundler", + "terraform"], commands=[ + "go mod tidy # Go", "npm install # npm", "pip freeze > requirements.txt # Python", "poetry lock # Python with Poetry", + "terraform providers lock # Terraform", ], examples=[], citations=[], diff --git a/tests/unit/test_assessors_stub.py b/tests/unit/test_assessors_stub.py index 2854f7da..c7a2f27a 100644 --- a/tests/unit/test_assessors_stub.py +++ b/tests/unit/test_assessors_stub.py @@ -18,12 +18,13 @@ class TestDependencyPinningAssessor: """Test DependencyPinningAssessor (formerly LockFilesAssessor).""" def test_no_lock_files(self, tmp_path): - """Test that assessor fails when no lock files present.""" - # Initialize git repository - subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) + """Test that assessor fails when dep manifests exist but no lock files.""" + subprocess.run( + ["git", "init"], cwd=tmp_path, capture_output=True, check=True + ) - # Initialize git repository - subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) + # Add a dependency manifest so the repo isn't "not_applicable" + (tmp_path / "pyproject.toml").write_text("[project]\nname = 'test'\n") repo = Repository( path=tmp_path, @@ -231,6 +232,252 @@ def test_backward_compatibility_alias(self): assert LockFilesAssessor is DependencyPinningAssessor + def test_subdirectory_go_sum_multi_module(self, tmp_path): + """Test that go.sum in subdirectories is detected for multi-module repos.""" + subprocess.run( + ["git", "init"], cwd=tmp_path, capture_output=True, check=True + ) + + # Create sub-module with go.mod + go.sum (no root-level lock file) + sub = tmp_path / "tools" / "mytool" + sub.mkdir(parents=True) + (sub / "go.mod").write_text("module example.com/tools/mytool\n\ngo 1.21\n") + (sub / "go.sum").write_text("github.com/pkg/errors v0.9.1 h1:abc=\n") + + repo = Repository( + path=tmp_path, + name="test-repo", + url=None, + branch="main", + commit_hash="abc123", + languages={"Go": 10}, + total_files=5, + total_lines=100, + ) + + assessor = DependencyPinningAssessor() + finding = assessor.assess(repo) + + assert finding.status == "pass" + assert finding.score == 100.0 + assert "go.sum" in finding.measured_value + + def test_subdirectory_lock_excludes_vendor(self, tmp_path): + """Test that lock files inside vendor/ are ignored.""" + subprocess.run( + ["git", "init"], cwd=tmp_path, capture_output=True, check=True + ) + + # go.mod at root so the repo is recognized as having deps + (tmp_path / "go.mod").write_text("module example.com/test\n\ngo 1.21\n") + + # go.sum only inside vendor/ — should NOT count + vendor = tmp_path / "vendor" / "github.com" / "pkg" + vendor.mkdir(parents=True) + (vendor / "go.sum").write_text("hash\n") + + repo = Repository( + path=tmp_path, + name="test-repo", + url=None, + branch="main", + commit_hash="abc123", + languages={"Go": 10}, + total_files=5, + total_lines=100, + ) + + assessor = DependencyPinningAssessor() + finding = assessor.assess(repo) + + # Should fail — vendor lock files don't count + assert finding.status == "fail" + assert finding.score == 0.0 + + def test_no_dependency_manifests_returns_not_applicable(self, tmp_path): + """Test that repos with no code/deps return not_applicable.""" + subprocess.run( + ["git", "init"], cwd=tmp_path, capture_output=True, check=True + ) + + # Only README + LICENSE — no package manager files at all + (tmp_path / "README.md").write_text("# My Project\n") + (tmp_path / "LICENSE").write_text("MIT\n") + + repo = Repository( + path=tmp_path, + name="test-repo", + url=None, + branch="main", + commit_hash="abc123", + languages={}, + total_files=2, + total_lines=2, + ) + + assessor = DependencyPinningAssessor() + finding = assessor.assess(repo) + + assert finding.status == "not_applicable" + + def test_has_go_mod_but_no_lock_still_fails(self, tmp_path): + """Test that having go.mod without go.sum still fails.""" + subprocess.run( + ["git", "init"], cwd=tmp_path, capture_output=True, check=True + ) + + (tmp_path / "go.mod").write_text( + "module example.com/test\n\ngo 1.21\n\n" + "require github.com/pkg/errors v0.9.1\n" + ) + + repo = Repository( + path=tmp_path, + name="test-repo", + url=None, + branch="main", + commit_hash="abc123", + languages={"Go": 10}, + total_files=5, + total_lines=100, + ) + + assessor = DependencyPinningAssessor() + finding = assessor.assess(repo) + + assert finding.status == "fail" + assert finding.score == 0.0 + + def test_terraform_versions_tf_range_constraints(self, tmp_path): + """Test Terraform repos with >= range constraints in versions.tf.""" + subprocess.run( + ["git", "init"], cwd=tmp_path, capture_output=True, check=True + ) + + tf_dir = tmp_path / "terraform" / "modules" / "vpc" + tf_dir.mkdir(parents=True) + (tf_dir / "versions.tf").write_text( + 'terraform {\n' + ' required_providers {\n' + ' aws = {\n' + ' source = "hashicorp/aws"\n' + ' version = ">= 6.0"\n' + ' }\n' + ' }\n' + '}\n' + ) + + repo = Repository( + path=tmp_path, + name="test-repo", + url=None, + branch="main", + commit_hash="abc123", + languages={"HCL": 10}, + total_files=5, + total_lines=50, + ) + + assessor = DependencyPinningAssessor() + finding = assessor.assess(repo) + + # Should score > 0 (partial credit for having constraints) + assert finding.score >= 35 + assert "versions.tf" in finding.measured_value + + def test_terraform_versions_tf_exact_pins(self, tmp_path): + """Test Terraform repos with exact version pins in versions.tf.""" + subprocess.run( + ["git", "init"], cwd=tmp_path, capture_output=True, check=True + ) + + tf_dir = tmp_path / "terraform" + tf_dir.mkdir() + (tf_dir / "versions.tf").write_text( + 'terraform {\n' + ' required_providers {\n' + ' aws = {\n' + ' source = "hashicorp/aws"\n' + ' version = "6.0.0"\n' + ' }\n' + ' }\n' + '}\n' + ) + + repo = Repository( + path=tmp_path, + name="test-repo", + url=None, + branch="main", + commit_hash="abc123", + languages={"HCL": 10}, + total_files=5, + total_lines=50, + ) + + assessor = DependencyPinningAssessor() + finding = assessor.assess(repo) + + assert finding.status == "pass" + assert finding.score == 100.0 + + def test_terraform_lock_hcl_detected(self, tmp_path): + """Test that .terraform.lock.hcl is detected as a strict lock file.""" + subprocess.run( + ["git", "init"], cwd=tmp_path, capture_output=True, check=True + ) + + (tmp_path / ".terraform.lock.hcl").write_text( + 'provider "registry.terraform.io/hashicorp/aws" {\n' + ' version = "6.0.0"\n' + '}\n' + ) + + repo = Repository( + path=tmp_path, + name="test-repo", + url=None, + branch="main", + commit_hash="abc123", + languages={"HCL": 10}, + total_files=5, + total_lines=50, + ) + + assessor = DependencyPinningAssessor() + finding = assessor.assess(repo) + + assert finding.status == "pass" + assert finding.score == 100.0 + assert ".terraform.lock.hcl" in finding.measured_value + + def test_subdirectory_requirements_txt(self, tmp_path): + """Test that requirements.txt in subdirectories is found.""" + subprocess.run( + ["git", "init"], cwd=tmp_path, capture_output=True, check=True + ) + + sub = tmp_path / "experiments" / "auth" + sub.mkdir(parents=True) + (sub / "requirements.txt").write_text("flask==3.1.1\nrequests==2.32.3\n") + + repo = Repository( + path=tmp_path, + name="test-repo", + url=None, + branch="main", + commit_hash="abc123", + languages={"Python": 10}, + total_files=5, + total_lines=50, + ) + + assessor = DependencyPinningAssessor() + finding = assessor.assess(repo) + + assert finding.status == "pass" + assert finding.score == 100.0 + class TestGitignoreAssessor: """Test GitignoreAssessor with language-specific pattern checking.""" From 35b1c3b9783f6d5b4f73feb166c27e4fa8ed52de Mon Sep 17 00:00:00 2001 From: Bill Murdock Date: Wed, 26 Aug 2026 15:42:33 -0400 Subject: [PATCH 2/2] fix: apply black/isort formatting and sync docs/attributes.md Formatting fixes requested in review, plus documentation of the new subdirectory search, Terraform fallback, and not_applicable behavior per the docs-sync convention in AGENTS.md. Co-Authored-By: Claude Fable 5 --- docs/attributes.md | 8 +- src/agentready/assessors/stub_assessors.py | 110 ++++++++++----------- tests/unit/test_assessors_stub.py | 62 +++++------- 3 files changed, 84 insertions(+), 96 deletions(-) diff --git a/docs/attributes.md b/docs/attributes.md index 33ad43ee..c0c2c2a4 100644 --- a/docs/attributes.md +++ b/docs/attributes.md @@ -510,9 +510,15 @@ Without a lock file, two installs of the same repo can get different dependency **Passes if** a recognized lock file is present (score >= 75): -- **Auto-managed lock files** (always fully pinned): `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `poetry.lock`, `Pipfile.lock`, `uv.lock`, `Cargo.lock`, `Gemfile.lock`, `go.sum` +- **Auto-managed lock files** (always fully pinned): `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `poetry.lock`, `Pipfile.lock`, `uv.lock`, `Cargo.lock`, `Gemfile.lock`, `go.sum`, `pdm.lock`, `.terraform.lock.hcl` - **Manual lock files** (`requirements.txt`): validated for version pinning quality; counts `==` (pinned) vs `>=`/unpinned usage and scores proportionally +**Subdirectory search**: If no lock file exists at the repository root, the assessor searches subdirectories recursively (excluding `vendor/`, `node_modules/`, `.venv/`, `venv/`, `__pycache__/`, `.git/`, and `.terraform/`), so multi-module repos and monorepos with per-package lock files are recognized. + +**Terraform fallback**: Repos with no lock files but with `versions.tf` provider constraints are scored on pinning quality: exact pins (`version = "6.0.0"`) count toward a full score, range constraints (`>=`, `<`, `~`, `!=`) score proportionally with a floor of 35 points. + +**Not applicable**: Repos with no dependency manifests at all (no `go.mod`, `package.json`, `pyproject.toml`, `Cargo.toml`, `Gemfile`, `requirements.txt`, `Dockerfile`, etc.) return `not_applicable` instead of failing, so documentation-only or config-only repos are not penalized. + **Freshness check**: Lock files older than 6 months incur a 15-point deduction. **For `requirements.txt`**: The assessor validates version pinning quality by counting lines with `==` (exact pins) versus `>=`, `~=`, or no specifier (unpinned). Score reflects the ratio of pinned to total dependencies. diff --git a/src/agentready/assessors/stub_assessors.py b/src/agentready/assessors/stub_assessors.py index f6061d02..959198a5 100644 --- a/src/agentready/assessors/stub_assessors.py +++ b/src/agentready/assessors/stub_assessors.py @@ -45,17 +45,34 @@ def attribute(self) -> Attribute: # Directories to exclude from recursive lock file searches _EXCLUDED_DIRS = { - "vendor", "node_modules", ".venv", "venv", - "__pycache__", ".git", ".terraform", + "vendor", + "node_modules", + ".venv", + "venv", + "__pycache__", + ".git", + ".terraform", } # Package manifests that indicate a repo actually uses dependencies _DEPENDENCY_MANIFESTS = [ - "go.mod", "package.json", "pyproject.toml", "setup.py", - "setup.cfg", "Gemfile", "Cargo.toml", "requirements.txt", - "Pipfile", "pom.xml", "build.gradle", "build.gradle.kts", - "composer.json", "mix.exs", "pubspec.yaml", - "Dockerfile", "Containerfile", + "go.mod", + "package.json", + "pyproject.toml", + "setup.py", + "setup.cfg", + "Gemfile", + "Cargo.toml", + "requirements.txt", + "Pipfile", + "pom.xml", + "build.gradle", + "build.gradle.kts", + "composer.json", + "mix.exs", + "pubspec.yaml", + "Dockerfile", + "Containerfile", ] def _rglob_filtered(self, root: Path, filename: str) -> list[Path]: @@ -80,26 +97,20 @@ def _score_terraform_constraints(self, repository: Repository) -> Finding | None import re tf_version_files = self._rglob_filtered(repository.path, "versions.tf") - tf_lock_files = self._rglob_filtered( - repository.path, ".terraform.lock.hcl" - ) + tf_lock_files = self._rglob_filtered(repository.path, ".terraform.lock.hcl") if not tf_version_files and not tf_lock_files: return None if tf_lock_files: - rel_paths = [ - str(p.relative_to(repository.path)) for p in tf_lock_files - ] + rel_paths = [str(p.relative_to(repository.path)) for p in tf_lock_files] return Finding( attribute=self.attribute, status="pass", score=100.0, measured_value=", ".join(rel_paths), threshold="lock file with pinned versions", - evidence=[ - f"Found Terraform lock file(s): {', '.join(rel_paths)}" - ], + evidence=[f"Found Terraform lock file(s): {', '.join(rel_paths)}"], remediation=None, error_message=None, ) @@ -109,9 +120,7 @@ def _score_terraform_constraints(self, repository: Repository) -> Finding | None for vf in tf_version_files: try: content = vf.read_text() - for m in re.finditer( - r'version\s*=\s*"([^"]+)"', content - ): + for m in re.finditer(r'version\s*=\s*"([^"]+)"', content): ver = m.group(1) if any(op in ver for op in [">", "<", "~", "!="]): ranged += 1 @@ -139,22 +148,25 @@ def _score_terraform_constraints(self, repository: Repository) -> Finding | None f"with provider constraints", f"{pinned} exact pins, {ranged} range constraints", ], - remediation=Remediation( - summary="Pin Terraform providers to exact versions", - steps=[ - "Run 'terraform providers lock' to generate " - ".terraform.lock.hcl", - "Or pin exact versions in versions.tf " - '(e.g., version = "6.0.0" not ">= 6.0")', - ], - tools=["terraform"], - commands=[ - "terraform providers lock " - "-platform=linux_amd64", - ], - examples=[], - citations=[], - ) if status == "fail" else None, + remediation=( + Remediation( + summary="Pin Terraform providers to exact versions", + steps=[ + "Run 'terraform providers lock' to generate " + ".terraform.lock.hcl", + "Or pin exact versions in versions.tf " + '(e.g., version = "6.0.0" not ">= 6.0")', + ], + tools=["terraform"], + commands=[ + "terraform providers lock " "-platform=linux_amd64", + ], + examples=[], + citations=[], + ) + if status == "fail" + else None + ), error_message=None, ) @@ -179,14 +191,8 @@ def assess(self, repository: Repository) -> Finding: manual_lock_files = ["requirements.txt"] # Python pip # 1. Check root-level lock files - found_strict = [ - f for f in strict_lock_files - if (repository.path / f).exists() - ] - found_manual = [ - f for f in manual_lock_files - if (repository.path / f).exists() - ] + found_strict = [f for f in strict_lock_files if (repository.path / f).exists()] + found_manual = [f for f in manual_lock_files if (repository.path / f).exists()] # 2. Recursive fallback for multi-module repos (e.g. Go workspaces, # monorepos with per-package lock files) @@ -195,8 +201,7 @@ def assess(self, repository: Repository) -> Finding: matches = self._rglob_filtered(repository.path, f) if matches: found_strict.extend( - str(m.relative_to(repository.path)) - for m in matches + str(m.relative_to(repository.path)) for m in matches ) if not found_manual and not found_strict: @@ -204,8 +209,7 @@ def assess(self, repository: Repository) -> Finding: matches = self._rglob_filtered(repository.path, f) if matches: found_manual.extend( - str(m.relative_to(repository.path)) - for m in matches + str(m.relative_to(repository.path)) for m in matches ) # 3. If nothing found, check for special ecosystems and edge cases @@ -235,17 +239,13 @@ def assess(self, repository: Repository) -> Finding: summary="Add lock file for dependency reproducibility", steps=[ "For Go: run 'go mod tidy' (generates go.sum)", - "For npm: run 'npm install' " - "(generates package-lock.json)", - "For Python: use 'pip freeze > requirements.txt'" - " or poetry", - "For Ruby: run 'bundle install' " - "(generates Gemfile.lock)", + "For npm: run 'npm install' " "(generates package-lock.json)", + "For Python: use 'pip freeze > requirements.txt'" " or poetry", + "For Ruby: run 'bundle install' " "(generates Gemfile.lock)", "For Terraform: run 'terraform providers lock'" " (generates .terraform.lock.hcl)", ], - tools=["go", "npm", "pip", "poetry", "bundler", - "terraform"], + tools=["go", "npm", "pip", "poetry", "bundler", "terraform"], commands=[ "go mod tidy # Go", "npm install # npm", diff --git a/tests/unit/test_assessors_stub.py b/tests/unit/test_assessors_stub.py index c7a2f27a..8cd28ad0 100644 --- a/tests/unit/test_assessors_stub.py +++ b/tests/unit/test_assessors_stub.py @@ -19,9 +19,7 @@ class TestDependencyPinningAssessor: def test_no_lock_files(self, tmp_path): """Test that assessor fails when dep manifests exist but no lock files.""" - subprocess.run( - ["git", "init"], cwd=tmp_path, capture_output=True, check=True - ) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) # Add a dependency manifest so the repo isn't "not_applicable" (tmp_path / "pyproject.toml").write_text("[project]\nname = 'test'\n") @@ -234,9 +232,7 @@ def test_backward_compatibility_alias(self): def test_subdirectory_go_sum_multi_module(self, tmp_path): """Test that go.sum in subdirectories is detected for multi-module repos.""" - subprocess.run( - ["git", "init"], cwd=tmp_path, capture_output=True, check=True - ) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) # Create sub-module with go.mod + go.sum (no root-level lock file) sub = tmp_path / "tools" / "mytool" @@ -264,9 +260,7 @@ def test_subdirectory_go_sum_multi_module(self, tmp_path): def test_subdirectory_lock_excludes_vendor(self, tmp_path): """Test that lock files inside vendor/ are ignored.""" - subprocess.run( - ["git", "init"], cwd=tmp_path, capture_output=True, check=True - ) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) # go.mod at root so the repo is recognized as having deps (tmp_path / "go.mod").write_text("module example.com/test\n\ngo 1.21\n") @@ -296,9 +290,7 @@ def test_subdirectory_lock_excludes_vendor(self, tmp_path): def test_no_dependency_manifests_returns_not_applicable(self, tmp_path): """Test that repos with no code/deps return not_applicable.""" - subprocess.run( - ["git", "init"], cwd=tmp_path, capture_output=True, check=True - ) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) # Only README + LICENSE — no package manager files at all (tmp_path / "README.md").write_text("# My Project\n") @@ -322,9 +314,7 @@ def test_no_dependency_manifests_returns_not_applicable(self, tmp_path): def test_has_go_mod_but_no_lock_still_fails(self, tmp_path): """Test that having go.mod without go.sum still fails.""" - subprocess.run( - ["git", "init"], cwd=tmp_path, capture_output=True, check=True - ) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) (tmp_path / "go.mod").write_text( "module example.com/test\n\ngo 1.21\n\n" @@ -350,21 +340,19 @@ def test_has_go_mod_but_no_lock_still_fails(self, tmp_path): def test_terraform_versions_tf_range_constraints(self, tmp_path): """Test Terraform repos with >= range constraints in versions.tf.""" - subprocess.run( - ["git", "init"], cwd=tmp_path, capture_output=True, check=True - ) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) tf_dir = tmp_path / "terraform" / "modules" / "vpc" tf_dir.mkdir(parents=True) (tf_dir / "versions.tf").write_text( - 'terraform {\n' - ' required_providers {\n' - ' aws = {\n' + "terraform {\n" + " required_providers {\n" + " aws = {\n" ' source = "hashicorp/aws"\n' ' version = ">= 6.0"\n' - ' }\n' - ' }\n' - '}\n' + " }\n" + " }\n" + "}\n" ) repo = Repository( @@ -387,21 +375,19 @@ def test_terraform_versions_tf_range_constraints(self, tmp_path): def test_terraform_versions_tf_exact_pins(self, tmp_path): """Test Terraform repos with exact version pins in versions.tf.""" - subprocess.run( - ["git", "init"], cwd=tmp_path, capture_output=True, check=True - ) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) tf_dir = tmp_path / "terraform" tf_dir.mkdir() (tf_dir / "versions.tf").write_text( - 'terraform {\n' - ' required_providers {\n' - ' aws = {\n' + "terraform {\n" + " required_providers {\n" + " aws = {\n" ' source = "hashicorp/aws"\n' ' version = "6.0.0"\n' - ' }\n' - ' }\n' - '}\n' + " }\n" + " }\n" + "}\n" ) repo = Repository( @@ -423,14 +409,12 @@ def test_terraform_versions_tf_exact_pins(self, tmp_path): def test_terraform_lock_hcl_detected(self, tmp_path): """Test that .terraform.lock.hcl is detected as a strict lock file.""" - subprocess.run( - ["git", "init"], cwd=tmp_path, capture_output=True, check=True - ) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) (tmp_path / ".terraform.lock.hcl").write_text( 'provider "registry.terraform.io/hashicorp/aws" {\n' ' version = "6.0.0"\n' - '}\n' + "}\n" ) repo = Repository( @@ -453,9 +437,7 @@ def test_terraform_lock_hcl_detected(self, tmp_path): def test_subdirectory_requirements_txt(self, tmp_path): """Test that requirements.txt in subdirectories is found.""" - subprocess.run( - ["git", "init"], cwd=tmp_path, capture_output=True, check=True - ) + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) sub = tmp_path / "experiments" / "auth" sub.mkdir(parents=True)