使用“http.client”Python时,即使引发异常,AssertRaises也会失败

2024-10-04 07:26:16 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在为使用库http.Client的项目创建一个API类

问题在于单元测试,当我试图引发错误并使用assertRaises进行测试时,assertRaises即使在引发正确异常的情况下也会失败

我有一个模块“Foo”

+---Foo
¦    __init__.py
¦    api.py

在我的\uuu init\uuu.py中,只有我的异常类和装饰器

import errno
import json
import os
import signal
from dataclasses import dataclass
from functools import wraps
from http.client import HTTPMessage, HTTPException
from typing import Optional


@dataclass
class APIResponse:
    method: str
    code: int
    reason: str
    length: Optional[int]
    headers: HTTPMessage
    message: HTTPMessage


@dataclass
class Token:
    access_token: str
    expires_in: int
    token_type: str


@dataclass
class ClientCredentials:
    client_id: str
    client_secret: str
    audience: str
    grant_type: str = "client_credentials"


class ClientCredentialsEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, ClientCredentials):
            return o.__dict__
        return json.JSONEncoder.default(self, o)


class AuthenticationError(HTTPException):
    pass


def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
    def decorator(func):
        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)

        def wrapper(*args, **kwargs):
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(seconds)
            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
            return result

        return wraps(func)(wrapper)

    return decorator

py包含我的api类,我正在测试该类的身份验证方法以获取访问令牌

import http.client
import json
import logging

from Foo import ClientCredentials, Token, ClientCredentialsEncoder, AuthenticationError, timeout
from settings import config, API_HOST, AUTH_DOMAIN


class API:

    def __init__(self, auth0_domain, client_credentials: ClientCredentials):
        self.auth0_domain: str = auth0_domain
        self.client_credentials = client_credentials

        self._session = http.client.HTTPSConnection(self.auth0_domain)

        self.token: Token = self.authenticate()

    @property
    def session(self):
        return self._session

    @timeout(10, "API Takes too long to respond.")
    def api_request(self, method: str, url: str, body: str, headers: dict):
        logging.debug(f"Senging {method} request to {url} for data: {body}...")

        self.session.request(method=method, url=url, body=body, headers=headers)

        res = self.session.getresponse()

        return res

    def authenticate(self) -> Token:
        logging.debug("Getting API authentiation...")
        method = "POST"
        url = "/oauth/token"
        body = json.dumps(
            obj=self.client_credentials,
            cls=ClientCredentialsEncoder
        )
        headers = {'content-type': "application/json"}

        response = self.api_request(method=method, url=url, body=body, headers=headers)
        status = response.status
        reason = response.reason

        data = response.read()

        json_dict = json.loads(data)
        self._session.close()

        if status == 403:
            logging.error(f"Error {status}: {reason}. Authentication fail.")
            raise AuthenticationError(
                f"Invalid Credentials. Error: '{json_dict['error']}', Description: {json_dict['error_description']}"
            )

        if status == 401:
            logging.error(f"Error {status}: {reason}. Authentication fail.")
            raise AuthenticationError(
                f"Invalid Credentials. Error: '{json_dict['error']}', Description: {json_dict['error_description']}"
            )

        logging.info(f"Response {status}. Authentication successful!")

        return Token(
            access_token=json_dict['access_token'],
            expires_in=json_dict['expires_in'],
            token_type=json_dict['token_type'],

        )

现在,我正在尝试测试我的身份验证,如果它获得了无效的凭据,它是否会引发正确的错误。 这是我的测试脚本

import logging
import unittest
from typing import Tuple

from Foo import ClientCredentials, Token, AuthenticationError
from Foo.api import API
from settings import config, API_HOST


