Python: issues understanding magicmock with unittests - python

Here is my class:
class WorkflowsCloudant(cloudant.Account):
def __init__(self, account_id):
super(WorkflowsCloudant, self).__init__(settings.COUCH_DB_ACCOUNT_NAME,
auth=(settings.COUCH_PUBLIC_KEY, settings.COUCH_PRIVATE_KEY))
self.db = self.database(settings.COUCH_DB_NAME)
self.account_id = account_id
def get_by_id(self, key, design='by_workflow_id', view='by_workflow_id', limit=None):
params = dict(key=key, include_docs=True, limit=limit)
docs = self.db.design(design).view(view, params=params)
if limit is 1:
doc = [doc['doc'] for doc in docs]
if doc:
workflow = doc[0]
if workflow.get("account_id") != self.account_id:
raise InvalidAccount("Invalid Account")
return workflow
else:
raise NotFound("Autoresponder Cannot Be Found")
return docs
Here is my test:
def test_get_by_id_single_invalid_account(self):
self.klass.account_id = 200
self.klass.db = mock.MagicMock()
self.klass.db.design.return_value.view.return_value = [{
'doc': test_workflow()
}]
# wc.get_by_id = mock.MagicMock(side_effect=InvalidAccount("Invalid Account"))
with self.assertRaises(InvalidAccount("Invalid Account")) as context:
self.klass.get_by_id('workflow_id', limit=1)
self.assertEqual('Invalid Account', str(context.exception))
I'm trying to get the above test to simple raise the exception of InvalidAccount but I'm unsure how to mock out the self.db.design.view response. That's what's causing my test to fail because it's trying to make a real call out

I think this is what you want.
def test_get_by_id_single_invalid_account(self):
self.klass.account_id = 200
self.klass.db = mock.MagicMock()
self.klass.db.design = mock.MagicMock()
view_mock = mock.MagicMock()
view_mock.return_value =[{
'doc': test_workflow()
}]
self.klass.db.design.return_value.view = view_mock
# wc.get_by_id = mock.MagicMock(side_effect=InvalidAccount("Invalid Account"))
with self.assertRaises(InvalidAccount("Invalid Account")) as context:
self.klass.get_by_id('workflow_id', limit=1)
self.assertEqual('Invalid Account', str(context.exception))

Related

assertion error omitting two identical items

