Skip to content

Commit 2add3e3

Browse files
committed
Add tests
Signed-off-by: Tushar Goel <[email protected]>
1 parent a1bb500 commit 2add3e3

File tree

3 files changed

+173
-2
lines changed

3 files changed

+173
-2
lines changed

src/fetchcode/cpan.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
1515
# specific language governing permissions and limitations under the License.
1616

17+
import urllib.parse
18+
1719
from packageurl import PackageURL
1820

1921
from fetchcode import fetch_json_response
@@ -34,8 +36,10 @@ def get_download_url(purl: str):
3436
return None
3537

3638
try:
37-
api = f"https://fastapi.metacpan.org/v1/release/{urllib.parse.quote(p.name)}/{urllib.parse.quote(p.version)}"
38-
data = fetch_json_response(api, stream=False, timeout=20)
39+
parsed_name = urllib.parse.quote(p.name)
40+
parsed_version = urllib.parse.quote(p.version)
41+
api = f"https://fastapi.metacpan.org/v1/release/{parsed_name}/{parsed_version}"
42+
data = fetch_json_response(url=api)
3943
url = data.get("download_url") or data.get("archive")
4044
if url and _http_exists(url):
4145
return url

tests/test_composer.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# fetchcode is a free software tool from nexB Inc. and others.
2+
# Visit https://github.com/aboutcode-org/fetchcode for support and download.
3+
#
4+
# Copyright (c) nexB Inc. and others. All rights reserved.
5+
# http://nexb.com and http://aboutcode.org
6+
#
7+
# This software is licensed under the Apache License version 2.0.
8+
#
9+
# You may not use this software except in compliance with the License.
10+
# You may obtain a copy of the License at:
11+
# http://apache.org/licenses/LICENSE-2.0
12+
# Unless required by applicable law or agreed to in writing, software distributed
13+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations under the License.
16+
17+
from unittest.mock import patch
18+
19+
import pytest
20+
21+
from fetchcode.composer import Composer
22+
23+
24+
def test_valid_composer_package_with_namespace():
25+
purl = "pkg:composer/laravel/[email protected]"
26+
name = "laravel/framework"
27+
expected_url = f"https://repo.packagist.org/p2/{name}.json "
28+
download_url = "https://github.com/laravel/framework/archive/refs/tags/v10.0.0.zip"
29+
30+
mock_data = {"packages": {name: [{"version": "10.0.0", "dist": {"url": download_url}}]}}
31+
32+
with patch("fetchcode.composer.fetch_json_response", return_value=mock_data) as mock_fetch:
33+
result = Composer.get_download_url(purl)
34+
assert result == download_url
35+
mock_fetch.assert_called_once_with(expected_url)
36+
37+
38+
def test_valid_composer_package_without_namespace():
39+
purl = "pkg:composer/[email protected]"
40+
name = "some-package"
41+
expected_url = f"https://repo.packagist.org/p2/{name}.json "
42+
download_url = "https://example.org/some-package-1.0.0.zip"
43+
44+
mock_data = {"packages": {name: [{"version": "1.0.0", "dist": {"url": download_url}}]}}
45+
46+
with patch("fetchcode.composer.fetch_json_response", return_value=mock_data) as mock_fetch:
47+
result = Composer.get_download_url(purl)
48+
assert result == download_url
49+
mock_fetch.assert_called_once_with(expected_url)
50+
51+
52+
def test_version_not_found_returns_none():
53+
purl = "pkg:composer/laravel/[email protected]"
54+
name = "laravel/framework"
55+
mock_data = {"packages": {name: [{"version": "9.0.0", "dist": {"url": "https://old.zip"}}]}}
56+
57+
with patch("fetchcode.composer.fetch_json_response", return_value=mock_data):
58+
result = Composer.get_download_url(purl)
59+
assert result is None
60+
61+
62+
def test_missing_packages_key_returns_none():
63+
purl = "pkg:composer/laravel/[email protected]"
64+
with patch("fetchcode.composer.fetch_json_response", return_value={}):
65+
result = Composer.get_download_url(purl)
66+
assert result is None
67+
68+
69+
def test_missing_package_name_in_data_returns_none():
70+
purl = "pkg:composer/laravel/[email protected]"
71+
mock_data = {"packages": {"some/other": []}}
72+
73+
with patch("fetchcode.composer.fetch_json_response", return_value=mock_data):
74+
result = Composer.get_download_url(purl)
75+
assert result is None
76+
77+
78+
def test_missing_version_raises():
79+
purl = "pkg:composer/laravel/framework"
80+
with pytest.raises(ValueError, match="Composer PURL must specify a name and version"):
81+
Composer.get_download_url(purl)

