Tuesday, October 10, 2023

Testing token verification endpoint

To test the API endpoint that verifies the verification link click, I needed to do some research on Pytest fixtures. Though I could have used fixtures in the previous tests, now there is a good use case as in order for the verification link to work, there must be a user already in the database and for this user we need to create a verification token. Both of these tasks would be best handled by a fixture to keep the test DRY.

For the creation of a user:
@pytest.fixture
def test_user():
    '''Create sample user for test'''
    sample_user = User.objects.create(username='someuser@somedomain.com')
    sample_user.set_password('somepassword')
    sample_user.save()
    return sample_user

All the fixture needs is the @pytest.fixture decorator and the return value of the function can be used directly as an object in the test function or another fixture. The fixture for creating the verification token will however be a little more complicated as the expiration time of the token needs to be dynamic. For this I use the “Factories as fixtures” feature with fixtures as described in the documentation:

@pytest.fixture
def verification_token(test_user):
    '''Creating tokens with JWT'''

    def _create_token(exp_time):
        '''Token with variable expiry time'''
        verification_token = RefreshToken.for_user(test_user)
        verification_token.set_exp(
            from_time=verification_token.current_time,
            lifetime=timedelta(seconds=exp_time)
        )
        return verification_token

    return _create_token

This fixture uses the test_user fixture. Instead of returning a token, the fixture returns another method which accepts the expiry time as an argument and returns the verification token. This can be used in the test function as:

def test_user_verification_endpoint(verification_token, test_user):
    '''Testing the verify-email endpoint'''
    client = APIClient()

    # Token with 60sec validity
    test_token1 = verification_token(60)

    api_response = client.get(
        '/user/verify-user/{token}'.format(
            token=test_token1
        ),
        format='json'
    )
    assert api_response.status_code == 200
    assert test_user.is_active == True

So here invoking the fixture name verification_token produces a function as the fixture does not return a value but rather a function. This function can be passed the expiration time. The above test verifies that the verification link works - is linked to a user in the database and also it results in the user becoming active. A few failing API requests can also be tested:

# Token with 1sec validity - expired token test
test_token2 = verification_token(1)
# Sleep for 2sec
time.sleep(2)

api_response = client.get(
    '/user/verify-user/{token}'.format(
        token=test_token2
    ),
    format='json'
)
print(api_response.data)
assert api_response.status_code == 400

# Tampered token test
test_token3 = str(verification_token(60))
test_token3 = test_token3[:-1]

api_response = client.get(
    '/user/verify-user/{token}'.format(
        token=test_token3
    ),
    format='json'
)
print(api_response.data)
assert api_response.status_code == 400

# Deleted user test
test_token4 = verification_token(60)
test_user.delete()

api_response = client.get(
    '/user/verify-user/{token}'.format(
        token=test_token4
    ),
    format='json'
)
print(api_response.data)
assert api_response.status_code == 400

I printed out the error messages and the last message is interesting:

Token is invalid or expired
Token is invalid or expired
User matching query does not exist.

This is because I am trying to fetch the user from the database with the user_id found in the JWT payload.

No comments:

Post a Comment