Files
computer-playbook/tests/integration/meta/workflows/test_security_coverage.py
T
kevinveenbirkenbachandClaude Opus 5 71888d86af refactor(ci): prefix workflows by trigger
The workflow directory mixed entry points, reusable stages and scheduled jobs into one flat list sorted by topic, so the first question about any file - what starts it - was the one the name did not answer. Every workflow now carries one of three prefixes, derived from its on: block: cron- for a schedule trigger, call- for one only reachable through workflow_call, entry- for everything a repository event or a human starts. Scheduled workflows additionally carry an alarm clock as the first character of their name.

Two lints keep it that way. One derives the expected prefix from the triggers and compares it against the file name; it flagged all 17 offenders before the rename and none after. The other resolves every .github/workflows/<file> string in the repository against the directory, which is what a rename silently breaks in Markdown links, Python constants and shell API URLs alike.

Renaming pulled 69 pre-existing comments into the comment lint's changed-file scope. The vendored CodeQL template got the nocheck marker its scorecard sibling already carried, two workflow headers moved above name: where they count as file headers, and the remaining banners were deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 17:05:51 +02:00

111 lines
3.5 KiB
Python

import fnmatch
import os
import unittest
from pathlib import Path
from utils.cache.files import iter_project_files
from utils.cache.yaml import load_yaml_any
from . import PROJECT_ROOT
SECURITY_WORKFLOW_PATH = (
PROJECT_ROOT / ".github" / "workflows" / "cron-security-codeql.yml"
)
SCANNED_HIDDEN_DIRS = {
".github",
}
"""Hidden directories worth scanning; the cached walker prunes the rest."""
CODEQL_FILENAME_INDICATORS: dict[str, list[str]] = {
"c-cpp": ["*.c", "*.cc", "*.cpp", "*.cxx", "*.h", "*.hh", "*.hpp", "*.hxx"],
"csharp": ["*.cs"],
"go": ["*.go"],
"java-kotlin": ["*.java", "*.kt", "*.kts"],
"javascript-typescript": ["*.js", "*.jsx", "*.ts", "*.tsx", "*.mjs", "*.cjs"],
"python": ["*.py"],
"ruby": ["*.rb"],
"rust": ["*.rs"],
"swift": ["*.swift"],
}
"""CodeQL language name -> filename patterns."""
CODEQL_PATH_INDICATORS: dict[str, list[str]] = {
"actions": [
".github/workflows/*.yml",
".github/workflows/*.yaml",
"action.yml",
"action.yaml",
],
}
def _load_active_languages() -> set[str]:
data = load_yaml_any(str(SECURITY_WORKFLOW_PATH))
include = data["jobs"]["analyze"]["strategy"]["matrix"]["include"]
return {
entry["language"]
for entry in include
if isinstance(entry, dict) and "language" in entry
}
def _matching_languages(rel_file: str, filename: str) -> set[str]:
matched = {
language
for language, patterns in CODEQL_FILENAME_INDICATORS.items()
if any(fnmatch.fnmatch(filename, pattern) for pattern in patterns)
}
matched.update(
language
for language, patterns in CODEQL_PATH_INDICATORS.items()
if any(fnmatch.fnmatch(rel_file, pattern) for pattern in patterns)
)
return matched
class TestSecurityWorkflowCoverage(unittest.TestCase):
def test_codeql_languages_match_repository_content(self):
"""The CodeQL workflow must only enable languages that are actually
present in the repository, and it must not miss any detected language."""
detected_languages: set[str] = set()
for path in iter_project_files():
rel_file = os.path.relpath(path, PROJECT_ROOT)
top = rel_file.split(os.sep, 1)[0]
if top.startswith(".") and top not in SCANNED_HIDDEN_DIRS:
continue
filename = Path(rel_file).name
detected_languages.update(_matching_languages(rel_file, filename))
active_languages = _load_active_languages()
missing_languages = sorted(detected_languages - active_languages)
extra_languages = sorted(active_languages - detected_languages)
problems: list[str] = []
if missing_languages:
problems.append(
"Detected languages missing from "
".github/workflows/cron-security-codeql.yml:"
)
problems.extend(f" - {language}" for language in missing_languages)
if extra_languages:
problems.append(
"Active CodeQL languages without matching repository files:"
)
problems.extend(f" - {language}" for language in extra_languages)
if problems:
self.fail(
"The CodeQL workflow matrix does not match the repository content.\n"
"Enable missing languages or comment out inactive ones.\n\n"
+ "\n".join(problems)
)
if __name__ == "__main__":
unittest.main()