Marco Ricci commited on 2024-07-22 13:37:03
              Zeige 10 geänderte Dateien mit 141 Einfügungen und 73 Löschungen.
            
| ... | ... | 
                      @@ -11,6 +11,8 @@ import collections  | 
                  
| 11 | 11 | 
                        import hashlib  | 
                    
| 12 | 12 | 
                        import math  | 
                    
| 13 | 13 | 
                        import unicodedata  | 
                    
| 14 | 
                        +from collections.abc import Callable  | 
                    |
| 15 | 
                        +from typing import TypeAlias  | 
                    |
| 14 | 16 | 
                         | 
                    
| 15 | 17 | 
                        from typing_extensions import assert_type  | 
                    
| 16 | 18 | 
                         | 
                    
| ... | ... | 
                      @@ -23,7 +25,8 @@ __version__ = '0.1.2'  | 
                  
| 23 | 25 | 
                         | 
                    
| 24 | 26 | 
                        class AmbiguousByteRepresentationError(ValueError):  | 
                    
| 25 | 27 | 
                        """The object has an ambiguous byte representation."""  | 
                    
| 26 | 
                        - def __init__(self):  | 
                    |
| 28 | 
                        +  | 
                    |
| 29 | 
                        + def __init__(self) -> None:  | 
                    |
| 27 | 30 | 
                                 super().__init__('text string has ambiguous byte representation')
                       | 
                    
| 28 | 31 | 
                         | 
                    
| 29 | 32 | 
                         | 
                    
| ... | ... | 
                      @@ -425,6 +428,8 @@ class Vault:  | 
                  
| 425 | 428 | 
                        a passphrase deterministically.  | 
                    
| 426 | 429 | 
                         | 
                    
| 427 | 430 | 
                        """  | 
                    
| 431 | 
                        + TestFunc: TypeAlias = Callable[[bytes | bytearray], bool]  | 
                    |
| 432 | 
                        + deterministic_signature_types: dict[str, TestFunc]  | 
                    |
| 428 | 433 | 
                                 deterministic_signature_types = {
                       | 
                    
| 429 | 434 | 
                        'ssh-ed25519': lambda k: k.startswith(  | 
                    
| 430 | 435 | 
                        b'\x00\x00\x00\x0bssh-ed25519'  | 
                    
| ... | ... | 
                      @@ -28,6 +28,7 @@ from typing_extensions import (  | 
                  
| 28 | 28 | 
                         | 
                    
| 29 | 29 | 
                        import derivepassphrase as dpp  | 
                    
| 30 | 30 | 
                        import ssh_agent_client  | 
                    
| 31 | 
                        +import ssh_agent_client.types  | 
                    |
| 31 | 32 | 
                        from derivepassphrase import types as dpp_types  | 
                    
| 32 | 33 | 
                         | 
                    
| 33 | 34 | 
                        if TYPE_CHECKING:  | 
                    
| ... | ... | 
                      @@ -159,7 +160,7 @@ def _get_suitable_ssh_keys(  | 
                  
| 159 | 160 | 
                         | 
                    
| 160 | 161 | 
                        """  | 
                    
| 161 | 162 | 
                        client: ssh_agent_client.SSHAgentClient  | 
                    
| 162 | 
                        - client_context: contextlib.AbstractContextManager  | 
                    |
| 163 | 
                        + client_context: contextlib.AbstractContextManager[Any]  | 
                    |
| 163 | 164 | 
                        match conn:  | 
                    
| 164 | 165 | 
                        case ssh_agent_client.SSHAgentClient():  | 
                    
| 165 | 166 | 
                        client = conn  | 
                    
| ... | ... | 
                      @@ -324,8 +325,15 @@ def _prompt_for_passphrase() -> str:  | 
                  
| 324 | 325 | 
                        The user input.  | 
                    
| 325 | 326 | 
                         | 
                    
| 326 | 327 | 
                        """  | 
                    
| 327 | 
                        - return click.prompt(  | 
                    |
| 328 | 
                        - 'Passphrase', default='', hide_input=True, show_default=False, err=True  | 
                    |
| 328 | 
                        + return cast(  | 
                    |
| 329 | 
                        + str,  | 
                    |
| 330 | 
                        + click.prompt(  | 
                    |
| 331 | 
                        + 'Passphrase',  | 
                    |
| 332 | 
                        + default='',  | 
                    |
| 333 | 
                        + hide_input=True,  | 
                    |
| 334 | 
                        + show_default=False,  | 
                    |
| 335 | 
                        + err=True,  | 
                    |
| 336 | 
                        + ),  | 
                    |
| 329 | 337 | 
                        )  | 
                    
| 330 | 338 | 
                         | 
                    
| 331 | 339 | 
                         | 
                    
| ... | ... | 
                      @@ -349,8 +357,8 @@ class OptionGroupOption(click.Option):  | 
                  
| 349 | 357 | 
                        option_group_name: str = ''  | 
                    
| 350 | 358 | 
                        epilog: str = ''  | 
                    
| 351 | 359 | 
                         | 
                    
| 352 | 
                        - def __init__(self, *args, **kwargs): # type: ignore  | 
                    |
| 353 | 
                        - if self.__class__ == __class__:  | 
                    |
| 360 | 
                        + def __init__(self, *args: Any, **kwargs: Any) -> None:  | 
                    |
| 361 | 
                        + if self.__class__ == __class__: # type: ignore[name-defined]  | 
                    |
| 354 | 362 | 
                        raise NotImplementedError  | 
                    
| 355 | 363 | 
                        super().__init__(*args, **kwargs)  | 
                    
| 356 | 364 | 
                         | 
                    
| ... | ... | 
                      @@ -809,7 +817,7 @@ def derivepassphrase(  | 
                  
| 809 | 817 | 
                        for name in param.opts + param.secondary_opts:  | 
                    
| 810 | 818 | 
                        params_by_str[name] = param  | 
                    
| 811 | 819 | 
                         | 
                    
| 812 | 
                        - def is_param_set(param: click.Parameter):  | 
                    |
| 820 | 
                        + def is_param_set(param: click.Parameter) -> bool:  | 
                    |
| 813 | 821 | 
                        return bool(ctx.params.get(param.human_readable_name))  | 
                    
| 814 | 822 | 
                         | 
                    
| 815 | 823 | 
                        def check_incompatible_options(  | 
                    
| ... | ... | 
                      @@ -89,7 +89,7 @@ class Sequin:  | 
                  
| 89 | 89 | 
                        """  | 
                    
| 90 | 90 | 
                        msg = 'sequence item out of range'  | 
                    
