"""Tests for the CHANGELOG parser — the single source of truth for versioning.""" import pytest from shared.changelog import ( bump_kind, entry_for, is_valid_bump, latest_entry, parse_changelog, top_version, version_entries, ) # A faithful slice of the real file: preamble prose, a plain version entry, a # legacy combined entry, and date-only historical headers with no version. SAMPLE = """# Changelog What's changed in the **After-Hours Shift Manager**. Newest first. Versions are `MAJOR.MINOR.PATCH`. --- ## v1.10.0 — June 11, 2026 **Release notes now announce themselves.** Big new feature line. ## v1.9.2 — June 1, 2026 **Setup-guide fix.** A patch. ## v1.9.0 / v1.9.1 — June 1, 2026 **Last-minute drops blocked.** Combined entry. *(v1.9.1 was a library update.)* --- ## May 2026 — Phone-system automation - Live phone routing follows the schedule. ## April 3, 2026 — Launch 🎉 The first version. """ def test_preamble_is_not_an_entry(): # The "# Changelog" h1 and prose before the first "##" must not parse as entries. entries = parse_changelog(SAMPLE) assert all("Changelog" not in e.title for e in entries) def test_top_version_skips_preamble(): assert top_version(SAMPLE) == "1.10.0" def test_latest_entry_fields(): entry = latest_entry(SAMPLE) assert entry.version == "1.10.0" assert entry.date_label == "June 11, 2026" assert "announce themselves" in entry.body def test_combined_header_reports_higher_version(): entry = entry_for(SAMPLE, "1.9.0") assert entry.versions == ("1.9.0", "1.9.1") assert entry.version == "1.9.1" # the higher of the two def test_combined_header_is_findable_by_either_version(): assert entry_for(SAMPLE, "1.9.1") == entry_for(SAMPLE, "v1.9.0") def test_body_excludes_thematic_break_rules(): # The "---" rule separating eras must not bleed into the combined entry's notes. assert "---" not in entry_for(SAMPLE, "1.9.0").body def test_date_only_headers_carry_no_version(): titles = {e.title for e in parse_changelog(SAMPLE) if not e.versions} assert "May 2026 — Phone-system automation" in titles assert "April 3, 2026 — Launch 🎉" in titles def test_version_entries_excludes_date_only(): versions = [e.version for e in version_entries(SAMPLE)] assert versions == ["1.10.0", "1.9.2", "1.9.1"] def test_entry_for_accepts_v_prefix(): assert entry_for(SAMPLE, "v1.10.0").version == "1.10.0" def test_entry_for_unknown_version_is_none(): assert entry_for(SAMPLE, "2.0.0") is None def test_empty_changelog_has_no_top_version(): assert top_version("# Changelog\n\nNothing yet.\n") is None assert latest_entry("# Changelog\n") is None @pytest.mark.parametrize( "new,prev,expected", [ ("1.10.0", "1.9.2", "minor"), # minor resets patch to 0 ("2.0.0", "1.9.2", "major"), ("1.9.3", "1.9.2", "patch"), ("1.11.0", "1.9.2", None), # skips a minor ("1.9.2", "1.9.2", None), # no change ("1.9.1", "1.9.2", None), # downgrade ("3.0.0", "1.9.2", None), # skips a major ], ) def test_bump_kind(new, prev, expected): assert bump_kind(new, prev) == expected assert is_valid_bump(new, prev) is (expected is not None)