tests/test_cpan.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# fetchcode is a free software tool from nexB Inc. and others.
2+
# Visit https://github.com/aboutcode-org/fetchcode for support and download.
3+
#
4+
# Copyright (c) nexB Inc. and others. All rights reserved.
5+
# http://nexb.com and http://aboutcode.org
6+
#
7+
# This software is licensed under the Apache License version 2.0.
8+
#
9+
# You may not use this software except in compliance with the License.
10+
# You may obtain a copy of the License at:
11+
# http://apache.org/licenses/LICENSE-2.0
12+
# Unless required by applicable law or agreed to in writing, software distributed
13+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations under the License.
16+
17+
from unittest.mock import patch
18+
19+
import pytest
20+
21+
from fetchcode.cpan import CPAN
22+
23+
get_download_url = CPAN.get_download_url
24+
25+
26+
@pytest.fixture
27+
def valid_purl():
28+
return "pkg:cpan/EXAMPLE/[email protected]"
29+
30+
31+
def test_success_from_metacpan_api(valid_purl):
32+
expected_url = "https://cpan.metacpan.org/authors/id/E/EX/EXAMPLE/Some-Module-1.2.3.tar.gz"
33+
34+
with patch("fetchcode.cpan.fetch_json_response") as mock_fetch, patch(
35+
"fetchcode.cpan._http_exists"
36+
) as mock_exists:
37+
mock_fetch.return_value = {"download_url": expected_url}
38+
mock_exists.return_value = True
39+
result = get_download_url(valid_purl)
40+
assert result == expected_url
41+
mock_fetch.assert_called_once()
42+
mock_exists.assert_called_once_with(expected_url)
43+
44+
45+
def test_fallback_to_author_path(valid_purl):
46+
expected_url = "https://cpan.metacpan.org/authors/id/E/EX/EXAMPLE/Some-Module-1.2.3.tar.gz"
47+
48+
with patch("fetchcode.cpan.fetch_json_response", side_effect=Exception("API error")), patch(
49+
"fetchcode.cpan._http_exists"
50+
) as mock_exists:
51+
52+
mock_exists.side_effect = lambda url: url.endswith(".tar.gz")
53+
54+
result = get_download_url(valid_purl)
55+
assert result == expected_url
56+
assert mock_exists.call_count >= 1
57+
58+
59+
def test_author_zip_fallback(valid_purl):
60+
tar_url = "https://cpan.metacpan.org/authors/id/E/EX/EXAMPLE/Some-Module-1.2.3.tar.gz"
61+
zip_url = "https://cpan.metacpan.org/authors/id/E/EX/EXAMPLE/Some-Module-1.2.3.zip"
62+
63+
with patch("fetchcode.cpan.fetch_json_response", return_value={}), patch(
64+
"fetchcode.cpan._http_exists"
65+
) as mock_exists:
66+
67+
mock_exists.side_effect = lambda url: url == zip_url
68+
69+
result = get_download_url(valid_purl)
70+
assert result == zip_url
71+
assert mock_exists.call_count == 2
72+
assert tar_url in [call[0][0] for call in mock_exists.call_args_list]
73+
74+
75+
def test_neither_api_nor_fallback_works(valid_purl):
76+
with patch("fetchcode.cpan.fetch_json_response", return_value={}), patch(
77+
"fetchcode.cpan._http_exists", return_value=False
78+
) as mock_exists:
79+
80+
result = get_download_url(valid_purl)
81+
assert result is None
82+
assert mock_exists.call_count == 2
83+
84+
85+
def test_missing_name_or_version():
86+
assert get_download_url("pkg:cpan/EXAMPLE/Some-Module") is None

0 commit comments

Comments
 (0)