Mocking an API call python? - python

I have a class:
class Base(object):
def __init__(self, api_key):
self.api_key = api_key
self.url = 'http://someurl.com/api/'
self.inital_params = {"api_token": self.api_key}
def make_request(self, endpoint, params = None):
if params:
params.update(self.initial_params)
url = self.url + endpoint
r = requests.get(url, params=params)
response = r.json()
data = response.get("data")
return data
Now, I am trying to mock a HTTP request via this class.
import responses
#responses.activate
def test():
responses.add(
responses.GET,
'http://someurl.com/api/endpoint',
json= {"error": "not found"},
status=404
)
base = Base(api_key)
resp = base.make_request(endpoint=endpoint)
assert resp.json() == {"error": "not found"}
But this isn't working. I am getting the following error: requests.exceptions.ConnectionError: Connection refused by Responses - the call doesn't match any registered mock. What url am I supposed to pass in in responses.add()?
Do I have to pass in the exact url I pass in to my requests.get() function within make_request() within my Base class?

Related

Openstack Compute API Update Server Tags

I am using Django Rest API on open stack version 2.1 Unable to update Server Tags it is giving response text:'{"itemNotFound": {"code": 404, "message": "The resource could not be found."}}'
the code explanation is there is an array of object contains multiple server objects with updated tags within a loop it will request and append responses in an array for front-end
#classmethod
def update_tags(self, params):
check(self, params)
responses = []
try:
headers = {
"X-Auth-Token": params.headers['x-auth-token'],
"Content-type": 'application/json'
}
for server in params.data['payload']:
replace_tags = {"tags": server["tags"]}
response=requests.put(os.environ.get('PROTOCOL')+'://'+os.environ.get('OPENSTACK_HOST')+':'+os.environ.get('COMPUTE_PORT')+'/v2.1/servers/' +server['id']+'/tags', data=json.dumps(replace_tags), headers=headers, verify=False)
responses.append(response)
return responses
except Exception as e:
raise e
parameters which are being passed from check method which is written below
def check(self, params):
self.connection = get_openstack_conn(params)
the get_openstack_conn method
def get_openstack_conn(params):
token = params.META.get("HTTP_X_AUTH_TOKEN")
scope = params.META.get("HTTP_X_SCOPE")
organization = params.META.get("HTTP_X_ORG")
print("environment variables")
print("PROTOCOL = ",os.environ.get('PROTOCOL'))
print("OPENSTACK_HOST = ",os.environ.get('OPENSTACK_HOST'))
print("KEY_STONE_PORT = ",os.environ.get('KEY_STONE_PORT'))
if(scope == 'projectScope'):
connector = openstack.connect(auth_url=os.environ.get('PROTOCOL')+'://'+os.environ.get('OPENSTACK_HOST') +
':'+os.environ.get('KEY_STONE_PORT')+'/v3', verify=False, auth_type="token", token=token, project_id=organization)
elif(scope == 'domainScope'):
connector = openstack.connect(auth_url=os.environ.get('PROTOCOL')+'://'+os.environ.get('OPENSTACK_HOST') +
':'+os.environ.get('KEY_STONE_PORT')+'/v3', verify=False, auth_type="token", token=token, domain_id=organization)
return connector

Python Mocking a request for a bearer token?

