pytest monkeypatch not able to patch the mongodb collection - python

I'm trying to unit test the following function with pytest.
#login_sys.route("/users/register", methods=["POST"])
def register():
users = mongo.db.users # TRYING TO PATCH THIS
print("--->{}".format(type(users)))
email = request.get_json()["email"]
response = users.find_one({"email": email})
if response:
result = jsonify({"result": "email already registered"})
return result
first_name = request.get_json()["first_name"]
last_name = request.get_json()["last_name"]
password = bcrypt.generate_password_hash(request.get_json()["password"]).decode(
"utf-8"
)
created = datetime.utcnow()
user_id = users.insert(
{
"first_name": first_name,
"last_name": last_name,
"email": email,
"password": password,
"created": created,
}
)
new_user = users.find_one({"_id": user_id})
result = {"email": new_user["email"] + " registered"}
return jsonify({"result": result})
Following is the test I wrote
#pytest.fixture(scope="module")
def client():
app.config['TESTING'] = True
app.register_blueprint(login_sys)
with app.test_client() as client:
return client
def test_register(client, monkeypatch):
print(type(client))
mimetype = 'application/json'
headers = {
'Content-Type': mimetype,
'Accept': mimetype
}
userInfo = {
"first_name": "admin",
"last_name": "admin",
"email": "admin#gmail.com",
"password": "admin",
}
def mock_db():
return mongomock.MongoClient().db.collection
print(type(mongomock.MongoClient().db.collection))
#class'mongomock.collection.Collection'>
monkeypatch.setattr(mongo.db, 'users', mock_db)
rv = client.post("/users/register", data=json.dumps(userInfo), headers=headers)
dprint(rv.data)
assert True
I'm trying to replace the mongo.db.users with the fake one I made with mongomock mongomock.MongoClient().db.collection. However, when I ran this test, it shows an attribute error shown below:
E AttributeError: 'function' object has no attribute 'find_one'
I'm not sure why the mock_db() returns a type function instead of type <class'mongomock.collection.Collection'>
let me know if anyone has any suggestions.
Thank you.

Related

Django: byte indices must be integers or slices, not str error

