- •ВВЕДЕНИЕ
- •1. ПОДГОТОВКА К РАЗРАБОТКЕ
- •1.1. НАЧАЛО РАБОТЫ
- •1.2. ИСПОЛЬЗОВАНИЕ КОНТЕЙНЕРОВ DOCKER
- •1.3. НАСТРОЙКА СУБД POSTGRESQL
- •1.4. НАСТРОЙКА ТЕСТОВ
- •1.6. СОЗДАНИЕ RESTFUL-МАРШРУТОВ
- •1.6.1. МАРШРУТ POST /USERS
- •1.6.3. МАРШРУТ GET /USERS
- •1.6.2. МАРШРУТ GET /USERS/:ID
- •1.7. ИСПОЛЬЗОВАНИЕ ШАБЛОНОВ JINJA
- •1.8. СТРУКТУРА ПРОЕКТА
- •2. ОТЛАДКА ПРИЛОЖЕНИЯ
- •2.1. ПОКРЫТИЕ ТЕСТАМИ И КАЧЕСТВО КОДА
- •2.1.1. ПОКРЫТИЕ КОДА ТЕСТАМИ
- •2.1.2. КАЧЕСТВО КОДА
- •2.2. НЕПРЕРЫВНАЯ ИНТЕГРАЦИЯ
- •2.3. ПАНЕЛЬ ИНСТРУМЕНТОВ ОТЛАДКИ FLASK
- •2.4. СТРУКТУРА ПРИЛОЖЕНИЯ
- •3. РАЗВИТИЕ ПРИЛОЖЕНИЯ
- •3.1. ПАКЕТ FLASK MIGRATE
- •3.1.1. ИЗМЕНЕНИЕ МОДЕЛИ
- •3.1.2. НАСТРОЙКА FLASK MIGRATE
- •3.2. РЕФАКТОРИНГ КОДА
- •3.3. ПАКЕТ FLASK BCRYPT
- •3.4. НАСТРОЙКА LOG ROUNDS
- •3.5. ИСПОЛЬЗОВАНИЕ ТОКЕНОВ JWT
- •3.5.1. ПАКЕТ PYJWT
- •3.5.2. РАСШИФРОВКА ТОКЕНА
- •3.6. МАРШРУТЫ ДЛЯ АУТЕНТИФИКАЦИИ
- •3.6.1. МАРШРУТ POST /AUTH/REGISTER
- •3.6.2. МАРШРУТ POST /AUTH/LOGIN
- •3.6.3. МАРШРУТ GET /AUTH/LOGOUT
- •3.6.4. МАРШРУТ GET /AUTH/STATUS
- •ЗАКЛЮЧЕНИЕ
- •СПИСОК ЛИТЕРАТУРЫ
response_object['status'] = 'success' response_object['message'] = 'Successfully registered.' response_object['auth_token'] = auth_token.decode() return jsonify(response_object), 201
else:
response_object['message'] = 'Sorry. That user already ex-
ists.'
return jsonify(response_object), 400
# обработка ошибок
except (exc.IntegrityError, ValueError) as e: db.session.rollback()
return jsonify(response_object), 400
После этого тесты будут выполняться успешно.
3.6.2. МАРШРУТ POST /AUTH/LOGIN
Создание маршрута начнём с написания тестов:
def test_registered_user_login(self): with self.client:
add_user('test', 'test@test.com', 'test') response = self.client.post(
'/auth/login',
data=json.dumps({
'email': 'test@test.com', 'password': 'test'
}), content_type='application/json'
)
data = json.loads(response.data.decode()) self.assertTrue(data['status'] == 'success') self.assertTrue(data['message'] == 'Successfully logged in.') self.assertTrue(data['auth_token']) self.assertTrue(response.content_type == 'application/ json') self.assertEqual(response.status_code, 200)
def test_not_registered_user_login(self): with self.client:
response = self.client.post(
67
'/auth/login',
data=json.dumps({
'email': 'test@test.com', 'password': 'test'
}), content_type='application/json'
)
data = json.loads(response.data.decode()) self.assertTrue(data['status'] == 'fail') self.assertTrue(data['message'] == 'User does not exist.') self.assertTrue(response.content_type == 'application/json') self.assertEqual(response.status_code, 404)
Запустим тесты. Запуск тестов должен завершиться неудачей. Теперь до-
бавим соответствующее представление:
@auth_blueprint.route('/auth/login', methods=['POST']) def login_user():
# получение данных
post_data = request.get_json() response_object = {
'status': 'fail',
'message': 'Invalid payload.'
}
if not post_data:
return jsonify(response_object), 400 email = post_data.get('email') password = post_data.get('password') try:
# получение данных пользователя
user = User.query.filter_by(email=email).first()
if user and bcrypt.check_password_hash(user.password, password):
auth_token = user.encode_auth_token(user.id) if auth_token:
response_object['status'] = 'success' response_object['message'] = 'Successfully logged in.' response_object['auth_token'] = auth_token.decode() return jsonify(response_object), 200
else:
68
response_object['message'] = 'User does not exist.' return jsonify(response_object), 404
except Exception as e: response_object['message'] = 'Try again.' return jsonify(response_object), 500
После этого тесты будут выполняться успешно.
3.6.3. МАРШРУТ GET /AUTH/LOGOUT
Создание маршрута начнём с написания тестов. Первый тест служит для проверки корректного выхода из приложения:
def test_valid_logout(self): add_user('test', 'test@test.com', 'test') with self.client:
# user login
resp_login = self.client.post( '/auth/login', data=json.dumps({
'email': 'test@test.com', 'password': 'test'
}), content_type='application/json'
)
# выход из системы с действующим токеном
token = json.loads(resp_login.data.decode())['auth_token'] response = self.client.get(
'/auth/logout',
headers={'Authorization': f'Bearer {token}'}
)
data = json.loads(response.data.decode()) self.assertTrue(data['status'] == 'success') self.assertTrue(data['message'] == 'Successfully logged out.') self.assertEqual(response.status_code, 200)
Второй тест служит для проверки некорректного выхода из приложения:
def test_invalid_logout_expired_token(self):
add_user('test', 'test@test.com', 'test')
with self.client:
resp_login = self.client.post(
69
'/auth/login',
data=json.dumps({
'email': 'test@test.com', 'password': 'test'
}), content_type='application/json'
)
# выход из системы с неверным токеном time.sleep(4)
token = json.loads(resp_login.data.decode())['auth_token'] response = self.client.get(
'/auth/logout',
headers={'Authorization': f'Bearer {token}'}
)
data = json.loads(response.data.decode()) self.assertTrue(data['status'] == 'fail') self.assertTrue(
data['message'] == 'Signature expired. Please log in again.')
self.assertEqual(response.status_code, 401)
def test_invalid_logout(self): with self.client:
response = self.client.get( '/auth/logout',
headers={'Authorization': 'Bearer invalid'}) data = json.loads(response.data.decode()) self.assertTrue(data['status'] == 'fail') self.assertTrue(
data['message'] == 'Invalid token. Please log in again.') self.assertEqual(response.status_code, 401)
Добавим необходимый импорт в файл:
import time
Обновим представление:
@auth_blueprint.route('/auth/logout', methods=['GET']) def logout_user():
# получение токена аутентификации
auth_header = request.headers.get('Authorization')
70
response_object = { 'status': 'fail',
'message': 'Provide a valid auth token.'
}
if auth_header:
auth_token = auth_header.split(' ')[1] resp = User.decode_auth_token(auth_token) if not isinstance(resp, str):
response_object['status'] = 'success' response_object['message'] = 'Successfully logged out.' return jsonify(response_object), 200
else:
response_object['message'] = resp return jsonify(response_object), 401
else:
return jsonify(response_object), 403
Запустим тесты:
------------------------------------------------------------------
Ran 35 tests in 5.040s
OK
Обратите внимание на строку time.sleep (4) в тесте test_invalid_logout_expired_token. Этот код добавляет дополнительные 4 секун-
ды к набору тестов. Чтобы ускорить процесс, обновим значение переменной
TOKEN_EXPIRATION_SECONDS для этого конкретного теста:
def test_invalid_logout_expired_token(self): add_user('test', 'test@test.com', 'test') current_app.config['TOKEN_EXPIRATION_SECONDS'] = -1 with self.client:
resp_login = self.client.post( '/auth/login', data=json.dumps({
'email': 'test@test.com', 'password': 'test'
}), content_type='application/json'
)
71
