Skip to content
This repository has been archived by the owner on Apr 26, 2020. It is now read-only.

Delete user endpoint #21

Merged
merged 19 commits into from
Nov 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions authentication/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,8 @@ def load_user_from_id(user_id):
there is no such user.
:rtype: ``User`` or ``None``.
"""
response = requests.get(
urljoin(STORAGE_URL, 'users/{email}').format(email=user_id),
headers={'Content-Type': 'application/json'},
)
url = urljoin(STORAGE_URL, 'users/{email}').format(email=user_id)
response = requests.get(url, headers={'Content-Type': 'application/json'})

if response.status_code == codes.OK:
details = json.loads(response.text)
Expand Down Expand Up @@ -204,6 +202,36 @@ def logout():
return jsonify({}), codes.OK


@app.route('/users/<email>', methods=['DELETE'])
@consumes('application/json')
def specific_user_route(email):
"""
Delete a particular user.

:reqheader Content-Type: application/json
:resheader Content-Type: application/json
:resjson string email: The email address of the deleted user.
:status 200: The user has been deleted.
:status 404: There is no user with the given ``email``.
"""
user = load_user_from_id(email)

if user is None:
return jsonify(
title='The requested user does not exist.',
detail='No user exists with the email "{email}"'.format(
email=email),
), codes.NOT_FOUND

requests.delete(
urljoin(STORAGE_URL, '/users/{email}'.format(email=email)),
headers={'Content-Type': 'application/json'},
)

return_data = jsonify(email=user.email)
return return_data, codes.OK


@app.route('/signup', methods=['POST'])
@consumes('application/json')
@jsonschema.validate('user', 'create')
Expand Down
63 changes: 63 additions & 0 deletions authentication/tests/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def request_callback(self, request):
Given a request to the storage service, send an equivalent request to
an in memory fake of the storage service and return some key details
of the response.

:param request: The incoming request to pass onto the storage app.
:return: A tuple of status code, response headers and response data
from the storage app.
"""
# The storage application is a ``werkzeug.test.Client`` and therefore
# has methods like 'head', 'get' and 'post'.
Expand Down Expand Up @@ -439,6 +443,65 @@ def test_fake_token(self):
self.assertIsNone(load_user_from_token(auth_token='fake_token'))


class DeleteUserTests(AuthenticationTests):
"""
Tests for the delete user endpoint at ``DELETE /users/<email>``.
"""

@responses.activate
def test_delete_user(self):
"""
A ``DELETE`` request to delete a user returns an OK status code and the
email of the deleted user. The user no longer exists.
"""
self.app.post(
'/signup',
content_type='application/json',
data=json.dumps(USER_DATA))

response = self.app.delete(
'/users/{email}'.format(email=USER_DATA['email']),
content_type='application/json')

self.assertEqual(response.headers['Content-Type'], 'application/json')
self.assertEqual(response.status_code, codes.OK)
self.assertEqual(
json.loads(response.data.decode('utf8')),
{'email': USER_DATA['email']})

self.assertIsNone(load_user_from_id(user_id=USER_DATA['email']))

@responses.activate
def test_non_existant_user(self):
"""
A ``DELETE`` request for a user which does not exist returns a
NOT_FOUND status code and error details.
"""
response = self.app.delete(
'/users/{email}'.format(email=USER_DATA['email']),
content_type='application/json')
self.assertEqual(response.headers['Content-Type'], 'application/json')
self.assertEqual(response.status_code, codes.NOT_FOUND)
expected = {
'title': 'The requested user does not exist.',
'detail': 'No user exists with the email "{email}"'.format(
email=USER_DATA['email']),
}
self.assertEqual(json.loads(response.data.decode('utf8')), expected)

def test_incorrect_content_type(self):
"""
If a Content-Type header other than 'application/json' is given, an
UNSUPPORTED_MEDIA_TYPE status code is given.
"""
response = self.app.delete(
'/users/{email}'.format(email=USER_DATA['email']),
content_type='text/html',
)

self.assertEqual(response.status_code, codes.UNSUPPORTED_MEDIA_TYPE)


class UserTests(unittest.TestCase):
"""
Tests for the ``User`` model.
Expand Down
25 changes: 21 additions & 4 deletions storage/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,29 @@ def on_validation_error(error):
), codes.BAD_REQUEST


@app.route('/users/<email>', methods=['GET'])
@app.route('/users/<email>', methods=['GET', 'DELETE'])
@consumes('application/json')
def get_user(email):
def specific_user_route(email):
"""
**DELETE**:

Delete a particular user.

:reqheader Content-Type: application/json
:resheader Content-Type: application/json
:resjson string email: The email address of the deleted user.
:resjson string password_hash: The password hash of the deleted user.
:status 200: The user has been deleted.
:status 404: There is no user with the given ``email``.

**GET**:

Get information about particular user.

:reqheader Content-Type: application/json
:resheader Content-Type: application/json
:resjson string email: The email address of the new user.
:resjson string password_hash: The password hash of the new user.
:resjson string email: The email address of the user.
:resjson string password_hash: The password hash of the user.
:status 200: The requested user's information is returned.
:status 404: There is no user with the given ``email``.
"""
Expand All @@ -103,6 +116,10 @@ def get_user(email):
email=email),
), codes.NOT_FOUND

elif request.method == 'DELETE':
db.session.delete(user)
db.session.commit()

return_data = jsonify(email=user.email, password_hash=user.password_hash)
return return_data, codes.OK

Expand Down
60 changes: 60 additions & 0 deletions storage/tests/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,63 @@ def test_incorrect_content_type(self):
"""
response = self.storage_app.get('/users', content_type='text/html')
self.assertEqual(response.status_code, codes.UNSUPPORTED_MEDIA_TYPE)


class DeleteUserTests(InMemoryStorageTests):
"""
Tests for deleting a user at ``DELETE /users/<email/``.
"""

def test_delete_user(self):
"""
A ``DELETE`` request to delete a user returns an OK status code and the
details of the deleted user. The user is no longer available when
getting the list of users.
"""
self.storage_app.post(
'/users',
content_type='application/json',
data=json.dumps(USER_DATA))

response = self.storage_app.delete(
'/users/{email}'.format(email=USER_DATA['email']),
content_type='application/json')
self.assertEqual(response.headers['Content-Type'], 'application/json')
self.assertEqual(response.status_code, codes.OK)
self.assertEqual(json.loads(response.data.decode('utf8')), USER_DATA)

users = self.storage_app.get(
'/users',
content_type='application/json',
)

self.assertEqual(json.loads(users.data.decode('utf8')), [])

def test_non_existant_user(self):
"""
A ``DELETE`` request for a user which does not exist returns a
NOT_FOUND status code and error details.
"""
response = self.storage_app.delete(
'/users/{email}'.format(email=USER_DATA['email']),
content_type='application/json')
self.assertEqual(response.headers['Content-Type'], 'application/json')
self.assertEqual(response.status_code, codes.NOT_FOUND)
expected = {
'title': 'The requested user does not exist.',
'detail': 'No user exists with the email "{email}"'.format(
email=USER_DATA['email']),
}
self.assertEqual(json.loads(response.data.decode('utf8')), expected)

def test_incorrect_content_type(self):
"""
If a Content-Type header other than 'application/json' is given, an
UNSUPPORTED_MEDIA_TYPE status code is given.
"""
response = self.storage_app.delete(
'/users/{email}'.format(email=USER_DATA['email']),
content_type='text/html',
)

self.assertEqual(response.status_code, codes.UNSUPPORTED_MEDIA_TYPE)