Насмешливый торнадо AsyncHTTPClient запросы / ответы - PullRequest
0 голосов
/ 26 июня 2019

У меня есть веб-приложение Tornado, которое отправляет запросы внешним службам и, похоже, не может смоделировать ответы на эти запросы.

Я пробовал tornado-http-mock и mock libsно безуспешно.

Начальная точка приложения app.py:

import tornado.ioloop
import tornado.web

from handlers import HealthCheckHandler, MainHandler, LoginHandler, PaymentHandler, UserDetailsHandler
from settings import PORT, tornado_settings


def make_app(settings):
    handlers = [
        ('/static/(.*)', tornado.web.StaticFileHandler, {'path': './public'}),
        ('/', MainHandler),
        ('/health', HealthCheckHandler),
        ('/login', LoginHandler),
        ('/user', UserDetailsHandler),
        ('/payment', PaymentHandler),
    ]
    return tornado.web.Application(handlers=handlers, **settings)


if __name__ == '__main__':
    app = make_app(tornado_settings)
    app.listen(PORT)
    tornado.ioloop.IOLoop.current().start()

Я пытаюсь проверить функциональность входа (это сервер OAuth2), который перенаправляет пользователя, когда естьнет code Передан параметр GET (если пользователь еще не вошел в систему) или он пытается обменять код с токеном доступа.Вот обработчик входа в систему.

import base64
import urllib.parse
import json
import traceback
import tornado.web
import tornado.httpclient

from .base import BaseHandler
from settings import OID_AUTH_API, OID_REDIRECT_URI, OID_CLIENT_ID, OID_CLIENT_PASSWORD
from lib import logger


class LoginHandler(BaseHandler):
    _redirect_uri = urllib.parse.quote(OID_REDIRECT_URI, safe='')
    _scope = 'openid+profile+email'
    _response_type = 'code'
    _http_client = tornado.httpclient.AsyncHTTPClient()

    async def get(self):
        try:
            code = self.get_argument('code', None)

            if (code is None):
                self.redirect('%s/authorization?client_id=%s&scope=%s&response_type=%s&redirect_uri=%s' % (
                    OID_AUTH_API, OID_CLIENT_ID, self._scope, self._response_type, self._redirect_uri), self.request.uri)
                return

            # exchange the authorization code with the access token
            grant_type = 'authorization_code'
            redirect_uri = self._redirect_uri
            authorization_header = '%s:%s' % (
                OID_CLIENT_ID, OID_CLIENT_PASSWORD)
            authorization_header_encoded = base64.b64encode(
                authorization_header.encode('UTF-8')).decode('UTF-8')
            url = '%s/token?grant_type=%s&code=%s&redirect_uri=%s' % (
                OID_AUTH_API, grant_type, code, redirect_uri)
            token_exchange_response = await self._http_client.fetch(
                url,
                method='POST',
                headers={
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Authorization': 'Basic %s' % authorization_header_encoded,
                    'Accept': 'application/json'
                },
                body='')

            token_exchange_response_body_dict = json.loads(
                token_exchange_response.body)

            access_token = token_exchange_response_body_dict.get('access_token')

            self.send_response({
                'access_token': access_token
            })
        except Exception as error:
            logger.log_error_with_traceback(error)
            self.send_response({
                'success': False,
                'message': 'Internal server error. Please try again later.'
            }, 500)

У меня есть два вопроса: 1. Как проверить функциональность перенаправления в случае, если код авторизации не был представлен?2. Как в этом случае макетировать запросы к серверу OAuth2?

Я пытался с tornado-http-mock, но я получаю ошибки.


import app
import json

from tornado.httpclient import HTTPClient, HTTPResponse, HTTPRequest, HTTPError
from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, AsyncHTTPClient

from tornado_mock.httpclient import get_response_stub, patch_http_client, set_stub

from .base import TestHandlerBase
from settings import OID_AUTH_API


