コード例 #1
0
 def test_chat_migrated(self):
     with pytest.raises(ChatMigrated, match="Group migrated to supergroup. New chat id: 1234"):
         raise ChatMigrated(1234)
     try:
         raise ChatMigrated(1234)
     except ChatMigrated as e:
         assert e.new_chat_id == 1234
コード例 #2
0
ファイル: request.py プロジェクト: arcturus5340/johnny
    def _parse(json_data):
        """Try and parse the JSON returned from Telegram.

        Returns:
            dict: A JSON parsed as Python dict with results - on error this dict will be empty.

        """

        decoded_s = json_data.decode('utf-8', 'replace')
        try:
            data = json.loads(decoded_s)
        except ValueError:
            raise TelegramError('Invalid server response')

        if not data.get('ok'):  # pragma: no cover
            description = data.get('description')
            parameters = data.get('parameters')
            if parameters:
                migrate_to_chat_id = parameters.get('migrate_to_chat_id')
                if migrate_to_chat_id:
                    raise ChatMigrated(migrate_to_chat_id)
                retry_after = parameters.get('retry_after')
                if retry_after:
                    raise RetryAfter(retry_after)
            if description:
                return description

        return data['result']
コード例 #3
0
    def _parse(json_data):
        """Try and parse the JSON returned from Telegram.

        Returns:
            dict: A JSON parsed as Python dict with results - on error this dict will be empty.

        """

        try:
            decoded_s = json_data.decode('utf-8')
            data = json.loads(decoded_s)
        except UnicodeDecodeError:
            logging.getLogger(__name__).debug(
                'Logging raw invalid UTF-8 response:\n%r', json_data)
            raise TelegramError(
                'Server response could not be decoded using UTF-8')
        except ValueError:
            raise TelegramError('Invalid server response')

        if not data.get('ok'):  # pragma: no cover
            description = data.get('description')
            parameters = data.get('parameters')
            if parameters:
                migrate_to_chat_id = parameters.get('migrate_to_chat_id')
                if migrate_to_chat_id:
                    raise ChatMigrated(migrate_to_chat_id)
                retry_after = parameters.get('retry_after')
                if retry_after:
                    raise RetryAfter(retry_after)
            if description:
                return description

        return data['result']
