Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/specify_cli/bundles/versioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ def same_version(actual: str, pinned: str) -> bool:
r"^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)"
r"(?:-(?:(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
r"(?:\+(?:[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
r"(?:\+(?:[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$",
flags=re.ASCII,
)


Expand Down
25 changes: 23 additions & 2 deletions tests/specify_cli/bundles/test_command_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from unittest.mock import patch # noqa: F401

import yaml # noqa: F401
import pytest
from typer.testing import CliRunner

from specify_cli import app
Expand All @@ -27,9 +28,12 @@ def test_validate_reports_invalid_manifest(project: Path):
assert "license" in result.output


def test_validate_accepts_valid_manifest(project: Path):
@pytest.mark.parametrize("version", ["1.2.0", "1.20.30-12alpha.1+build.01", "V10.20.30"])
def test_validate_accepts_valid_manifest(project: Path, version: str):
data = valid_manifest_dict()
data["bundle"]["version"] = version
(project / "bundle.yml").write_text(
yaml.safe_dump(valid_manifest_dict()), encoding="utf-8"
yaml.safe_dump(data), encoding="utf-8"
)
# Offline mode does not fail on references it cannot verify (synthetic ids
# here); they surface as warnings while structure is confirmed valid.
Expand All @@ -38,6 +42,23 @@ def test_validate_accepts_valid_manifest(project: Path):
assert "valid" in result.output


@pytest.mark.parametrize("version", ["1٢.2.3", "1.2.3-1٢", "1.2.3-٢alpha"])
@pytest.mark.parametrize("field", ["bundle", "extension"])
def test_validate_rejects_non_ascii_version(project: Path, version: str, field: str):
data = valid_manifest_dict()
if field == "bundle":
data["bundle"]["version"] = version
else:
data["provides"]["extensions"][0]["version"] = version
(project / "bundle.yml").write_text(yaml.safe_dump(data), encoding="utf-8")

result = runner.invoke(app, ["bundle", "validate", "--offline"])

assert result.exit_code == 1, result.output
assert "invalid" in result.output
assert version in result.output


def test_validate_escapes_manifest_markup_in_errors(project: Path):
data = valid_manifest_dict()
# An invalid constraint is echoed back inside the validation error.
Expand Down
9 changes: 9 additions & 0 deletions tests/specify_cli/bundles/test_versioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
("1.2.3-alpha1", True),
("1.2.3-beta2", True),
("v1.2.3", True),
("1.20.30-12alpha.1+build.01", True),
("V10.20.30", True),
# SemVer identifiers use ASCII digits, not Python's broader Unicode \d.
("1٢.2.3", False),
("1.2٣.3", False),
("1.2.3٤", False),
("1.2.3-1٢", False),
("1.2.3-٢alpha", False),
("1.2.3-1beta", False),
("not-a-version", False),
("", False),
# packaging.version.Version accepts these partial versions; SemVer must not.
Expand Down
Loading