I'm trying to figure out how to mock my request for a bearer token in python.
I have a class:
class grab_apitokens(object):
def __init__(self, consumer_key, first_api_url, second_api_user, second_api_password, second_api_url):
self.consumer_key = consumer_key
self.second_api_user = second_api_user
self.second_api_password = second_api_password
self.first_api_url = first_api_url
self.second_api_url = second_api_url
def logintofirstsite(self):
b64val = base64.b64encode(self.consumer_key.encode()).decode()
headers = {"Authorization": "Basic %s" % b64val}
data = {'grant_type': 'client_credentials', 'validity_period': '3600'}
try:
response = requests.post(self.first_api_url, headers=headers, data=data)
decodedresponse = json.loads(response.content.decode())
access_token = decodedresponse['access_token']
return access_token
except:
return None
def logintosecondsite(self):
header = {"accept": "application/json", "Content-Type": "application/x-www-form-urlencoded"}
logindata = {'grant_type': 'password',
'username': "" + self.second_api_user + "", 'password': "" + self.second_api_password + ""
}
try:
returnedfromsite = requests.post(self.second_api_url + '/api/V1/token',
headers=header, data=logindata)
return returnedfromsite.json()['access_token']
except:
return None
What I can't figure out is how to mock that requests call and what it would look like in Python.
My test currently looks like:
class MyTestCase(unittest.TestCase):
def setUp(self) -> None: # PROBLEM BEGINS HERE
self.grab_apitokens = grab_apitokens(actual_key, actual_site1, actual_user, actual_pass, actual_2nd_site)
#patch('grab_apitokens.requests.get')
def test_login(self, mock_get):
mock_get.return_value.ok = True
response = self.grab_apitokens.logintosite()
assert_is_not_none(response)
# self.assertEqual(True, False)
if __name__ == '__main__':
unittest.main()
How would I mock the requests.post functionality?
With the help of a good mentor I figured out that my approach was all wrong. Here's what I ended up with for the unit test:
class MyTestCase(unittest.TestCase):
def setUp(self) -> None:
self.grab_apitokens = grab_apitokens("complete","gibberish","it really doesnt","matter","what is","in","here")
#patch('grab_apitokens.requests.posts')
def test_login(self, mock_get):
mock_json = {'token': 'foo'}
mock_get.return_value = Mock(ok=True)
mock_get.return_value.json.return_value = mock_json
mock_get.return_value.content = b'{"token": "foo"}'
response = self.grab_apitokens.logintofirstsite()
assert_equal(response, "foo")
if __name__ == '__main_
To understand what this does, I needed to know that what we're really mocking isn't the method logintofirstsite(), we're mocking the response that requests.post is making in the method. With mock_get, we're inject requests.posts to always be: {'token': 'foo'} . So everything after
response = requests.post(self.first_api_url, headers=headers, data=data)
in logintofirstsite() is what I'm really testing. Which is:
decodedresponse = json.loads(response.content.decode())
access_token = decodedresponse['token']
return access_token
The setup before the requests.post call doesn't matter one bit. Since with {'token': 'foo'} is what my requests.post call returns, the returned value after that bit of logic is 'foo', and so the assert_equal back in MyTestCase passes.

How do I Mock a method that makes multiple POST and GET request all requiring different response data?

