How can I mock a boto3 sns call in unit tests? - django

I am trying to unit test a function:
#shared_task()
def push_notification(message=None, message_type=None, user_id=None, data={}):
# Get the aws arn from token table
aws_token_data = AwsDeviceToken.objects.filter(user_id=user_id).latest("id")
client = boto3.client('sns', **aws.AWS_CREDENTIAL)
message = {
'default': message,
more stuff here
'data': data})
}
message = json.dumps(message, ensure_ascii=False)
response = client.publish(
TargetArn=str(aws_token_data.aws_PLATFORM_endpoint_arn),
Message=message,
MessageStructure='json',
MessageAttributes={}
)
return response
When users register for our service they get a topic arn based on their device type.
I have tried:
def test_push_notification(self):
with mock.patch('boto3.client') as mock_client:
data = {'Some data': "to be sent"}
push_notification(
message="your invitation has been accepted",
message_type='b2g_accepted',
user=self.user,
data=data
)
self.assertEqual(mock_client.call_count, 1)
Where self.user is a user registered in the setUp method of TestCase. This fails, the call_count is 0
I am scratching my head trying to figure out a way to test this function but mostly coming up with third party modules or examples for S3.
Any help is appreciated

You need to mock where it is imported (unless it is a class method)
So if your push_notification function is in a module called my_module, you should write:
With mock.patch('my_module.boto3.client') as mock_client

Related

Unit testing with AWS Cognito and GraphQL

