Mock Stripe Methods in Python for testing - unit-testing

So I am trying to mock all the stripe web hooks in the method so that I can write the Unit test for it. I am using the mock library for mocking the stripe methods. Here is the method I am trying to mock:
class AddCardView(APIView):
"""
* Add card for the customer
"""
permission_classes = (
CustomerPermission,
)
def post(self, request, format=None):
name = request.DATA.get('name', None)
cvc = request.DATA.get('cvc', None)
number = request.DATA.get('number', None)
expiry = request.DATA.get('expiry', None)
expiry_month, expiry_year = expiry.split("/")
customer_obj = request.user.contact.business.customer
customer = stripe.Customer.retrieve(customer_obj.stripe_id)
try:
card = customer.sources.create(
source={
"object": "card",
"number": number,
"exp_month": expiry_month,
"exp_year": expiry_year,
"cvc": cvc,
"name": name
}
)
# making it the default card
customer.default_source = card.id
customer.save()
except CardError as ce:
logger.error("Got CardError for customer_id={0}, CardError={1}".format(customer_obj.pk, ce.json_body))
return Response({"success": False, "error": "Failed to add card"})
else:
customer_obj.card_last_4 = card.get('last4')
customer_obj.card_kind = card.get('type', '')
customer_obj.card_fingerprint = card.get('fingerprint')
customer_obj.save()
return Response({"success": True})
This is the method for unit testing:
#mock.patch('stripe.Customer.retrieve')
#mock.patch('stripe.Customer.create')
def test_add_card(self,create_mock,retrieve_mock):
response = {
'default_card': None,
'cards': {
"count": 0,
"data": []
}
}
# save_mock.return_value = response
create_mock.return_value = response
retrieve_mock.return_value = response
self.api_client.client.login(username = self.username, password = self.password)
res = self.api_client.post('/biz/api/auth/card/add')
print res
Now stripe.Customer.retrieve is being mocked properly. But I am not able to mock customer.sources.create. I am really stuck on this.

This is the right way of doing it:
#mock.patch('stripe.Customer.retrieve')
def test_add_card_failure(self, retrieve_mock):
data = {
'name': "shubham",
'cvc': 123,
'number': "4242424242424242",
'expiry': "12/23",
}
e = CardError("Card Error", "", "")
retrieve_mock.return_value.sources.create.return_value = e
self.api_client.client.login(username=self.username, password=self.password)
res = self.api_client.post('/biz/api/auth/card/add', data=data)
self.assertEqual(self.deserialize(res)['success'], False)

Even though the given answer is correct, there is a way more comfortable solution using vcrpy. That is creating a cassette (record) once a given record does not exist yet. When it does, the mocking is done transparently and the record will be replayed. Beautiful.
Having a vanilla pyramid application, using py.test, my test now looks like this:
import vcr
# here we have some FactoryBoy fixtures
from tests.fixtures import PaymentServiceProviderFactory, SSOUserFactory
def test_post_transaction(sqla_session, test_app):
# first we need a PSP and a User existent in the DB
psp = PaymentServiceProviderFactory() # type: PaymentServiceProvider
user = SSOUserFactory()
sqla_session.add(psp, user)
sqla_session.flush()
with vcr.use_cassette('tests/casettes/tests.checkout.services.transaction_test.test_post_transaction.yaml'):
# with that PSP we create a new PSPTransaction ...
res = test_app.post(url='/psps/%s/transaction' % psp.id,
params={
'token': '4711',
'amount': '12.44',
'currency': 'EUR',
})
assert 201 == res.status_code
assert 'id' in res.json_body

