2

我做了什么

我编写了一个身份验证类,用于使用应用程序的API 密钥及其API 密钥秘密从 Twitter获取应用程序的不记名令牌,如Twitter 开发人员文档中所示。

我用requests_mock这种方式模拟了适当的端点:

@pytest.fixture
def mock_post_bearer_token_endpoint(
    requests_mock, basic_auth_string, bearer_token
):
    requests_mock.post(
        "https://api.twitter.com/oauth2/token",
        request_headers={
            "Authorization": f"Basic {basic_auth_string}",
            "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
        },
        json={"token_type": "bearer", "access_token": f"{bearer_token}"},
    )

我的测试方法是:

@pytest.mark.usefixtures("mock_post_bearer_token_endpoint")
def test_basic_auth(api_key, api_key_secret, bearer_token):
    response = requests.post(
        'https://api.twitter.com/oauth2/token',
        data={"grant_type": "client_credentials"},
        auth=TwitterBasicAuth(api_key, api_key_secret),
    )
    assert response.json()['access_token'] == bearer_token

TwitterBasicAuth我写的身份验证类在哪里,夹具basic_auth_string是一个硬编码的字符串,可以通过适当地转换夹具获得api_keyapi_key_secret

它有效。

问题

但我真的对模拟端点不检查有效负载这一事实感到困扰。在这种特殊情况下,有效负载对于获取承载令牌至关重要。

我已经梳理了requests_mock(and responses, too) 的文档,但还没有弄清楚如何使端点仅在发布正确的有效负载时才使用不记名令牌进行响应。

请帮忙。

4

2 回答 2

2

更新的答案

我根据gold_cy 的评论编写了一个自定义匹配器,如果请求具有正确的 url 路径、标头和 json 有效负载,该匹配器接受请求并返回适当制作的 OK 响应。否则它会返回 403 响应,正如我对 Twitter API 所期望的那样。

@pytest.fixture
def mock_post_bearer_token_endpoint(
    requests_mock, basic_auth_string, bearer_token
):
    def matcher(req):
        if req.path != "/oauth2/token":
            # no mock address
            return None
        if req.headers.get("Authorization") != f"Basic {basic_auth_string}":
            return create_forbidden_response()
        if (
            req.headers.get("Content-Type")
            != "application/x-www-form-urlencoded;charset=UTF-8"
        ):
            return create_forbidden_response()
        if req.json().get("grant_type") != "client_credentials":
            return create_forbidden_response()

        resp = requests.Response()
        resp._content = json.dumps(
            {"token_type": "bearer", "access_token": f"{bearer_token}"}
        ).encode()
        resp.status_code = 200

        return resp

    requests_mock._adapter.add_matcher(matcher)
    yield

def create_forbidden_response():
    resp = requests.Response()
    resp.status_code = 403
    return resp

较早的答案

我接受了gold_cy 的评论并编写了一个额外的匹配器,它接受请求并检查有效负载中是否存在感兴趣的数据。

@pytest.fixture(name="mock_post_bearer_token_endpoint")
def fixture_mock_post_bearer_token_endpoint(
    requests_mock, basic_auth_string, bearer_token
):
    def match_grant_type_in_payload(request):
        if request.json().get("grant_type") == "client_credentials":
            return True
        resp = Response()
        resp.status_code = 403
        resp.raise_for_status()

    requests_mock.post(
        "https://api.twitter.com/oauth2/token",
        request_headers={
            "Authorization": f"Basic {basic_auth_string}",
            "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
        },
        json={"token_type": "bearer", "access_token": f"{bearer_token}"},
        additional_matcher=match_grant_type_in_payload,
    )

我选择引发Http403错误(而不是仅仅返回 False)以减少确定引发异常的原因的认知负担——返回 False 会导致引发错误requests_mock.exceptions.NoMockAddress,我认为在这种情况下描述性不够。

我仍然认为有更好的方法来解决这个问题,我会继续寻找它。

于 2020-09-30T12:51:15.620 回答
2

我认为这里的误解是您需要将所有内容都放入匹配器中,并让 NoMatchException 告诉您是否正确。

匹配器可能是返回正确响应所需的最简单的东西,然后您可以将所有请求/响应检查作为正常单元测试处理的一部分。

例如,如果您需要根据请求的正文切换响应值,则附加匹配器很有用,通常 true/false 就足够了。

例如,我没有尝试为此查找 twitter auth:

import requests
import requests_mock

class TwitterBasicAuth(requests.auth.AuthBase):

    def __init__(self, api_key, api_key_secret):
        self.api_key = api_key
        self.api_key_secret = api_key_secret

    def __call__(self, r):
        r.headers['x-api-key'] = self.api_key
        r.headers['x-api-key-secret'] = self.api_key_secret
        return r


with requests_mock.mock() as m:
    api_key = 'test'
    api_key_secret = 'val'

    m.post(
        "https://api.twitter.com/oauth2/token",
        json={"token_type": "bearer", "access_token": "token"},
    )

    response = requests.post(
        'https://api.twitter.com/oauth2/token',
        data={"grant_type": "client_credentials"},
        auth=TwitterBasicAuth(api_key, api_key_secret),
    )

    assert response.json()['token_type'] == "bearer"
    assert response.json()['access_token'] == "token"
    assert m.last_request.headers['x-api-key'] == api_key
    assert m.last_request.headers['x-api-key-secret'] == api_key_secret

https://requests-mock.readthedocs.io/en/latest/history.html

于 2020-12-15T03:31:24.800 回答