As far as I can tell the assert calls for id and goal_id AND my code provides them both...
enter image description here
goal_routes.py
from datetime import datetime from typing import OrderedDict from
urllib.request import OpenerDirector from flask import Blueprint,
jsonify, request, make_response, abort from app import db from
app.models.goal import Goal from app.models.task import Task from
app.task_routes import validate_task
Create a Goal: goal_bp = Blueprint("goal_bp", name, url_prefix="/goals")
#goal_bp.route("", methods = ["POST"]) def create_goals():
request_body = request.get_json()
if "title" in request_body:
new_goal = Goal(
title = request_body["title"]
)
else:
return jsonify({"details":"Invalid data"}), 400
db.session.add(new_goal)
db.session.commit()
goal_response = {"goal": new_goal.to_dictionary()}
return (jsonify(goal_response), 201)
Get Goals #goal_bp.route("", methods = ["GET"]) def get_goals():
sort = request.args.get("sort")
#Sort by assending (is default?)
if sort == "asc":
goals =Goal.query.order_by(Goal.title)
#Sort by decending
elif sort == "desc":
goals =Goal.query.order_by(Goal.title.desc())
#No Sort
else:
goals = Goal.query.all()
goals_response = []
for goal in goals:
goals_response.append(goal.to_dictionary())
# If No Saved Goals wil stil return 200
return (jsonify(goals_response), 200)
Get One Goal: One Saved Goal #goal_bp.route("/<goal_id>", methods=["GET"]) def get_one_goal(goal_id):
goal = validate_goal(goal_id)
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Update Goal #goal_bp.route("/<goal_id>", methods=["PUT"]) def update_goal(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()
goal.title = request_body["title"]
db.session.commit()
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Goal Complete #goal_bp.route("/<goal_id>/mark_complete", methods=["PATCH"]) def goal_complete(goal_id):
goal = validate_goal(goal_id)
goal.completed_at = datetime.utcnow()
db.session.commit()
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Goal Incomplete #goal_bp.route("/<goal_id>/mark_incomplete", methods=["PATCH"]) def goal_incomplete(goal_id):
goal = validate_goal(goal_id)
goal.completed_at = None
db.session.commit()
goal_response = {"goal": goal.to_dictionary()}
return (jsonify(goal_response), 200)
Delete Goal: Deleting a Goal #goal_bp.route("/<goal_id>", methods=["DELETE"]) def delete_goal(goal_id):
goal = validate_goal(goal_id)
db.session.delete(goal)
db.session.commit()
response = {"details": f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"}
return (jsonify(response), 200)
Validate there are no matching Goal: Get, Update, and Delete
def validate_goal(goal_id):
try:
goal_id = int(goal_id)
except:
abort(make_response({"message": f"Goal {goal_id} is invalid"}, 400))
goal = Goal.query.get(goal_id)
if not goal:
abort(make_response({"message": f"Goal {goal_id} not found"}, 404))
return goal
#goal_bp.route("/<goal_id>/tasks", methods=["POST"]) def
post_task_ids_to_goal(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()
for task_id in request_body["task_ids"]:
task = Task.query.get(task_id)
task.goal_id = goal_id
task.goal = goal
db.session.commit()
return jsonify({"id":goal.goal_id, "task_ids": request_body["task_ids"]}), 200
#goal_bp.route("/<goal_id>/tasks", methods=["GET"]) def
get_tasks_for_goal(goal_id):
goal = validate_goal(goal_id)
task_list = [task.to_dictionary() for task in goal.tasks]
goal_dict = goal.to_dictionary()
goal_dict["tasks"] = task_list
return jsonify(goal_dict)
goal.py
from app import db
class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable=False)
tasks = db.relationship("Task", back_populates="goals", lazy = True)
def to_dictionary(self):
goal_dict = {
"id": self.goal_id,
"title": self.title
}
if self.tasks:
goal_dict["tasks"] = [task.task_id for task in self.tasks]
return goal_dict

Unittesting function in Python that has try/except statament

My class has a method called updateTag that executes a command against AWS endpoint.
I created a unit test for it and I mocked the AWS call, but I don't know how to test the except block.
When I execute the updateTag, if DryRun is False it should return 0 and for True it should return 1
Could you help me?
Here is my function:
def updateTag(self, resourceInterface: boto3.resource, commandExpression: str):
response = {'command': commandExpression,
'returnCode': bool, 'error': []}
try:
eval('resourceInterface.' + commandExpression)
response['returnCode'] = 0
except Exception as e:
response['returnCode'] = 1
response['error'] = e.args
return response
and here is my unit test code:
def test_updateTag_with_string_command_and_dryrun_set_false_should_pass(self):
commandsExpression = "Image('ami-0b1116af7f69faeab').create_tags(DryRun=False,Tags=[{'Key':'state', 'Value': 'expired'}, {'Key': 'timestamp', 'Value':'expired#2019-11-13T22:46:32.725Z'}])"
boto3 = MagicMock()
ec2Resource = boto3.resource('ec2')
imageLifecycle = Lifecycle(self.lifeCycleRules, self.dryRunEnabled)
response = imageLifecycle.updateTag(ec2Resource, commandsExpression)
self.assertEqual(response['returnCode'],0)
def test_updateTag_with_string_command_and_dryrun_set_true_should_pass(self):
commandsExpression = "Image('ami-00000000000000000').create_tags(DryRun=True,Tags=[{'Key':'state', 'Value': 'expired'}, {'Key': 'timestamp', 'Value':'expired#2019-11-13T22:46:32.725Z'}])"
boto3 = MagicMock()
ec2Resource = boto3.resource('ec2')
imageLifecycle = Lifecycle(self.lifeCycleRules, self.dryRunEnabled)
response = imageLifecycle.updateTag(ec2Resource, commandsExpression)
self.assertEqual(response['returnCode'],1)
Thank you.

TypeError: Model constructor takes no positional arguments

While creating an answer for a question in the API (see code below), I got the following error:
TypeError: Model constructor takes no positional arguments
Can someone tell me how to solve this? I am using ndb model
import webapp2
import json
from google.appengine.ext import ndb
class AnswerExchange(ndb.Model):
answer=ndb.StringProperty(indexed=False,default="No Message")
class AnswerHandler(webapp2.RequestHandler):
def create_answer(self,question_id):
try:
query = StackExchange.query(StackExchange.questionId == question_id)
questions = query.fetch(1)
ans_json = json.loads(self.request.body)
answerObj = AnswerExchange(answer=ans_json["answer"])
answerObj.put()
questions.answerName=answerObj
questions.put()
except:
raise webapp2.exc.HTTPBadRequest()
class StackExchange(ndb.Model):
questionId=ndb.StringProperty(indexed=True)
questionName=ndb.StringProperty(indexed=False)
#answerID=ndb.StringProperty(indexed=True)
answerName=ndb.StructuredProperty(AnswerExchange,repeated=True)
class StackHandler(webapp2.RequestHandler):
def get_questions(self):
#p = self.request.get('p') #get QueryString Parameter
#self.response.write(p)
query = StackExchange.query()
questions = query.fetch(6)
self.response.content_type = 'application/json'
self.response.write(json.dumps([d.to_dict() for d in questions]))
def get_question(self, question_id):
query = StackExchange.query(StackExchange.questionId == question_id)
questions = query.fetch(1)
self.response.content_type = 'application/json'
self.response.write(json.dumps([d.to_dict() for d in questions]))
def create_question(self):
try:
question_json = json.loads(self.request.body)
# if id in dept_json:
questionNo = question_json["questionId"]
questionToUpdate = StackExchange.query(StackExchange.questionId == questionNo);
#print deptToUpdate.count()
if questionToUpdate.count() == 0:
question = StackExchange(questionId=question_json["questionId"], questionName=question_json["questionName"])
else:
question = questionToUpdate.get()
question.questionName = question_json["questionName"]
#key = dept.put()
question.put()
except:
raise webapp2.exc.HTTPBadRequest()
self.response.headers['Location'] = webapp2.uri_for('get_question', question_id=questionNo)
self.response.status_int = 201
self.response.content_type = 'application/json'
#self.response.write(json.dumps())
def create_answer(self,question_id):
#try:
question_json = json.loads(self.request.body)
questionNo = question_json["questionId"]
query = StackExchange.query(StackExchange.questionId == questionNo)
questions = query.fetch(1)
ans_json = json.loads(self.request.body)
answerObj = AnswerExchange(ans_json["answer"])
#answerObj.put()
#self.response.write(ans_json["answerName"])
questions.answerName = answerObj
questions.put();
#except:
# raise webapp2.exc.HTTPBadRequest()
def get_answer(self, question_id):
query = StackExchange.query(StackExchange.questionId == question_id)
questions = query.fetch(1)
self.response.content_type = 'application/json'
self.response.write(json.dumps([d.to_dict() for d in questions]))
app = webapp2.WSGIApplication([
webapp2.Route(r'/api/v1/questions/<question_id>', methods=['GET'], handler='api.StackHandler:get_question', name='get_question'),
webapp2.Route(r'/api/v1/questions', methods=['GET'], handler='api.StackHandler:get_questions', name='get_questions'),
webapp2.Route(r'/api/v1/questions', methods=['POST'], handler='api.StackHandler:create_question', name='create_question'),
webapp2.Route(r'/api/v1/questions/<question_id>/answers', methods=['POST'], handler='api.StackHandler:create_answer', name='create_answer'),
webapp2.Route(r'/api/v1/questions/<question_id>/answers', methods=['GET'], handler='api.StackHandler:get_answer', name='get_answer')
], debug=True)
Change,
answerObj = AnswerExchange(ans_json["answer"])
in create_answer method StackHandler class
to
answerObj = AnswerExchange(answer=ans_json["answer"])

How to remove nested if/else statements when accounting for Error handling

Howdie do,
So I have this API that does the following:
1) It receives a XML request. It first parses that request to ensure the request is in the correct format.
2) If the XML is in the correct format, it checks to ensure that the correct user is authenicating.
3) If authenication is successful, it then retreives a URI from a database that the API will send a get request to.
4) If the response is successful, meaning it's a XML reply, it will use XSLT to transform the request into a format
5) It then adds the request to the database and returns the transformed XML to the user that queried the API
I have error handling involved at each step, but the issue is, I've had to nest 5 if else statements to accomplish this.
I know there has to be a better way to rewrite this error handling logic without so many nested if statements, but I'm not sure how. The subsequent steps rely on the previous to ensure that if any error occurs, it's returned properly to the user.
Below is my main Flask-API that I've created. The second file is a module that I've created which does a lot of the error processing. Those functions return a state(True/False) and the response to the main Flask-API.
Can someone give me some ideas on how to rewrite this API without the nesting? The API works 100% and does what it should for catching errors, but I just know there's a better way
Main API:
#dbConnect.app.route('/services/tracking/getShipmentStatus', methods=['POST'])
def parsexml2():
parseStatus, returnValues = func.parseNWRequest(request.data, 'SS')
if parseStatus:
authStatus, authResponse = func.checkAuthorization(returnValues['bu'], request.headers['Authorization'], 'SS')
if authStatus:
getURIStatus, uriResponse = func.getURI('SS')
if getURIStatus:
search = {'bu': returnValues['bu'], 'starttime': returnValues['start'], 'endtime': returnValues['end'],
'requestid': returnValues['requestid'], 'pagesize': returnValues['page']}
responseStatus, depascoResponse = func.sendDepascoRequest(uriResponse, search, 'SS')
if responseStatus:
nakedResponse = func.transformXML(depascoResponse.content, 'transformTracking.xsl')
# Write Request to db
file_name = 'WS_SS_' + returnValues['bu']
request_file_size = request.headers['Content-Length']
if func.addToDb(file_name, 'text/xml', 'SS.Request', returnValues['bu'], 'Y', request.data,
request_file_size) or func.addToDb(file_name, 'text/xml', 'SS.Result',
returnValues['bu'], 'Y', nakedResponse):
pass
return Response(nakedResponse, mimetype='text/xml')
else:
return Response(depascoResponse, mimetype='text/xml')
else:
return Response(uriResponse, mimetype='text/xml')
else:
return Response(authResponse, mimetype='text/xml')
else:
return Response(returnValues, mimetype='text/xml')
Imported module functions:
def transformXML(response, xsl):
xml = ET.tostring(ET.fromstring(response))
xslt = ET.XSLT(ET.parse(xsl))
transformedXML = xslt(ET.XML(xml))
return ET.tostring(transformedXML, pretty_print=True)
def addToDb(filename, mime, docType, customer_code, activeState, document_blob, filesize=None):
try:
response = dbConnect.Documents(file_name=filename, mime_type=mime, file_size=filesize, doc_type=docType,
customer_code=customer_code, is_active=activeState, document_blob=document_blob)
dbConnect.db.session.add(response)
dbConnect.db.session.commit()
dbConnect.db.session.close()
except exc.SQLAlchemyError:
return False
else:
return True
def generateXMLErrorResponse(errorMessage, api):
E = ElementMaker()
if api == 'SS':
GETSHIPMENTSTATUSRESPONSE = E.getShipmentStatusResponse
GETSHIPMENTSTATUSRESULT = E.getShipmentStatusResult
OUTCOME = E.outcome
RESULT = E.result
ERROR = E.error
xml_error = GETSHIPMENTSTATUSRESPONSE(
GETSHIPMENTSTATUSRESULT(
OUTCOME(
RESULT('Failure'),
ERROR(errorMessage)
)
)
)
return ET.tostring(xml_error, pretty_print=True)
elif api == 'IS':
GETINVENTORYSTATUSRESPONSE = E.getInventoryStatusResponse
GETINVENTORYSTATUSRESULT = E.getInventoryStatusResult
OUTCOME = E.outcome
RESULT = E.result
ERROR = E.error
xml_error = GETINVENTORYSTATUSRESPONSE(
GETINVENTORYSTATUSRESULT(
OUTCOME(
RESULT('Failure'),
ERROR(errorMessage)
)
)
)
return ET.tostring(xml_error, pretty_print=True)
def checkAuthorization(bu, headers, status):
error = 'Invalid clientCode for account type'
auth_search = re.search('username="(.*?)"', headers)
auth_user = auth_search.group(1)
if auth_user.upper() != "ADMIN":
if (str(bu).upper() != auth_user.upper()) and status == 'SS':
return False, Response(generateXMLErrorResponse(error, status), mimetype='text/xml')
elif (str(bu).upper() != auth_user.upper()) and status == 'IS':
return False, Response(generateXMLErrorResponse(error, status), mimetype='text/xml')
else:
return True, 'User authenticated'
else:
return True, 'User authenticated'
def getURI(api):
try:
if api == 'SS':
tracking = dbConnect.db.session.query(dbConnect.AppParam).\
filter(dbConnect.AppParam.name == 'TRACKING_WEB_SERVICE_URI').first()
return True, tracking.value
elif api == 'IS':
inventory = dbConnect.db.session.query(dbConnect.AppParam).\
filter(dbConnect.AppParam.name == 'INVENTORY_WEB_SERVICE_URI').first()
return True, inventory.value
except exc.OperationalError:
sendEmail()
return False, generateXMLErrorResponse('Service Unavailable', api)
def sendEmail():
msg = MIMEText('Unable to connect to DB')
msg['Subject'] = "Database server down!"
msg['From'] = ''
msg['To'] = ''
s = smtplib.SMTP('localhost')
s.sendmail(msg['From'], msg['To'], msg.as_string())
s.quit()
return True
def parseNWRequest(nwRequest, api):
returnValues = {}
if api == 'SS':
try:
xml = xmltodict.parse(nwRequest)
returnValues['start'] = xml['getShipmentStatus']['getShipmentStatusRequest']['startTime']
returnValues['end'] = xml['getShipmentStatus']['getShipmentStatusRequest']['endTime']
if validateDate(returnValues['start'], returnValues['end']):
returnValues['bu'] = xml['getShipmentStatus']['getShipmentStatusRequest']['clientCode']
returnValues['page'] = xml['getShipmentStatus']['getShipmentStatusRequest']['pageSize']
returnValues['requestid'] = xml['getShipmentStatus']['getShipmentStatusRequest']['requestId']
return True, returnValues
else:
return False, generateXMLErrorResponse('Invalid startDate/endDate', api)
except xmltodict.expat.ExpatError:
return False, generateXMLErrorResponse('Invalid Formed XML', api)
elif api == 'IS':
try:
xml = xmltodict.parse(request.data)
returnValues['bu'] = xml['getInventoryStatus']['getInventoryStatusRequest']['clientCode']
returnValues['facility'] = xml['getInventoryStatus']['getInventoryStatusRequest']['facility']
return True, returnValues
except xmltodict.expat.ExpatError:
return False, generateXMLErrorResponse('Invalid Formed XML', api)
def validateDate(startDate, endDate):
try:
datetime.strptime(startDate, '%Y-%m-%dT%H:%M:%S')
datetime.strptime(endDate, '%Y-%m-%dT%H:%M:%S')
except ValueError:
return False
else:
return True
def sendDepascoRequest(uri, search, api):
auth=('', '')
depascoResponse = requests.get(uri, auth=auth, params=search)
try:
depascoResponse.raise_for_status()
except requests.exceptions.HTTPError:
return False, generateXMLErrorResponse(depascoResponse.content, api)
else:
return True, requests.get(uri, auth=auth, params=search)
******* UPDATE **********
Thanks to the accepted answer, I removed all layers of the nested if statements. I have my functions just raise a custom exception which is handled in the main program.
__author__ = 'jw1050'
from functions import parseNWRequest, checkAuthorization, getURI, sendDepascoRequest, transformXML, addToDb, APIError
from flask import request
from flask import Response
import dbConnect
import lxml.etree as ET
from sqlalchemy import text
#dbConnect.app.route('/services/inventory/getInventoryStatus', methods=['POST'])
def parsexml():
try:
returnValues = parseNWRequest(request.data, 'IS')
checkAuthorization(returnValues['bu'], request.headers['Authorization'], 'IS')
uri = getURI('IS')
search = {'bu': returnValues['bu'], 'facility': returnValues['facility']}
depascoResponse = sendDepascoRequest(uri, search, 'IS')
s = text("Select sku, allocated from fgw_allocated_sku_count where client_code = :c and fulfillment_location = :t")
result = dbConnect.db.engine.execute(s, c=returnValues['bu'], t=returnValues['facility']).fetchall()
root = ET.fromstring(depascoResponse.content)
for row in result:
for element in root.iter('Item'):
xmlSKU = element.find('SKU').text
if xmlSKU == row[0]:
newQonOrder = int(element.find('QuantityOnOrder').text) + row[1]
element.find('QuantityOnOrder').text = str(newQonOrder)
newQAvailable = int(element.find('QuantityAvailable').text) - newQonOrder
element.find('QuantityAvailable').text = str(newQAvailable)
nakedResponse = transformXML(ET.tostring(root), 'transformInventory.xsl')
# Write Request to db
file_name = 'WS_IS_' + returnValues['bu']
request_file_size = request.headers['Content-Length']
addToDb('SS', file_name, 'text/xml', 'IS.Req', returnValues['bu'], 'Y', request.data, request_file_size)
addToDb('SS', file_name, 'text/xml', 'IS.Result', returnValues['bu'], 'Y', nakedResponse)
return Response(nakedResponse, mimetype='text/xml')
except APIError as error:
return Response(error.errorResponse, mimetype='text/xml')
#dbConnect.app.route('/services/tracking/getShipmentStatus', methods=['POST'])
def parsexml2():
try:
returnValues = parseNWRequest(request.data, 'SS')
checkAuthorization(returnValues['bu'], request.headers['Authorization'], 'SS')
uri = getURI('SS')
search = {'bu': returnValues['bu'], 'starttime': returnValues['start'], 'endtime': returnValues['end'],
'requestid': returnValues['requestid'], 'pagesize': returnValues['page']}
depascoResponse = sendDepascoRequest(uri, search, 'SS')
nakedResponse = transformXML(depascoResponse.content, 'transformTracking.xsl')
# Write Request to db
file_name = 'WS_SS_' + returnValues['bu']
request_file_size = request.headers['Content-Length']
addToDb('SS', file_name, 'text/xml', 'SS.Request', returnValues['bu'], 'Y', request.data, request_file_size)
addToDb('SS', file_name, 'text/xml', 'SS.Result', returnValues['bu'], 'Y', nakedResponse)
return Response(nakedResponse, mimetype='text/xml')
except APIError as error:
return Response(error.errorResponse, mimetype='text/xml')
if __name__ == '__main__':
dbConnect.app.run(host='localhost', port=int("5010"), debug=True)
My custom exception class:
class APIError(Exception):
def __init__(self, errorResponse):
self.errorResponse = errorResponse
An example of a function that raises my custom exception:
def validateDate(startDate, endDate):
try:
datetime.strptime(startDate, '%Y-%m-%dT%H:%M:%S')
datetime.strptime(endDate, '%Y-%m-%dT%H:%M:%S')
except ValueError:
raise APIError(generateXMLErrorResponse('Invalid format for startDate/endDate', 'SS'))
else:
return True
Another way is to raise exceptions and handle them.
import myexceptions
try:
# a complicated hunk of stuff that raises exceptions when
# things don't work out. The exceptions can be raised down
# inside functions, if breaking this lump into functions
# makes it easier to follow.
except MyExceptionA:
return Response( ...) # appropriate response for error A
except MyExceptionB
return Response( ...) # appropriate response for error B
except ...
I like to leave my methods ASAP, so in your case, it would look like:
if !X:
return...
if !Y:
return...
if !Z:
return...
though generally, I prefer
if X:
return...
if Y:
return...