class MyTestCase(unittest.TestCase):
    def setUp(self):
        pass

    def test_token(self):
        api = API(*self.good_credentials)

        self.assertIsInstance(api.authenticate(), Token)

    def test_invalid_credentials(self):
        api = API(*self.invalid_credentials)

        # self.assertRaises(AuthenticationError, api.authenticate)
        with self.assertRaises(AuthenticationError) as error_msg:
            api.authenticate()
        self.assertEqual(error_msg, "Invalid Credentials. Error: 'access_denied', Description: Unauthorized")

    @unittest.skip("Can't get the right assertRaises")
    def test_invalid_auth_domain(self):
        api = API(*self.invalid_auth_domain)
        with self.assertRaises(TimeoutError) as e:
            api.authenticate()
        self.assertEqual(e, "API Takes too long to respond.")
        # self.assertRaises(TimeoutError, api2.authenticate())

    @property
    def good_credentials(self) -> Tuple[str, ClientCredentials]:
        auth0_domain = "my.auth.domain"
        credentials = ClientCredentials(
            client_id=config.read_param('api.value.client-id'),
            # client_id='aw;oieja;wlejf',
            client_secret=config.read_param('api.value.client-secret'),
            audience=API_HOST,
        )

        return auth0_domain, credentials

    @property
    def invalid_auth_domain(self) -> Tuple[str, ClientCredentials]:
        auth0_domain = "my.auth.domain"
        credentials = ClientCredentials(
            client_id=config.read_param('api.value.client-id'),
            client_secret=config.read_param('api.value.client-secret'),
            audience=API_HOST,
        )

        return auth0_domain, credentials

    @property
    def invalid_credentials(self) -> Tuple[str, ClientCredentials]:
        auth0_domain = "my.auth.domain"
        credentials = ClientCredentials(
            # client_id=config.read_param('api.value.client-id'),
            client_id='aw;oieja;wlejf',
            client_secret=config.read_param('api.value.client-secret'),
            audience=API_HOST,
        )

        return auth0_domain, credentials


if __name__ == '__main__':
    unittest.main()

正如您在我的测试中所看到的,我为assertRaises尝试了两种方法:

# Trial 1
with self.assertRaises(AuthenticationError) as e:
    api.authenticate()
self.assertEqual(error_msg, "Invalid Credentials. Error: 'access_denied', Description: Unauthorized")

# Trial 2
self.assertRaises(AuthenticationError, api.authenticate)

即使引发了正确的异常,assertRaises也会失败。 以下是我在运行unittest后从terminal获得的日志:

Ran 3 tests in 0.842s

FAILED (errors=1, skipped=1)

Error
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
    yield
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 615, in run
    testMethod()
  File "/Users/pluggle/Documents/Gitlab/Laveness/onboarding/test/api_authentication_test.py", line 22, in test_invalid_credentials
    api = API(*self.invalid_credentials)
  File "/Users/pluggle/Documents/Gitlab/Laveness/onboarding/Foo/api.py", line 17, in __init__
    self.token: Token = self.authenticate()
  File "/Users/pluggle/Documents/Gitlab/Laveness/onboarding/Foo/api.py", line 66, in authenticate
    f"Invalid Credentials. Error: '{json_dict['error']}', Description: {json_dict['error_description']}"
Foo.AuthenticationError: Invalid Credentials. Error: 'access_denied', Description: Unauthorized


Assertion failed

Assertion failed

Process finished with exit code 1

Assertion failed

Assertion failed

从这个问题:AssertRaises fails even though exception is raised

他似乎和我有同样的问题,但他是从两个不同的路径导入错误的,其中对我来说,我非常确定我的异常是从我的Foo包上的\uuuu init\uuuuuuuuuuy.py导入的

真希望有人能帮我。我真的觉得这里有些东西我忽略了

非常感谢


Tags: importselfclientapijsonreturndomaindef
1条回答
网友
1楼 · 发布于 2024-10-04 07:26:16

如果您读取stacktrace,它将在此行上上升:

        api = API(*self.invalid_credentials)

它是assertRaises前面的一行,因此不会被它捕获

这是因为API.__init__本身调用self.authenticate()

相关问题 更多 >