I'm currently writing tests for my software but got stuck at the point.
I try to get data from my db with a normal GraphQL Query but my endpoint is first checking, if the idToken within the header is valid.
For the user handling I'm using AWS Cognito but couldn't find a good way to mock the login to retrieve the valid token to query and mutate the data within various endpoints.
Any idea how to handle this case?
Here is my code from the graphene docs (https://docs.graphene-python.org/projects/django/en/latest/testing/):
# Create a fixture using the graphql_query helper and `client` fixture from `pytest-django`.
import json
import pytest
from graphene_django.utils.testing import graphql_query
# https://docs.graphene-python.org/projects/django/en/latest/testing/
#pytest.fixture
def client_query(client):
def func(*args, **kwargs):
return graphql_query(*args, **kwargs, client=client)
return func
# Test you query using the client_query fixture
def test_some_query(client_query):
response = client_query(
'''
query GetAllProjectConfig{
getAllProjectConfig{
project{
id
slug
name
}
config{
id
}
}
}
''',
)
content = json.loads(response.content)
assert 'errors' not in content
The answer was not so hard:
auth_data = {'USERNAME': username, 'PASSWORD': password}
# auth the user on cognito
def auth_cognito_user():
provider_client = boto3.client(
'cognito-idp', region_name=os.environ.get('region_name'))
resp = provider_client.admin_initiate_auth(
UserPoolId=userpool_id, AuthFlow='ADMIN_NO_SRP_AUTH', AuthParameters=auth_data, ClientId=client_id)
# print("RESPONSE COGNITO", resp['AuthenticationResult']['IdToken'])
return resp['AuthenticationResult']['IdToken']

Django Session Variables Don't Work In Stripe Webhook?

I am trying to use data saved in django session variables to run a function once the webhook has confirmed that 'checkout.session.completed' but I always get a key error. I am 100% sure the keys exist in the session variables.
Here is my webhook:
#csrf_exempt
def stripe_webhook(request):
# You can find your endpoint's secret in your webhook settings
endpoint_secret = 'secret'
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
event = None
try:
event = stripe.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
except ValueError as e:
# Invalid payload
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError as e:
# Invalid signature
return HttpResponse(status=400)
# Handle the checkout.session.completed event
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
fulfull_order(session)
return HttpResponse(status=200)
Here is my fulfill order function:
def fulfull_order(session):
generator = PlanMaker(goal=request.session['goal'], gender=request.session['gender'])
/// send email code.
This line generator = PlanMaker(goal=request.session['goal'], gender=request.session['gender'])
Always gives a key error on request.session['goal'] The key definitely exists, it just seems it is inaccessible from the webhook view.
How to solve?
You should save the information you want to the metadata field when creating the checkout.Session.
def checkout(request):
session = stripe.checkout.Session.create(
payment_method_types=['card'],
line_items=[{
'price': 'price_key',
'quantity': 1,
}],
mode='payment',
success_url=request.build_absolute_uri(reverse('success_url')) + '?session_id={CHECKOUT_SESSION_ID}',
cancel_url=request.build_absolute_uri(reverse('cancel_url')),
metadata={'someKeyHere': 'your session variable data'}
)
return JsonResponse({
'session_id' : session.id,
'stripe_public_key' : settings.STRIPE_PUBLISHABLE_KEY
})
then you can access the information like session['metadata']['someKeyHere']
The webhook event is a separate request coming directly from Stripe that would not be related to any Django session and so this lack of session data would seem expected. As #Anthony suggests you can store this information in the Checkout Session metadata when you create the session. The metadata will be included in the webhook object.

Mocking external API in Django

I am trying to mock external api in Django but not sure how to do it properly.
Basically, it must mock the json data from external API and then create a new object if all values are valid.
The program fetches the geolocation data based on given IP address and saves the object in database if response data includes all required fields. So, how I can mock this process to test a new object creation?
services.py
import os
import requests
from .exceptions import ExternalApiException
def get_location(ip):
url = f'http://api.ipstack.com/{ip}'
params = {'access_key': os.environ.get('ACCESS_KEY')}
try:
res = requests.get(url, params=params)
data = res.json()
return {
'ip':data['ip'],
'country_name':data['country_name'],
'region_code':data['region_code'],
'city':data['city'],
'latitude':data['latitude'],
'longitude':data['longitude'],
'zip_code':data['zip']
}
except requests.exceptions.ConnectionError:
raise ExternalApiException('Connection error occured during the fetch process')
except requests.exceptions.Timeout:
raise ExternalApiException("Connection timeout. Please check your internet connection and try again later")
except requests.exceptions.TooManyRedirects:
raise ExternalApiException("Too many redirects")
except requests.exceptions.RequestException:
raise SystemExit(e)
tests.py
#I am lost in this part
#patch('geolocation.services.get_location')
def test_create_basic_geolocation(self, mock_request):
"""Test creating geolocation data"""
payload = {
'ip': '',
}
res = self.client.post(LOCATIONS_URL, payload)
self.assertTrue(res.data['ip'])
Thanks for any help.
Just assign return_value on mocked instance like this
#patch('geolocation.services.get_location')
def test_create_basic_geolocation(self, mock_request):
"""Test creating geolocation data"""
mock_request.return_value = {"ip": "hello", "country_name": "test"}
payload = {
'ip': '',
}
res = self.client.post(LOCATIONS_URL, payload)
self.assertTrue(res.data['ip'])

python web py automated testing

I am having an issue with automated testing in web py framework.
I am going through the last exercise of learn python the hard way. In this exercise we make a web application "engine" that runs a map of rooms.
I want to be able to automate test every single room, but there is one problem, is that the engine depends on the previous room to decide which room to go to next (and user input).
if web.config.get("_session") is None:
store = web.session.DiskStore("sessions")
session = web.session.Session(app, store, initializer={"room":None})
web.config._session = session
else:
session = web.config._session
This class handles GET request sent to /
class Index(object):
def GET(self):
session.room = map.START
web.seeother("/game")
This class handles GET and POST requests to /game
class GameEngine(object):
def GET(self):
if session.room:
return render.show_room(room=session.room)
else:
return render.you_died()
def POST(self):
form = web.input(action=None)
if session.room and form.action:
session.room = session.room.go(form.action)
web.seeother("/game")
In my automated testing I use two things: first I use the app.request API:
app.request(localpart='/', method='GET',data=None,
host='0.0.0.0:8080', headers=None, https=False)
create a response object, something like:
resp = app.request("/game", method = "GET")
Second I pass the resp object to this function to check for certain things:
from nose.tools import *
import re
def assert_response(resp, contains=None, matches=None, headers=None,
status="200"):
assert status in resp.status, "Expected response %r not in %r" %
(status, resp.status)
if status == "200":
assert resp.data, "Response data is empty"
if contains:
assert contains in resp.data, "Response does not contain %r" %
contains
if matches:
reg = re.compile(matches)
assert reg.matces(resp.data), "Response does not match %r" %
matches
if headers:
assert_equal(resp.headers, headers)
We can pass variables as a dictionary to the keyword argument data in the API app.request to modify the web.input().
my question is: in my automated test module how do we "pass" a value that overwrite the room value in the initializer dictionary in our session:
session = web.session.Session(app, store, initializer={"room":None})
In the app module its done by setting
session.room = map.START
and then session.room updates using:
if session.room and form.action:
session.room = session.room.go(form.action)
Thanks for taking the time to read this, and any insights would be appreciated!
Alright I finally found it! The main issue here was that every time I make a http request through app.request it gives me a new session ID.
The trick that I found thanks to this post:
How to initialize session data in automated test? (python 2.7, webpy, nosetests)
is to record the session ID of the request to reuse that ID in my automated tests by passing it to the headers keyword argument in the request!
record the session ID using this function (which I placed as suggested in the post in tests/tools.py):
def get_session_id(resp):
cookies_str = resp.headers['Set-Cookie']
if cookies_str:
for kv in cookies_str.split(';'):
if 'webpy_session_id=' in kv:
return kv
then in the automated tests something like:
def test_session():
resp = app.request('/')
session_id = get_session_id(resp)
resp1 = app.request('/game', headers={'Cookie':session_id})
assert_response(resp1, status='200', contains='Central Corridor')
I hope that helps in the future for programmers who get stuck on the same issue!

How to Test Stripe Webhooks with Mock Data

I'm trying to write a unit test that posts a mock event to my stripe webhook.
I went and pulled an event from my logs and tried sending that with the test mode enabled, but I (somewhat predictably) got an error:
a similar object exists in live mode, but a test mode key was used to make this request.
Fair enough. So how do I create a mock event that I can actually send to my webhook and get it processed correctly?
Here's my current test:
class StripeTest(TestCase):
def setUp(self):
self.client = Client()
def test_receiving_a_callback(self):
with open('donate/test_assets/stripe_event.json', 'r') as f:
stripe_event = simplejson.load(f)
self.client.post('/donate/callbacks/stripe/',
data=simplejson.dumps(stripe_event),
content_type='application/json')
The solution is to create your own mock data. In the code below we create a test payment by creating a stripe token, then submitting it via the front end (at the /donate/ endpoint).
Once the front end has worked properly, you can get the event from stripe and then send it to your development machine's webhook endpoint.
This is more work than I expected, and I don't love that my tests are hitting the network, but it seems to be a decent solution. I feel a lot more confident about my payments than before.
def test_making_a_donation_and_getting_the_callback(self):
"""These two tests must live together because they need to be done sequentially.
First, we place a donation using the client. Then we send a mock callback to our
webhook, to make sure it accepts it properly.
"""
stripe.api_key = settings.STRIPE_SECRET_KEY
# Create a stripe token (this would normally be done via javascript in the front
# end when the submit button was pressed)
token = stripe.Token.create(
card={
'number': '4242424242424242',
'exp_month': '6',
'exp_year': str(datetime.today().year + 1),
'cvc': '123',
}
)
# Place a donation as an anonymous (not logged in) person using the
# token we just got
r = self.client.post('/donate/', data={
'amount': '25',
'payment_provider': 'cc',
'first_name': 'Barack',
'last_name': 'Obama',
'address1': '1600 Pennsylvania Ave.',
'address2': 'The Whitehouse',
'city': 'DC',
'state': 'DC',
'zip_code': '20500',
'email': 'barack#freelawproject.org',
'referrer': 'footer',
'stripeToken': token.id,
})
self.assertEqual(r.status_code, 302) # 302 because we redirect after a post.
# Get the stripe event so we can post it to the webhook
# We don't know the event ID, so we have to get the latest ones, then filter...
events = stripe.Event.all()
event = None
for obj in events.data:
if obj.data.object.card.fingerprint == token.card.fingerprint:
event = obj
break
self.assertIsNotNone(event, msg="Unable to find correct event for token: %s" % token.card.fingerprint)
# Finally, we can test the webhook!
r = self.client.post('/donate/callbacks/stripe/',
data=simplejson.dumps(event),
content_type='application/json')
# Does it return properly?
self.assertEqual(r.status_code, 200)