コード例 #4
0
class TestErrors:
    def test_telegram_error(self):
        with pytest.raises(TelegramError, match="^test message$"):
            raise TelegramError("test message")
        with pytest.raises(TelegramError, match="^Test message$"):
            raise TelegramError("Error: test message")
        with pytest.raises(TelegramError, match="^Test message$"):
            raise TelegramError("[Error]: test message")
        with pytest.raises(TelegramError, match="^Test message$"):
            raise TelegramError("Bad Request: test message")

    def test_unauthorized(self):
        with pytest.raises(Unauthorized, match="test message"):
            raise Unauthorized("test message")
        with pytest.raises(Unauthorized, match="^Test message$"):
            raise Unauthorized("Error: test message")
        with pytest.raises(Unauthorized, match="^Test message$"):
            raise Unauthorized("[Error]: test message")
        with pytest.raises(Unauthorized, match="^Test message$"):
            raise Unauthorized("Bad Request: test message")

    def test_invalid_token(self):
        with pytest.raises(InvalidToken, match="Invalid token"):
            raise InvalidToken

    def test_network_error(self):
        with pytest.raises(NetworkError, match="test message"):
            raise NetworkError("test message")
        with pytest.raises(NetworkError, match="^Test message$"):
            raise NetworkError("Error: test message")
        with pytest.raises(NetworkError, match="^Test message$"):
            raise NetworkError("[Error]: test message")
        with pytest.raises(NetworkError, match="^Test message$"):
            raise NetworkError("Bad Request: test message")

    def test_bad_request(self):
        with pytest.raises(BadRequest, match="test message"):
            raise BadRequest("test message")
        with pytest.raises(BadRequest, match="^Test message$"):
            raise BadRequest("Error: test message")
        with pytest.raises(BadRequest, match="^Test message$"):
            raise BadRequest("[Error]: test message")
        with pytest.raises(BadRequest, match="^Test message$"):
            raise BadRequest("Bad Request: test message")

    def test_timed_out(self):
        with pytest.raises(TimedOut, match="^Timed out$"):
            raise TimedOut

    def test_chat_migrated(self):
        with pytest.raises(ChatMigrated, match="Group migrated to supergroup. New chat id: 1234"):
            raise ChatMigrated(1234)
        try:
            raise ChatMigrated(1234)
        except ChatMigrated as e:
            assert e.new_chat_id == 1234

    def test_retry_after(self):
        with pytest.raises(RetryAfter, match="Flood control exceeded. Retry in 12.0 seconds"):
            raise RetryAfter(12)

    def test_conflict(self):
        with pytest.raises(Conflict, match='Something something.'):
            raise Conflict('Something something.')

    @pytest.mark.parametrize(
        "exception, attributes",
        [
            (TelegramError("test message"), ["message"]),
            (Unauthorized("test message"), ["message"]),
            (InvalidToken(), ["message"]),
            (NetworkError("test message"), ["message"]),
            (BadRequest("test message"), ["message"]),
            (TimedOut(), ["message"]),
            (ChatMigrated(1234), ["message", "new_chat_id"]),
            (RetryAfter(12), ["message", "retry_after"]),
            (Conflict("test message"), ["message"]),
            (TelegramDecryptionError("test message"), ["message"])
        ],
    )
    def test_errors_pickling(self, exception, attributes):
        print(exception)
        pickled = pickle.dumps(exception)
        unpickled = pickle.loads(pickled)
        assert type(unpickled) is type(exception)
        assert str(unpickled) == str(exception)

        for attribute in attributes:
            assert getattr(unpickled, attribute) == getattr(exception, attribute)

    def test_pickling_test_coverage(self):
        """
        This test is only here to make sure that new errors will override __reduce__ properly.
        Add the new error class to the below covered_subclasses dict, if it's covered in the above
        test_errors_pickling test.
        """
        def make_assertion(cls):
            assert {sc for sc in cls.__subclasses__()} == covered_subclasses[cls]
            for subcls in cls.__subclasses__():
                make_assertion(subcls)

        covered_subclasses = defaultdict(set)
        covered_subclasses.update({
            TelegramError: {Unauthorized, InvalidToken, NetworkError, ChatMigrated, RetryAfter,
                            Conflict, TelegramDecryptionError},
            NetworkError: {BadRequest, TimedOut}
        })

        make_assertion(TelegramError)
