Files
workflows-py/tests/dev_cli/test_changesets.py
T

293 lines
7.3 KiB
Python

# SPDX-License-Identifier: MIT
# Copyright (c) 2026 LlamaIndex Inc.
from __future__ import annotations
import json
from pathlib import Path
from unittest.mock import Mock, patch
from urllib.error import HTTPError
import pytest
from dev_cli.changesets import (
PackageJson,
PyProjectContainer,
current_version,
is_published,
pep440_to_semver,
semver_to_pep440,
sync_package_version_with_pyproject,
)
def test_current_version(tmp_path: Path) -> None:
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text(
"""
[project]
name = "test-package"
version = "1.2.3"
dependencies = []
""".strip()
)
name, version = current_version(pyproject)
assert name == "test-package"
assert version == "1.2.3"
def test_current_version_normalizes_version(tmp_path: Path) -> None:
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text(
"""
[project]
name = "test-package"
version = "01.02.03"
dependencies = []
""".strip()
)
name, version = current_version(pyproject)
assert name == "test-package"
assert version == "1.2.3"
def test_pyproject_container_parse() -> None:
toml_text = """
[project]
name = "my-package"
version = "0.1.0"
dependencies = ["requests>=2.0.0"]
""".strip()
toml_doc, py_doc = PyProjectContainer.parse(toml_text)
assert py_doc.project.name == "my-package"
assert py_doc.project.version == "0.1.0"
assert py_doc.project.dependencies == ["requests>=2.0.0"]
def test_is_published_returns_true_when_version_exists() -> None:
mock_response = Mock()
mock_response.read.return_value = json.dumps(
{"releases": {"1.0.0": [], "1.1.0": []}}
).encode()
with patch("urllib.request.urlopen", return_value=mock_response):
result = is_published("test-package", "1.0.0")
assert result is True
def test_is_published_returns_false_when_version_missing() -> None:
mock_response = Mock()
mock_response.read.return_value = json.dumps({"releases": {"1.0.0": []}}).encode()
with patch("urllib.request.urlopen", return_value=mock_response):
result = is_published("test-package", "1.1.0")
assert result is False
def test_is_published_returns_false_when_package_not_found() -> None:
mock_error = HTTPError("url", 404, "Not Found", {}, None) # type: ignore[arg-type]
with patch("urllib.request.urlopen", side_effect=mock_error):
result = is_published("nonexistent-package", "1.0.0")
assert result is False
def test_is_published_raises_on_other_http_errors() -> None:
mock_error = HTTPError("url", 500, "Internal Server Error", {}, None) # type: ignore[arg-type]
with patch("urllib.request.urlopen", side_effect=mock_error):
with pytest.raises(HTTPError) as exc_info:
is_published("test-package", "1.0.0")
assert exc_info.value.code == 500
def test_sync_package_version_updates_version(tmp_path: Path) -> None:
package_dir = tmp_path / "package"
package_dir.mkdir()
pyproject = package_dir / "pyproject.toml"
pyproject.write_text(
"""
[project]
name = "test-package"
version = "1.0.0"
dependencies = []
""".strip()
)
packages = {
"test-js-package": PackageJson(
name="test-js-package",
version="2.0.0",
path=package_dir,
private=False,
)
}
sync_package_version_with_pyproject(package_dir, packages, "test-js-package")
_, py_doc = PyProjectContainer.parse(pyproject.read_text())
assert py_doc.project.version == "2.0.0"
def test_sync_package_version_skips_when_no_pyproject(tmp_path: Path) -> None:
package_dir = tmp_path / "package"
package_dir.mkdir()
packages = {
"test-js-package": PackageJson(
name="test-js-package",
version="2.0.0",
path=package_dir,
private=False,
)
}
# Should not raise an error
sync_package_version_with_pyproject(package_dir, packages, "test-js-package")
def test_sync_package_version_skips_when_versions_match(tmp_path: Path) -> None:
package_dir = tmp_path / "package"
package_dir.mkdir()
pyproject = package_dir / "pyproject.toml"
original_content = """
[project]
name = "test-package"
version = "2.0.0"
dependencies = []
""".strip()
pyproject.write_text(original_content)
packages = {
"test-js-package": PackageJson(
name="test-js-package",
version="2.0.0",
path=package_dir,
private=False,
)
}
sync_package_version_with_pyproject(package_dir, packages, "test-js-package")
# Content should be unchanged
assert pyproject.read_text() == original_content
def test_sync_package_version_converts_semver_prerelease(tmp_path: Path) -> None:
package_dir = tmp_path / "package"
package_dir.mkdir()
pyproject = package_dir / "pyproject.toml"
pyproject.write_text(
"""
[project]
name = "test-package"
version = "1.0.0"
dependencies = []
""".strip()
)
packages = {
"test-js-package": PackageJson(
name="test-js-package",
version="1.2.3-a.4",
path=package_dir,
private=False,
)
}
sync_package_version_with_pyproject(package_dir, packages, "test-js-package")
_, py_doc = PyProjectContainer.parse(pyproject.read_text())
assert py_doc.project.version == "1.2.3a4"
# -- semver_to_pep440 tests --
@pytest.mark.parametrize(
("semver", "expected"),
[
("1.2.3-a.0", "1.2.3a0"),
("1.2.3-a.4", "1.2.3a4"),
("2.0.0-b.1", "2.0.0b1"),
("0.1.0-rc.3", "0.1.0rc3"),
("10.20.30-a.99", "10.20.30a99"),
],
)
def test_semver_to_pep440_prerelease(semver: str, expected: str) -> None:
assert semver_to_pep440(semver) == expected
@pytest.mark.parametrize(
"version",
[
"1.2.3",
"0.0.1",
"10.20.30",
],
)
def test_semver_to_pep440_stable_passthrough(version: str) -> None:
assert semver_to_pep440(version) == version
def test_semver_to_pep440_rejects_non_pep440_label() -> None:
with pytest.raises(ValueError, match="Unsupported pre-release label 'alpha'"):
semver_to_pep440("1.2.3-alpha.1")
# -- pep440_to_semver tests --
@pytest.mark.parametrize(
("pep440", "expected"),
[
("1.2.3a0", "1.2.3-a.0"),
("1.2.3a4", "1.2.3-a.4"),
("2.0.0b1", "2.0.0-b.1"),
("0.1.0rc3", "0.1.0-rc.3"),
("10.20.30a99", "10.20.30-a.99"),
],
)
def test_pep440_to_semver_prerelease(pep440: str, expected: str) -> None:
assert pep440_to_semver(pep440) == expected
@pytest.mark.parametrize(
"version",
[
"1.2.3",
"0.0.1",
"10.20.30",
],
)
def test_pep440_to_semver_stable_passthrough(version: str) -> None:
assert pep440_to_semver(version) == version
# -- roundtrip tests --
@pytest.mark.parametrize(
"semver",
[
"1.2.3-a.4",
"2.0.0-b.1",
"0.1.0-rc.3",
],
)
def test_roundtrip_semver_to_pep440_and_back(semver: str) -> None:
pep440 = semver_to_pep440(semver)
assert pep440_to_semver(pep440) == semver
@pytest.mark.parametrize(
"pep440",
[
"1.2.3a4",
"2.0.0b1",
"0.1.0rc3",
],
)
def test_roundtrip_pep440_to_semver_and_back(pep440: str) -> None:
semver = pep440_to_semver(pep440)
assert semver_to_pep440(semver) == pep440