I am trying to get some data from a payload using a webhook, in the response that I sent along side I added the meta data that should return with the payload and this is how I did it (code below), if you take a close look at the keys, there I one called meta, now I am trying to get some of the data that is in the meta from my webhook view like this:
views.py webhook
#csrf_exempt
#require_http_methods(['POST', 'GET'])
def webhook(request):
payload = request.body
order_id = payload['meta']['order_id']
print(order_id)
return HttpResponse("testing...")
Sending the reponse to the payment gateway
#csrf_exempt
def process_payment(request, name, email, amount, order_id):
auth_token = "FLWSECK_TEST-9efb9ee17d8afe40c6c890294a1163de-X"
hed = {'Authorization': 'Bearer ' + auth_token}
data = {
"tx_ref":''+str(math.floor(1000000 + random.random()*9000000)),
"amount":amount,
"currency":"USD",
"order_id":order_id,
"redirect_url":f"http://localhost:3000/service-detail/booking/confirmation/{order_id}",
"payment_options":"card",
"meta":{
"order_id":order_id,
"consumer_id":23,
"consumer_mac":"92a3-912ba-1192a"
},
"customer":{
"email":email,
"name":name,
"order_id":order_id
},
"customizations":{
"title":"ForecastFaceoff",
"description":"Leading Political Betting Platform",
"logo":"https://i.im.ge/2022/08/03m/FELzix.stridearn-high-quality-logo-circle.jpg"
}
}
url = ' https://api.flutterwave.com/v3/payments'
response = requests.post(url, json=data, headers=hed)
response=response.json()
link=response['data']['link']
return redirect(link)
payload
{
"event": "charge.completed",
"data": {
"id": 4136234873,
"tx_ref": "6473247093",
"flw_ref": "FLW-MOCK-b33e86ab2342316fec664110e8eb842a3c2f956",
"device_fingerprint": "df38c8854324598c54e16feacc65348a5e446",
"amount": 152,
"currency": "USD",
"charged_amount": 152,
"app_fee": 5.78,
"merchant_fee": 0,
"processor_response": "Approved. Successful",
"auth_model": "VBVSECUREertrCODE",
"ip": "52.209.154.143",
"narration": "CARD Transaction ",
"status": "successful",
"payment_type": "card",
"created_at": "2023-02-06T11:19:45.000Z",
"account_id": 685622,
"customer": {
"id": 1970338,
"name": "destiny ",
"phone_number": null,
"email": "******#gmail.com",
"created_at": "2023-02-06T11:19:45.000Z"
},
"card": {
"first_6digits": "653188",
"last_4digits": "2340",
"issuer": "MASTERCARD CREDIT",
"country": "NG",
"type": "MASTERCARD",
"expiry": "49/32"
}
},
"event.type": "CARD_TRANSACTION"
}
The metadata is not even showing in the payload, must it show up there in the payload before I can grab the data that is sent with it?
request.body is byte not a dict, so you need to load the string as JSON.
payload = json.loads(request.body)
The meta data is not in the payload dictionary. At first try to extract the data key from the payload like this:
#csrf_exempt
#require_http_methods(['POST', 'GET'])
def webhook(request):
payload = json.loads(request.body.decode("utf-8"))
data = payload.get("data")
if data:
order_id = data.get("meta", {}).get("order_id")
print(order_id)
return HttpResponse("just testing")
Edit:
The error message
as you described above in comment JSONDecodeError: Expecting value: line 1 column 1 (char 0) indicates that json.loads() is expecting a JSON string as input.
It may be possible that request body is empty. You can check that by printing the request.body before calling json.loads().
Try this:
#csrf_exempt
#require_http_methods(['POST', 'GET'])
def webhook(request):
payload = json.loads(request.body.decode("utf-8"))
data = payload.get("data")
if data and "meta" in data:
order_id = data.get("meta").get("order_id")
print(order_id)
else:
print("Meta key not found in data or data not found in payload")
print(payload)
return HttpResponse("just testing")
It would tell you whether the "meta" is present in data dictionary or not and also prints payload dict at the end.

Error AttributeError: 'function' object has no attribute 'json' occures when importing functions from other files

I'm writting an automated test which uses 3 files helpers.py where I store fucntion of registering, test_mails.py where the test is executed and it runs checking for registration for 5 mails in list, login_credentials.py where json dictionaries are stored. test_mails.py uses unittest library and import main sing_up function for trying to register 5 emails through api requests. The aim of test is to check if system doesn't let these mails to pass. However, when in test_mails.pyI try to use methods from libs json orrequesrts with sing_up function I get these types of errors. AttributeError: 'function' object has no attribute 'json'. This also applies to status_code method How to solve it?
Here are files:
helpers.py
import json
import requests
def sign_up(data):
with requests.Session() as session:
sign_up = session.post(
f'https://siteexample/api/register',
headers={
'content-type': 'application/json',
},
data=json.dumps(data)
)
print(sign_up.content)
print(sign_up.status_code)
test.mails.py
import unittest
import requests
import json
from login_credentials import data, emails_list
from helpers import *
class Mails(unittest.TestCase):
def test_sign_up_public_emails(self):
emails_dict_ls = {}
for email in emails_list:
data_copy = data.copy()
data_copy["email"] = email
emails_dict_ls.update(data_copy)
sign_up(emails_dict_ls)
if 'email' in sign_up.json() and "User with this Email already exists." in
sign_up.json()['email']:
registering = False
else:
registering = True
assert self.assertEqual(sign_up.status_code, 400)
assert sign_up.json()['status_code'] == "NOT_CONFIRMED_EMAIL"
return registering
login_credentials.py
data = {
"company": "Company",
"phone": "+1111111",
"email": "",
"password1": "defaultpassword",
"password2": "defaultpassword",
"terms_agree": True,
"first_name": "TestUser",
"last_name": "One"
}
emails_list = ['mailtest.unconfirm#yahoo.com',
'mailtest.unconfirm#gmail.com',
'mailtest.unconfirm#ukr.net',
'mailtest.unconfirm#outlook.com',
'mailtest.unconfirm#icloud.com'
]
In the sign_up function in helpers.py. You'll have to return the response so that .json() can be called.
Also, you can use json=data instead of data=json.dumps(data) since it's already a built-in function of python-requests.
def sign_up(data):
with requests.Session() as session:
response = session.post(
f'https://siteexample/api/register',
headers={
'content-type': 'application/json',
},
json=data
)
return response
In test_mails.py, You're just calling the function and not storing the value in a variable.
def test_sign_up_public_emails(self):
emails_dict_ls = {}
for email in emails_list:
data_copy = data.copy()
data_copy["email"] = email
emails_dict_ls.update(data_copy)
sign_resp = sign_up(emails_dict_ls)
if 'email' in sign_resp.json()