コード例 #5
0
class TestErrors:
    def test_telegram_error(self):
        with pytest.raises(TelegramError, match="^test message$"):
            raise TelegramError("test message")
        with pytest.raises(TelegramError, match="^Test message$"):
            raise TelegramError("Error: test message")
        with pytest.raises(TelegramError, match="^Test message$"):
            raise TelegramError("[Error]: test message")
        with pytest.raises(TelegramError, match="^Test message$"):
            raise TelegramError("Bad Request: test message")

    def test_unauthorized(self):
        with pytest.raises(Forbidden, match="test message"):
            raise Forbidden("test message")
        with pytest.raises(Forbidden, match="^Test message$"):
            raise Forbidden("Error: test message")
        with pytest.raises(Forbidden, match="^Test message$"):
            raise Forbidden("[Error]: test message")
        with pytest.raises(Forbidden, match="^Test message$"):
            raise Forbidden("Bad Request: test message")

    def test_invalid_token(self):
        with pytest.raises(InvalidToken, match="Invalid token"):
            raise InvalidToken

    def test_network_error(self):
        with pytest.raises(NetworkError, match="test message"):
            raise NetworkError("test message")
        with pytest.raises(NetworkError, match="^Test message$"):
            raise NetworkError("Error: test message")
        with pytest.raises(NetworkError, match="^Test message$"):
            raise NetworkError("[Error]: test message")
        with pytest.raises(NetworkError, match="^Test message$"):
            raise NetworkError("Bad Request: test message")

    def test_bad_request(self):
        with pytest.raises(BadRequest, match="test message"):
            raise BadRequest("test message")
        with pytest.raises(BadRequest, match="^Test message$"):
            raise BadRequest("Error: test message")
        with pytest.raises(BadRequest, match="^Test message$"):
            raise BadRequest("[Error]: test message")
        with pytest.raises(BadRequest, match="^Test message$"):
            raise BadRequest("Bad Request: test message")

    def test_timed_out(self):
        with pytest.raises(TimedOut, match="^Timed out$"):
            raise TimedOut

    def test_chat_migrated(self):
        with pytest.raises(
                ChatMigrated,
                match="Group migrated to supergroup. New chat id: 1234"):
            raise ChatMigrated(1234)
        try:
            raise ChatMigrated(1234)
        except ChatMigrated as e:
            assert e.new_chat_id == 1234

    def test_retry_after(self):
        with pytest.raises(
                RetryAfter,
                match="Flood control exceeded. Retry in 12 seconds"):
            raise RetryAfter(12)

    def test_conflict(self):
        with pytest.raises(Conflict, match="Something something."):
            raise Conflict("Something something.")

    @pytest.mark.parametrize(
        "exception, attributes",
        [
            (TelegramError("test message"), ["message"]),
            (Forbidden("test message"), ["message"]),
            (InvalidToken(), ["message"]),
            (NetworkError("test message"), ["message"]),
            (BadRequest("test message"), ["message"]),
            (TimedOut(), ["message"]),
            (ChatMigrated(1234), ["message", "new_chat_id"]),
            (RetryAfter(12), ["message", "retry_after"]),
            (Conflict("test message"), ["message"]),
            (PassportDecryptionError("test message"), ["message"]),
            (InvalidCallbackData("test data"), ["callback_data"]),
        ],
    )
    def test_errors_pickling(self, exception, attributes):
        pickled = pickle.dumps(exception)
        unpickled = pickle.loads(pickled)
        assert type(unpickled) is type(exception)
        assert str(unpickled) == str(exception)

        for attribute in attributes:
            assert getattr(unpickled,
                           attribute) == getattr(exception, attribute)

    @pytest.mark.parametrize(
        "inst",
        [
            (TelegramError("test message")),
            (Forbidden("test message")),
            (InvalidToken()),
            (NetworkError("test message")),
            (BadRequest("test message")),
            (TimedOut()),
            (ChatMigrated(1234)),
            (RetryAfter(12)),
            (Conflict("test message")),
            (PassportDecryptionError("test message")),
            (InvalidCallbackData("test data")),
        ],
    )
    def test_slot_behaviour(self, inst, mro_slots):
        for attr in inst.__slots__:
            assert getattr(inst, attr,
                           "err") != "err", f"got extra slot '{attr}'"
        assert len(mro_slots(inst)) == len(set(
            mro_slots(inst))), "duplicate slot"

    def test_coverage(self):
        """
        This test is only here to make sure that new errors will override __reduce__ and set
        __slots__ properly.
        Add the new error class to the below covered_subclasses dict, if it's covered in the above
        test_errors_pickling and test_slots_behavior tests.
        """
        def make_assertion(cls):
            assert set(cls.__subclasses__()) == covered_subclasses[cls]
            for subcls in cls.__subclasses__():
                make_assertion(subcls)

        covered_subclasses = defaultdict(set)
        covered_subclasses.update({
            TelegramError: {
                Forbidden,
                InvalidToken,
                NetworkError,
                ChatMigrated,
                RetryAfter,
                Conflict,
                PassportDecryptionError,
                InvalidCallbackData,
            },
            NetworkError: {BadRequest, TimedOut},
        })

        make_assertion(TelegramError)

    def test_string_representations(self):
        """We just randomly test a few of the subclasses - should suffice"""
        e = TelegramError("This is a message")
        assert repr(e) == "TelegramError('This is a message')"
        assert str(e) == "This is a message"

        e = RetryAfter(42)
        assert repr(
            e) == "RetryAfter('Flood control exceeded. Retry in 42 seconds')"
        assert str(e) == "Flood control exceeded. Retry in 42 seconds"

        e = BadRequest("This is a message")
        assert repr(e) == "BadRequest('This is a message')"
        assert str(e) == "This is a message"