| 91 | 91 | 
                         | 
                    
| 92 | 
                        - def uint8_to_bits(value):  | 
                    |
| 92 | 
                        + def uint8_to_bits(value: int) -> Iterator[int]:  | 
                    |
| 93 | 93 | 
                        """Yield individual bits of an 8-bit number, MSB first."""  | 
                    
| 94 | 94 | 
                        for i in (0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01):  | 
                    
| 95 | 95 | 
                        yield 1 if value | i == value else 0  | 
                    
| ... | ... | 
                      @@ -388,5 +388,5 @@ class SequinExhaustedError(Exception):  | 
                  
| 388 | 388 | 
                         | 
                    
| 389 | 389 | 
                        """  | 
                    
| 390 | 390 | 
                         | 
                    
| 391 | 
                        - def __init__(self):  | 
                    |
| 391 | 
                        + def __init__(self) -> None:  | 
                    |
| 392 | 392 | 
                                 super().__init__('Sequin is exhausted')
                       | 
                    
| ... | ... | 
                      @@ -34,7 +34,7 @@ _socket = socket  | 
                  
| 34 | 34 | 
                        class TrailingDataError(RuntimeError):  | 
                    
| 35 | 35 | 
                        """The result contained trailing data."""  | 
                    
| 36 | 36 | 
                         | 
                    
| 37 | 
                        - def __init__(self):  | 
                    |
| 37 | 
                        + def __init__(self) -> None:  | 
                    |
| 38 | 38 | 
                                 super().__init__('Overlong response from SSH agent')
                       | 
                    
| 39 | 39 | 
                         | 
                    
| 40 | 40 | 
                         | 
                    
| ... | ... | 
                      @@ -274,7 +274,7 @@ class SSHAgentClient:  | 
                  
| 274 | 274 | 
                        raise EOFError(msg)  | 
                    
| 275 | 275 | 
                        return response[0], response[1:]  | 
                    
| 276 | 276 | 
                         | 
                    
| 277 | 
                        - def list_keys(self) -> Sequence[ssh_types.KeyCommentPair]:  | 
                    |
| 277 | 
                        + def list_keys(self) -> Sequence[types.KeyCommentPair]:  | 
                    |
| 278 | 278 | 
                        """Request a list of keys known to the SSH agent.  | 
                    
| 279 | 279 | 
                         | 
                    
| 280 | 280 | 
                        Returns:  | 
                    
| ... | ... | 
                      @@ -290,9 +290,9 @@ class SSHAgentClient:  | 
                  
| 290 | 290 | 
                         | 
                    