IMO, the following method is better than the rest of the answers
import unittest
import stripe
import json
from unittest.mock import patch
from stripe.http_client import RequestsClient # to mock the request session
stripe.api_key = "foo"
stripe.default_http_client = RequestsClient() # assigning the default HTTP client
null = None
false = False
true = True
charge_resp = {
"id": "ch_1FgmT3DotIke6IEFVkwh2N6Y",
"object": "charge",
"amount": 1000,
"amount_captured": 1000,
"amount_refunded": 0,
"billing_details": {
"address": {
"city": "Los Angeles",
"country": "USA",
},
"email": null,
"name": "Jerin",
"phone": null
},
"captured": true,
}
def get_customer_city_from_charge(stripe_charge_id):
# this is our function and we are writing unit-test for this function
charge_response = stripe.Charge.retrieve("foo-bar")
return charge_response.billing_details.address.city
class TestStringMethods(unittest.TestCase):
#patch("stripe.default_http_client._session")
def test_get_customer_city_from_charge(self, mock_session):
mock_response = mock_session.request.return_value
mock_response.content.decode.return_value = json.dumps(charge_resp)
mock_response.status_code = 200
city_name = get_customer_city_from_charge("some_id")
self.assertEqual(city_name, "Los Angeles")
if __name__ == '__main__':
unittest.main()
Advantages of this method
You can generate the corresponding class objects (here, the charge_response variable is a type of Charge--(source code))
You can use the dot (.) operator over the response (as we can do with real stripe SDK)
dot operator support for deep attributes

Related

Get random questions in a quizgame django API