I have looked at How to mock REST API and I have read the answers but I still can't seem to get my head around how I would go about dealing with a method that executes multiple GET and POST requests. Here is some of my code below.
I have a class, UserAliasGroups(). Its __init__() method executes requests.post() to login into the external REST API. I have in my unit test this code to handling the mocking of the login and it works as expected.
#mock.patch('aliases.user_alias_groups.requests.get')
#mock.patch('aliases.user_alias_groups.requests.post')
def test_user_alias_groups_class(self, mock_post, mock_get):
init_response = {
'HID-SessionData': 'token==',
'errmsg': '',
'success': True
}
mock_response = Mock()
mock_response.json.return_value = init_response
mock_response.status_code = status.HTTP_201_CREATED
mock_post.return_value = mock_response
uag = UserAliasGroups(auth_user='TEST_USER.gen',
auth_pass='FakePass',
groups_api_url='https://example.com')
self.assertEqual(uag.headers, {'HID-SessionData': 'token=='})
I also have defined several methods like obtain_request_id(), has_group_been_deleted(), does_group_already_exists() and others. I also define a method called create_user_alias_group() that calls obtain_request_id(), has_group_been_deleted(), does_group_already_exists() and others.
I also have code in my unit test to mock a GET request to the REST API to test my has_group_been_deleted() method that looks like this:
has_group_been_deleted_response = {
'error_code': 404,
'error_message': 'A group with this ID does not exist'
}
mock_response = Mock()
mock_response.json.return_value = has_group_been_deleted_response
mock_response.status_code = status.HTTP_404_NOT_FOUND
mock_get.return_value = mock_response
Now I can get to my question. Below is the pertinent part of my code.
class UserAliasGroups:
def __init__(
self,
auth_user=settings.GENERIC_USER,
auth_pass=settings.GENERIC_PASS,
groups_api_url=settings.GROUPS_API_URL
):
""" __init__() does the login to groups. """
self.auth_user = auth_user
self.auth_pass = auth_pass
self.headers = None
self.groups_api_url = groups_api_url
# Initializes a session with the REST API service. Each login session times out after 5 minutes of inactivity.
self.login_url = f'{self.groups_api_url}/api/login'
response = requests.post(self.login_url, json={}, headers={'Content-type': 'application/json'},
auth=(auth_user, auth_pass))
if response.status_code is not 201:
try:
json = response.json()
except:
json = "Could not decode json."
raise self.UserAliasGroupsException(f"Error: User {self.auth_user}, failed to login into "
f"{self.login_url} {json}")
response_json = response.json()
self.headers = {'HID-SessionData': response_json['HID-SessionData']}
def obtain_request_id(self, request_reason):
payload = {'request_reason': request_reason}
url = f'{self.groups_api_url}/api/v1/session/requests'
response = requests.post(url=url, json=payload, headers=self.headers)
if response.status_code is not status.HTTP_200_OK:
try:
json = response.json()
except:
json = "Could not decode json."
msg = f'obtain_request_id() Error url={url} {response.status_code} {json}.'
raise self.UserAliasGroupsException(msg)
request_id = response.json().get('request_id')
return request_id
def has_group_been_deleted(self, group_name):
url = f'{self.groups_api_url}/api/v1/groups/{group_name}/attributes/RESATTR_GROUP_DELETED_ON'
response = requests.get(url=url, headers=self.headers)
return response.status_code == status.HTTP_200_OK
def does_group_already_exists(self, group_name):
url = f'{self.groups_api_url}/api/v1/groups/{group_name}'
response = requests.get(url=url, headers=self.headers)
if response.status_code is status.HTTP_200_OK:
# check if the group has been "deleted".
return not self.has_group_been_deleted(group_name=group_name)
return False
def create_user_alias_group(
self,
... long list of params omitted for brevity ...
):
if check_exists:
# Check if group already exists or not.
if self.does_group_already_exists(group_name):
msg = f'Cannot create group {group_name}. Group already exists.'
raise self.UserAliasGroupsException(msg)
... more code omitted for brevity ...
My question is how do I write my unit test to deal with multiple calls to requests.post() and request.get() all resulting in different responses in my create_user_alias_group() method?
I want to call create_user_alias_group() in my unit test so I have to figure out how to mock multiple requests.get() and requests.post() calls.
Do I have use multiple decorators like this:
#mock.patch('aliases.user_alias_groups.obtain_request_id.requests.post')
#mock.patch('aliases.user_alias_groups.does_group_already_exists.requests.get')
#mock.patch('aliases.user_alias_groups.has_group_been_deleted.requests.get')
def test_user_alias_groups_class(self, mock_post, mock_get):
...
?
Thanks for looking my long question :)
You can use mock.side_effect which takes an iterable. Then different calls will return different values:
mock = Mock()
mock.side_effect = ['a', 'b', 'c']
This way the first call to mock returns "a", then the next one "b" and so on. (In your case, you'll set mock_get.side_effect).

mock botocore.vendored requests with requests_mock and pytest

I am trying to mock a get request with requests_mock, but it doesn't seem to get it right.
My function calling a third-party API defined in a file lookup.py:
from botocore.vendored import requests
def get_data():
url = 'https://abc.something.com/datapackage'
url_params={
'v': 2,
'auth_apikey':'xyz'
}
resp = requests.get(url, params=url_params)
return resp.json()
I am using py.test to run my tests and in my test file. I have a fixture:
import requests_mock
import requests, pytest
from lookup import get_data
#pytest.fixture
def req_mock(request):
m = requests_mock.Mocker()
m.start()
request.addfinalizer(m.stop)
return m
def test_api_gets_data(req_mock):
sample={
'key1':123
}
lookup_url = 'https://abc.something.com/datapackage'
query_params = {
'v': 2,
'auth_apikey':'xyz'
}
req_mock.get(lookup_url, json=sample)
resp = get_data()
Apparently, requests_mock isn't able to use the same session as the requests in the get function, so it isn't getting mocked.
Is there a better way to do this?
I'm using Python 3.6, requests 2.18, requests-mock 1.52 and pytest 3.0.7.
at lookup.py files, lookup_url will raise NameError cause name lookup_url not defined.
use url and don't forget to change params=url_params, so the code will be something like this:
resp = requests.get(url, params=url_params)
Apparently you can't mock from botocore.vendored import requests with requests_mock.
Instead use unittest.mock to mock the response.
from unittest import mock
class MockResponse:
def __init__(self, status_code, json_data=None):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
def raise_for_status(self):
if self.status_code >= 500:
raise Exception
item_not_found = {
"Response": {
"StatusCode": "ItemNotFound",
}
}
item_not_found_resp = MockResponse(200, item_not_found)
#mock.patch('botocore.vendored.requests.get', return_value=item_not_found_resp)
def test_api_returns_not_found_when_third_party_api_returns_item_not_found(mc):
resp = get(e1, c)
exp_resp = {
"statusCode": 404,
"body": json.dumps({
'error': 'no item found'
})
}
request_url = mc.call_args[0][0]
request_params = mc.call_args[1]['params']
assert lookup_url == request_url
assert query_params == request_params
assert exp_resp == resp

How to reset Requests?

Im trying to write a REST Client for a closed API. I call a specific function twice but it only works the first time. It's the same command. If i run the script twice with batch It works. Im suspecting Requests keeps the connection alive or caches data. How do i "reset" Requests ?
base_headers = {"Connection":"keep-alive",
"User-Agent":user_agent,
"Accept-Encoding":"gzip",
"Host":"xxxxxxxxxxx",
"Content-Type":"application/json; charset=UTF-8"}
global auth
auth = 'xxxxxx'
def create_header(doAuth = True):
headers = base_headers
if doAuth:
headers['Authorization'] = auth
return headers
def do_get(url):
return requests.get(url, headers=create_header());
def do_put(url, payload):
if payload is None:
return requests.put(url, headers=create_header())
return requests.put(url, data=payload, headers=create_header())
def upvote(postid):
return do_put("xxxxxxxxxxxxx" % postid, None).content
def set_pos(longtitude, latitude, place, country="DE"):
payload = {'xxxxx':'xxxx'}
json_payload = json.dumps(payload)
return do_put("xxxxxxxxxxxxxxx",json_payload).content
def do_post(url, payload, doAuth=True):
return requests.post(url, data=payload, headers=create_header(doAuth=doAuth))
def new_acc(place, latitude, longtitude):
access = get_access_token(place, latitude, longtitude)
print access
global auth
auth = "Bearer %s" % access['access_token']
set_pos(longtitude, latitude, place) # Does a POST
for i in range(1,30):
posts = new_acc(uni['city'], uni['latitude'], uni['longtitude'])
upvote('xxxxxxxxxxxxx')
Basically the first upvote() goes through every time but every succeding does not.
I'm almost positive that keep-alive is not doing this. I would suggest writing some handlers to debug the response if you don't get the expected response code after the request.
Also, might I suggest a slightly different design that avoids global?
class RESTClient(object):
BASE_HEADERS = {"Connection":"keep-alive",
"Accept-Encoding":"gzip",
"Host":"xxxxxxxxxxx",
"Content-Type":"application/json; charset=UTF-8"}
def __init__(self, user_agent, auth = None):
self.headers = dict(self.BASE_HEADERS)
self.headers['User-Agent'] = user_agent
self.auth = auth
def create_header(self):
headers = dict(self.headers)
if auth:
headers['Authorization'] = auth
return headers
def do_get(self, url):
return requests.get(url, headers=self.create_header());
def do_put(self, url, payload = None):
if not payload:
return requests.put(url, headers=self.create_header())
return requests.put(url, data=payload, headers=self.create_header())
def upvote(self, postid):
return do_put("xxxxxxxxxxxxx" % postid).content
def set_pos(self, longtitude, latitude, place, country="DE"):
payload = {'xxxxx':'xxxx'}
json_payload = json.dumps(payload)
return do_put("xxxxxxxxxxxxxxx",json_payload).content
def do_post(self, url, payload, do_auth = None):
return requests.post(url, data=payload, headers=self.create_header())
def new_acc(self, place, latitude, longtitude):
access = get_access_token(place, latitude, longtitude)
print access
self.auth = "Bearer %s" % access['access_token']
set_pos(longtitude, latitude, place)
rc = RESTClient('Mozilla/5.0 ... Firefox/38.0', 'my-auth-token')
rc.upvote(post_id)
etc...
It could be that your use of globals is breaking things. Having not run your code, I can't check, but I would avoid using globals and favor tighter control of your state via objects.
EDIT:
Given that this answer was accepted by the OP, I am assuming the defect was caused by mutation of globals.

Categories

Resources