Python, Github: Get all branches - python

I would like to get all the GitHub branches of a certain repository. Using the GitHub API as documented here, https://developer.github.com/v3/repos/branches/#list-branches I try to use the GET request documented.
However, when I try unit testing, the response always evaluates to None and I get error: AttributeError: 'NoneType' object has no attribute 'json'. I am unsure if my issue is with the API call, or I am testing the call. See code below:
#staticmethod
def get_branches(git_base, org, repo):
github_repo_url = f"https://{git_base}/api/v3/repos/{org}/{repo}"
collected_all_branches: bool = False
page = 1
github_branches = []
# collect all branches in repo
while not collected_all_branches:
response = (requests.get(f"{github_repo_url}/branches/?per_page=100&page={page}",
headers=HEADERS))
if len(response.json()) == 0:
collected_all_branches = True
else:
github_branches.extend([response["name"] for response in response.json()])
page = page + 1
return github_branches
Unit Test:
#patch.object(requests, "get", return_value=None)
def test_get_branches(self, mock_get):
r = GithubService.get_branches("test", "test", "test")
mock_get.assert_called_with("test", "test", "test")

The test is not set up correctly. Since the unit test verifies the arguments of the requests.get() call you can modify it like this:
#patch("requests.get")
def test_get_branches(self, mock_get):
# Give the actual HEADERS you use in the get_branches() function call, for example something like:
HEADERS = {"Accept": "application/vnd.github+json"}
r = GithubService.get_branches("test", "test", "test")
mock_get.assert_called_with("https://api.github.com/repos/test/test/branches/?per_page=100&page=1", headers=HEADERS)
The test will now pass successfully.
Also, the Github API call is incorrect. The documentation you have linked to says the URL must be of this format:
https://api.github.com/repos/OWNER/REPO/branches
So in get_branches() make the following change:
github_repo_url = f"https://api.github.com/repos/{org}/{repo}"

Related

Can't mock a third party api call

I have a view that perform a third party API call:
from .atoca_api_calls import atoca_api_call
def atoca_api_call_view(request):
# some code
data = atoca_api_call(**params) # the actual API call happens in this func
# some code
My intention is to mock just the atoca_api_call() func and not the whole view.
class AtocaTestCase(TestCase):
def setUp(self):
# some code
#patch('crm.atoca_api_calls.atoca_api_call')
def test_atoca_call(self, mock_atoca_api_call):
mock_atoca_api_call.return_value = MagicMock(
status_code=200,
response=some_json # just returns some json
)
url = reverse('crm:atoca-api-call') # url related to `atoca_api_call_view`
response = self.client.post(url, some_others_params)
# various asserts
The test works fine but atoca_api_call() is not mocked.
I'm aware of where to patch:
#patch('crm.views.atoca_api_call', autospec=True)
Raises a ValueError:
ValueError: Failed to insert expression "<MagicMock name='mock.__getitem__().__getitem__().__getitem__()().resolve_expression()' id='140048216397424'>" on crm.Company.atoca_id. F() expressions can only be used to update, not to insert.
It's probably a simple issue I don't catch for inexperience, any help is really appreciated.
As i see the atoca_api_call() it is mocked. The issue is the return value.
mock_atoca_api_call.return_value = MagicMock(
status_code=200,
response=some_json # just returns some json
)
This should ve a proper response i assume a JsonResponse or Response(data) not sure what are you returning. Try with:
mock_atoca_api_call.return_value = JsonResponse(
some_json # just returns some json
)

Testing function that interacts with Django Rest Framework API using python mock library