TypeError when trying to set a DB value

I'm trying to get my Flask server to update to a Mongo Atlas DB on request. When a request is passed with the required arguments, it will post to a Discord webhook then attempt to update DB values.
Example: Going to https://(redacted for privacy)/(redacted for privacy)?iid=123&username=wow&price=22 with JUST the webhook code (doesn't touch DB) will do this:
(sent to discord webhook) and give a success message (outputs as 200 in the console).
But when I enable the DB code, the same link will through a 500 error. Anything I can do? The console outputs as;
balance = mycol.find_one({"UserID": uid})['balance']
TypeError: 'NoneType' object is not subscriptable
I don't understand why this won't work. Here's the code that works, but only posts to the webhook (commented stuff is DB related):
#balance = mycol.find_one({"UserID": uid})['balance']
#res = mycol.find_one({"UserID": uid})
#newvalues = { "$set": { "UserID": uid, "balance": int(balance) + int(cashback_amt)} }
#mycol.update_one(res, newvalues)
img_url = f"https://www.roblox.com/bust-thumbnail/image?userId={uid}&width=420&height=420&format=png"
data = {
"content" : "**New purchase!**",
"username" : "robate"
}
data["embeds"] = [
{
"description" : f"**ItemID**: `{item_id}`\n**Item Price**: `{price}`\n**Item Cashback**: `{cashback_amt}`",
"title" : "New purchase made",
"color": 65311,
"thumbnail": {"url": img_url}
}
]
result = requests.post(purclog, json = data)
return jsonify({"res": True})
And the non-working DB included code:
balance = mycol.find_one({"UserID": uid})['balance']
res = mycol.find_one({"UserID": uid})
newvalues = { "$set": { "UserID": uid, "balance": int(balance) + int(cashback_amt)} }
mycol.update_one(res, newvalues)
img_url = f"https://www.roblox.com/bust-thumbnail/image?userId={uid}&width=420&height=420&format=png"
data = {
"content" : "**New purchase!**",
"username" : "robate"
}
data["embeds"] = [
{
"description" : f"**ItemID**: `{item_id}`\n**Item Price**: `{price}`\n**Item Cashback**: `{cashback_amt}`",
"title" : "New purchase made",
"color": 65311,
"thumbnail": {"url": img_url}
}
]
result = requests.post(purclog, json = data)
return jsonify({"res": True})
Any help is severely appreciated. Have a good day!
EDIT: The UserID is defined here:
try:
uid = requests.get(f"https://api.roblox.com/users/get-by-username?username={username}").json()['Id']
except:
return
balance = mycol.find_one({"UserID": uid})['balance']

How to get message from E11000 DuplicationKeyError?

