diff --git a/src/fosslight_util/_get_downloadable_url.py b/src/fosslight_util/_get_downloadable_url.py index 706ac23..e3321d9 100755 --- a/src/fosslight_util/_get_downloadable_url.py +++ b/src/fosslight_util/_get_downloadable_url.py @@ -366,8 +366,6 @@ def version_exists(pkg_type, origin_name, version): versions = r.json().get('versions', []) return any(v.get('version') == version for v in versions if isinstance(v, dict)) elif pkg_type == 'go': - if not version.startswith('v'): - version = f'v{version}' r = requests.get(f'https://proxy.golang.org/{origin_name}/@v/list', timeout=5) if r.status_code == 200: listed = r.text.splitlines() @@ -448,7 +446,7 @@ def extract_name_version_from_link(link, checkout_version): if origin_name.endswith('/'): origin_name = origin_name[:-1] oss_name = f"go:{origin_name}" - oss_version = match.group(2) + oss_version = match.group(2) or "" elif key == "cargo": oss_name = f"cargo:{origin_name}" oss_version = match.group(2) @@ -471,9 +469,19 @@ def extract_name_version_from_link(link, checkout_version): if oss_version: try: if not version_exists(pkg_type, origin_name, oss_version): - if oss_version.startswith('v') and version_exists(pkg_type, origin_name, oss_version[1:]): - logger.info(f'Version {oss_version} not found; using without leading v: {oss_version[1:]}') - oss_version = oss_version[1:] + alternate_version = ( + oss_version[1:] if oss_version.startswith('v') else f'v{oss_version}' + ) + if alternate_version and version_exists(pkg_type, origin_name, alternate_version): + if oss_version.startswith('v'): + logger.info( + f'Version {oss_version} not found; using without leading v: {alternate_version}' + ) + else: + logger.info( + f'Version {oss_version} not found; using with leading v: {alternate_version}' + ) + oss_version = alternate_version else: logger.info(f'Version {oss_version} not found for {oss_name}; will attempt latest fallback') need_latest = True @@ -811,6 +819,8 @@ def get_download_location_for_go(link): if oss_name.endswith('/'): oss_name = oss_name[:-1] oss_version = dn_loc_re[0][1] + if oss_version and not oss_version.startswith('v'): + oss_version = f'v{oss_version}' new_link = f'{host}/{oss_name}/@v/{oss_version}.zip' try: diff --git a/src/fosslight_util/constant.py b/src/fosslight_util/constant.py index c6c1a38..ba2c1cd 100755 --- a/src/fosslight_util/constant.py +++ b/src/fosslight_util/constant.py @@ -52,7 +52,7 @@ "npm2": r'https?:\/\/www\.npmjs\.com\/package\/(\@[^\/]+\/[^\/]+)(?:\/v\/)?([^\/]*)', "pub": r'https?:\/\/pub\.dev\/packages\/([^\/]+)(?:\/versions\/)?([^\/]*)', "cocoapods": r'https?:\/\/cocoapods\.org\/pods\/([^\/]+)', - "go": r'https?:\/\/pkg.go.dev\/([^\@]+)\@?v?([^\/]*)', + "go": r'https?:\/\/pkg.go.dev\/([^\@]+)(?:@([^\/]*))?', "cargo": r'https?:\/\/crates\.io\/crates\/([^\/]+)\/?([^\/]*)', "packagist": r'https?:\/\/packagist\.org\/packages\/([^\/]+\/[^\/#?]+)(?:\/([^\/#?]+))?(?:\#v?([^\/#?]*))?', } diff --git a/tests/test_download_package_version.py b/tests/test_download_package_version.py new file mode 100644 index 0000000..0646c8c --- /dev/null +++ b/tests/test_download_package_version.py @@ -0,0 +1,132 @@ +# Copyright (c) 2026 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 +"""Package-manager version lookup tries a leading v before the latest release.""" + +import pytest + +from fosslight_util import _get_downloadable_url as downloadable_url + + +def test_go_link_keeps_v_prefix_in_version(monkeypatch): + seen = [] + + def fake_exists(_pkg_type, _origin_name, version): + seen.append(version) + return version == "v0.20.2" + + monkeypatch.setattr(downloadable_url, "version_exists", fake_exists) + monkeypatch.setattr( + downloadable_url, + "get_latest_package_version", + lambda *_args, **_kwargs: pytest.fail("latest fallback must not run"), + ) + + name, version, link, pkg_type = downloadable_url.extract_name_version_from_link( + "https://pkg.go.dev/github.com/go-openapi/jsonreference@v0.20.2", + "", + ) + + assert pkg_type == "go" + assert name == "go:github.com/go-openapi/jsonreference" + assert seen == ["v0.20.2"] + assert version == "v0.20.2" + assert link == "https://pkg.go.dev/github.com/go-openapi/jsonreference@v0.20.2" + + +def test_go_version_without_v_is_retried_with_v_prefix(monkeypatch): + seen = [] + + def fake_exists(_pkg_type, _origin_name, version): + seen.append(version) + return version == "v0.20.2" + + monkeypatch.setattr(downloadable_url, "version_exists", fake_exists) + monkeypatch.setattr( + downloadable_url, + "get_latest_package_version", + lambda *_args, **_kwargs: pytest.fail("latest fallback must not run"), + ) + + name, version, link, pkg_type = downloadable_url.extract_name_version_from_link( + "https://pkg.go.dev/github.com/go-openapi/jsonreference@0.20.2", + "", + ) + + assert pkg_type == "go" + assert name == "go:github.com/go-openapi/jsonreference" + assert seen == ["0.20.2", "v0.20.2"] + assert version == "v0.20.2" + assert link.endswith("@v0.20.2") + + +def test_unprefixed_checkout_version_uses_v_prefix_when_present(monkeypatch): + seen = [] + + def fake_exists(_pkg_type, _origin_name, version): + seen.append(version) + return version == "v0.20.2" + + monkeypatch.setattr(downloadable_url, "version_exists", fake_exists) + + _name, version, link, pkg_type = downloadable_url.extract_name_version_from_link( + "https://pkg.go.dev/github.com/go-openapi/jsonreference", + "0.20.2", + ) + + assert pkg_type == "go" + assert seen == ["0.20.2", "v0.20.2"] + assert version == "v0.20.2" + assert link.endswith("@v0.20.2") + + +def test_missing_v_and_plain_version_falls_back_to_latest(monkeypatch): + monkeypatch.setattr(downloadable_url, "version_exists", lambda *_args, **_kwargs: False) + monkeypatch.setattr(downloadable_url, "get_latest_package_version", lambda *_args, **_kwargs: "v0.21.0") + + _name, version, link, _pkg_type = downloadable_url.extract_name_version_from_link( + "https://pkg.go.dev/github.com/go-openapi/jsonreference@0.0.1", + "", + ) + + assert version == "v0.21.0" + assert link.endswith("@v0.21.0") + + +def test_leading_v_still_falls_back_to_version_without_v(monkeypatch): + def fake_exists(_pkg_type, _origin_name, version): + return version == "1.2.3" + + monkeypatch.setattr(downloadable_url, "version_exists", fake_exists) + + _name, version, link, pkg_type = downloadable_url.extract_name_version_from_link( + "https://pypi.org/project/example/v1.2.3", + "", + ) + + assert pkg_type == "pypi" + assert version == "1.2.3" + assert link.endswith("/example/1.2.3") + + +def test_go_download_url_adds_missing_v_prefix(monkeypatch): + requested = [] + + class _Response: + def getcode(self): + return 200 + + def fake_urlopen(url, timeout=0): + requested.append(url) + return _Response() + + monkeypatch.setattr(downloadable_url, "urlopen", fake_urlopen) + + found, new_link = downloadable_url.get_download_location_for_go( + "pkg.go.dev/github.com/go-openapi/jsonreference@0.20.2" + ) + + assert found is True + assert requested == [ + "https://proxy.golang.org/github.com/go-openapi/jsonreference/@v/v0.20.2.zip" + ] + assert new_link == requested[0]