tests/sources/test_sourcerank.py
import unittest
from unittest import mock
from unittest.mock import Mock, MagicMock, patch, PropertyMock
import requests
from pip_rating.sources.sourcerank import SourceRank, RETRY_WAIT
SOURCERANK_PAGE = """
<!DOCTYPE html>
<html lang="en">
<ul class="list-group">
<li class="list-group-item">
<span class="badge alert-success">
1
</span>
Basic info present?
<small class='text-muted'><i title="Description, homepage/repository link and keywords present?" class="fa fa-question-circle tip"></i></small>
</li>
<li class="list-group-item">
<span class="badge alert-success">
1
</span>
Source repository present?
</li>
<li class="list-group-item">
<span class="badge alert-success">
1
</span>
Readme present?
</li>
<li class="list-group-item">
<span class="badge alert-success">
1
</span>
License present?
</li>
<li class="list-group-item">
<span class="badge alert-success">
1
</span>
Has multiple versions?
<small class='text-muted'><i title="Has the package had more than one release?" class="fa fa-question-circle tip"></i></small>
</li>
<li class="list-group-item">
<span class="badge alert-warning">
0
</span>
Follows SemVer?
<small class='text-muted'><i title="Every version has a valid SemVer number" class="fa fa-question-circle tip"></i></small>
</li>
<li class="list-group-item">
<span class="badge alert-success">
1
</span>
Recent release?
<small class='text-muted'><i title="Within the past 6 months?" class="fa fa-question-circle tip"></i></small>
</li>
<li class="list-group-item">
<span class="badge alert-success">
1
</span>
Not brand new?
<small class='text-muted'><i title="Existed for at least 6 months" class="fa fa-question-circle tip"></i></small>
</li>
<li class="list-group-item">
<span class="badge alert-success">
1
</span>
1.0.0 or greater?
</li>
<li class="list-group-item">
<span class="badge alert-success">
10
</span>
Dependent Packages
<small class='text-muted'><i title="Logarithmic scale times two" class="fa fa-question-circle tip"></i></small>
</li>
<li class="list-group-item">
<span class="badge alert-success">
5
</span>
Dependent Repositories
<small class='text-muted'><i title="Logarithmic scale" class="fa fa-question-circle tip"></i></small>
</li>
<li class="list-group-item">
<span class="badge alert-success">
5
</span>
Stars
<small class='text-muted'><i title="Logarithmic scale" class="fa fa-question-circle tip"></i></small>
</li>
<li class="list-group-item">
<span class="badge alert-success">
2
</span>
Contributors
<small class='text-muted'><i title="Logarithmic scale divided by two" class="fa fa-question-circle tip"></i></small>
</li>
<li class="list-group-item">
<span class="badge alert-success">
2
</span>
Libraries.io subscribers
<small class='text-muted'><i title="Logarithmic scale divided by two" class="fa fa-question-circle tip"></i></small>
</li>
<li class='list-group-item'>
<span class='badge alert-info'>
32
</span>
<strong>Total</strong>
</li>
</ul>
"""
class TestSourceRank(unittest.TestCase):
"""Test the SourceRank class."""
@patch("pip_rating.sources.sourcerank.SourceRank.get_breakdown")
@patch("pip_rating.sources.sourcerank.datetime")
def test_get_cache_data(
self, mock_datetime: MagicMock, mock_get_breakdown: MagicMock
):
"""Test the get_cache_data method."""
mock_package = Mock()
mock_get_breakdown.return_value = {"breakdown": "breakdown"}
self.assertEqual(
{
"package_name": mock_package.name,
"updated_at": mock_datetime.datetime.now.return_value.isoformat.return_value,
"breakdown": mock_get_breakdown.return_value,
},
SourceRank(mock_package).get_cache_data(),
)
@patch(
"pip_rating.sources.sourcerank.SourceBase.is_cache_expired",
new_callable=PropertyMock,
)
def test_breakdown(self, mock_is_cache_expired: MagicMock):
"""Test the breakdown property."""
mock_package = Mock()
mock_breakdown = Mock()
with self.subTest("Test cache not expired"), patch(
"pip_rating.sources.sourcerank.SourceRank.get_from_cache"
) as mock_get_from_cache:
mock_is_cache_expired.return_value = False
mock_get_from_cache.return_value = {
"breakdown": mock_breakdown,
}
sourcerank = SourceRank(mock_package)
self.assertEqual(mock_breakdown, sourcerank.breakdown)
mock_get_from_cache.assert_called_once_with()
with self.subTest("Test cache expired"), patch(
"pip_rating.sources.sourcerank.SourceRank.save_to_cache"
) as mock_save_to_cache:
mock_is_cache_expired.return_value = True
mock_save_to_cache.return_value = {
"breakdown": mock_breakdown,
}
sourcerank = SourceRank(mock_package)
self.assertEqual(mock_breakdown, sourcerank.breakdown)
mock_save_to_cache.assert_called_once_with()
@patch("pip_rating.sources.sourcerank.time")
@patch("pip_rating.sources.sourcerank.requests")
def test_request(self, mock_requests: MagicMock, mock_time: MagicMock):
"""Test the request method."""
with self.subTest("Test successful request without 429 error"):
mock_requests.get.return_value.__enter__.return_value.status_code = 200
mock_requests.get.return_value.__enter__.return_value.content = (
SOURCERANK_PAGE
)
mock_package = Mock()
sourcerank = SourceRank(mock_package)
self.assertEqual(SOURCERANK_PAGE, sourcerank.request())
mock_requests.get.assert_called_once_with(
f"https://libraries.io/pypi/{mock_package.real_name}/sourcerank"
)
mock_requests.reset_mock()
with self.subTest("Test successful request with 429 error"):
mock_requests.get.return_value.__enter__.return_value.raise_for_status.side_effect = [
requests.HTTPError(response=Mock(status_code=429)),
Mock(),
]
mock_package = Mock()
sourcerank = SourceRank(mock_package)
self.assertEqual(SOURCERANK_PAGE, sourcerank.request())
mock_requests.get.assert_has_calls(
[
mock.call(
f"https://libraries.io/pypi/{mock_package.real_name}/sourcerank"
)
]
* 2,
any_order=True,
)
mock_time.sleep.assert_called_once_with(RETRY_WAIT)
@patch("pip_rating.sources.sourcerank.requests")
def test_get_breakdown(self, mock_requests: MagicMock):
"""Test the get_breakdown method."""
mock_requests.get.return_value.__enter__.return_value.content = SOURCERANK_PAGE
mock_package = Mock()
self.assertEqual(
[
("basic_info_present", 1),
("source_repository_present", 1),
("readme_present", 1),
("license_present", 1),
("has_multiple_versions", 1),
("follows_semver", 0),
("recent_release", 1),
("not_brand_new", 1),
("is_1_or_greater", 1),
("dependent_projects", 10),
("dependent_repositories", 5),
("stars", 5),
("contributors", 2),
("librariesio_subscribers", 2),
("total", 32),
],
list(SourceRank(mock_package).get_breakdown()),
)