I am trying to recreate game of hacks because there isnt an API to create my own questions , and implement on external site , however I am using django with restful framework for this task. (I am not sure , if this is the right to achieve this). I will do this via server because I dont want people change js and bypass the stuff or even disable js and stop time , and continue with the same question
apiview.py
#api_view(['GET', 'POST'])
def questions_view(request):
if request.method == 'GET':
questions = Question.objects.all()
serializer = QuestionListPageSerializer(questions, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = QuestionListPageSerializer(data=request.data)
if serializer.is_valid():
question = serializer.save()
return Response(QuestionListPageSerializer(question).data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers
class QuestionListPageSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
question_text = serializers.CharField(max_length=200)
pub_date = serializers.DateTimeField()
was_published_recently = serializers.BooleanField(read_only=True) # Serializer is smart enough to understand that was_published_recently is a method on Question
code = serializers.CharField(max_length=200)
def create(self, validated_data):
return Question.objects.create(**validated_data)
def update(self, instance, validated_data):
for key, value in validated_data.items():
setattr(instance, key, value)
instance.save()
return instance
question list my app
HTTP 200 OK
Allow: POST, OPTIONS, GET
Content-Type: application/json
Vary: Accept
[
{
"id": 1,
"question_text": "helllo",
"pub_date": "2020-01-15T02:30:40Z",
"was_published_recently": false,
"code": "int main (int argc, char *argv[])\r\n{\r\n\tchar *val = argv[argc -1];\r\n\tsystem(val);\r\n\treturn(0);\r\n}"
},
{
"id": 2,
"question_text": "What is the meaning of life?",
"pub_date": "2020-01-15T02:30:40Z",
"was_published_recently": false,
"code": "SOME STRING"
}
]
question with answer my app
HTTP 200 OK
Allow: PATCH, OPTIONS, DELETE, GET
Content-Type: application/json
Vary: Accept
{
"id": 1,
"question_text": "helllo",
"pub_date": "2020-01-15T02:30:40Z",
"was_published_recently": false,
"code": "int main (int argc, char *argv[])\r\n{\r\n\tchar *val = argv[argc -1];\r\n\tsystem(val);\r\n\treturn(0);\r\n}",
"choices": [
{
"id": 1,
"choice_text": "11111"
}
]
}
game of hacks external app
{
"_id": "53fb4014e5d4d40400c7fa4f",
"answers": [
"LDAP Injection",
"CGI Reflected XSS",
"Connection String Injection",
"Reflected XSS"
],
"batch_score": 39.89902034664657,
"checkmarx": true,
"from": "Gilad",
"language": "PHP",
"level": "1",
"question": "What vulnerability the following code contains?",
"snippets": [
{
"code": "<?php\n$dn = $_GET['host'];\n$filter=\"(|(sn=$person*)(givenname=$person*))\";\n$justthese = array(\"ou\", \"sn\", \"givenname\", \"mail\");\n$sr=ldap_search($ds, $dn, $dn, $justthese);\n$info = ldap_get_entries($ds, $sr);\necho $info[\"count\"].\" entries returned\n\";?>"
}
],
"tip": ""
}
example
http://www.gameofhacks.com/api/answer
answer
{quest: true, score: 5700}
quest: true
score: 5700
http://www.gameofhacks.com/api/question # make random instead of api/poll/question/{id} ?
You can use python random library in your view:
from random import randint
def random_question(request):
i = randint(0, Question.objects.count() - 1)
question = Question.objects.all()[i] # get question at random position
....

Graphene errors messages

I wonder if it is possible to translate the validation error messages that graphene provides? For example: "Authentication credentials were not provided" as shown in the code example below.
{
"errors": [
{
"message": "Authentication credentials were not provided",
"locations": [
{
"line": 2,
"column": 3
}
]
}
],
"data": {
"viewer": null
}
}
Create a custom error type
import graphene
from graphene_django.utils import camelize
class ErrorType(graphene.Scalar):
#staticmethod
def serialize(errors):
if isinstance(errors, dict):
if errors.get("__all__", False):
errors["non_field_errors"] = errors.pop("__all__")
return camelize(errors)
raise Exception("`errors` should be dict!")
Add it to your mutations
class MyMutation(graphene.Mutation):
# add the custom error type
errors = graphene.Field(ErrorType)
form = SomeForm
#classmethod
def mutate(cls, root, info, **kwargs):
f = cls.form(kwargs)
if f.is_valid():
pass
else:
# pass the form error to your custom error type
return cls(errors=f.errors.get_json_data())
Example
django-graphql-auth uses a similar error type, and it works like this, for example for registration:
mutation {
register(
email:"skywalker#email.com",
username:"skywalker",
password1: "123456",
password2:"123"
) {
success,
errors,
token,
refreshToken
}
}
should return:
{
"data": {
"register": {
"success": false,
"errors": {
"password2": [
{
"message": "The two password fields didn’t match.",
"code": "password_mismatch"
}
]
},
"token": null,
"refreshToken": null
}
}
}
My Django form errors types, for example:
from graphene.utils.str_converters import to_camel_case
class DjangoFormError(graphene.ObjectType):
field = graphene.String()
message = graphene.String()
#classmethod
def list_from_errors_dict(cls: Type[T], django_form_errors: dict) -> List[T]:
return [
cls(field=to_camel_case(field), message=' '.join(messages))
for field, messages in django_form_errors.items()
]
class DjangoFormErrorsByIdx(graphene.ObjectType):
form_idx = graphene.Int()
errors = graphene.List(DjangoFormError)
#classmethod
def list_from_idx_dict(cls: Type[T], errors_by_idx_dict: dict) -> List[T]:
return [
cls(
form_idx=idx,
errors=DjangoFormError.list_from_errors_dict(django_form_errors),
)
for idx, django_form_errors in errors_by_idx_dict.items()
]
# ...
# in mutation
if not django_form.is_valid():
form_errors = DjangoFormError.list_from_errors_dict(
django_form.errors
)

How to send multiple objects through HttpResponse or JsonResponse in django

I have two objects influencer_data and user_list in my views function.I want to send both influencer_data and user_list through the HttpResponse method and obtain the data in Json format.
My views function is:
def index(request):
influencers = Influencer.objects.all()
influencer_data = serializers.serialize("json",influencers)
user_list = UserList.objects.all()
user_list = serializers.serialize("json",user_list)
context = {
'influencer_data':influencer_data,
'user_list':user_list,
}
return HttpResponse(influencer_data,user_list, content_type='application/json')
When I pass both influencer_data and user_list I get the error
__init__() got multiple values for argument 'content_type'
When I change the return HttpResponse statement to
return HttpResponse(context, content_type='application/json')
I get
influencer_datauser_list
i.e just the key values from the dictionary
When I change the return statement to
return HttpResponse(json.dumps(context), content_type='application/json')
I get the output as:
"influencer_data": "[{\"model\": \"influencer_listings.influencer\", \"pk\": 8794, \"fields\": {\"full_name\": \"F A I Z S H A I K H \\ud83c\\udf08\", \"username\": \"mr_faizzz_07\", \"photo\": \"\", \"email_id\": \"\", \"external_url\": \"\", \"location_city\": \"Mumbai\", \"categories\": \"\", \"hashtags\": \"['#foryou', '#blessyou', '#all', '#faizanshaikh', '#keepsmiling', '#blessed', '#look',
(The Json object becomes a string)
When I pass only one object i.e either influencer_data or user_list. I get a Json object i.e it works correctly(I want data in the given format)
[
{
"model": "influencer_listings.influencer",
"pk": 8794,
"fields": {
"full_name": "F A I Z S H A I K H 🌈",
"username": "mr_faizzz_07",
"photo": "",
"email_id": "",
"external_url": "",
"location_city": "Mumbai",
"categories": "",
"hashtags": "['#foryou', '#blessyou', '#all', '#faizanshaikh', '#keepsmiling', '#blessed', '#look', '#ramzan', '#loveyou', '#lover', '#cuteboys', '#keepgoing', '#picoftheday', '#feathers', '#brothers', '#faizshaikhhhh', '#pictures', '#jummahmubarak', '#lovers']",
How should I deal with this?
def index(request):
influencers = Influencer.objects.all().values()
user_list = UserList.objects.all().values()
context = {
'influencer_data': influencer_data,
'user_list': user_list,
}
data = json.dumps(context, indent=4, sort_keys=True, default=str)
return HttpResponse(data, content_type='application/json')

django rest framework: getting two post request in the back end

I have an Angular app posting registration message to my django back-end, however, when I click on register button on the page, I got tow post request in the django logging, like this
[17/Apr/2018 22:13:47] "OPTIONS /user/register HTTP/1.1" 200 0
[17/Apr/2018 22:13:47] "POST /user/register HTTP/1.1" 500 27
[17/Apr/2018 22:13:47] "POST /user/register HTTP/1.1" 201 91
Chrome dev tool - Network
It is just annoying when testing locally, but when I deploy this on a ubuntu server(with uwsgi and nginx), the back-end seems to crash. I am not sure if this is the problem, I am just checking every possibility that I can think of.
BTW: I am using sqlite, it is because of the transaction?
Angular registration.component.js
import { Component, OnInit, AfterViewInit, ViewChild } from '#angular/core';
import { NgForm } from "#angular/forms";
import { HttpClient } from "#angular/common/http";
#Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit, AfterViewInit {
formData = {} as any;
isHide: boolean;
constructor(
private http: HttpClient,
) {
this.isHide = true;
}
formErrors = {
'email': '',
'userName': '',
'password1': '',
'password2': '',
'phone': ''
};
validationMessages = {
'email': {
'required': '邮箱必须填写.',
'pattern': '邮箱格式不对',
},
'userName': {
'required': '用户名必填.',
'minlength': '用户名太短',
},
'password1': {
'required': '请输入密码',
'minlength': '密码太短',
},
'password2': {
'required': '请重复输入密码',
'minlength': '密码太短',
},
'phone': {
'required': '手机号必须填写.',
'pattern': '手机号格式不对',
},
};
#ViewChild('registerForm') registerForm: NgForm;
ngAfterViewInit(): void {
this.registerForm.valueChanges.subscribe(data => this.onValueChanged(data));
}
onValueChanged(data) {
if (this.formErrors) {
for (const field in this.formErrors) {
this.formErrors[field] = '';
const control = this.registerForm.form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
if (control.errors) {
for (const key in control.errors) {
this.formErrors[field] += messages[key] + '';
}
}
}
}
}
}
doJumpIndex() {
console.log("zhuye");
}
doJumpLogin() {
console.log("login");
}
doSubmit(obj: any) {
if (!this.registerForm.valid) {
this.onValueChanged(obj);
return;
}
let url = 'http://localhost:8000/user/register';
this.http.post(url, obj).subscribe(
data => {
console.log(data);
if (true) {
this.isHide = false;
}
},
err => {
console.log(err);
});
}
ngOnInit() {
}
}
The following code was adopted from https://github.com/iboto/django-rest-framework-user-registration
my UserRegistrationAPIView
class UserRegistrationAPIView(generics.CreateAPIView):
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.UserRegistrationSerializer
queryset = User.objects.all()
UserRegistrationSerializer
class UserRegistrationSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True,
label="Email Address"
)
password1 = serializers.CharField(
required=True,
label="Password",
style={'input_type': 'password'}
)
password2 = serializers.CharField(
required=True,
label="Confirm Password",
style={'input_type': 'password'}
)
invite_code = serializers.CharField(
required=False
)
class Meta(object):
model = User
fields = ['username', 'email', 'password1', 'password2', 'invite_code']
def validate_email(self, value):
if User.objects.filter(email=value).exists():
raise serializers.ValidationError("Email already exists.")
return value
def validate_username(self, value):
if User.objects.filter(username=value).exists():
raise serializers.ValidationError("Username already exists.")
return value
def validate_invite_code(self, value):
data = self.get_initial()
email = data.get('email')
if value:
self.invitation = TeamInvitation.objects.validate_code(email, value)
if not self.invitation:
raise serializers.ValidationError("Invite code is not valid / expired.")
self.team = self.invitation.invited_by.team.last()
return value
def create(self, validated_data):
team = getattr(self, 'team', None)
user_data = {
'username': validated_data.get('username'),
'email': validated_data.get('email'),
'password': validated_data.get('password1')
}
is_active = True if team else False
user = UserProfile.objects.create_user_profile(
data=user_data,
is_active=is_active,
site=get_current_site(self.context['request']),
send_email=True
)
if team:
team.members.add(user)
if hasattr(self, 'invitation'):
TeamInvitation.objects.accept_invitation(self.invitation)
TeamInvitation.objects.decline_pending_invitations(email_ids=[validated_data.get('email')])
return validated_data
urls.py
urlpatterns = [
path('login', views.UserLoginAPIView.as_view(), name='login'),
path('register', views.UserRegistrationAPIView.as_view(), name='register'),
path('profile', views.UserProfileAPIView.as_view(), name='user_profile'),
path('password_reset', views.PasswordResetAPIView.as_view(), name='password_change'),
re_path(r'^verify/(?P<verification_key>.+)/$',
views.UserEmailVerificationAPIView.as_view(),
name='email_verify'),
re_path(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm'),
]
It looks to me like your doSubmit angular function might be firing twice. From the logs, it looks like it's sending a request with incorrect information (the first call is 27 bytes long, the second (correct one) is 91 bytes). This first request is getting a HTTP 500 error (Server side error). i.e. it's causing a bug in the server.
On your local testing server a 500 error won't crash the server as it will just log the error and reload (because that's what testing servers do) but on your proper server it is crashing.
I'd say take a look at the doSubmit function and make sure it's not letting any incorrect values past the first if (!this.registerForm.valid) first (sometimes I find when Angular is first creating the objects functions fire that I wouldn't have expected to, for example). And secondly, I'd add some logging to figure out what part of UserRegistrationAPIView is failing (because that's the first function Django calls and somewhere along the way an uncaught exception is being thrown).
If you check the main servers logs they might tell you why the 500 internal server error is being thrown. (Saved in /var/log/apache2/error.log by default if you're running an apache server).

in tastypie, how can i set name for json result

In tastypie, I want set json result name.
I have a class that I use for it but I can set name in.
enter cclass ContentResource(ModelResource):
class Meta:
results = ListField(attribute='results')
queryset = Content.objects.all()
resource_name = 'content'
max_limit = None
#filtering = {"title": "contains"}
def alter_list_data_to_serialize(self, request, data_dict):
if isinstance(data_dict, dict):
if 'meta' in data_dict:
# Get rid of the "meta".
del(data_dict['meta'])
# Rename the objects.
data_dict['Mobile'] = data_dict['objects']
del(data_dict['objects'])
return data_dict
ode here it returns this
{"Mobile":
[
{
"added": "2015-07-23T11:30:20.911835",
"content_cast": "",
"content_company": "HamrahCinema",
"content_description": "so nice",
"content_director": "",
"content_duration": "2:20",
"content_filelanguage": null,
}
]
}
when I use /content/api/content every thing is ok, but when I use /content/api/content/1,"mobile" is removed.
as educated guess, I would suggest using alter_detail_data_to_serialize