class TestLoginHandler(AsyncHTTPTestCase):
    def get_app(self):
        test_app = app.make_app({})
        self.app_http_client = test_app.http_client = AsyncHTTPClient(force_instance=True)
        return test_app

    def test_token_code_exchange(self):        
        patch_http_client(self.app_http_client)

        set_stub(self.app_http_client, '%s/token' % (OID_AUTH_API), request_method='POST', response_body='oauth_server_token')

        response = self.fetch('/login?code=123')

        self.assertEqual(response.code, 200)
        print(response.body)

Я получаю следующую ошибкучто указывает на то, что метод POST не поддерживается (кажется, что запрос на самом деле отправляется на сервер и не подвергается мошенничеству).

Login Error code: 405 | Response body: 
[E 190626 13:29:33 web:2246] 500 GET /login?code=123 (127.0.0.1) 238.10ms
======================================================================
FAIL: test_token_code_exchange (tests.handlers.login.TestLoginHandler)
----------------------------------------------------------------------

Traceback (most recent call last):
  File "/Users/.../venv/lib/python3.7/site-packages/tornado/testing.py", line 98, in __call__
    result = self.orig_method(*args, **kwargs)
  File "/Users/.../tests/handlers/login.py", line 60, in test_token_code_exchange
    self.assertEqual(response.code, 200)
AssertionError: 500 != 200

Я ожидаю получить ответ с заглушкой, но, очевидно,Я не понимаю этого.Что мне здесь не хватает?Есть ли другое решение для этого?

1 Ответ

0 голосов
/ 05 июля 2019

Вы можете использовать mock.patch и gen.coroutine для имитации внешнего запроса в торнадо. Вы можете попробовать что-то вроде этого:

Извлечение внешнего запроса в метод, подобный ...

async def new_fetch(self, url, authorization_header_encoded):
    return await self._http_client.fetch(
        url,
        method='POST',
        headers={
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': 'Basic %s' % authorization_header_encoded,
            'Accept': 'application/json'
        },
        body='')

Измените свой LoginHandler для вызова этого нового метода:

token_exchange_response = await new_fetch(url, authorization_header_encoded)

В вашем TestLoginHandler создайте новый метод для имитации ответа http и украсьте этот метод с помощью декоратора gen.coroutine и используйте декоратор mock.patch, чтобы макетировать метод внешнего запроса в вашем методе теста:

import app
import json

from tornado.httpclient import HTTPClient, HTTPResponse, HTTPRequest, HTTPError
from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, AsyncHTTPClient
# from tornado_mock.httpclient import get_response_stub, patch_http_client, 
from tornado import gen 

from .base import TestHandlerBase
from settings import OID_AUTH_API


class TestLoginHandler(AsyncHTTPTestCase):
    @gen.coroutine
    def mock_fetch(self, url, authorization_header_encoded)
        request = HTTPRequest(
            headers=authorization_header_encoded,
            method='POST',
            body='',
            url=url)
        resp = HTTPResponse(request, HTTPStatus.OK, buffer=json.dumps({}))
        resp._body = json.dumps({"YOUR_RESPONSE_BODY":"YOUR_RESPONSE_BODY"})
        return resp

    def get_app(self):
        test_app = app.make_app({})
        # self.app_http_client = test_app.http_client = AsyncHTTPClient(force_instance=True)
        return test_app

    @mock.patch("full.path.to.LoginHandler.new_fetch")
    def test_token_code_exchange(self, mocked_method):
        mocked_method.return_value = self.mock_fetch('optional_url', 'optional_header') 

        # patch_http_client(self.app_http_client)

        # set_stub(self.app_http_client, '%s/token' % (OID_AUTH_API), request_method='POST', response_body='oauth_server_token')

        response = self.fetch('/login?code=123')

        self.assertEqual(response.code, 200)
        print(response.body)

Я не тестировал этот код, я пишу только для того, чтобы передать вам и идею сделать этот макет, так что, возможно, вам нужно будет отрегулировать несколько вещей в этом коде

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...