Files
Fenris/tests/test_changelog.py
T

203 lines
5.5 KiB
Python

"""Release-note changelog extraction tests (DC-6, DC-7)."""
import importlib.util
import json
import subprocess
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
EXTRACTOR_PATH = REPO_ROOT / "scripts" / "extract_changelog.py"
RELEASE_REQUEST_PATH = REPO_ROOT / "scripts" / "release_request.py"
CHANGELOG_PATH = REPO_ROOT / "CHANGELOG.md"
def _extractor_module():
spec = importlib.util.spec_from_file_location("extract_changelog", EXTRACTOR_PATH)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def test_extracts_the_requested_version_section_verbatim():
extractor = _extractor_module()
changelog = """# Changelog
## [Unreleased]
## [1.4.0] - 2026-09-10
### Added
- Show a release summary to consumers.
## [1.3.0] - 2026-09-01
### Fixed
- Preserve the observation history during upgrades.
"""
expected = """## [1.4.0] - 2026-09-10
### Added
- Show a release summary to consumers.
"""
assert extractor.extract_version_section(changelog, "1.4.0") == expected
def test_checked_in_changelog_keeps_unreleased_first_and_categories_limited():
lines = CHANGELOG_PATH.read_text(encoding="utf-8").splitlines()
unreleased = lines.index("## [Unreleased]")
version_headings = [
index for index, line in enumerate(lines)
if line.startswith("## [") and line != "## [Unreleased]"
]
first_version = version_headings[0] if version_headings else len(lines)
categories = [
line.removeprefix("### ")
for line in lines[unreleased + 1:first_version]
if line.startswith("### ")
]
assert unreleased < first_version
assert set(categories) <= {"Added", "Changed", "Fixed"}
@pytest.mark.parametrize(
("changelog", "expected_error"),
[
("# Changelog\n\n## [Unreleased]\n", "missing"),
(
"# Changelog\n\n## [Unreleased]\n\n## [1.4.0] - 2026-09-10\n",
"empty",
),
(
"# Changelog\n\n## [Unreleased]\n\n## [1.4.0] - 2026-02-30\n\n- Add a note.\n",
"malformed release date",
),
],
)
def test_fails_closed_for_missing_empty_or_malformed_sections(
changelog, expected_error
):
extractor = _extractor_module()
with pytest.raises(extractor.ChangelogError, match=expected_error):
extractor.extract_version_section(changelog, "1.4.0")
def test_command_emits_a_workflow_error_and_nonzero_status(tmp_path):
changelog = tmp_path / "CHANGELOG.md"
changelog.write_text("# Changelog\n\n## [Unreleased]\n", encoding="utf-8")
result = subprocess.run(
[sys.executable, str(EXTRACTOR_PATH), str(changelog), "1.4.0"],
capture_output=True,
text=True,
check=False,
)
assert result.returncode != 0
assert result.stderr.startswith("::error::")
assert "missing" in result.stderr
def test_assembles_a_release_body_without_changing_the_section():
extractor = _extractor_module()
section = "## [1.4.0] - 2026-09-10\n\n### Added\n\n- Show a release summary.\n"
footer = "## Install\n\nUse the package channel.\n"
assert extractor.assemble_release_body(section, footer) == (
section + "\n" + footer
)
def test_command_can_write_the_complete_release_body(tmp_path):
changelog = tmp_path / "CHANGELOG.md"
changelog.write_text(
"# Changelog\n\n## [Unreleased]\n\n## [1.4.0] - 2026-09-10\n\n"
"### Added\n\n- Show a release summary.\n",
encoding="utf-8",
)
footer = tmp_path / "footer.md"
footer.write_text("## Install\n\nUse the package channel.\n", encoding="utf-8")
result = subprocess.run(
[
sys.executable,
str(EXTRACTOR_PATH),
str(changelog),
"1.4.0",
"--footer",
str(footer),
],
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0
assert result.stdout == (
"## [1.4.0] - 2026-09-10\n\n### Added\n\n- Show a release summary.\n\n"
"## Install\n\nUse the package channel.\n"
)
def test_release_request_command_reports_create_or_patch_decisions(tmp_path):
body = tmp_path / "release-body.md"
body.write_text("## [1.4.0] - 2026-09-10\n", encoding="utf-8")
create = subprocess.run(
[
sys.executable,
str(RELEASE_REQUEST_PATH),
"--version",
"1.4.0",
"--body-file",
str(body),
],
capture_output=True,
text=True,
check=False,
)
existing = tmp_path / "existing-release.json"
existing.write_text('{"id": 17, "assets": []}', encoding="utf-8")
patch = subprocess.run(
[
sys.executable,
str(RELEASE_REQUEST_PATH),
"--version",
"1.4.0",
"--body-file",
str(body),
"--existing-release",
str(existing),
],
capture_output=True,
text=True,
check=False,
)
assert create.returncode == patch.returncode == 0
assert json.loads(create.stdout) == {
"method": "POST",
"path": "/releases",
"payload": {
"tag_name": "v1.4.0",
"name": "v1.4.0",
"body": "## [1.4.0] - 2026-09-10\n",
},
}
assert json.loads(patch.stdout) == {
"method": "PATCH",
"path": "/releases/17",
"payload": {"body": "## [1.4.0] - 2026-09-10\n"},
}