| 291 | 291 | 
                        """  | 
                    
| 292 | 292 | 
                        response_code, response = self.request(  | 
                    
| 293 | 
                        - ssh_types.SSH_AGENTC.REQUEST_IDENTITIES.value, b''  | 
                    |
| 293 | 
                        + types.SSH_AGENTC.REQUEST_IDENTITIES.value, b''  | 
                    |
| 294 | 294 | 
                        )  | 
                    
| 295 | 
                        - if response_code != ssh_types.SSH_AGENT.IDENTITIES_ANSWER.value:  | 
                    |
| 295 | 
                        + if response_code != types.SSH_AGENT.IDENTITIES_ANSWER.value:  | 
                    |
| 296 | 296 | 
                        msg = (  | 
                    
| 297 | 297 | 
                        f'error return from SSH agent: '  | 
                    
| 298 | 298 | 
                                         f'{response_code = }, {response = }'
                       | 
                    
| ... | ... | 
                      @@ -313,7 +313,7 @@ class SSHAgentClient:  | 
                  
| 313 | 313 | 
                        return bytes(buf)  | 
                    
| 314 | 314 | 
                         | 
                    
| 315 | 315 | 
                        key_count = int.from_bytes(shift(4), 'big')  | 
                    
| 316 | 
                        - keys: collections.deque[ssh_types.KeyCommentPair]  | 
                    |
| 316 | 
                        + keys: collections.deque[types.KeyCommentPair]  | 
                    |
| 317 | 317 | 
                        keys = collections.deque()  | 
                    
| 318 | 318 | 
                        for _ in range(key_count):  | 
                    
| 319 | 319 | 
                        key_size = int.from_bytes(shift(4), 'big')  | 
                    
| ... | ... | 
                      @@ -321,7 +321,7 @@ class SSHAgentClient:  | 
                  
| 321 | 321 | 
                        comment_size = int.from_bytes(shift(4), 'big')  | 
                    
| 322 | 322 | 
                        comment = shift(comment_size)  | 
                    
| 323 | 323 | 
                        # Both `key` and `comment` are not wrapped as SSH strings.  | 
                    
| 324 | 
                        - keys.append(ssh_types.KeyCommentPair(key, comment))  | 
                    |
| 324 | 
                        + keys.append(types.KeyCommentPair(key, comment))  | 
                    |
| 325 | 325 | 
                        if response_stream:  | 
                    
| 326 | 326 | 
                        raise TrailingDataError  | 
                    
| 327 | 327 | 
                        return keys  | 
                    
| ... | ... | 
                      @@ -379,9 +379,9 @@ class SSHAgentClient:  | 
                  
| 379 | 379 | 
                        request_data.extend(self.string(payload))  | 
                    
| 380 | 380 | 
                        request_data.extend(self.uint32(flags))  | 
                    
| 381 | 381 | 
                        response_code, response = self.request(  | 
                    
| 382 | 
                        - ssh_types.SSH_AGENTC.SIGN_REQUEST.value, request_data  | 
                    |
| 382 | 
                        + types.SSH_AGENTC.SIGN_REQUEST.value, request_data  | 
                    |
| 383 | 383 | 
                        )  | 
                    
| 384 | 
                        - if response_code != ssh_types.SSH_AGENT.SIGN_RESPONSE.value:  | 
                    |
| 384 | 
                        + if response_code != types.SSH_AGENT.SIGN_RESPONSE.value:  | 
                    |
| 385 | 385 | 
                                     msg = f'signing data failed: {response_code = }, {response = }'
                       | 
                    
| 386 | 386 | 
                        raise RuntimeError(msg)  | 
                    
| 387 | 387 | 
                        return self.unstring(response)  | 
                    
| ... | ... | 
                      @@ -402,7 +402,7 @@ def isolated_config(  | 
                  
| 402 | 402 | 
                        monkeypatch: Any,  | 
                    
| 403 | 403 | 
                        runner: click.testing.CliRunner,  | 
                    
| 404 | 404 | 
                        config: Any,  | 
                    
| 405 | 
                        -):  | 
                    |
| 405 | 
                        +) -> Iterator[None]:  | 
                    |
| 406 | 406 | 
                        prog_name = derivepassphrase.cli.PROG_NAME  | 
                    
| 407 | 407 | 
                             env_name = prog_name.replace(' ', '_').upper() + '_PATH'
                       | 
                    
| 408 | 408 | 
                        with runner.isolated_filesystem():  | 
                    
| ... | ... | 
                      @@ -28,31 +28,33 @@ class TestVault:  | 
                  
| 28 | 28 | 
                                     ('twitter', twitter_phrase),
                       | 
                    
| 29 | 29 | 
                        ],  | 
                    
| 30 | 30 | 
                        )  | 
                    
| 31 | 
                        - def test_200_basic_configuration(self, service, expected):  | 
                    |
| 31 | 
                        + def test_200_basic_configuration(  | 
                    |
| 32 | 
                        + self, service: bytes | str, expected: bytes  | 
                    |
| 33 | 
                        + ) -> None:  | 
                    |
| 32 | 34 | 
                        assert Vault(phrase=self.phrase).generate(service) == expected  | 
                    
| 33 | 35 | 
                         | 
                    
| 34 | 
                        - def test_201_phrase_dependence(self):  | 
                    |
| 36 | 
                        + def test_201_phrase_dependence(self) -> None:  | 
                    |
| 35 | 37 | 
                        assert (  | 
                    
| 36 | 38 | 
                                     Vault(phrase=(self.phrase + b'X')).generate('google')
                       | 
                    
| 37 | 39 | 
                        == b'n+oIz6sL>K*lTEWYRO%7'  | 
                    
| 38 | 40 | 
                        )  | 
                    
| 39 | 41 | 
                         | 
                    
| 40 | 
                        - def test_202_reproducibility_and_bytes_service_name(self):  | 
                    |
| 42 | 
                        + def test_202_reproducibility_and_bytes_service_name(self) -> None:  | 
                    |
| 41 | 43 | 
                        assert Vault(phrase=self.phrase).generate(b'google') == Vault(  | 
                    
| 42 | 44 | 
                        phrase=self.phrase  | 
                    
| 43 | 45 | 
                                 ).generate('google')
                       | 
                    
| 44 | 46 | 
                         | 
                    
| 45 | 
                        - def test_203_reproducibility_and_bytearray_service_name(self):  | 
                    |
| 47 | 
                        + def test_203_reproducibility_and_bytearray_service_name(self) -> None:  | 
                    |
| 46 | 48 | 
                        assert Vault(phrase=self.phrase).generate(b'google') == Vault(  | 
                    
| 47 | 49 | 
                        phrase=self.phrase  | 
                    
| 48 | 50 | 
                        ).generate(bytearray(b'google'))  | 
                    
| 49 | 51 | 
                         | 
                    
| 50 | 
                        - def test_210_nonstandard_length(self):  | 
                    |
| 52 | 
                        + def test_210_nonstandard_length(self) -> None:  | 
                    |
| 51 | 53 | 
                        assert (  | 
                    
| 52 | 54 | 
                                     Vault(phrase=self.phrase, length=4).generate('google') == b'xDFu'
                       | 
                    
| 53 | 55 | 
                        )  | 
                    
| 54 | 56 | 
                         | 
                    
| 55 | 
                        - def test_211_repetition_limit(self):  | 
                    |
| 57 | 
                        + def test_211_repetition_limit(self) -> None:  | 
                    |
| 56 | 58 | 
                        assert (  | 
                    
| 57 | 59 | 
                        Vault(  | 
                    
| 58 | 60 | 
                        phrase=b'', length=24, symbol=0, number=0, repeat=1  | 
                    
| ... | ... | 
                      @@ -60,37 +62,37 @@ class TestVault:  | 
                  
| 60 | 62 | 
                        == b'IVTDzACftqopUXqDHPkuCIhV'  | 
                    
| 61 | 63 | 
                        )  | 
                    
| 62 | 64 | 
                         | 
                    
| 63 | 
                        - def test_212_without_symbols(self):  | 
                    |
| 65 | 
                        + def test_212_without_symbols(self) -> None:  | 
                    |
| 64 | 66 | 
                        assert (  | 
                    
| 65 | 67 | 
                                     Vault(phrase=self.phrase, symbol=0).generate('google')
                       | 
                    
| 66 | 68 | 
                        == b'XZ4wRe0bZCazbljCaMqR'  | 
                    
| 67 | 69 | 
                        )  | 
                    
| 68 | 70 | 
                         | 
                    
| 69 | 
                        - def test_213_no_numbers(self):  | 
                    |
| 71 | 
                        + def test_213_no_numbers(self) -> None:  | 
                    |
| 70 | 72 | 
                        assert (  | 
                    
| 71 | 73 | 
                                     Vault(phrase=self.phrase, number=0).generate('google')
                       | 
                    
| 72 | 74 | 
                        == b'_*$TVH.%^aZl(LUeOT?>'  | 
                    
| 73 | 75 | 
                        )  | 
                    
| 74 | 76 | 
                         | 
                    
| 75 | 
                        - def test_214_no_lowercase_letters(self):  | 
                    |
| 77 | 
                        + def test_214_no_lowercase_letters(self) -> None:  | 
                    |
| 76 | 78 | 
                        assert (  | 
                    
| 77 | 79 | 
                                     Vault(phrase=self.phrase, lower=0).generate('google')
                       | 
                    
| 78 | 80 | 
                                     == b':{?)+7~@OA:L]!0E$)(+'
                       | 
                    
| 79 | 81 | 
                        )  | 
                    
| 80 | 82 | 
                         | 
                    
| 81 | 
                        - def test_215_at_least_5_digits(self):  | 
                    |
| 83 | 
                        + def test_215_at_least_5_digits(self) -> None:  | 
                    |
| 82 | 84 | 
                        assert (  | 
                    
| 83 | 85 | 
                                     Vault(phrase=self.phrase, length=8, number=5).generate('songkick')
                       | 
                    
| 84 | 86 | 
                        == b'i0908.7['  | 
                    
| 85 | 87 | 
                        )  | 
                    
| 86 | 88 | 
                         | 
                    
| 87 | 
                        - def test_216_lots_of_spaces(self):  | 
                    |
| 89 | 
                        + def test_216_lots_of_spaces(self) -> None:  | 
                    |
| 88 | 90 | 
                        assert (  | 
                    
| 89 | 91 | 
                                     Vault(phrase=self.phrase, space=12).generate('songkick')
                       | 
                    
| 90 | 92 | 
                        == b' c 6 Bq % 5fR '  | 
                    
| 91 | 93 | 
                        )  | 
                    
| 92 | 94 | 
                         | 
                    
| 93 | 
                        - def test_217_all_character_classes(self):  | 
                    |
| 95 | 
                        + def test_217_all_character_classes(self) -> None:  | 
                    |
| 94 | 96 | 
                        assert (  | 
                    
| 95 | 97 | 
                        Vault(  | 
                    
| 96 | 98 | 
                        phrase=self.phrase,  | 
                    
| ... | ... | 
                      @@ -104,7 +106,7 @@ class TestVault:  | 
                  
| 104 | 106 | 
                        == b': : fv_wqt>a-4w1S R'  | 
                    
| 105 | 107 | 
                        )  | 
                    
| 106 | 108 | 
                         | 
                    
| 107 | 
                        - def test_218_only_numbers_and_very_high_repetition_limit(self):  | 
                    |
| 109 | 
                        + def test_218_only_numbers_and_very_high_repetition_limit(self) -> None:  | 
                    |
| 108 | 110 | 
                        generated = Vault(  | 
                    
| 109 | 111 | 
                        phrase=b'',  | 
                    
| 110 | 112 | 
                        length=40,  | 
                    
| ... | ... | 
                      @@ -130,13 +132,13 @@ class TestVault:  | 
                  
| 130 | 132 | 
                        for substring in forbidden_substrings:  | 
                    
| 131 | 133 | 
                        assert substring not in generated  | 
                    
| 132 | 134 | 
                         | 
                    
| 133 | 
                        - def test_219_very_limited_character_set(self):  | 
                    |
| 135 | 
                        + def test_219_very_limited_character_set(self) -> None:  | 
                    |
| 134 | 136 | 
                        generated = Vault(  | 
                    
| 135 | 137 | 
                        phrase=b'', length=24, lower=0, upper=0, space=0, symbol=0  | 
                    
| 136 | 138 | 
                                 ).generate('testing')
                       | 
                    
| 137 | 139 | 
                        assert generated == b'763252593304946694588866'  | 
                    
| 138 | 140 | 
                         | 
                    
| 139 | 
                        - def test_220_character_set_subtraction(self):  | 
                    |
| 141 | 
                        + def test_220_character_set_subtraction(self) -> None:  | 
                    |
| 140 | 142 | 
                        assert Vault._subtract(b'be', b'abcdef') == bytearray(b'acdf')  | 
                    
| 141 | 143 | 
                         | 
                    
| 142 | 144 | 
                        @pytest.mark.parametrize(  | 
                    
| ... | ... | 
                      @@ -230,13 +232,13 @@ class TestVault:  | 
                  
| 230 | 232 | 
                        assert binstr(s) == bytes(s)  | 
                    
| 231 | 233 | 
                        assert binstr(binstr(s)) == bytes(s)  | 
                    
| 232 | 234 | 
                         | 
                    
| 233 | 
                        - def test_310_too_many_symbols(self):  | 
                    |
| 235 | 
                        + def test_310_too_many_symbols(self) -> None:  | 
                    |
| 234 | 236 | 
                        with pytest.raises(  | 
                    
| 235 | 237 | 
                        ValueError, match='requested passphrase length too short'  | 
                    
| 236 | 238 | 
                        ):  | 
                    
| 237 | 239 | 
                        Vault(phrase=self.phrase, symbol=100)  | 
                    
| 238 | 240 | 
                         | 
                    
| 239 | 
                        - def test_311_no_viable_characters(self):  | 
                    |
| 241 | 
                        + def test_311_no_viable_characters(self) -> None:  | 
                    |
| 240 | 242 | 
                        with pytest.raises(ValueError, match='no allowed characters left'):  | 
                    
| 241 | 243 | 
                        Vault(  | 
                    
| 242 | 244 | 
                        phrase=self.phrase,  | 
                    
| ... | ... | 
                      @@ -248,7 +250,7 @@ class TestVault:  | 
                  
| 248 | 250 | 
                        symbol=0,  | 
                    
| 249 | 251 | 
                        )  | 
                    
| 250 | 252 | 
                         | 
                    
| 251 | 
                        - def test_320_character_set_subtraction_duplicate(self):  | 
                    |
| 253 | 
                        + def test_320_character_set_subtraction_duplicate(self) -> None:  | 
                    |
| 252 | 254 | 
                        with pytest.raises(ValueError, match='duplicate characters'):  | 
                    
| 253 | 255 | 
                        Vault._subtract(b'abcdef', b'aabbccddeeff')  | 
                    
| 254 | 256 | 
                        with pytest.raises(ValueError, match='duplicate characters'):  | 
                    
| ... | ... | 
                      @@ -8,7 +8,7 @@ import contextlib  | 
                  
| 8 | 8 | 
                        import json  | 
                    
| 9 | 9 | 
                        import os  | 
                    
| 10 | 10 | 
                        import socket  | 
                    
| 11 | 
                        -from typing import TYPE_CHECKING, cast  | 
                    |
| 11 | 
                        +from typing import TYPE_CHECKING  | 
                    |
| 12 | 12 | 
                         | 
                    
| 13 | 13 | 
                        import click.testing  | 
                    
| 14 | 14 | 
                        import pytest  | 
                    
| ... | ... | 
                      @@ -196,7 +196,7 @@ for opt, config in SINGLES.items():  | 
                  
| 196 | 196 | 
                         | 
                    
| 197 | 197 | 
                         | 
                    
| 198 | 198 | 
                        class TestCLI:  | 
                    
| 199 | 
                        - def test_200_help_output(self):  | 
                    |
| 199 | 
                        + def test_200_help_output(self) -> None:  | 
                    |
| 200 | 200 | 
                        runner = click.testing.CliRunner(mix_stderr=False)  | 
                    
| 201 | 201 | 
                        result = runner.invoke(  | 
                    
| 202 | 202 | 
                        cli.derivepassphrase, ['--help'], catch_exceptions=False  | 
                    
| ... | ... | 
                      @@ -390,11 +390,10 @@ class TestCLI:  | 
                  
| 390 | 390 | 
                        )  | 
                    
| 391 | 391 | 
                        def test_210_invalid_argument_range(self, option: str) -> None:  | 
                    
| 392 | 392 | 
                        runner = click.testing.CliRunner(mix_stderr=False)  | 
                    
| 393 | 
                        - value: str | int  | 
                    |
| 394 | 393 | 
                        for value in '-42', 'invalid':  | 
                    
| 395 | 394 | 
                        result = runner.invoke(  | 
                    
| 396 | 395 | 
                        cli.derivepassphrase,  | 
                    
| 397 | 
                        - [option, cast(str, value), '-p', DUMMY_SERVICE],  | 
                    |
| 396 | 
                        + [option, value, '-p', DUMMY_SERVICE],  | 
                    |
| 398 | 397 | 
                        input=DUMMY_PASSPHRASE,  | 
                    
| 399 | 398 | 
                        catch_exceptions=False,  | 
                    
| 400 | 399 | 
                        )  | 
                    
| ... | ... | 
                      @@ -872,7 +871,7 @@ contents go here  | 
                  
| 872 | 871 | 
                        ):  | 
                    
| 873 | 872 | 
                        custom_error = 'custom error message'  | 
                    
| 874 | 873 | 
                         | 
                    
| 875 | 
                        - def raiser():  | 
                    |
| 874 | 
                        + def raiser() -> None:  | 
                    |
| 876 | 875 | 
                        raise RuntimeError(custom_error)  | 
                    
| 877 | 876 | 
                         | 
                    
| 878 | 877 | 
                        monkeypatch.setattr(cli, '_select_ssh_key', raiser)  | 
                    
| ... | ... | 
                      @@ -925,7 +924,7 @@ class TestCLIUtils:  | 
                  
| 925 | 924 | 
                        @click.command()  | 
                    
| 926 | 925 | 
                                 @click.option('--heading', default='Our menu:')
                       | 
                    
| 927 | 926 | 
                                 @click.argument('items', nargs=-1)
                       | 
                    
| 928 | 
                        - def driver(heading, items):  | 
                    |
| 927 | 
                        + def driver(heading: str, items: list[str]) -> None:  | 
                    |
| 929 | 928 | 
                        # from https://montypython.fandom.com/wiki/Spam#The_menu  | 
                    
| 930 | 929 | 
                        items = items or [  | 
                    
| 931 | 930 | 
                        'Egg and bacon',  | 
                    
| ... | ... | 
                      @@ -1001,7 +1000,7 @@ Your selection? (1-10, leave empty to abort):\x20  | 
                  
| 1001 | 1000 | 
                        @click.command()  | 
                    
| 1002 | 1001 | 
                                 @click.option('--item', default='baked beans')
                       | 
                    
| 1003 | 1002 | 
                                 @click.argument('prompt')
                       | 
                    
| 1004 | 
                        - def driver(item, prompt):  | 
                    |
| 1003 | 
                        + def driver(item: str, prompt: str) -> None:  | 
                    |
| 1005 | 1004 | 
                        try:  | 
                    
| 1006 | 1005 | 
                        cli._prompt_for_selection(  | 
                    
| 1007 | 1006 | 
                        [item], heading='', single_choice_prompt=prompt  | 
                    
| ... | ... | 
                      @@ -27,7 +27,9 @@ class TestStaticFunctionality:  | 
                  
| 27 | 27 | 
                        ([1, 7, 5, 5], 8, 0o1755),  | 
                    
| 28 | 28 | 
                        ],  | 
                    
| 29 | 29 | 
                        )  | 
                    
| 30 | 
                        - def test_200_big_endian_number(self, sequence, base, expected):  | 
                    |
| 30 | 
                        + def test_200_big_endian_number(  | 
                    |
| 31 | 
                        + self, sequence: list[int], base: int, expected: int  | 
                    |
| 32 | 
                        + ) -> None:  | 
                    |
| 31 | 33 | 
                        assert (  | 
                    
| 32 | 34 | 
                        sequin.Sequin._big_endian_number(sequence, base=base)  | 
                    
| 33 | 35 | 
                        ) == expected  | 
                    
| ... | ... | 
                      @@ -41,8 +43,12 @@ class TestStaticFunctionality:  | 
                  
| 41 | 43 | 
                        ],  | 
                    
| 42 | 44 | 
                        )  | 
                    
| 43 | 45 | 
                        def test_300_big_endian_number_exceptions(  | 
                    
| 44 | 
                        - self, exc_type, exc_pattern, sequence, base  | 
                    |
| 45 | 
                        - ):  | 
                    |
| 46 | 
                        + self,  | 
                    |
| 47 | 
                        + exc_type: type[Exception],  | 
                    |
| 48 | 
                        + exc_pattern: str,  | 
                    |
| 49 | 
                        + sequence: list[int],  | 
                    |
| 50 | 
                        + base: int,  | 
                    |
| 51 | 
                        + ) -> None:  | 
                    |
| 46 | 52 | 
                        with pytest.raises(exc_type, match=exc_pattern):  | 
                    
| 47 | 53 | 
                        sequin.Sequin._big_endian_number(sequence, base=base)  | 
                    
| 48 | 54 | 
                         | 
                    
| ... | ... | 
                      @@ -61,11 +67,16 @@ class TestSequin:  | 
                  
| 61 | 67 | 
                                     ('OK', False, bitseq('0100111101001011')),
                       | 
                    
| 62 | 68 | 
                        ],  | 
                    
| 63 | 69 | 
                        )  | 
                    
| 64 | 
                        - def test_200_constructor(self, sequence, is_bitstring, expected):  | 
                    |
| 70 | 
                        + def test_200_constructor(  | 
                    |
| 71 | 
                        + self,  | 
                    |
| 72 | 
                        + sequence: str | bytes | bytearray | list[int],  | 
                    |
| 73 | 
                        + is_bitstring: bool,  | 
                    |
| 74 | 
                        + expected: list[int],  | 
                    |
| 75 | 
                        + ) -> None:  | 
                    |
| 65 | 76 | 
                        seq = sequin.Sequin(sequence, is_bitstring=is_bitstring)  | 
                    
| 66 | 77 | 
                                 assert seq.bases == {2: collections.deque(expected)}
                       | 
                    
| 67 | 78 | 
                         | 
                    
| 68 | 
                        - def test_201_generating(self):  | 
                    |
| 79 | 
                        + def test_201_generating(self) -> None:  | 
                    |
| 69 | 80 | 
                        seq = sequin.Sequin(  | 
                    
| 70 | 81 | 
                        [1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], is_bitstring=True  | 
                    
| 71 | 82 | 
                        )  | 
                    
| ... | ... | 
                      @@ -83,7 +94,7 @@ class TestSequin:  | 
                  
| 83 | 94 | 
                        with pytest.raises(ValueError, match='invalid target range'):  | 
                    
| 84 | 95 | 
                        seq.generate(0)  | 
                    
| 85 | 96 | 
                         | 
                    
| 86 | 
                        - def test_210_internal_generating(self):  | 
                    |
| 97 | 
                        + def test_210_internal_generating(self) -> None:  | 
                    |
| 87 | 98 | 
                        seq = sequin.Sequin(  | 
                    
| 88 | 99 | 
                        [1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], is_bitstring=True  | 
                    
| 89 | 100 | 
                        )  | 
                    
| ... | ... | 
                      @@ -101,7 +112,7 @@ class TestSequin:  | 
                  
| 101 | 112 | 
                        with pytest.raises(ValueError, match='invalid base:'):  | 
                    
| 102 | 113 | 
                        seq._generate_inner(16, base=1)  | 
                    
| 103 | 114 | 
                         | 
                    
| 104 | 
                        - def test_211_shifting(self):  | 
                    |
| 115 | 
                        + def test_211_shifting(self) -> None:  | 
                    |
| 105 | 116 | 
                        seq = sequin.Sequin([1, 0, 1, 0, 0, 1, 0, 0, 0, 1], is_bitstring=True)  | 
                    
| 106 | 117 | 
                                 assert seq.bases == {
                       | 
                    
| 107 | 118 | 
                        2: collections.deque([1, 0, 1, 0, 0, 1, 0, 0, 0, 1])  | 
                    
| ... | ... | 
                      @@ -130,7 +141,11 @@ class TestSequin:  | 
                  
| 130 | 141 | 
                        ],  | 
                    
| 131 | 142 | 
                        )  | 
                    
| 132 | 143 | 
                        def test_300_constructor_exceptions(  | 
                    
| 133 | 
                        - self, sequence, is_bitstring, exc_type, exc_pattern  | 
                    |
| 134 | 
                        - ):  | 
                    |
| 144 | 
                        + self,  | 
                    |
| 145 | 
                        + sequence: list[int] | str,  | 
                    |
| 146 | 
                        + is_bitstring: bool,  | 
                    |
| 147 | 
                        + exc_type: type[Exception],  | 
                    |
| 148 | 
                        + exc_pattern: str,  | 
                    |
| 149 | 
                        + ) -> None:  | 
                    |
| 135 | 150 | 
                        with pytest.raises(exc_type, match=exc_pattern):  | 
                    
| 136 | 151 | 
                        sequin.Sequin(sequence, is_bitstring=is_bitstring)  | 
                    
| ... | ... | 
                      @@ -11,6 +11,7 @@ import io  | 
                  
| 11 | 11 | 
                        import os  | 
                    
| 12 | 12 | 
                        import socket  | 
                    
| 13 | 13 | 
                        import subprocess  | 
                    
| 14 | 
                        +from typing import TYPE_CHECKING  | 
                    |
| 14 | 15 | 
                         | 
                    
| 15 | 16 | 
                        import click  | 
                    
| 16 | 17 | 
                        import click.testing  | 
                    
| ... | ... | 
                      @@ -22,6 +23,9 @@ import derivepassphrase.cli  | 
                  
| 22 | 23 | 
                        import ssh_agent_client  | 
                    
| 23 | 24 | 
                        import tests  | 
                    
| 24 | 25 | 
                         | 
                    
| 26 | 
                        +if TYPE_CHECKING:  | 
                    |
| 27 | 
                        + from collections.abc import Iterator  | 
                    |
| 28 | 
                        +  | 
                    |
| 25 | 29 | 
                         | 
                    
| 26 | 30 | 
                        class TestStaticFunctionality:  | 
                    
| 27 | 31 | 
                        @pytest.mark.parametrize(  | 
                    
| ... | ... | 
                      @@ -31,13 +35,15 @@ class TestStaticFunctionality:  | 
                  
| 31 | 35 | 
                        for val in tests.SUPPORTED_KEYS.values()  | 
                    
| 32 | 36 | 
                        ],  | 
                    
| 33 | 37 | 
                        )  | 
                    
| 34 | 
                        - def test_100_key_decoding(self, public_key, public_key_data):  | 
                    |
| 38 | 
                        + def test_100_key_decoding(  | 
                    |
| 39 | 
                        + self, public_key: bytes, public_key_data: bytes  | 
                    |
| 40 | 
                        + ) -> None:  | 
                    |
| 35 | 41 | 
                        keydata = base64.b64decode(public_key.split(None, 2)[1])  | 
                    
| 36 | 42 | 
                        assert (  | 
                    
| 37 | 43 | 
                        keydata == public_key_data  | 
                    
| 38 | 44 | 
                        ), "recorded public key data doesn't match"  | 
                    
| 39 | 45 | 
                         | 
                    
| 40 | 
                        - def test_200_constructor_no_running_agent(self, monkeypatch):  | 
                    |
| 46 | 
                        + def test_200_constructor_no_running_agent(self, monkeypatch: Any) -> None:  | 
                    |
| 41 | 47 | 
                                 monkeypatch.delenv('SSH_AUTH_SOCK', raising=False)
                       | 
                    
| 42 | 48 | 
                        sock = socket.socket(family=socket.AF_UNIX)  | 
                    
| 43 | 49 | 
                        with pytest.raises(  | 
                    
| ... | ... | 
                      @@ -51,7 +57,7 @@ class TestStaticFunctionality:  | 
                  
| 51 | 57 | 
                        (16777216, b'\x01\x00\x00\x00'),  | 
                    
| 52 | 58 | 
                        ],  | 
                    
| 53 | 59 | 
                        )  | 
                    
| 54 | 
                        - def test_210_uint32(self, input, expected):  | 
                    |
| 60 | 
                        + def test_210_uint32(self, input: int, expected: bytes | bytearray) -> None:  | 
                    |
| 55 | 61 | 
                        uint32 = ssh_agent_client.SSHAgentClient.uint32  | 
                    
| 56 | 62 | 
                        assert uint32(input) == expected  | 
                    
| 57 | 63 | 
                         | 
                    
| ... | ... | 
                      @@ -66,7 +72,9 @@ class TestStaticFunctionality:  | 
                  
| 66 | 72 | 
                        ),  | 
                    
| 67 | 73 | 
                        ],  | 
                    
| 68 | 74 | 
                        )  | 
                    
| 69 | 
                        - def test_211_string(self, input, expected):  | 
                    |
| 75 | 
                        + def test_211_string(  | 
                    |
| 76 | 
                        + self, input: bytes | bytearray, expected: bytes | bytearray  | 
                    |
| 77 | 
                        + ) -> None:  | 
                    |
| 70 | 78 | 
                        string = ssh_agent_client.SSHAgentClient.string  | 
                    
| 71 | 79 | 
                        assert bytes(string(input)) == expected  | 
                    
| 72 | 80 | 
                         | 
                    
| ... | ... | 
                      @@ -80,7 +88,9 @@ class TestStaticFunctionality:  | 
                  
| 80 | 88 | 
                        ),  | 
                    
| 81 | 89 | 
                        ],  | 
                    
| 82 | 90 | 
                        )  | 
                    
| 83 | 
                        - def test_212_unstring(self, input, expected):  | 
                    |
| 91 | 
                        + def test_212_unstring(  | 
                    |
| 92 | 
                        + self, input: bytes | bytearray, expected: bytes | bytearray  | 
                    |
| 93 | 
                        + ) -> None:  | 
                    |
| 84 | 94 | 
                        unstring = ssh_agent_client.SSHAgentClient.unstring  | 
                    
| 85 | 95 | 
                        unstring_prefix = ssh_agent_client.SSHAgentClient.unstring_prefix  | 
                    
| 86 | 96 | 
                        assert bytes(unstring(input)) == expected  | 
                    
| ... | ... | 
                      @@ -96,7 +106,9 @@ class TestStaticFunctionality:  | 
                  
| 96 | 106 | 
                        (-1, OverflowError, "can't convert negative int to unsigned"),  | 
                    
| 97 | 107 | 
                        ],  | 
                    
| 98 | 108 | 
                        )  | 
                    
| 99 | 
                        - def test_310_uint32_exceptions(self, value, exc_type, exc_pattern):  | 
                    |
| 109 | 
                        + def test_310_uint32_exceptions(  | 
                    |
| 110 | 
                        + self, value: int, exc_type: type[Exception], exc_pattern: str  | 
                    |
| 111 | 
                        + ) -> None:  | 
                    |
| 100 | 112 | 
                        uint32 = ssh_agent_client.SSHAgentClient.uint32  | 
                    
| 101 | 113 | 
                        with pytest.raises(exc_type, match=exc_pattern):  | 
                    
| 102 | 114 | 
                        uint32(value)  | 
                    
| ... | ... | 
                      @@ -107,7 +119,9 @@ class TestStaticFunctionality:  | 
                  
| 107 | 119 | 
                                     ('some string', TypeError, 'invalid payload type'),
                       | 
                    
| 108 | 120 | 
                        ],  | 
                    
| 109 | 121 | 
                        )  | 
                    
| 110 | 
                        - def test_311_string_exceptions(self, input, exc_type, exc_pattern):  | 
                    |
| 122 | 
                        + def test_311_string_exceptions(  | 
                    |
| 123 | 
                        + self, input: Any, exc_type: type[Exception], exc_pattern: str  | 
                    |
| 124 | 
                        + ) -> None:  | 
                    |
| 111 | 125 | 
                        string = ssh_agent_client.SSHAgentClient.string  | 
                    
| 112 | 126 | 
                        with pytest.raises(exc_type, match=exc_pattern):  | 
                    
| 113 | 127 | 
                        string(input)  | 
                    
| ... | ... | 
                      @@ -133,8 +147,13 @@ class TestStaticFunctionality:  | 
                  
| 133 | 147 | 
                        ],  | 
                    
| 134 | 148 | 
                        )  | 
                    
| 135 | 149 | 
                        def test_312_unstring_exceptions(  | 
                    
| 136 | 
                        - self, input, exc_type, exc_pattern, has_trailer, parts  | 
                    |
| 137 | 
                        - ):  | 
                    |
| 150 | 
                        + self,  | 
                    |
| 151 | 
                        + input: bytes | bytearray,  | 
                    |
| 152 | 
                        + exc_type: type[Exception],  | 
                    |
| 153 | 
                        + exc_pattern: str,  | 
                    |
| 154 | 
                        + has_trailer: bool,  | 
                    |
| 155 | 
                        + parts: tuple[bytes | bytearray, bytes | bytearray] | None,  | 
                    |
| 156 | 
                        + ) -> None:  | 
                    |
| 138 | 157 | 
                        unstring = ssh_agent_client.SSHAgentClient.unstring  | 
                    
| 139 | 158 | 
                        unstring_prefix = ssh_agent_client.SSHAgentClient.unstring_prefix  | 
                    
| 140 | 159 | 
                        with pytest.raises(exc_type, match=exc_pattern):  | 
                    
| ... | ... | 
                      @@ -151,7 +170,9 @@ class TestAgentInteraction:  | 
                  
| 151 | 170 | 
                        @pytest.mark.parametrize(  | 
                    
| 152 | 171 | 
                        ['keytype', 'data_dict'], list(tests.SUPPORTED_KEYS.items())  | 
                    
| 153 | 172 | 
                        )  | 
                    
| 154 | 
                        - def test_200_sign_data_via_agent(self, keytype, data_dict):  | 
                    |
| 173 | 
                        + def test_200_sign_data_via_agent(  | 
                    |
| 174 | 
                        + self, keytype: str, data_dict: tests.SSHTestKey  | 
                    |
| 175 | 
                        + ) -> None:  | 
                    |
| 155 | 176 | 
                        del keytype # Unused.  | 
                    
| 156 | 177 | 
                        private_key = data_dict['private_key']  | 
                    
| 157 | 178 | 
                        try:  | 
                    
| ... | ... | 
                      @@ -200,7 +221,9 @@ class TestAgentInteraction:  | 
                  
| 200 | 221 | 
                        @pytest.mark.parametrize(  | 
                    
| 201 | 222 | 
                        ['keytype', 'data_dict'], list(tests.UNSUITABLE_KEYS.items())  | 
                    
| 202 | 223 | 
                        )  | 
                    
| 203 | 
                        - def test_201_sign_data_via_agent_unsupported(self, keytype, data_dict):  | 
                    |
| 224 | 
                        + def test_201_sign_data_via_agent_unsupported(  | 
                    |
| 225 | 
                        + self, keytype: str, data_dict: tests.SSHTestKey  | 
                    |
| 226 | 
                        + ) -> None:  | 
                    |
| 204 | 227 | 
                        del keytype # Unused.  | 
                    
| 205 | 228 | 
                        private_key = data_dict['private_key']  | 
                    
| 206 | 229 | 
                        try:  | 
                    
| ... | ... | 
                      @@ -243,7 +266,7 @@ class TestAgentInteraction:  | 
                  
| 243 | 266 | 
                        derivepassphrase.Vault.phrase_from_key(public_key_data)  | 
                    
| 244 | 267 | 
                         | 
                    
| 245 | 268 | 
                        @staticmethod  | 
                    
| 246 | 
                        - def _params():  | 
                    |
| 269 | 
                        + def _params() -> Iterator[tuple[bytes, bool]]:  | 
                    |
| 247 | 270 | 
                        for value in tests.SUPPORTED_KEYS.values():  | 
                    
| 248 | 271 | 
                        key = value['public_key_data']  | 
                    
| 249 | 272 | 
                        yield (key, False)  | 
                    
| ... | ... | 
                      @@ -254,8 +277,10 @@ class TestAgentInteraction:  | 
                  
| 254 | 277 | 
                        yield (key, True)  | 
                    
| 255 | 278 | 
                         | 
                    
| 256 | 279 | 
                        @pytest.mark.parametrize(['key', 'single'], list(_params()))  | 
                    
| 257 | 
                        - def test_210_ssh_key_selector(self, monkeypatch, key, single):  | 
                    |
| 258 | 
                        - def key_is_suitable(key: bytes):  | 
                    |
| 280 | 
                        + def test_210_ssh_key_selector(  | 
                    |
| 281 | 
                        + self, monkeypatch: Any, key: bytes, single: bool  | 
                    |
| 282 | 
                        + ) -> None:  | 
                    |
| 283 | 
                        + def key_is_suitable(key: bytes) -> bool:  | 
                    |
| 259 | 284 | 
                                     return key in {
                       | 
                    
| 260 | 285 | 
                        v['public_key_data'] for v in tests.SUPPORTED_KEYS.values()  | 
                    
| 261 | 286 | 
                        }  | 
                    
| ... | ... | 
                      @@ -288,7 +313,7 @@ class TestAgentInteraction:  | 
                  
| 288 | 313 | 
                                 b64_key = base64.standard_b64encode(key).decode('ASCII')
                       | 
                    
| 289 | 314 | 
                         | 
                    
| 290 | 315 | 
                        @click.command()  | 
                    
| 291 | 
                        - def driver():  | 
                    |
| 316 | 
                        + def driver() -> None:  | 
                    |
| 292 | 317 | 
                        key = derivepassphrase.cli._select_ssh_key()  | 
                    
| 293 | 318 | 
                                     click.echo(base64.standard_b64encode(key).decode('ASCII'))
                       | 
                    
| 294 | 319 | 
                         | 
                    
| ... | ... | 
                      @@ -310,7 +335,7 @@ class TestAgentInteraction:  | 
                  
| 310 | 335 | 
                         | 
                    
| 311 | 336 | 
                        del _params  | 
                    
| 312 | 337 | 
                         | 
                    
| 313 | 
                        - def test_300_constructor_bad_running_agent(self, monkeypatch):  | 
                    |
| 338 | 
                        + def test_300_constructor_bad_running_agent(self, monkeypatch: Any) -> None:  | 
                    |
| 314 | 339 | 
                                 monkeypatch.setenv('SSH_AUTH_SOCK', os.environ['SSH_AUTH_SOCK'] + '~')
                       | 
                    
| 315 | 340 | 
                        sock = socket.socket(family=socket.AF_UNIX)  | 
                    
| 316 | 341 | 
                        with pytest.raises(OSError): # noqa: PT011  | 
                    
| ... | ... | 
                      @@ -323,7 +348,9 @@ class TestAgentInteraction:  | 
                  
| 323 | 348 | 
                        b'\x00\x00\x00\x1f some bytes missing',  | 
                    
| 324 | 349 | 
                        ],  | 
                    
| 325 | 350 | 
                        )  | 
                    
| 326 | 
                        - def test_310_truncated_server_response(self, monkeypatch, response):  | 
                    |
| 351 | 
                        + def test_310_truncated_server_response(  | 
                    |
| 352 | 
                        + self, monkeypatch: Any, response: bytes  | 
                    |
| 353 | 
                        + ) -> None:  | 
                    |
| 327 | 354 | 
                        client = ssh_agent_client.SSHAgentClient()  | 
                    
| 328 | 355 | 
                        response_stream = io.BytesIO(response)  | 
                    
| 329 | 356 | 
                         | 
                    
| ... | ... | 
                      @@ -354,8 +381,13 @@ class TestAgentInteraction:  | 
                  
| 354 | 381 | 
                        ],  | 
                    
| 355 | 382 | 
                        )  | 
                    
| 356 | 383 | 
                        def test_320_list_keys_error_responses(  | 
                    
| 357 | 
                        - self, monkeypatch, response_code, response, exc_type, exc_pattern  | 
                    |
| 358 | 
                        - ):  | 
                    |
| 384 | 
                        + self,  | 
                    |
| 385 | 
                        + monkeypatch: Any,  | 
                    |
| 386 | 
                        + response_code: int,  | 
                    |
| 387 | 
                        + response: bytes | bytearray,  | 
                    |
| 388 | 
                        + exc_type: type[Exception],  | 
                    |
| 389 | 
                        + exc_pattern: str,  | 
                    |
| 390 | 
                        + ) -> None:  | 
                    |
| 359 | 391 | 
                        client = ssh_agent_client.SSHAgentClient()  | 
                    
| 360 | 392 | 
                        monkeypatch.setattr(  | 
                    
| 361 | 393 | 
                        client,  | 
                    
| ... | ... | 
                      @@ -386,8 +418,14 @@ class TestAgentInteraction:  | 
                  
| 386 | 418 | 
                        ],  | 
                    
| 387 | 419 | 
                        )  | 
                    
| 388 | 420 | 
                        def test_330_sign_error_responses(  | 
                    
| 389 | 
                        - self, monkeypatch, key, check, response, exc_type, exc_pattern  | 
                    |
| 390 | 
                        - ):  | 
                    |
| 421 | 
                        + self,  | 
                    |
| 422 | 
                        + monkeypatch: Any,  | 
                    |
| 423 | 
                        + key: bytes | bytearray,  | 
                    |
| 424 | 
                        + check: bool,  | 
                    |
| 425 | 
                        + response: tuple[int, bytes | bytearray],  | 
                    |
| 426 | 
                        + exc_type: type[Exception],  | 
                    |
| 427 | 
                        + exc_pattern: str,  | 
                    |
| 428 | 
                        + ) -> None:  | 
                    |
| 391 | 429 | 
                        client = ssh_agent_client.SSHAgentClient()  | 
                    
| 392 | 430 | 
                        monkeypatch.setattr(client, 'request', lambda a, b: response) # noqa: ARG005  | 
                    
| 393 | 431 | 
                        KeyCommentPair = ssh_agent_client.types.KeyCommentPair # noqa: N806  |