So I am writing unittests for a project and I am testing register() function.
Here is it:
def register():
# Get information about user
username = request.get_json().get("username")
password = request.get_json().get("password")
name = request.get_json().get("name")
email = request.get_json().get("email")
# Put information about user in a tuple
values = (
None,
username,
User.hash_password(password),
name,
email,
None
)
try:
# Create user and update session
User(*values).create()
ActiveUser.logged_in = True
ActiveUser.username = username
info_log.info("User %s registered successfully." % username)
return jsonify(success=True, message="Registration successful!")
except pymongo.errors.DuplicateKeyError as e:
# Catch pymongo exception
return jsonify(success=False, message="Duplicated username or email!"), 403
I want to have three tests: valid, invalid (duplicate username), invalid (duplicate email).
# Register helper function
def register(self, username, password, name, email):
return self.app.post(
"/register",
data = json.dumps(dict(username = username, password = password, name = name, email = email)),
content_type='application/json',
follow_redirects = True
)
def test_02_valid_user_registration(self):
response = self.register('test', '12345678', 'Tester 1', 'test#mail.mail')
self.assertEqual(response.status_code, 200)
self.assertIn(b'Registration successful!', response.data)
def test_03_invalid_user_registration_duplicate_username(self):
response = self.register('test', '12345678', 'Tester 2', 'test1#mail.mail')
self.assertEqual(response.status_code, 403)
self.assertIn(b'Duplicate username or email!', response.data)
def test_04_invalid_user_registration_duplicate_email(self):
response = self.register('test2', '12345678', 'Tester 3', 'test#mail.mail')
self.assertEqual(response.status_code, 403)
self.assertIn(b'Duplicate username or email!', response.data)
As expected I get DuplicateKeyError, because I have set Unique for those parameters in the database.
pymongo.errors.DuplicateKeyError: E11000 duplicate key error collection: user.users index: username_1 dup key: { username: "test" }
Is there a way to get which is the duplicated item from the DuplicateKeyError, so I can have separate unit tests for duplicate username and email?
I know this is more of a component/integration testing rather than unit testing, but this is the only way I know how to do it in Python 3.
So I started digging through the implementation of DuplicateKeyError and I found that it contains code and details.
I printed the details of the error and got this:
{
"message": {
"code": 11000,
"errmsg": "E11000 duplicate key error collection: user.users index: username_1 dup key: {
username: \"test\"
}",
"index": 0,
"keyPattern": {
"username": 1
},
"keyValue": {
"username": "test"
}
},
"success": false
}
After that it was easy to get the two tests to work.
try:
# code
except pymongo.errors.DuplicateKeyError as e:
# Catch pymongo exception
key = list(e.details.get("keyValue").keys())[0]
value = e.details.get("keyValue").get(key)
return jsonify(success=False, message="Duplicate %s: %s" % (key, value)), 403
And the tests:
def test_03_invalid_user_registration_duplicate_username(self):
response = self.register("test", "12345678", "Tester 2", "test1#mail.mail")
self.assertEqual(response.status_code, 403)
self.assertIn(b"Duplicate username: test", response.data)
def test_04_invalid_user_registration_duplicate_email(self):
response = self.register("test", "12345678", "Tester 3", "test#mail.mail")
self.assertEqual(response.status_code, 403)
self.assertIn(b"Duplicate email: test#mail.mail", response.data)

Django test - How to send a HTTP Post Multipart with JSON

in my django tests, I want to send an HTTP Post Multipart which contains 2 parameters:
a JSON string
a file
def test_upload_request(self):
temp_file = tempfile.NamedTemporaryFile(delete=False).name
with open(temp_file) as f:
file_form = {
"file": f
}
my_json = json.dumps({
"list": {
"name": "test name",
"description": "test description"
}
})
response = self.client.post(reverse('api:upload'),
my_json,
content=file_form,
content_type="application/json")
os.remove(temp_file)
def upload(request):
print request.FILES['file']
print json.loads(request.body)
My code doesn't work. Any help ?
If necessary, I can use an external python lib (I'm trying with requests)
Thank you
With application/json content-type, you cannot upload file.
Try following:
view:
def upload(request):
print request.FILES['file']
print json.loads(request.POST['json'])
return HttpResponse('OK')
test:
def test_upload_request(self):
with tempfile.NamedTemporaryFile() as f:
my_json = json.dumps({
"list": {
"name": "test name",
"description": "test description"
}
})
form = {
"file": f,
"json": my_json,
}
response = self.client.post(reverse('api:upload'),
data=form)
self.assertEqual(response.status_code, 200)

Categories