|
| 1 | +# License: MIT |
| 2 | +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH |
| 3 | + |
| 4 | +"""Tests for the cookiecutter migration script.""" |
| 5 | + |
| 6 | +import importlib.util |
| 7 | +import json |
| 8 | +from pathlib import Path |
| 9 | +from types import ModuleType |
| 10 | + |
| 11 | + |
| 12 | +def _load_migrate_module() -> ModuleType: |
| 13 | + """Load the migration script as a module.""" |
| 14 | + module_path = Path(__file__).resolve().parents[1] / "cookiecutter" / "migrate.py" |
| 15 | + spec = importlib.util.spec_from_file_location("cookiecutter_migrate", module_path) |
| 16 | + assert spec is not None |
| 17 | + assert spec.loader is not None |
| 18 | + |
| 19 | + module = importlib.util.module_from_spec(spec) |
| 20 | + spec.loader.exec_module(module) |
| 21 | + return module |
| 22 | + |
| 23 | + |
| 24 | +def test_migrate_workflow_file_adds_missing_release_notes_permissions( |
| 25 | + tmp_path: Path, |
| 26 | +) -> None: |
| 27 | + """Add the release notes permissions block when it is missing.""" |
| 28 | + migrate = _load_migrate_module() |
| 29 | + workflow = tmp_path / "release-notes-check.yml" |
| 30 | + workflow.write_text( |
| 31 | + """name: Release Notes Check |
| 32 | +
|
| 33 | +on: |
| 34 | + merge_group: |
| 35 | + pull_request: |
| 36 | + types: |
| 37 | + - \"opened\" |
| 38 | +
|
| 39 | +jobs: |
| 40 | + check-release-notes: |
| 41 | + name: Check release notes are updated |
| 42 | + runs-on: ubuntu-slim |
| 43 | + steps: |
| 44 | + - name: Check for a release notes update |
| 45 | + if: github.event_name == 'pull_request' |
| 46 | + uses: brettcannon/check-for-changed-files@871d7b8b5917a4f6f06662e2262e8ffc51dff6d1 # v1.2.1 |
| 47 | + with: |
| 48 | + file-pattern: \"RELEASE_NOTES.md\" |
| 49 | + prereq-pattern: \"{proto,py}/**\" |
| 50 | + skip-label: \"cmd:skip-release-notes\" |
| 51 | +""", |
| 52 | + encoding="utf-8", |
| 53 | + ) |
| 54 | + |
| 55 | + migrate._migrate_workflow_file( # pylint: disable=protected-access |
| 56 | + workflow, |
| 57 | + migrate._RELEASE_NOTES_CHECK_REPLACEMENTS, # pylint: disable=protected-access |
| 58 | + description="updated release notes check workflow", |
| 59 | + ) |
| 60 | + |
| 61 | + assert workflow.read_text(encoding="utf-8") == ( |
| 62 | + "name: Release Notes Check\n\n" |
| 63 | + "on:\n" |
| 64 | + " merge_group:\n" |
| 65 | + " pull_request:\n" |
| 66 | + " types:\n" |
| 67 | + ' - "opened"\n\n' |
| 68 | + "jobs:\n" |
| 69 | + " check-release-notes:\n" |
| 70 | + " name: Check release notes are updated\n" |
| 71 | + " runs-on: ubuntu-slim\n" |
| 72 | + " permissions:\n" |
| 73 | + " # Read pull request metadata to evaluate labels and changed files.\n" |
| 74 | + " pull-requests: read\n" |
| 75 | + " steps:\n" |
| 76 | + " - name: Check for a release notes update\n" |
| 77 | + " if: github.event_name == 'pull_request'\n" |
| 78 | + " uses: brettcannon/check-for-changed-files@871d7b8b5917a4f6f06662e2262e8ffc51dff6d1 # v1.2.1\n" |
| 79 | + " with:\n" |
| 80 | + ' file-pattern: "RELEASE_NOTES.md"\n' |
| 81 | + ' prereq-pattern: "{proto,py}/**"\n' |
| 82 | + ' skip-label: "cmd:skip-release-notes"\n' |
| 83 | + ) |
| 84 | + assert migrate._manual_steps == [] # pylint: disable=protected-access |
| 85 | + |
| 86 | + |
| 87 | +def test_migrate_workflow_file_reports_missing_patterns(tmp_path: Path) -> None: |
| 88 | + """Report the full missing patterns when a workflow cannot be migrated.""" |
| 89 | + migrate = _load_migrate_module() |
| 90 | + workflow = tmp_path / "release-notes-check.yml" |
| 91 | + workflow.write_text( |
| 92 | + """name: Release Notes Check |
| 93 | +
|
| 94 | +jobs: |
| 95 | + check-release-notes: |
| 96 | + name: Check release notes are updated |
| 97 | +""", |
| 98 | + encoding="utf-8", |
| 99 | + ) |
| 100 | + |
| 101 | + migrate._migrate_workflow_file( # pylint: disable=protected-access |
| 102 | + workflow, |
| 103 | + migrate._RELEASE_NOTES_CHECK_REPLACEMENTS, # pylint: disable=protected-access |
| 104 | + description="updated release notes check workflow", |
| 105 | + ) |
| 106 | + |
| 107 | + assert len(migrate._manual_steps) == 1 # pylint: disable=protected-access |
| 108 | + assert ( |
| 109 | + f"Could not find the expected pattern(s) in {workflow}." |
| 110 | + in migrate._manual_steps[0] # pylint: disable=protected-access |
| 111 | + ) |
| 112 | + assert "Pattern 1:" in migrate._manual_steps[0] # pylint: disable=protected-access |
| 113 | + assert "Pattern 2:" in migrate._manual_steps[0] # pylint: disable=protected-access |
| 114 | + assert "permissions:" in migrate._manual_steps[0] # pylint: disable=protected-access |
| 115 | + assert "runs-on: ubuntu-slim" in migrate._manual_steps[0] # pylint: disable=protected-access |
| 116 | + |
| 117 | + |
| 118 | +def test_migrate_workflow_file_ignores_private_ci_publish_patterns( |
| 119 | + tmp_path: Path, |
| 120 | +) -> None: |
| 121 | + """Ignore private-repo CI publish patterns when they are absent.""" |
| 122 | + migrate = _load_migrate_module() |
| 123 | + workflow = tmp_path / "ci.yaml" |
| 124 | + workflow.write_text("name: CI\n", encoding="utf-8") |
| 125 | + |
| 126 | + replacements = [ |
| 127 | + (pattern, f"replacement {index}\n") |
| 128 | + for index, pattern in enumerate( |
| 129 | + migrate._PRIVATE_REPO_CI_OPTIONAL_PATTERNS, # pylint: disable=protected-access |
| 130 | + start=1, |
| 131 | + ) |
| 132 | + ] |
| 133 | + |
| 134 | + migrate._migrate_workflow_file( # pylint: disable=protected-access |
| 135 | + workflow, |
| 136 | + replacements, |
| 137 | + description="updated main CI workflow", |
| 138 | + ignore_missing_patterns=migrate._PRIVATE_REPO_CI_OPTIONAL_PATTERNS, # pylint: disable=protected-access |
| 139 | + ) |
| 140 | + |
| 141 | + assert workflow.read_text(encoding="utf-8") == "name: CI\n" |
| 142 | + assert migrate._manual_steps == [] # pylint: disable=protected-access |
| 143 | + |
| 144 | + |
| 145 | +def test_migrate_ci_workflows_warns_about_private_publish_jobs( |
| 146 | + tmp_path: Path, |
| 147 | + monkeypatch, |
| 148 | +) -> None: |
| 149 | + """Warn when a private repository still has CI publish jobs.""" |
| 150 | + migrate = _load_migrate_module() |
| 151 | + template_root = Path(__file__).resolve().parents[1] / "cookiecutter" |
| 152 | + workflow_dir = tmp_path / ".github" / "workflows" |
| 153 | + workflow_dir.mkdir(parents=True) |
| 154 | + workflow_dir.joinpath("ci-pr.yaml").write_text( |
| 155 | + ( |
| 156 | + template_root |
| 157 | + / "{{cookiecutter.github_repo_name}}" |
| 158 | + / ".github" |
| 159 | + / "workflows" |
| 160 | + / "ci-pr.yaml" |
| 161 | + ).read_text(encoding="utf-8"), |
| 162 | + encoding="utf-8", |
| 163 | + ) |
| 164 | + workflow_dir.joinpath("ci.yaml").write_text( |
| 165 | + ( |
| 166 | + template_root |
| 167 | + / "{{cookiecutter.github_repo_name}}" |
| 168 | + / ".github" |
| 169 | + / "workflows" |
| 170 | + / "ci.yaml" |
| 171 | + ).read_text(encoding="utf-8"), |
| 172 | + encoding="utf-8", |
| 173 | + ) |
| 174 | + (tmp_path / ".cookiecutter-replay.json").write_text( |
| 175 | + json.dumps({"cookiecutter": {"github_org": "private-org", "license": "MIT"}}), |
| 176 | + encoding="utf-8", |
| 177 | + ) |
| 178 | + |
| 179 | + monkeypatch.chdir(tmp_path) |
| 180 | + migrate.migrate_ci_workflows() |
| 181 | + |
| 182 | + assert len(migrate._manual_steps) == 1 # pylint: disable=protected-access |
| 183 | + assert ( |
| 184 | + "still contains `publish-docs`, `publish-to-pypi`" in migrate._manual_steps[0] |
| 185 | + ) # pylint: disable=protected-access |
| 186 | + assert "appears to be private" in migrate._manual_steps[0] # pylint: disable=protected-access |
0 commit comments