How to check if a request mocked by requests_mock added some query parameters to a URL?
I have a function func thats do a HTTP POST on the url with some query string on the URL and I want to check if was called with this query string.
This is my attempt, but fails.
query is a empty string and qs is a empty dict.
I have sure that my func is appending the query string on the request.
with requests_mock.Mocker() as mock:
mock.post(url, text=xml)
func() # This function will call url + query string
history = mock.request_history[0]
assert history.method == "POST" # OK
assert history.query is None # Returns an empty string, AssertionError: assert '' is None
assert history.qs is None # Returns an empty dict, assert {} is None
My func
def credilink():
url = settings["url"]
params = settings["params"]
params["CC"] = query
response = requests.post(url, params=params)
# ...
I tried to reproduce your problem and was unable to...
Here is the code I'm running:
import requests
import requests_mock
url = "http://example.com"
settings = dict(url=url, params=dict(a=1))
query = "some-query"
xml = "some-xml"
def credilink():
url = settings["url"]
params = settings["params"]
params["CC"] = query
response = requests.post(url, params=params)
return response.text
# ...
def test():
with requests_mock.Mocker() as mock:
mock.post(url, text=xml)
data = credilink() # This function will call url + query string
history = mock.request_history[0]
assert history.method == "POST" # OK
assert history.qs == dict(a=['1'], cc=[query])
assert history.query == f"a=1&cc={query}"
assert data == xml
The assertions pass in this snippet.
Maybe it's some version problem? I used requests==2.25.1 and requests-mock==1.8.0.
In my case, the problem was in mock:// URL schema, as it's present in the requests-mock samples
session.get('mock://test.com/path')
But requests library skips query arguments for non "http" URLs. Here is the comment from the source code
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
# `data` etc to work around exceptions from `url_parse`, which
# handles RFC 3986 only.
if ':' in url and not url.lower().startswith('http'):
self.url = url
return
Related
I need to make several requests, using either get or post method, I want to write the requests into one statement like
response = getattr(requests, method)(url, parameters)
when method == 'get', Above should work as response = getattr(requests, method)(url, params=parameters), when method == 'post', it should work as response = getattr(requests, method)(url, json=parameters)
You can rely on destructuring for something that doesn't look too terribly ugly.
response = getattr(requests, method)(url, **{('params' if method=='get' else 'json'):parameters})
def getattr(method):
if method == "get": return requests.get
elif method == "post": return requests.post
When you call getattr(method) it will return a function object accordingly. Next you can call the returned function object accordingly.
response = getattr("get")(url, params=parameters) translates to response = requests.get(url, params=parameters)
params = xxx
json =yyy
if method == 'GET':
response = getattr(requests, method)(url, parameters = params)
elif method == 'POST':
response = getattr(requests, method)(url, parameters = json)
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).
I am trying to create an ansible module using the python requests library, and the module runs without errors but returns the response
"{\n \"code\" : \"generic_err_missing_required_header\",\n \"message\" : \"Missing header: [X-chkp-sid]\"\n}"
even though the header appears to be correctly defined.
Module code below:
from ansible.module_utils.basic import AnsibleModule
import requests
def somefunction(sid):
url = '<someurl>'
headers = {
'Content-Type': 'application/json',
'X-chkp-sid': sid,
}
data = {
'type': 'tag'
}
response = requests.post(url,headers,data,verify=False,timeout=10)
return response.content
def main():
module_args = dict(
sid = dict(type='str', required=True)
)
result = dict(
changed=False,
original_message='',
message=''
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
if module.check_mode:
return result
sid = module.params['sid']
result=somefunction(sid)
module.exit_json(somefunction_output=result)
if __name__ == '__main__':
main()
I have gotten a successful response using the ansible uri module and identical header/body parameters. Any other ideas?
Most arguments to requests.post() should be keyword-style args instead of plain positional args.
Try this instead:
response = requests.post(url,data=data,headers=headers,verify=False,timeout=10)
somefunction() does not return anything, therefore the return value is None.
Perhaps you wanted return requests.post(url,headers,data,verify=False,timeout=10)?
I am currently working on shopware API When I am parsing the URL of
like
http://192.168.0.100/shopware531/api
and give me an error that:~
connection_type = SCHEME_TO_CONNECTION[scheme]
KeyError: u' http'
Using the
def buildHttpQuery(self, taxonomy, parameters):
if taxonomy.startswith('/'):
taxonomy = taxonomy[1:]
if not self.baseurl.endswith('/'):
self.baseurl += '/'
url = urljoin(self.baseurl, taxonomy)
url_parts = list(urlparse(url))
query = dict(parse_qsl(url_parts[4]))
query.update(parameters)
url_parts[4] = urlencode(query)
url = urlunparse(url_parts)
return url
and url return is :~ http://192.168.0.100/shopware531/api
I have the similiar problem but with bytes. I have link like: b"https://google.com" and I use httplib2.request(str(link)) because this request wants string instead of bytes. Later on debugger I saw that str function transforms b'https://google.com' into b'https://google.com' and cause KeyError. So after using b'https://google.com'.decode('utf-8') it works.
The functionality I am trying to test is as follows. I am trying to mock the client in this function, which comes from my auth module. I am trying to make the client's get function return a Mock object, which contains a property text which refers to our mock_response (seen further down).
def get_person_by_email(email):
client = auth.client()
# print(client) = <Mock name='mock_client()' id='4324810640'>
response = client.get(url="http://..." + email)
# print(response) = <Mock name='mock_client().get()' id='4575780304'>
# print(response.text) = <Mock name='mock_client().get().text' id='4348534608'>
return jsonify(loads(utils.strip_security_string(response.text)))
The function which is throwing TypeError: 'Mock' object has no attribute '__getitem__' is:
def strip_security_string(json_string):
return "\n".join(json_string.split("\n")[1:])
Which simply removes the first line from the response.
And finally, the code which is trying to test the above functionality:
def test_get_person_by_email():
with app.test_client() as client:
with app.app_context():
mock_response = """security-string
{"key":"value"}"""
mock_client = Mock(name='mock_client')
mock_client.get.return_value = Mock(text=mock_response)
with patch.object(auth, 'client', mock_client):
response = client.get("http://.../email/email#domain.com")
I'm not an mock expert but you could try to change mock_client().get.return_value with mock_client.get.return_value. That's because the code use auth.client() instead of auth.client
If you don't want to access to the mock_client() in the Mock creation stage you can do
mock_client_obj = Mock(name='mock_client_obj')
mock_client_obj.get.return_value = Mock(text=mock_response)
mock_client = Mock(name='mock_client',return_value=mock_client_obj)
Or a more simple
mock_client = Mock(name='mock_client')
mock_client.return_value.get.return_value.text = mock_response
On closer look, mock_response is not set to a valid JSON value. Perhaps you mean something like
mock_reponse= """[ "security-string", { "key": "value" } ]"""
See http://json.org for the grammar that describes correct values.