ApplicantPortal/test/test_auth_api.py
2025-03-12 20:43:26 -06:00

318 lines
12 KiB
Python

from datetime import datetime, timedelta
from time import sleep
from flask_jwt_extended import create_access_token, create_refresh_token
from .conftest import MOCK_USER_1, assert_200, assert_401, assert_404, assert_400, assert_message, assert_response_json_equal, assert_405, assert_in, assert_true, assert_false, \
assert_equal, assert_402, assert_not_in, assert_error_type, assert_406
from app.model import Roles, User
from app_common.const import NOT_UNIQUE, BAD_USER_ROLES, MALFORMED_DATA, USER_NOT_ACTIVE_MESSAGE
def test_login(client):
url = '/api/auth/ajax/login'
# we should get a 200 with valid user login data but no refresh token by default
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password})
assert_200(resp)
assert_in('access_token', resp.json)
assert_not_in('refresh_token', resp.json)
# if we set remember to false we shouldn't get a refresh token
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password, 'remember': False})
assert_200(resp)
assert_in('access_token', resp.json)
assert_not_in('refresh_token', resp.json)
# if we set remember to true, we should get a refresh token
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password, 'remember': True})
assert_200(resp)
assert_in('access_token', resp.json)
assert_in('refresh_token', resp.json)
# if the user is already logged in, we should get an error
headers = {'Auth-Token': f'Bearer {resp.json.get("access_token")}'}
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password}, headers=headers)
assert_401(resp)
# we should get a 401 and a message if the password is incorrect
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': 'bad-password'})
assert_401(resp)
assert_message(resp, 'Incorrect Password')
# we should get a 404 if the user does not exist
resp = client.post(url, data={'user_id': 'not-a-user', 'password': 'bad-password'})
assert_404(resp)
# if malformed data is passed, we should get an error
resp = client.post(url, data={'this': 'is', 'bad': 'data'})
assert_400(resp)
assert_error_type(resp, MALFORMED_DATA)
# trying to post with a non-active user should produce a 406 message and have the correct message
user = User.objects(user_id=MOCK_USER_1.user_id).first()
user.active = False
user.save()
assert_false(User.objects(user_id=MOCK_USER_1.user_id).first().active)
# this should be the case for requests with headers, though that should be impossible without accidentally modifying the database
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password}, headers=headers)
assert_406(resp)
assert_message(resp, USER_NOT_ACTIVE_MESSAGE)
# this is the test case that should actually happen
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password})
assert_406(resp)
assert_message(resp, USER_NOT_ACTIVE_MESSAGE)
def test_register(client, mocker):
# we want to mock sending the confirmation email.
mocker.patch('app.auth.email.send_email_to_user')
url = '/api/auth/ajax/register'
data = {
'user_id': 'foo',
'email': 'foo@bar.com',
'first_name': 'foo',
'last_name': 'bar',
'date_of_birth': datetime.utcnow().strftime('%b %d, %Y'),
'password': '123',
'phone_number': '222-333-4444',
'roles': Roles.APPLICANT.value
}
resp = client.post(url, data=data)
assert_200(resp)
# if the user is already logged in, we should get an error
headers = {'Auth-Token': f'Bearer {create_access_token(MOCK_USER_1)}'}
resp = client.post(url, data=data, headers=headers)
assert_401(resp)
# we should get an error trying to register the same user...
resp = client.post(url, data=data)
assert_400(resp)
assert_response_json_equal(resp, {'fields': ['user_id', 'email'], 'error_type': NOT_UNIQUE}, exclude_paths=["root['message']"])
# we should only see the duplicate fields in the response
data['user_id'] = 'bar'
resp = client.post(url, data=data)
assert_400(resp)
assert_response_json_equal(resp, {'fields': ['email'], 'error_type': NOT_UNIQUE}, exclude_paths=["root['message']"])
# now we should only see the phone number as bad
data['email'] = 'foo@baz.com'
resp = client.post(url, data=data)
assert_200(resp)
# sending an invalid username should give an error
data['user_id'] = 'Invalid user ID'
data['email'] = 'new@unique.com'
resp = client.post(url, data=data)
assert_400(resp)
assert_error_type(resp, MALFORMED_DATA)
# sending a bad role should produce a 400 error
data['user_id'] = 'valid_user_id'
data['roles'] = 'bad,roles'
resp = client.post(url, data=data)
assert_400(resp)
assert_error_type(resp, BAD_USER_ROLES)
# if malformed data is passed, we should get an error
data = {
'this': 'is',
'not': 'valid',
'data': 'to_pass'
}
resp = client.post(url, data=data)
assert_400(resp)
assert_error_type(resp, MALFORMED_DATA)
def test_update_password(client, mocker):
url = '/api/auth/ajax/update_password'
access_token = create_access_token(MOCK_USER_1)
headers = {'Auth-Token': f'Bearer {access_token}'}
data = {'old_password': MOCK_USER_1.password, 'new_password': 'foo'}
resp = client.post(url, data=data, headers=headers)
assert_200(resp)
# if the password is incorrect, we should get a 401 error
data['old_password'] = 'wrong'
resp = client.post(url, data=data, headers=headers)
assert_401(resp)
# if there is no access token given, we should get a 401
resp = client.post(url, data=data)
assert_401(resp)
# if the access token is revoked, we should get a 401
mocker.patch('app.auth._is_token_revoked', return_value=False)
resp = client.post(url, data=data, headers=headers)
assert_401(resp)
def test_refresh_token(client):
url = '/api/auth/ajax/refresh_access_token'
refresh_token = create_refresh_token(MOCK_USER_1)
headers = {'Auth-Token': f'Bearer {refresh_token}'}
# if the refresh token is valid, we should get and a new access token that works
resp = client.get(url, headers=headers)
assert_200(resp)
assert_in('access_token', resp.json)
access_token = resp.json['access_token']
# the new access token should be valid
resp = client.post('/api/auth/ajax/test_access_token', headers={'Auth-Token': f'Bearer {access_token}'})
assert_200(resp)
def test_expired_access_token(client):
url = '/api/auth/ajax/test_access_token'
access_token = create_access_token(MOCK_USER_1, expires_delta=timedelta(seconds=1))
headers = {'Auth-Token': f'Bearer {access_token}'}
resp = client.post(url, headers=headers)
assert_200(resp)
# sleep for 2 seconds to let the token expire
sleep(2)
resp = client.post(url, headers=headers)
assert_405(resp)
def test_logout(client):
url = '/api/auth/ajax/logout'
access_token = create_access_token(MOCK_USER_1)
headers = {'Auth-Token': f'Bearer {access_token}'}
# If we hit the logout endpoint, we should get a 200
resp = client.post(url, headers=headers)
assert_200(resp)
# the access token should now be blacklisted and we should get a 401 error
resp = client.post(url, headers=headers)
assert_401(resp)
# if no authentication header is given, we should get a 401
resp = client.post(url)
assert_401(resp)
def test_password_reset(client, mocker):
url = '/api/auth/ajax/request_password_reset'
mock_send_email = mocker.patch('app.auth.api.send_password_reset_email')
def get_reset_url():
# the call count for sending the password reset email should go up by 1
previous_mocked_count = mock_send_email.call_count
response = client.post(url, data={'user_id': MOCK_USER_1.user_id})
assert_200(response)
assert mock_send_email.call_count == previous_mocked_count + 1
token = mock_send_email.call_args.args[1]
return f'/api/auth/ajax/reset_password?token={token}'
# sending a user ID should cause a call to the mocked function
reset_url = get_reset_url()
resp = client.post(reset_url, data={'new_password': 'foobar'})
assert_200(resp)
# make sure the new user password is what is expected
user = User.objects(user_id=MOCK_USER_1.user_id).first()
assert user.check_password('foobar')
# Trying to reuse the token should fail
resp = client.post(reset_url, data={'new_password': 'foobar1'})
assert_401(resp)
# if we sleep for over 1 second (the test config has a 1 second timeout) the we should get a 401 for a fresh token
reset_url = get_reset_url()
sleep(2)
resp = client.post(reset_url, data={'new_password': 'foobar1'})
assert_401(resp)
# if the password is being set to the same, we should get a 400
reset_url = get_reset_url()
resp = client.post(reset_url, data={'new_password': 'foobar'})
assert_400(resp)
assert_in('password_reset_token', resp.json)
assert_error_type(resp, MALFORMED_DATA)
# using the new password reset token should work
reset_url = f'/api/auth/ajax/reset_password?token={resp.json["password_reset_token"]}'
resp = client.post(reset_url, data={'new_password': 'foobar1'})
assert_200(resp)
def test_activate(client):
base_url = 'api/auth/ajax/activate'
user = User.objects(user_id=MOCK_USER_1.user_id).first()
user.active = False
user.save()
# make sure the user isn't active
assert_false(user.active)
token = user.get_activation_token(secret_key=client.application.config['SECRET_KEY'])
url = f'{base_url}?token={token}'
resp = client.post(url)
assert_200(resp)
# the user should now be active
user = User.objects(user_id=MOCK_USER_1.user_id).first()
assert_true(user.active)
# if the user is already active, we should get a 401 error
resp = client.post(url)
assert_402(resp)
user.active = False
user.save()
# if the token expires, we should get a 401 error
token = user.get_activation_token(expire_secs=1, secret_key=client.application.config['SECRET_KEY'])
sleep(2)
url = f'{base_url}?token={token}'
resp = client.post(url)
assert_401(resp)
# the user should not have been activated
user = User.objects(user_id=MOCK_USER_1.user_id).first()
assert_false(user.active)
def test_send_confirm_email(client, mocker):
mock_send_email = mocker.patch('app.auth.api.send_activate_account_email')
prev_call_count = mock_send_email.call_count
url = '/api/auth/ajax/send_confirm_email'
user = User.objects(user_id=MOCK_USER_1.user_id).first()
user.active = False
user.save()
assert_false(user.active)
resp = client.post(url, data={'email': MOCK_USER_1.email})
assert_200(resp)
assert_equal(mock_send_email.call_count, prev_call_count + 1)
prev_call_count = mock_send_email.call_count
# the token we got should allow us to activate the correct user account
token = mock_send_email.call_args.args[1]
user = user.from_activation_token(token, secret_key=client.application.config['SECRET_KEY'])
assert_equal(user.user_id, MOCK_USER_1.user_id)
# if no user can be found we should get a 404 and no email should be sent
resp = client.post(url, data={'email': 'bad@email.com'})
assert_404(resp)
assert_equal(prev_call_count, mock_send_email.call_count)
def test_send_forgot_user_id_email(client, mocker):
mock_send_email = mocker.patch('app.auth.api.send_forgot_user_id_email')
prev_call_count = mock_send_email.call_count
url = '/api/auth/ajax/forgot_user_id'
user = User.objects(user_id=MOCK_USER_1.user_id).first()
data = {'email': user.email}
resp = client.post(url, data=data)
assert_200(resp)
# an email should have been sent
assert_equal(mock_send_email.call_count, prev_call_count + 1)
prev_call_count = mock_send_email.call_count
# the email should have been sent to the correct user's email address
user = mock_send_email.call_args.args[0]
assert_equal(user.email, MOCK_USER_1.email)
# providing a bad user email should result in a 404
data['email'] = 'DNE@fake.com'
resp = client.post(url, data=data)
assert_404(resp)
assert_equal(mock_send_email.call_count, prev_call_count)
# providing a non-email address value should provide a 400 error
data['email'] = 'not an email address'
resp = client.post(url, data=data)
assert_400(resp)