Flask/mongodb webapp inconsistent test

I have a Flask integration test backed by a 1-node mongodb that randomly fails:
pytest/test_webapi.py:59: in test_register_test
> assert res.status_code == 302
E assert <Response streamed [404 NOT FOUND]>.status_code == 302
Fail rate is roughly 50%.
Test in test_webapi.py looking thus:
def test_register_user(self):
res = self.client.get("/logout")
class MySMTPServer(smtpd.SMTPServer):
mails = []
def process_message(self, peer, mailfrom, rcpttos, data):
self.mails.append((rcpttos[0], data))
server = MySMTPServer(('localhost', 12345), None)
t = threading.Thread(target=asyncore.loop, args=(1,))
t.start()
time.sleep(.1)
try:
res = self.client.post("/register", data=self.registration)
assert res.status_code == 200
mail, hash = server.mails[0]
self.conn.fsync()
time.sleep(.1)
res = self.client.get('/activate/' + hash)
assert res.status_code == 302
finally:
server.close()
The relevant Flask methods from webapi.py:
#app.route("/register", methods=["POST"])
def register_user():
mail = flask.request.form['mail']
user = flask.request.form["user"]
pw = flask.request.form["pass"]
hash = users.register(user, pw, mail=mail)
return flask.jsonify({'_id': None}) # XXX
#app.route('/activate/<hash>', methods=['GET'])
def activate_user(hash):
key = users.activate(hash=hash)
if not key:
flask.abort(404)
return flask.redirect("/")
... are backed by action methods:
make_key = lambda : base64.encodestring(os.urandom(32)).strip()
def register(self, user, pw, **kw):
hash = self.make_key()
user = self.new(user, pw, activation=hash, **kw)
self._send_mail(**user)
return hash
def activate(self, hash):
user = self.users.find_one({'activation': hash})
if not user:
return None
key = self.make_key()
activation = {
'$unset': {'activation': 1},
'$set': {'status': 'active', 'key': key} }
self.users.update({'_id': user['_id']}, activation)
return user
... where self.users is a mongodb collection.
self.new() persists the entity using safe=True.
Interestingly, several other tests doing similar things never seem to encounter this problem.
I had thought that this would be enough to make sure that the persisted object would be visible to other threads in the pymongo connection pool. What part of the mongodb/pymongo documentation should I have read more carefully? Or is there some weird interaction with asyncore?
(ported from comments thread)
In the context of the tests, is the data value just the base64-encoded string of the activation key? base64 contains letters and digits, but also "+" and "/", both of which will be misinterpreted by URL parsers (particularly "/").

Categories