コード例 #6
0
    async def _request_wrapper(
        self,
        url: str,
        method: str,
        request_data: RequestData = None,
        read_timeout: ODVInput[float] = DEFAULT_NONE,
        write_timeout: ODVInput[float] = DEFAULT_NONE,
        connect_timeout: ODVInput[float] = DEFAULT_NONE,
        pool_timeout: ODVInput[float] = DEFAULT_NONE,
    ) -> bytes:
        """Wraps the real implementation request method.

        Performs the following tasks:
        * Handle the various HTTP response codes.
        * Parse the Telegram server response.

        Args:
            url (:obj:`str`): The URL to request.
            method (:obj:`str`): HTTP method (i.e. 'POST', 'GET', etc.).
            request_data (:class:`telegram.request.RequestData`, optional): An object containing
                information about parameters and files to upload for the request.
            read_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
                amount of time (in seconds) to wait for a response from Telegram's server instead
                of the time specified during creating of this object. Defaults to
                :attr:`DEFAULT_NONE`.
            write_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
                amount of time (in seconds) to wait for a write operation to complete (in terms of
                a network socket; i.e. POSTing a request or uploading a file) instead of the time
                specified during creating of this object. Defaults to :attr:`DEFAULT_NONE`.
            connect_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the
                maximum amount of time (in seconds) to wait for a connection attempt to a server
                to succeed instead of the time specified during creating of this object. Defaults
                to :attr:`DEFAULT_NONE`.
            pool_timeout (:obj:`float` | :obj:`None`, optional): If passed, specifies the maximum
                amount of time (in seconds) to wait for a connection to become available instead
                of the time specified during creating of this object. Defaults to
                :attr:`DEFAULT_NONE`.

        Returns:
            bytes: The payload part of the HTTP server response.

        Raises:
            TelegramError

        """
        # TGs response also has the fields 'ok' and 'error_code'.
        # However, we rather rely on the HTTP status code for now.

        try:
            code, payload = await self.do_request(
                url=url,
                method=method,
                request_data=request_data,
                read_timeout=read_timeout,
                write_timeout=write_timeout,
                connect_timeout=connect_timeout,
                pool_timeout=pool_timeout,
            )
        except asyncio.CancelledError as exc:
            # TODO: in py3.8+, CancelledError is a subclass of BaseException, so we can drop this
            #  clause when we drop py3.7
            raise exc
        except TelegramError as exc:
            raise exc
        except Exception as exc:
            raise NetworkError(
                f"Unknown error in HTTP implementation: {repr(exc)}") from exc

        if HTTPStatus.OK <= code <= 299:
            # 200-299 range are HTTP success statuses
            return payload

        response_data = self.parse_json_payload(payload)

        description = response_data.get("description")
        if description:
            message = description
        else:
            message = "Unknown HTTPError"

        # In some special cases, we can raise more informative exceptions:
        # see https://core.telegram.org/bots/api#responseparameters and
        # https://core.telegram.org/bots/api#making-requests
        parameters = response_data.get("parameters")
        if parameters:
            migrate_to_chat_id = parameters.get("migrate_to_chat_id")
            if migrate_to_chat_id:
                raise ChatMigrated(migrate_to_chat_id)
            retry_after = parameters.get("retry_after")
            if retry_after:
                raise RetryAfter(retry_after)

            message += f"\nThe server response contained unknown parameters: {parameters}"

        if code == HTTPStatus.FORBIDDEN:
            raise Forbidden(message)
        if code in (HTTPStatus.NOT_FOUND, HTTPStatus.UNAUTHORIZED):
            # TG returns 404 Not found for
            #   1) malformed tokens
            #   2) correct tokens but non-existing method, e.g. api.tg.org/botTOKEN/unkonwnMethod
            # We can basically rule out 2) since we don't let users make requests manually
            # TG returns 401 Unauthorized for correctly formatted tokens that are not valid
            raise InvalidToken(message)
        if code == HTTPStatus.BAD_REQUEST:
            raise BadRequest(message)
        if code == HTTPStatus.CONFLICT:
            raise Conflict(message)
        if code == HTTPStatus.BAD_GATEWAY:
            raise NetworkError(description or "Bad Gateway")
        raise NetworkError(f"{message} ({code})")