I want to unit test a module that interacts with an API built with Django Rest Framework. To do that I'm using python mock library. As I'm not experienced using mocks to test interaction with an API, I'm having problems.
The function that sends an object to an API endpoint is this (in short):
def send_experiment_to_portal(experiment: Experiment):
rest = RestApiClient()
if not rest.active:
return None
# general params
params = {"nes_id": str(experiment.id),
"title": experiment.title,
"description": experiment.description,
"data_acquisition_done":
str(experiment.data_acquisition_is_concluded),
"project_url": experiment.source_code_url,
"ethics_committee_url": experiment.ethics_committee_project_url
}
action_keys = ['experiments', 'create']
portal_experiment = rest.client.action(
rest.schema, action_keys, params=params,
)
return portal_experiment
The RestAPIClient is the class that authenticates to the API:
class RestApiClient(object):
client = None
schema = None
active = False
def __init__(self):
auth = coreapi.auth.BasicAuthentication(
username=settings.PORTAL_API['USER'],
password=settings.PORTAL_API['PASSWORD']
)
self.client = coreapi.Client(auth=auth)
try:
url = settings.PORTAL_API['URL'] + \
(
':' + settings.PORTAL_API['PORT'] if
settings.PORTAL_API['PORT'] else ''
) \
+ '/api/schema/'
self.schema = self.client.get(url)
self.active = True
except:
self.active = False
The RestApiClient class is using the coreapi client module to connect to the API.
So, at the end, I'm starting from test if the params argument in rest.client.action has the keys/values that are foreseen by the API using python mock library.
I'm starting writing the initial test like:
class PortalAPITest(TestCase):
#patch('experiment.portal.RestApiClient')
def test_send_experiment_to_portal(self, mockRestApiClientClass):
research_project = ObjectsFactory.create_research_project()
experiment = ObjectsFactory.create_experiment(research_project)
result = send_experiment_to_portal(experiment)
So when I debug the test, looking inside the send_experiment_to_portal function rest.client.action.call_args has the value:
call(<MagicMock name='RestApiClient().schema' id='139922756141240'>, ['expreiments', 'create'], params={'description': 'Descricao do Experimento-Update', 'ethics_committee_url': None, 'project_url': None, 'title': 'Experimento-Update', 'nes_id': '37', 'data_acquisition_done': 'False'})
But mockRestApiClientClass.client.action.call_args has None. I suspect that I'm making wrong. I should mock rest.client.action and not the RestApiClient class.
Trying to use result value does not work too.
The value of result is
<MagicMock name='RestApiClient().client.action()' id='139821809672144'>
and result.call_args is None too.
But how to do that? How to get the mocking rest instance outside send_experiment_to_portal function?
After struggling for a couple of days I could find a solution.
Mocking RestApiClient returns the mock of the class not its value, that is the value of rest variable. We get the rest value calling return_value from the mock object.
So, we have to call mockRestApiClientClass.return_value instead of mockRestApiClientClass.
By doing that we have access to the rest methods and attributes called from inside send_experiment_end_message_to_portal, and then
mockRestApiClientClass.return_value.client.action.call_args will have the same content of the observed rest content inside send_experiment_end_message_to_portal function context.

Convert flask.Response to requests.Response

I'm writing some tests and I'd like to convert flask.Response objects to corresponding requests.Response objects. So I have 2 Flask apps (e.g. A and B), and A makes internal calls to B (via requests.post(url, json=payload)). My goal is to properly mock those calls without even launching any servers, and the current solution looks like the following:
from unittest import mock
...
def mock_B_request(url, json):
response = app_B.test_client().post(url, json=json) # flask.Response
# Some hacking should be done here,
# since flask.Respone doesn't have `.ok`, `.json()`, etc.,
# so it will break the code inside app_A
return response
...
# Inside the actual test method
with mock.patch('requests.post', side_effect=mock_B_request):
response = app_A.test_client().post(url, json=payload)
result = response.get_json()
...
Has someone already encountered with such a problem? What is the easiest solution here?
Eventually, I have found a very easy working solution:
def mock_B_request(url, **kwargs):
response = app_B.test_client().post(url, json=kwargs['json'])
# Hack a bit in order to make flask.Response
# support the same API as requests.Response
response_mock = mock.MagicMock()
response.status_code = response.status_code
response_mock.ok = response.status_code == 200
response_mock.json = lambda: response.get_json()
return response_mock
I hope it will be helpful for somebody.

How to test redirection in Django using pytest?

I already know that one can implement a class that inherits from SimpleTestCase, and one can test redirection by:
SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, host=None, msg_prefix='', fetch_redirect_response=True)
However, I am wondering what is the way I can check for redirection using pytest:
#pytest.mark.django_db
def test_redirection_to_home_when_group_does_not_exist(create_social_user):
"""Some docstring defining what the test is checking."""
c = Client()
c.login(username='TEST_USERNAME', password='TEST_PASSWORD')
response = c.get(reverse('pledges:home_group',
kwargs={'group_id': 100}),
follow=True)
SimpleTestCase.assertRedirects(response, reverse('pledges:home'))
However, I am getting the following error:
SimpleTestCase.assertRedirects(response, reverse('pledges:home'))
E TypeError: assertRedirects() missing 1 required positional argument: 'expected_url'
Is there any way I can use pytest to verify redirection with Django? Or I should go the way using a class that inherits from SimpleTestCase?
This is an instance method, so it will never work like a class method. You should be able to simply change the line:
SimpleTestCase.assertRedirects(...)
into:
SimpleTestCase().assertRedirects(...)
i.e. we're creating an instance in order to provide a bound method.
I'm not an expert with pytest so probably is not elegant but you can check for the Location header like
test_whatever(self, user):
client = Client()
url = reverse('admin:documents_document_add')
client.force_login(user)
response = client.post(url, {<something>})
# if the document is added correctly we redirect
assert response.status_code == 302
assert response['Location'] == reverse('admin:documents_document_changelist')

Google SafeBrowsing API: always getting an error

I use google safe browsing API. So I tested this simple code:
from safebrowsinglookup import SafebrowsinglookupClient
class TestMe:
def testMe(self):
self.key='my_Valid_Key_Here'
self.client=SafebrowsinglookupClient(self.key)
self.response=self.client.lookup('http://www.google.com')
print(self.response)
if __name__=="__main__":
TM=TestMe()
TM.testMe()
I always get this whatever the website I test:
{'website_I_tried','error'}
Note that I had to change some lines in the source code after I installed this API because it was written in Python 2 and I am using Python 3.4.1. How can I resolve this problem?
Update:
To understand why the above problem occured to me, I run this code:
from safebrowsinglookup import SafebrowsinglookupClient
class TestMe:
def testMe(self):
self.key = 'my_key_here'
self.client=SafebrowsinglookupClient(self.key,debug=1)
urls = ['http://www.google.com/','http://addonrock.ru/Debugger.js/']
self.results = self.client.lookup(*urls)
print(self.results['http://www.google.com/'])
if __name__ == "__main__":
TM=TestMe()
TM.testMe()
Now, I got this message:
BODY:
2
http://www.google.com/
http://addonrock.ru/Debugger.js/
URL: https://sb-ssl.google.com/safebrowsing/api/lookup?client=python&apikey=ABQIAAAAAU6Oj8JFgQpt0AXtnVwBYxQYl9AeQCxMD6irIIDtWpux_GHGQQ&appver=0.1&pver=3.0
Unexpected server response
name 'urllib2' is not defined
error
error
The library doesn't support Python3.x.
In this case, you can either make it support Python3 (there is also an opened pull request for Python3 Compatibility), or make the request to "Google Safebrowsing API" manually.
Here's an example using requests:
import requests
key = 'your key here'
URL = "https://sb-ssl.google.com/safebrowsing/api/lookup?client=api&apikey={key}&appver=1.0&pver=3.0&url={url}"
def is_safe(key, url):
response = requests.get(URL.format(key=key, url=url))
return response.text != 'malware'
print(is_safe(key, 'http://addonrock.ru/Debugger.js/')) # prints False
print(is_safe(key, 'http://google.com')) # prints True
Just the same, but without third-party packages (using urllib.request):
from urllib.request import urlopen
key = 'your key here'
URL = "https://sb-ssl.google.com/safebrowsing/api/lookup?client=python&apikey={key}&appver=1.0&pver=3.0&url={url}"
def is_safe(key, url):
response = urlopen(URL.format(key=key, url=url)).read().decode("utf8")
return response != 'malware'
print(is_safe(key, 'http://addonrock.ru/Debugger.js/')) # prints False
print(is_safe(key, 'http://google.com')) # prints True

Categories