Marco Ricci commited on 2025-02-01 15:55:49
              Zeige 8 geänderte Dateien mit 1807 Einfügungen und 1778 Löschungen.
            
Collect all calls to `pytest.mark.parametrize` in a per-module enum. This deduplicates the definitions and highlights inconsistencies. (For the CLI test module, the shell formatter functions were moved above the enum to avoid NameErrors.)
| ... | ... | 
                      @@ -7,6 +7,7 @@ from __future__ import annotations  | 
                  
| 7 | 7 | 
                        import base64  | 
                    
| 8 | 8 | 
                        import contextlib  | 
                    
| 9 | 9 | 
                        import copy  | 
                    
| 10 | 
                        +import enum  | 
                    |
| 10 | 11 | 
                        import errno  | 
                    
| 11 | 12 | 
                        import io  | 
                    
| 12 | 13 | 
                        import json  | 
                    
| ... | ... | 
                      @@ -294,6 +295,1019 @@ def vault_config_exporter_shell_interpreter( # noqa: C901  | 
                  
| 294 | 295 | 
                        )  | 
                    
| 295 | 296 | 
                         | 
                    
| 296 | 297 | 
                         | 
                    
| 298 | 
                        +def bash_format(item: click.shell_completion.CompletionItem) -> str:  | 
                    |
| 299 | 
                        + """A formatter for `bash`-style shell completion items.  | 
                    |
| 300 | 
                        +  | 
                    |
| 301 | 
                        + The format is `type,value`, and is dictated by [`click`][].  | 
                    |
| 302 | 
                        +  | 
                    |
| 303 | 
                        + """  | 
                    |
| 304 | 
                        + type, value = ( # noqa: A001  | 
                    |
| 305 | 
                        + item.type,  | 
                    |
| 306 | 
                        + item.value,  | 
                    |
| 307 | 
                        + )  | 
                    |
| 308 | 
                        +    return f'{type},{value}'
                       | 
                    |
| 309 | 
                        +  | 
                    |
| 310 | 
                        +  | 
                    |
| 311 | 
                        +def fish_format(item: click.shell_completion.CompletionItem) -> str:  | 
                    |
| 312 | 
                        + r"""A formatter for `fish`-style shell completion items.  | 
                    |
| 313 | 
                        +  | 
                    |
| 314 | 
                        + The format is `type,value<tab>help`, and is dictated by [`click`][].  | 
                    |
| 315 | 
                        +  | 
                    |
| 316 | 
                        + """  | 
                    |
| 317 | 
                        + type, value, help = ( # noqa: A001  | 
                    |
| 318 | 
                        + item.type,  | 
                    |
| 319 | 
                        + item.value,  | 
                    |
| 320 | 
                        + item.help,  | 
                    |
| 321 | 
                        + )  | 
                    |
| 322 | 
                        +    return f'{type},{value}\t{help}' if help else f'{type},{value}'
                       | 
                    |
| 323 | 
                        +  | 
                    |
| 324 | 
                        +  | 
                    |
| 325 | 
                        +def zsh_format(item: click.shell_completion.CompletionItem) -> str:  | 
                    |
| 326 | 
                        + r"""A formatter for `zsh`-style shell completion items.  | 
                    |
| 327 | 
                        +  | 
                    |
| 328 | 
                        + The format is `type<newline>value<newline>help<newline>`, and is  | 
                    |
| 329 | 
                        + dictated by [`click`][]. Upstream `click` currently (v8.2.0) does  | 
                    |
| 330 | 
                        + not deal with colons in the value correctly when the help text is  | 
                    |
| 331 | 
                        + non-degenerate. Our formatter here does, provided the upstream  | 
                    |
| 332 | 
                        + `zsh` completion script is used; see the  | 
                    |
| 333 | 
                        + [`cli_machinery.ZshComplete`][] class. A request is underway to  | 
                    |
| 334 | 
                        + merge this change into upstream `click`; see  | 
                    |
| 335 | 
                        + [`pallets/click#2846`][PR2846].  | 
                    |
| 336 | 
                        +  | 
                    |
| 337 | 
                        + [PR2846]: https://github.com/pallets/click/pull/2846  | 
                    |
| 338 | 
                        +  | 
                    |
| 339 | 
                        + """  | 
                    |
| 340 | 
                        + empty_help = '_'  | 
                    |
| 341 | 
                        + help_, value = (  | 
                    |
| 342 | 
                        +        (item.help, item.value.replace(':', r'\:'))
                       | 
                    |
| 343 | 
                        + if item.help and item.help == empty_help  | 
                    |
| 344 | 
                        + else (empty_help, item.value)  | 
                    |
| 345 | 
                        + )  | 
                    |
| 346 | 
                        +    return f'{item.type}\n{value}\n{help_}'
                       | 
                    |
| 347 | 
                        +  | 
                    |
| 348 | 
                        +  | 
                    |
| 349 | 
                        +class Parametrizations(enum.Enum):  | 
                    |
| 350 | 
                        + EAGER_ARGUMENTS = pytest.mark.parametrize(  | 
                    |
| 351 | 
                        + 'arguments',  | 
                    |
| 352 | 
                        + [['--help'], ['--version']],  | 
                    |
| 353 | 
                        + ids=['help', 'version'],  | 
                    |
| 354 | 
                        + )  | 
                    |
| 355 | 
                        + CHARSET_NAME = pytest.mark.parametrize(  | 
                    |
| 356 | 
                        + 'charset_name', ['lower', 'upper', 'number', 'space', 'dash', 'symbol']  | 
                    |
| 357 | 
                        + )  | 
                    |
| 358 | 
                        + COMMAND_NON_EAGER_ARGUMENTS = pytest.mark.parametrize(  | 
                    |
| 359 | 
                        + ['command', 'non_eager_arguments'],  | 
                    |
| 360 | 
                        + [  | 
                    |
| 361 | 
                        + pytest.param(  | 
                    |
| 362 | 
                        + [],  | 
                    |
| 363 | 
                        + [],  | 
                    |
| 364 | 
                        + id='top-nothing',  | 
                    |
| 365 | 
                        + ),  | 
                    |
| 366 | 
                        + pytest.param(  | 
                    |
| 367 | 
                        + [],  | 
                    |
| 368 | 
                        + ['export'],  | 
                    |
| 369 | 
                        + id='top-export',  | 
                    |
| 370 | 
                        + ),  | 
                    |
| 371 | 
                        + pytest.param(  | 
                    |
| 372 | 
                        + ['export'],  | 
                    |
| 373 | 
                        + [],  | 
                    |
| 374 | 
                        + id='export-nothing',  | 
                    |
| 375 | 
                        + ),  | 
                    |
| 376 | 
                        + pytest.param(  | 
                    |
| 377 | 
                        + ['export'],  | 
                    |
| 378 | 
                        + ['vault'],  | 
                    |
| 379 | 
                        + id='export-vault',  | 
                    |
| 380 | 
                        + ),  | 
                    |
| 381 | 
                        + pytest.param(  | 
                    |
| 382 | 
                        + ['export', 'vault'],  | 
                    |
| 383 | 
                        + [],  | 
                    |
| 384 | 
                        + id='export-vault-nothing',  | 
                    |
| 385 | 
                        + ),  | 
                    |
| 386 | 
                        + pytest.param(  | 
                    |
| 387 | 
                        + ['export', 'vault'],  | 
                    |
| 388 | 
                        + ['--format', 'this-format-doesnt-exist'],  | 
                    |
| 389 | 
                        + id='export-vault-args',  | 
                    |
| 390 | 
                        + ),  | 
                    |
| 391 | 
                        + pytest.param(  | 
                    |
| 392 | 
                        + ['vault'],  | 
                    |
| 393 | 
                        + [],  | 
                    |
| 394 | 
                        + id='vault-nothing',  | 
                    |
| 395 | 
                        + ),  | 
                    |
| 396 | 
                        + pytest.param(  | 
                    |
| 397 | 
                        + ['vault'],  | 
                    |
| 398 | 
                        + ['--export', './'],  | 
                    |
| 399 | 
                        + id='vault-args',  | 
                    |
| 400 | 
                        + ),  | 
                    |
| 401 | 
                        + ],  | 
                    |
| 402 | 
                        + )  | 
                    |
| 403 | 
                        + # TODO(the-13th-letter): Add "configure service passphrase" example.  | 
                    |
| 404 | 
                        + UNICODE_NORMALIZATION_COMMAND_LINES = pytest.mark.parametrize(  | 
                    |
| 405 | 
                        + 'command_line',  | 
                    |
| 406 | 
                        + [  | 
                    |
| 407 | 
                        + pytest.param(  | 
                    |
| 408 | 
                        + ['--config', '--phrase'],  | 
                    |
| 409 | 
                        + id='configure global passphrase',  | 
                    |
| 410 | 
                        + ),  | 
                    |
| 411 | 
                        + pytest.param(  | 
                    |
| 412 | 
                        + ['--phrase', '--', DUMMY_SERVICE],  | 
                    |
| 413 | 
                        + id='interactive passphrase',  | 
                    |
| 414 | 
                        + ),  | 
                    |
| 415 | 
                        + ],  | 
                    |
| 416 | 
                        + )  | 
                    |
| 417 | 
                        + DELETE_CONFIG_INPUT = pytest.mark.parametrize(  | 
                    |
| 418 | 
                        + ['command_line', 'config', 'result_config'],  | 
                    |
| 419 | 
                        + [  | 
                    |
| 420 | 
                        + pytest.param(  | 
                    |
| 421 | 
                        + ['--delete-globals'],  | 
                    |
| 422 | 
                        +                {'global': {'phrase': 'abc'}, 'services': {}},
                       | 
                    |
| 423 | 
                        +                {'services': {}},
                       | 
                    |
| 424 | 
                        + id='globals',  | 
                    |
| 425 | 
                        + ),  | 
                    |
| 426 | 
                        + pytest.param(  | 
                    |
| 427 | 
                        + ['--delete', '--', DUMMY_SERVICE],  | 
                    |
| 428 | 
                        +                {
                       | 
                    |
| 429 | 
                        +                    'global': {'phrase': 'abc'},
                       | 
                    |
| 430 | 
                        +                    'services': {DUMMY_SERVICE: {'notes': '...'}},
                       | 
                    |
| 431 | 
                        + },  | 
                    |
| 432 | 
                        +                {'global': {'phrase': 'abc'}, 'services': {}},
                       | 
                    |
| 433 | 
                        + id='service',  | 
                    |
| 434 | 
                        + ),  | 
                    |
| 435 | 
                        + pytest.param(  | 
                    |
| 436 | 
                        + ['--clear'],  | 
                    |
| 437 | 
                        +                {
                       | 
                    |
| 438 | 
                        +                    'global': {'phrase': 'abc'},
                       | 
                    |
| 439 | 
                        +                    'services': {DUMMY_SERVICE: {'notes': '...'}},
                       | 
                    |
| 440 | 
                        + },  | 
                    |
| 441 | 
                        +                {'services': {}},
                       | 
                    |
| 442 | 
                        + id='all',  | 
                    |
| 443 | 
                        + ),  | 
                    |
| 444 | 
                        + ],  | 
                    |
| 445 | 
                        + )  | 
                    |
| 446 | 
                        + COLORFUL_COMMAND_INPUT = pytest.mark.parametrize(  | 
                    |
| 447 | 
                        + ['command_line', 'input'],  | 
                    |
| 448 | 
                        + [  | 
                    |
| 449 | 
                        + (  | 
                    |
| 450 | 
                        + ['vault', '--import', '-'],  | 
                    |
| 451 | 
                        +                '{"services": {"": {"length": 20}}}',
                       | 
                    |
| 452 | 
                        + ),  | 
                    |
| 453 | 
                        + ],  | 
                    |
| 454 | 
                        + ids=['cmd'],  | 
                    |
| 455 | 
                        + )  | 
                    |
| 456 | 
                        + CONFIG_EDITING_VIA_CONFIG_FLAG_FAILURES = pytest.mark.parametrize(  | 
                    |
| 457 | 
                        + ['command_line', 'input', 'err_text'],  | 
                    |
| 458 | 
                        + [  | 
                    |
| 459 | 
                        + pytest.param(  | 
                    |
| 460 | 
                        + [],  | 
                    |
| 461 | 
                        + '',  | 
                    |
| 462 | 
                        + 'Cannot update the global settings without any given settings',  | 
                    |
| 463 | 
                        + id='None',  | 
                    |
| 464 | 
                        + ),  | 
                    |
| 465 | 
                        + pytest.param(  | 
                    |
| 466 | 
                        + ['--', 'sv'],  | 
                    |
| 467 | 
                        + '',  | 
                    |
| 468 | 
                        + 'Cannot update the service-specific settings without any given settings',  | 
                    |
| 469 | 
                        + id='None-sv',  | 
                    |
| 470 | 
                        + ),  | 
                    |
| 471 | 
                        + pytest.param(  | 
                    |
| 472 | 
                        + ['--phrase', '--', 'sv'],  | 
                    |
| 473 | 
                        + '',  | 
                    |
| 474 | 
                        + 'No passphrase was given',  | 
                    |
| 475 | 
                        + id='phrase-sv',  | 
                    |
| 476 | 
                        + ),  | 
                    |
| 477 | 
                        + pytest.param(  | 
                    |
| 478 | 
                        + ['--key'],  | 
                    |
| 479 | 
                        + '',  | 
                    |
| 480 | 
                        + 'No SSH key was selected',  | 
                    |
| 481 | 
                        + id='key-sv',  | 
                    |
| 482 | 
                        + ),  | 
                    |
| 483 | 
                        + ],  | 
                    |
| 484 | 
                        + )  | 
                    |
| 485 | 
                        + CONFIG_EDITING_VIA_CONFIG_FLAG = pytest.mark.parametrize(  | 
                    |
| 486 | 
                        + ['command_line', 'input', 'result_config'],  | 
                    |
| 487 | 
                        + [  | 
                    |
| 488 | 
                        + pytest.param(  | 
                    |
| 489 | 
                        + ['--phrase'],  | 
                    |
| 490 | 
                        + 'my passphrase\n',  | 
                    |
| 491 | 
                        +                {'global': {'phrase': 'my passphrase'}, 'services': {}},
                       | 
                    |
| 492 | 
                        + id='phrase',  | 
                    |
| 493 | 
                        + ),  | 
                    |
| 494 | 
                        + pytest.param(  | 
                    |
| 495 | 
                        + ['--key'],  | 
                    |
| 496 | 
                        + '1\n',  | 
                    |
| 497 | 
                        +                {
                       | 
                    |
| 498 | 
                        +                    'global': {'key': DUMMY_KEY1_B64, 'phrase': 'abc'},
                       | 
                    |
| 499 | 
                        +                    'services': {},
                       | 
                    |
| 500 | 
                        + },  | 
                    |
| 501 | 
                        + id='key',  | 
                    |
| 502 | 
                        + ),  | 
                    |
| 503 | 
                        + pytest.param(  | 
                    |
| 504 | 
                        + ['--phrase', '--', 'sv'],  | 
                    |
| 505 | 
                        + 'my passphrase\n',  | 
                    |
| 506 | 
                        +                {
                       | 
                    |
| 507 | 
                        +                    'global': {'phrase': 'abc'},
                       | 
                    |
| 508 | 
                        +                    'services': {'sv': {'phrase': 'my passphrase'}},
                       | 
                    |
| 509 | 
                        + },  | 
                    |
| 510 | 
                        + id='phrase-sv',  | 
                    |
| 511 | 
                        + ),  | 
                    |
| 512 | 
                        + pytest.param(  | 
                    |
| 513 | 
                        + ['--key', '--', 'sv'],  | 
                    |
| 514 | 
                        + '1\n',  | 
                    |
| 515 | 
                        +                {
                       | 
                    |
| 516 | 
                        +                    'global': {'phrase': 'abc'},
                       | 
                    |
| 517 | 
                        +                    'services': {'sv': {'key': DUMMY_KEY1_B64}},
                       | 
                    |
| 518 | 
                        + },  | 
                    |
| 519 | 
                        + id='key-sv',  | 
                    |
| 520 | 
                        + ),  | 
                    |
| 521 | 
                        + pytest.param(  | 
                    |
| 522 | 
                        + ['--key', '--length', '15', '--', 'sv'],  | 
                    |
| 523 | 
                        + '1\n',  | 
                    |
| 524 | 
                        +                {
                       | 
                    |
| 525 | 
                        +                    'global': {'phrase': 'abc'},
                       | 
                    |
| 526 | 
                        +                    'services': {'sv': {'key': DUMMY_KEY1_B64, 'length': 15}},
                       | 
                    |
| 527 | 
                        + },  | 
                    |
| 528 | 
                        + id='key-length-sv',  | 
                    |
| 529 | 
                        + ),  | 
                    |
| 530 | 
                        + ],  | 
                    |
| 531 | 
                        + )  | 
                    |
| 532 | 
                        + COMPLETABLE_PATH_ARGUMENT = pytest.mark.parametrize(  | 
                    |
| 533 | 
                        + 'command_prefix',  | 
                    |
| 534 | 
                        + [  | 
                    |
| 535 | 
                        + pytest.param(  | 
                    |
| 536 | 
                        +                ('export', 'vault'),
                       | 
                    |
| 537 | 
                        + id='derivepassphrase-export-vault',  | 
                    |
| 538 | 
                        + ),  | 
                    |
| 539 | 
                        + pytest.param(  | 
                    |
| 540 | 
                        +                ('vault', '--export'),
                       | 
                    |
| 541 | 
                        + id='derivepassphrase-vault--export',  | 
                    |
| 542 | 
                        + ),  | 
                    |
| 543 | 
                        + pytest.param(  | 
                    |
| 544 | 
                        +                ('vault', '--import'),
                       | 
                    |
| 545 | 
                        + id='derivepassphrase-vault--import',  | 
                    |
| 546 | 
                        + ),  | 
                    |
| 547 | 
                        + ],  | 
                    |
| 548 | 
                        + )  | 
                    |
| 549 | 
                        + COMPLETABLE_OPTIONS = pytest.mark.parametrize(  | 
                    |
| 550 | 
                        + ['command_prefix', 'incomplete', 'completions'],  | 
                    |
| 551 | 
                        + [  | 
                    |
| 552 | 
                        + pytest.param(  | 
                    |
| 553 | 
                        + (),  | 
                    |
| 554 | 
                        + '-',  | 
                    |
| 555 | 
                        +                frozenset({
                       | 
                    |
| 556 | 
                        + '--help',  | 
                    |
| 557 | 
                        + '-h',  | 
                    |
| 558 | 
                        + '--version',  | 
                    |
| 559 | 
                        + '--debug',  | 
                    |
| 560 | 
                        + '--verbose',  | 
                    |
| 561 | 
                        + '-v',  | 
                    |
| 562 | 
                        + '--quiet',  | 
                    |
| 563 | 
                        + '-q',  | 
                    |
| 564 | 
                        + }),  | 
                    |
| 565 | 
                        + id='derivepassphrase',  | 
                    |
| 566 | 
                        + ),  | 
                    |
| 567 | 
                        + pytest.param(  | 
                    |
| 568 | 
                        +                ('export',),
                       | 
                    |
| 569 | 
                        + '-',  | 
                    |
| 570 | 
                        +                frozenset({
                       | 
                    |
| 571 | 
                        + '--help',  | 
                    |
| 572 | 
                        + '-h',  | 
                    |
| 573 | 
                        + '--version',  | 
                    |
| 574 | 
                        + '--debug',  | 
                    |
| 575 | 
                        + '--verbose',  | 
                    |
| 576 | 
                        + '-v',  | 
                    |
| 577 | 
                        + '--quiet',  | 
                    |
| 578 | 
                        + '-q',  | 
                    |
| 579 | 
                        + }),  | 
                    |
| 580 | 
                        + id='derivepassphrase-export',  | 
                    |
| 581 | 
                        + ),  | 
                    |
| 582 | 
                        + pytest.param(  | 
                    |
| 583 | 
                        +                ('export', 'vault'),
                       | 
                    |
| 584 | 
                        + '-',  | 
                    |
| 585 | 
                        +                frozenset({
                       | 
                    |
| 586 | 
                        + '--help',  | 
                    |
| 587 | 
                        + '-h',  | 
                    |
| 588 | 
                        + '--version',  | 
                    |
| 589 | 
                        + '--debug',  | 
                    |
| 590 | 
                        + '--verbose',  | 
                    |
| 591 | 
                        + '-v',  | 
                    |
| 592 | 
                        + '--quiet',  | 
                    |
| 593 | 
                        + '-q',  | 
                    |
| 594 | 
                        + '--format',  | 
                    |
| 595 | 
                        + '-f',  | 
                    |
| 596 | 
                        + '--key',  | 
                    |
| 597 | 
                        + '-k',  | 
                    |
| 598 | 
                        + }),  | 
                    |
| 599 | 
                        + id='derivepassphrase-export-vault',  | 
                    |
| 600 | 
                        + ),  | 
                    |
| 601 | 
                        + pytest.param(  | 
                    |
| 602 | 
                        +                ('vault',),
                       | 
                    |
| 603 | 
                        + '-',  | 
                    |
| 604 | 
                        +                frozenset({
                       | 
                    |
| 605 | 
                        + '--help',  | 
                    |
| 606 | 
                        + '-h',  | 
                    |
| 607 | 
                        + '--version',  | 
                    |
| 608 | 
                        + '--debug',  | 
                    |
| 609 | 
                        + '--verbose',  | 
                    |
| 610 | 
                        + '-v',  | 
                    |
| 611 | 
                        + '--quiet',  | 
                    |
| 612 | 
                        + '-q',  | 
                    |
| 613 | 
                        + '--phrase',  | 
                    |
| 614 | 
                        + '-p',  | 
                    |
| 615 | 
                        + '--key',  | 
                    |
| 616 | 
                        + '-k',  | 
                    |
| 617 | 
                        + '--length',  | 
                    |
| 618 | 
                        + '-l',  | 
                    |
| 619 | 
                        + '--repeat',  | 
                    |
| 620 | 
                        + '-r',  | 
                    |
| 621 | 
                        + '--upper',  | 
                    |
| 622 | 
                        + '--lower',  | 
                    |
| 623 | 
                        + '--number',  | 
                    |
| 624 | 
                        + '--space',  | 
                    |
| 625 | 
                        + '--dash',  | 
                    |
| 626 | 
                        + '--symbol',  | 
                    |
| 627 | 
                        + '--config',  | 
                    |
| 628 | 
                        + '-c',  | 
                    |
| 629 | 
                        + '--notes',  | 
                    |
| 630 | 
                        + '-n',  | 
                    |
| 631 | 
                        + '--delete',  | 
                    |
| 632 | 
                        + '-x',  | 
                    |
| 633 | 
                        + '--delete-globals',  | 
                    |
| 634 | 
                        + '--clear',  | 
                    |
| 635 | 
                        + '-X',  | 
                    |
| 636 | 
                        + '--export',  | 
                    |
| 637 | 
                        + '-e',  | 
                    |
| 638 | 
                        + '--import',  | 
                    |
| 639 | 
                        + '-i',  | 
                    |
| 640 | 
                        + '--overwrite-existing',  | 
                    |
| 641 | 
                        + '--merge-existing',  | 
                    |
| 642 | 
                        + '--unset',  | 
                    |
| 643 | 
                        + '--export-as',  | 
                    |
| 644 | 
                        + }),  | 
                    |
| 645 | 
                        + id='derivepassphrase-vault',  | 
                    |
| 646 | 
                        + ),  | 
                    |
| 647 | 
                        + ],  | 
                    |
| 648 | 
                        + )  | 
                    |
| 649 | 
                        + COMPLETABLE_SUBCOMMANDS = pytest.mark.parametrize(  | 
                    |
| 650 | 
                        + ['command_prefix', 'incomplete', 'completions'],  | 
                    |
| 651 | 
                        + [  | 
                    |
| 652 | 
                        + pytest.param(  | 
                    |
| 653 | 
                        + (),  | 
                    |
| 654 | 
                        + '',  | 
                    |
| 655 | 
                        +                frozenset({'export', 'vault'}),
                       | 
                    |
| 656 | 
                        + id='derivepassphrase',  | 
                    |
| 657 | 
                        + ),  | 
                    |
| 658 | 
                        + pytest.param(  | 
                    |
| 659 | 
                        +                ('export',),
                       | 
                    |
| 660 | 
                        + '',  | 
                    |
| 661 | 
                        +                frozenset({'vault'}),
                       | 
                    |
| 662 | 
                        + id='derivepassphrase-export',  | 
                    |
| 663 | 
                        + ),  | 
                    |
| 664 | 
                        + ],  | 
                    |
| 665 | 
                        + )  | 
                    |
| 666 | 
                        + BAD_CONFIGS = pytest.mark.parametrize(  | 
                    |
| 667 | 
                        + 'config',  | 
                    |
| 668 | 
                        + [  | 
                    |
| 669 | 
                        +            {'global': '', 'services': {}},
                       | 
                    |
| 670 | 
                        +            {'global': 0, 'services': {}},
                       | 
                    |
| 671 | 
                        +            {
                       | 
                    |
| 672 | 
                        +                'global': {'phrase': 'abc'},
                       | 
                    |
| 673 | 
                        + 'services': False,  | 
                    |
| 674 | 
                        + },  | 
                    |
| 675 | 
                        +            {
                       | 
                    |
| 676 | 
                        +                'global': {'phrase': 'abc'},
                       | 
                    |
| 677 | 
                        + 'services': True,  | 
                    |
| 678 | 
                        + },  | 
                    |
| 679 | 
                        +            {
                       | 
                    |
| 680 | 
                        +                'global': {'phrase': 'abc'},
                       | 
                    |
| 681 | 
                        + 'services': None,  | 
                    |
| 682 | 
                        + },  | 
                    |
| 683 | 
                        + ],  | 
                    |
| 684 | 
                        + )  | 
                    |
| 685 | 
                        + BASE_CONFIG_VARIATIONS = pytest.mark.parametrize(  | 
                    |
| 686 | 
                        + 'config',  | 
                    |
| 687 | 
                        + [  | 
                    |
| 688 | 
                        +            {'global': {'phrase': 'my passphrase'}, 'services': {}},
                       | 
                    |
| 689 | 
                        +            {'global': {'key': DUMMY_KEY1_B64}, 'services': {}},
                       | 
                    |
| 690 | 
                        +            {
                       | 
                    |
| 691 | 
                        +                'global': {'phrase': 'abc'},
                       | 
                    |
| 692 | 
                        +                'services': {'sv': {'phrase': 'my passphrase'}},
                       | 
                    |
| 693 | 
                        + },  | 
                    |
| 694 | 
                        +            {
                       | 
                    |
| 695 | 
                        +                'global': {'phrase': 'abc'},
                       | 
                    |
| 696 | 
                        +                'services': {'sv': {'key': DUMMY_KEY1_B64}},
                       | 
                    |
| 697 | 
                        + },  | 
                    |
| 698 | 
                        +            {
                       | 
                    |
| 699 | 
                        +                'global': {'phrase': 'abc'},
                       | 
                    |
| 700 | 
                        +                'services': {'sv': {'key': DUMMY_KEY1_B64, 'length': 15}},
                       | 
                    |
| 701 | 
                        + },  | 
                    |
| 702 | 
                        + ],  | 
                    |
| 703 | 
                        + )  | 
                    |
| 704 | 
                        + BASE_CONFIG_WITH_KEY_VARIATIONS = pytest.mark.parametrize(  | 
                    |
| 705 | 
                        + 'config',  | 
                    |
| 706 | 
                        + [  | 
                    |
| 707 | 
                        + pytest.param(  | 
                    |
| 708 | 
                        +                {
                       | 
                    |
| 709 | 
                        +                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 710 | 
                        +                    'services': {DUMMY_SERVICE: {}},
                       | 
                    |
| 711 | 
                        + },  | 
                    |
| 712 | 
                        + id='global_config',  | 
                    |
| 713 | 
                        + ),  | 
                    |
| 714 | 
                        + pytest.param(  | 
                    |
| 715 | 
                        +                {'services': {DUMMY_SERVICE: {'key': DUMMY_KEY2_B64}}},
                       | 
                    |
| 716 | 
                        + id='service_config',  | 
                    |
| 717 | 
                        + ),  | 
                    |
| 718 | 
                        + pytest.param(  | 
                    |
| 719 | 
                        +                {
                       | 
                    |
| 720 | 
                        +                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 721 | 
                        +                    'services': {DUMMY_SERVICE: {'key': DUMMY_KEY2_B64}},
                       | 
                    |
| 722 | 
                        + },  | 
                    |
| 723 | 
                        + id='full_config',  | 
                    |
| 724 | 
                        + ),  | 
                    |
| 725 | 
                        + ],  | 
                    |
| 726 | 
                        + )  | 
                    |
| 727 | 
                        + CONFIG_WITH_KEY = pytest.mark.parametrize(  | 
                    |
| 728 | 
                        + 'config',  | 
                    |
| 729 | 
                        + [  | 
                    |
| 730 | 
                        + pytest.param(  | 
                    |
| 731 | 
                        +                {
                       | 
                    |
| 732 | 
                        +                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 733 | 
                        +                    'services': {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS},
                       | 
                    |
| 734 | 
                        + },  | 
                    |
| 735 | 
                        + id='global',  | 
                    |
| 736 | 
                        + ),  | 
                    |
| 737 | 
                        + pytest.param(  | 
                    |
| 738 | 
                        +                {
                       | 
                    |
| 739 | 
                        +                    'global': {'phrase': DUMMY_PASSPHRASE.rstrip('\n')},
                       | 
                    |
| 740 | 
                        +                    'services': {
                       | 
                    |
| 741 | 
                        +                        DUMMY_SERVICE: {
                       | 
                    |
| 742 | 
                        + 'key': DUMMY_KEY1_B64,  | 
                    |
| 743 | 
                        + **DUMMY_CONFIG_SETTINGS,  | 
                    |
| 744 | 
                        + }  | 
                    |
| 745 | 
                        + },  | 
                    |
| 746 | 
                        + },  | 
                    |
| 747 | 
                        + id='service',  | 
                    |
| 748 | 
                        + ),  | 
                    |
| 749 | 
                        + ],  | 
                    |
| 750 | 
                        + )  | 
                    |
| 751 | 
                        + VALID_TEST_CONFIGS = pytest.mark.parametrize(  | 
                    |
| 752 | 
                        + 'config',  | 
                    |
| 753 | 
                        + [  | 
                    |
| 754 | 
                        + conf.config  | 
                    |
| 755 | 
                        + for conf in TEST_CONFIGS  | 
                    |
| 756 | 
                        + if tests.is_valid_test_config(conf)  | 
                    |
| 757 | 
                        + ],  | 
                    |
| 758 | 
                        + )  | 
                    |
| 759 | 
                        + KEY_OVERRIDING_IN_CONFIG = pytest.mark.parametrize(  | 
                    |
| 760 | 
                        + ['config', 'command_line'],  | 
                    |
| 761 | 
                        + [  | 
                    |
| 762 | 
                        + pytest.param(  | 
                    |
| 763 | 
                        +                {
                       | 
                    |
| 764 | 
                        +                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 765 | 
                        +                    'services': {},
                       | 
                    |
| 766 | 
                        + },  | 
                    |
| 767 | 
                        + ['--config', '-p'],  | 
                    |
| 768 | 
                        + id='global',  | 
                    |
| 769 | 
                        + ),  | 
                    |
| 770 | 
                        + pytest.param(  | 
                    |
| 771 | 
                        +                {
                       | 
                    |
| 772 | 
                        +                    'services': {
                       | 
                    |
| 773 | 
                        +                        DUMMY_SERVICE: {
                       | 
                    |
| 774 | 
                        + 'key': DUMMY_KEY1_B64,  | 
                    |
| 775 | 
                        + **DUMMY_CONFIG_SETTINGS,  | 
                    |
| 776 | 
                        + },  | 
                    |
| 777 | 
                        + },  | 
                    |
| 778 | 
                        + },  | 
                    |
| 779 | 
                        + ['--config', '-p', '--', DUMMY_SERVICE],  | 
                    |
| 780 | 
                        + id='service',  | 
                    |
| 781 | 
                        + ),  | 
                    |
| 782 | 
                        + pytest.param(  | 
                    |
| 783 | 
                        +                {
                       | 
                    |
| 784 | 
                        +                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 785 | 
                        +                    'services': {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy()},
                       | 
                    |
| 786 | 
                        + },  | 
                    |
| 787 | 
                        + ['--config', '-p', '--', DUMMY_SERVICE],  | 
                    |
| 788 | 
                        + id='service-over-global',  | 
                    |
| 789 | 
                        + ),  | 
                    |
| 790 | 
                        + ],  | 
                    |
| 791 | 
                        + )  | 
                    |
| 792 | 
                        + COMPLETION_FUNCTION_INPUTS = pytest.mark.parametrize(  | 
                    |
| 793 | 
                        + ['config', 'comp_func', 'args', 'incomplete', 'results'],  | 
                    |
| 794 | 
                        + [  | 
                    |
| 795 | 
                        + pytest.param(  | 
                    |
| 796 | 
                        +                {'services': {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy()}},
                       | 
                    |
| 797 | 
                        + cli_helpers.shell_complete_service,  | 
                    |
| 798 | 
                        + ['vault'],  | 
                    |
| 799 | 
                        + '',  | 
                    |
| 800 | 
                        + [DUMMY_SERVICE],  | 
                    |
| 801 | 
                        + id='base_config-service',  | 
                    |
| 802 | 
                        + ),  | 
                    |
| 803 | 
                        + pytest.param(  | 
                    |
| 804 | 
                        +                {'services': {}},
                       | 
                    |
| 805 | 
                        + cli_helpers.shell_complete_service,  | 
                    |
| 806 | 
                        + ['vault'],  | 
                    |
| 807 | 
                        + '',  | 
                    |
| 808 | 
                        + [],  | 
                    |
| 809 | 
                        + id='empty_config-service',  | 
                    |
| 810 | 
                        + ),  | 
                    |
| 811 | 
                        + pytest.param(  | 
                    |
| 812 | 
                        +                {
                       | 
                    |
| 813 | 
                        +                    'services': {
                       | 
                    |
| 814 | 
                        + DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 815 | 
                        + 'newline\nin\nname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 816 | 
                        + }  | 
                    |
| 817 | 
                        + },  | 
                    |
| 818 | 
                        + cli_helpers.shell_complete_service,  | 
                    |
| 819 | 
                        + ['vault'],  | 
                    |
| 820 | 
                        + '',  | 
                    |
| 821 | 
                        + [DUMMY_SERVICE],  | 
                    |
| 822 | 
                        + id='incompletable_newline_config-service',  | 
                    |
| 823 | 
                        + ),  | 
                    |
| 824 | 
                        + pytest.param(  | 
                    |
| 825 | 
                        +                {
                       | 
                    |
| 826 | 
                        +                    'services': {
                       | 
                    |
| 827 | 
                        + DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 828 | 
                        + 'backspace\bin\bname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 829 | 
                        + }  | 
                    |
| 830 | 
                        + },  | 
                    |
| 831 | 
                        + cli_helpers.shell_complete_service,  | 
                    |
| 832 | 
                        + ['vault'],  | 
                    |
| 833 | 
                        + '',  | 
                    |
| 834 | 
                        + [DUMMY_SERVICE],  | 
                    |
| 835 | 
                        + id='incompletable_backspace_config-service',  | 
                    |
| 836 | 
                        + ),  | 
                    |
| 837 | 
                        + pytest.param(  | 
                    |
| 838 | 
                        +                {
                       | 
                    |
| 839 | 
                        +                    'services': {
                       | 
                    |
| 840 | 
                        + DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 841 | 
                        + 'colon:in:name': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 842 | 
                        + }  | 
                    |
| 843 | 
                        + },  | 
                    |
| 844 | 
                        + cli_helpers.shell_complete_service,  | 
                    |
| 845 | 
                        + ['vault'],  | 
                    |
| 846 | 
                        + '',  | 
                    |
| 847 | 
                        + sorted([DUMMY_SERVICE, 'colon:in:name']),  | 
                    |
| 848 | 
                        + id='brittle_colon_config-service',  | 
                    |
| 849 | 
                        + ),  | 
                    |
| 850 | 
                        + pytest.param(  | 
                    |
| 851 | 
                        +                {
                       | 
                    |
| 852 | 
                        +                    'services': {
                       | 
                    |
| 853 | 
                        + DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 854 | 
                        + 'colon:in:name': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 855 | 
                        + 'newline\nin\nname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 856 | 
                        + 'backspace\bin\bname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 857 | 
                        + 'nul\x00in\x00name': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 858 | 
                        + 'del\x7fin\x7fname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 859 | 
                        + }  | 
                    |
| 860 | 
                        + },  | 
                    |
| 861 | 
                        + cli_helpers.shell_complete_service,  | 
                    |
| 862 | 
                        + ['vault'],  | 
                    |
| 863 | 
                        + '',  | 
                    |
| 864 | 
                        + sorted([DUMMY_SERVICE, 'colon:in:name']),  | 
                    |
| 865 | 
                        + id='brittle_incompletable_multi_config-service',  | 
                    |
| 866 | 
                        + ),  | 
                    |
| 867 | 
                        + pytest.param(  | 
                    |
| 868 | 
                        +                {'services': {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy()}},
                       | 
                    |
| 869 | 
                        + cli_helpers.shell_complete_path,  | 
                    |
| 870 | 
                        + ['vault', '--import'],  | 
                    |
| 871 | 
                        + '',  | 
                    |
| 872 | 
                        +                [click.shell_completion.CompletionItem('', type='file')],
                       | 
                    |
| 873 | 
                        + id='base_config-path',  | 
                    |
| 874 | 
                        + ),  | 
                    |
| 875 | 
                        + pytest.param(  | 
                    |
| 876 | 
                        +                {'services': {}},
                       | 
                    |
| 877 | 
                        + cli_helpers.shell_complete_path,  | 
                    |
| 878 | 
                        + ['vault', '--import'],  | 
                    |
| 879 | 
                        + '',  | 
                    |
| 880 | 
                        +                [click.shell_completion.CompletionItem('', type='file')],
                       | 
                    |
| 881 | 
                        + id='empty_config-path',  | 
                    |
| 882 | 
                        + ),  | 
                    |
| 883 | 
                        + ],  | 
                    |
| 884 | 
                        + )  | 
                    |
| 885 | 
                        + COMPLETABLE_SERVICE_NAMES = pytest.mark.parametrize(  | 
                    |
| 886 | 
                        + ['config', 'incomplete', 'completions'],  | 
                    |
| 887 | 
                        + [  | 
                    |
| 888 | 
                        + pytest.param(  | 
                    |
| 889 | 
                        +                {'services': {}},
                       | 
                    |
| 890 | 
                        + '',  | 
                    |
| 891 | 
                        + frozenset(),  | 
                    |
| 892 | 
                        + id='no_services',  | 
                    |
| 893 | 
                        + ),  | 
                    |
| 894 | 
                        + pytest.param(  | 
                    |
| 895 | 
                        +                {'services': {}},
                       | 
                    |
| 896 | 
                        + 'partial',  | 
                    |
| 897 | 
                        + frozenset(),  | 
                    |
| 898 | 
                        + id='no_services_partial',  | 
                    |
| 899 | 
                        + ),  | 
                    |
| 900 | 
                        + pytest.param(  | 
                    |
| 901 | 
                        +                {'services': {DUMMY_SERVICE: {'length': 10}}},
                       | 
                    |
| 902 | 
                        + '',  | 
                    |
| 903 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 904 | 
                        + id='one_service',  | 
                    |
| 905 | 
                        + ),  | 
                    |
| 906 | 
                        + pytest.param(  | 
                    |
| 907 | 
                        +                {'services': {DUMMY_SERVICE: {'length': 10}}},
                       | 
                    |
| 908 | 
                        + DUMMY_SERVICE[:4],  | 
                    |
| 909 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 910 | 
                        + id='one_service_partial',  | 
                    |
| 911 | 
                        + ),  | 
                    |
| 912 | 
                        + pytest.param(  | 
                    |
| 913 | 
                        +                {'services': {DUMMY_SERVICE: {'length': 10}}},
                       | 
                    |
| 914 | 
                        + DUMMY_SERVICE[-4:],  | 
                    |
| 915 | 
                        + frozenset(),  | 
                    |
| 916 | 
                        + id='one_service_partial_miss',  | 
                    |
| 917 | 
                        + ),  | 
                    |
| 918 | 
                        + ],  | 
                    |
| 919 | 
                        + )  | 
                    |
| 920 | 
                        + SERVICE_NAME_COMPLETION_INPUTS = pytest.mark.parametrize(  | 
                    |
| 921 | 
                        + ['config', 'key', 'incomplete', 'completions'],  | 
                    |
| 922 | 
                        + [  | 
                    |
| 923 | 
                        + pytest.param(  | 
                    |
| 924 | 
                        +                {
                       | 
                    |
| 925 | 
                        +                    'services': {
                       | 
                    |
| 926 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 927 | 
                        +                        'newline\nin\nname': {'length': 10},
                       | 
                    |
| 928 | 
                        + },  | 
                    |
| 929 | 
                        + },  | 
                    |
| 930 | 
                        + 'newline\nin\nname',  | 
                    |
| 931 | 
                        + '',  | 
                    |
| 932 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 933 | 
                        + id='newline',  | 
                    |
| 934 | 
                        + ),  | 
                    |
| 935 | 
                        + pytest.param(  | 
                    |
| 936 | 
                        +                {
                       | 
                    |
| 937 | 
                        +                    'services': {
                       | 
                    |
| 938 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 939 | 
                        +                        'newline\nin\nname': {'length': 10},
                       | 
                    |
| 940 | 
                        + },  | 
                    |
| 941 | 
                        + },  | 
                    |
| 942 | 
                        + 'newline\nin\nname',  | 
                    |
| 943 | 
                        + 'serv',  | 
                    |
| 944 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 945 | 
                        + id='newline_partial_other',  | 
                    |
| 946 | 
                        + ),  | 
                    |
| 947 | 
                        + pytest.param(  | 
                    |
| 948 | 
                        +                {
                       | 
                    |
| 949 | 
                        +                    'services': {
                       | 
                    |
| 950 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 951 | 
                        +                        'newline\nin\nname': {'length': 10},
                       | 
                    |
| 952 | 
                        + },  | 
                    |
| 953 | 
                        + },  | 
                    |
| 954 | 
                        + 'newline\nin\nname',  | 
                    |
| 955 | 
                        + 'newline',  | 
                    |
| 956 | 
                        +                frozenset({}),
                       | 
                    |
| 957 | 
                        + id='newline_partial_specific',  | 
                    |
| 958 | 
                        + ),  | 
                    |
| 959 | 
                        + pytest.param(  | 
                    |
| 960 | 
                        +                {
                       | 
                    |
| 961 | 
                        +                    'services': {
                       | 
                    |
| 962 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 963 | 
                        +                        'nul\x00in\x00name': {'length': 10},
                       | 
                    |
| 964 | 
                        + },  | 
                    |
| 965 | 
                        + },  | 
                    |
| 966 | 
                        + 'nul\x00in\x00name',  | 
                    |
| 967 | 
                        + '',  | 
                    |
| 968 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 969 | 
                        + id='nul',  | 
                    |
| 970 | 
                        + ),  | 
                    |
| 971 | 
                        + pytest.param(  | 
                    |
| 972 | 
                        +                {
                       | 
                    |
| 973 | 
                        +                    'services': {
                       | 
                    |
| 974 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 975 | 
                        +                        'nul\x00in\x00name': {'length': 10},
                       | 
                    |
| 976 | 
                        + },  | 
                    |
| 977 | 
                        + },  | 
                    |
| 978 | 
                        + 'nul\x00in\x00name',  | 
                    |
| 979 | 
                        + 'serv',  | 
                    |
| 980 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 981 | 
                        + id='nul_partial_other',  | 
                    |
| 982 | 
                        + ),  | 
                    |
| 983 | 
                        + pytest.param(  | 
                    |
| 984 | 
                        +                {
                       | 
                    |
| 985 | 
                        +                    'services': {
                       | 
                    |
| 986 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 987 | 
                        +                        'nul\x00in\x00name': {'length': 10},
                       | 
                    |
| 988 | 
                        + },  | 
                    |
| 989 | 
                        + },  | 
                    |
| 990 | 
                        + 'nul\x00in\x00name',  | 
                    |
| 991 | 
                        + 'nul',  | 
                    |
| 992 | 
                        +                frozenset({}),
                       | 
                    |
| 993 | 
                        + id='nul_partial_specific',  | 
                    |
| 994 | 
                        + ),  | 
                    |
| 995 | 
                        + pytest.param(  | 
                    |
| 996 | 
                        +                {
                       | 
                    |
| 997 | 
                        +                    'services': {
                       | 
                    |
| 998 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 999 | 
                        +                        'backspace\bin\bname': {'length': 10},
                       | 
                    |
| 1000 | 
                        + },  | 
                    |
| 1001 | 
                        + },  | 
                    |
| 1002 | 
                        + 'backspace\bin\bname',  | 
                    |
| 1003 | 
                        + '',  | 
                    |
| 1004 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 1005 | 
                        + id='backspace',  | 
                    |
| 1006 | 
                        + ),  | 
                    |
| 1007 | 
                        + pytest.param(  | 
                    |
| 1008 | 
                        +                {
                       | 
                    |
| 1009 | 
                        +                    'services': {
                       | 
                    |
| 1010 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 1011 | 
                        +                        'backspace\bin\bname': {'length': 10},
                       | 
                    |
| 1012 | 
                        + },  | 
                    |
| 1013 | 
                        + },  | 
                    |
| 1014 | 
                        + 'backspace\bin\bname',  | 
                    |
| 1015 | 
                        + 'serv',  | 
                    |
| 1016 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 1017 | 
                        + id='backspace_partial_other',  | 
                    |
| 1018 | 
                        + ),  | 
                    |
| 1019 | 
                        + pytest.param(  | 
                    |
| 1020 | 
                        +                {
                       | 
                    |
| 1021 | 
                        +                    'services': {
                       | 
                    |
| 1022 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 1023 | 
                        +                        'backspace\bin\bname': {'length': 10},
                       | 
                    |
| 1024 | 
                        + },  | 
                    |
| 1025 | 
                        + },  | 
                    |
| 1026 | 
                        + 'backspace\bin\bname',  | 
                    |
| 1027 | 
                        + 'back',  | 
                    |
| 1028 | 
                        +                frozenset({}),
                       | 
                    |
| 1029 | 
                        + id='backspace_partial_specific',  | 
                    |
| 1030 | 
                        + ),  | 
                    |
| 1031 | 
                        + pytest.param(  | 
                    |
| 1032 | 
                        +                {
                       | 
                    |
| 1033 | 
                        +                    'services': {
                       | 
                    |
| 1034 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 1035 | 
                        +                        'del\x7fin\x7fname': {'length': 10},
                       | 
                    |
| 1036 | 
                        + },  | 
                    |
| 1037 | 
                        + },  | 
                    |
| 1038 | 
                        + 'del\x7fin\x7fname',  | 
                    |
| 1039 | 
                        + '',  | 
                    |
| 1040 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 1041 | 
                        + id='del',  | 
                    |
| 1042 | 
                        + ),  | 
                    |
| 1043 | 
                        + pytest.param(  | 
                    |
| 1044 | 
                        +                {
                       | 
                    |
| 1045 | 
                        +                    'services': {
                       | 
                    |
| 1046 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 1047 | 
                        +                        'del\x7fin\x7fname': {'length': 10},
                       | 
                    |
| 1048 | 
                        + },  | 
                    |
| 1049 | 
                        + },  | 
                    |
| 1050 | 
                        + 'del\x7fin\x7fname',  | 
                    |
| 1051 | 
                        + 'serv',  | 
                    |
| 1052 | 
                        +                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 1053 | 
                        + id='del_partial_other',  | 
                    |
| 1054 | 
                        + ),  | 
                    |
| 1055 | 
                        + pytest.param(  | 
                    |
| 1056 | 
                        +                {
                       | 
                    |
| 1057 | 
                        +                    'services': {
                       | 
                    |
| 1058 | 
                        +                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 1059 | 
                        +                        'del\x7fin\x7fname': {'length': 10},
                       | 
                    |
| 1060 | 
                        + },  | 
                    |
| 1061 | 
                        + },  | 
                    |
| 1062 | 
                        + 'del\x7fin\x7fname',  | 
                    |
| 1063 | 
                        + 'del',  | 
                    |
| 1064 | 
                        +                frozenset({}),
                       | 
                    |
| 1065 | 
                        + id='del_partial_specific',  | 
                    |
| 1066 | 
                        + ),  | 
                    |
| 1067 | 
                        + ],  | 
                    |
| 1068 | 
                        + )  | 
                    |
| 1069 | 
                        + CONNECTION_HINTS = pytest.mark.parametrize(  | 
                    |
| 1070 | 
                        + 'conn_hint', ['none', 'socket', 'client']  | 
                    |
| 1071 | 
                        + )  | 
                    |
| 1072 | 
                        + SERVICE_NAME_EXCEPTIONS = pytest.mark.parametrize(  | 
                    |
| 1073 | 
                        + 'exc_type', [RuntimeError, KeyError, ValueError]  | 
                    |
| 1074 | 
                        + )  | 
                    |
| 1075 | 
                        + EXPORT_FORMAT_OPTIONS = pytest.mark.parametrize(  | 
                    |
| 1076 | 
                        + 'export_options',  | 
                    |
| 1077 | 
                        + [  | 
                    |
| 1078 | 
                        + [],  | 
                    |
| 1079 | 
                        + ['--export-as=sh'],  | 
                    |
| 1080 | 
                        + ],  | 
                    |
| 1081 | 
                        + )  | 
                    |
| 1082 | 
                        + FORCE_COLOR = pytest.mark.parametrize(  | 
                    |
| 1083 | 
                        + 'force_color',  | 
                    |
| 1084 | 
                        + [False, True],  | 
                    |
| 1085 | 
                        + ids=['noforce', 'force'],  | 
                    |
| 1086 | 
                        + )  | 
                    |
| 1087 | 
                        +    INCOMPLETE = pytest.mark.parametrize('incomplete', ['', 'partial'])
                       | 
                    |
| 1088 | 
                        + ISATTY = pytest.mark.parametrize(  | 
                    |
| 1089 | 
                        + 'isatty',  | 
                    |
| 1090 | 
                        + [False, True],  | 
                    |
| 1091 | 
                        + ids=['notty', 'tty'],  | 
                    |
| 1092 | 
                        + )  | 
                    |
| 1093 | 
                        + KEY_INDEX = pytest.mark.parametrize(  | 
                    |
| 1094 | 
                        +        'key_index', [1, 2, 3], ids=lambda i: f'index{i}'
                       | 
                    |
| 1095 | 
                        + )  | 
                    |
| 1096 | 
                        + UNICODE_NORMALIZATION_ERROR_INPUTS = pytest.mark.parametrize(  | 
                    |
| 1097 | 
                        + ['main_config', 'command_line', 'input', 'error_message'],  | 
                    |
| 1098 | 
                        + [  | 
                    |
| 1099 | 
                        + pytest.param(  | 
                    |
| 1100 | 
                        + textwrap.dedent(r"""  | 
                    |
| 1101 | 
                        + [vault]  | 
                    |
| 1102 | 
                        + default-unicode-normalization-form = 'XXX'  | 
                    |
| 1103 | 
                        + """),  | 
                    |
| 1104 | 
                        + ['--import', '-'],  | 
                    |
| 1105 | 
                        +                json.dumps({
                       | 
                    |
| 1106 | 
                        +                    'services': {
                       | 
                    |
| 1107 | 
                        + DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 1108 | 
                        +                        'with_normalization': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 1109 | 
                        + },  | 
                    |
| 1110 | 
                        + }),  | 
                    |
| 1111 | 
                        + (  | 
                    |
| 1112 | 
                        + "Invalid value 'XXX' for config key "  | 
                    |
| 1113 | 
                        + 'vault.default-unicode-normalization-form'  | 
                    |
| 1114 | 
                        + ),  | 
                    |
| 1115 | 
                        + id='global',  | 
                    |
| 1116 | 
                        + ),  | 
                    |
| 1117 | 
                        + pytest.param(  | 
                    |
| 1118 | 
                        + textwrap.dedent(r"""  | 
                    |
| 1119 | 
                        + [vault.unicode-normalization-form]  | 
                    |
| 1120 | 
                        + with_normalization = 'XXX'  | 
                    |
| 1121 | 
                        + """),  | 
                    |
| 1122 | 
                        + ['--import', '-'],  | 
                    |
| 1123 | 
                        +                json.dumps({
                       | 
                    |
| 1124 | 
                        +                    'services': {
                       | 
                    |
| 1125 | 
                        + DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 1126 | 
                        +                        'with_normalization': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 1127 | 
                        + },  | 
                    |
| 1128 | 
                        + }),  | 
                    |
| 1129 | 
                        + (  | 
                    |
| 1130 | 
                        + "Invalid value 'XXX' for config key "  | 
                    |
| 1131 | 
                        + 'vault.with_normalization.unicode-normalization-form'  | 
                    |
| 1132 | 
                        + ),  | 
                    |
| 1133 | 
                        + id='service',  | 
                    |
| 1134 | 
                        + ),  | 
                    |
| 1135 | 
                        + ],  | 
                    |
| 1136 | 
                        + )  | 
                    |
| 1137 | 
                        + UNICODE_NORMALIZATION_WARNING_INPUTS = pytest.mark.parametrize(  | 
                    |
| 1138 | 
                        + ['main_config', 'command_line', 'input', 'warning_message'],  | 
                    |
| 1139 | 
                        + [  | 
                    |
| 1140 | 
                        + pytest.param(  | 
                    |
| 1141 | 
                        + '',  | 
                    |
| 1142 | 
                        + ['--import', '-'],  | 
                    |
| 1143 | 
                        +                json.dumps({
                       | 
                    |
| 1144 | 
                        +                    'global': {'phrase': 'Du\u0308sseldorf'},
                       | 
                    |
| 1145 | 
                        +                    'services': {},
                       | 
                    |
| 1146 | 
                        + }),  | 
                    |
| 1147 | 
                        + 'The $.global passphrase is not NFC-normalized',  | 
                    |
| 1148 | 
                        + id='global-NFC',  | 
                    |
| 1149 | 
                        + ),  | 
                    |
| 1150 | 
                        + pytest.param(  | 
                    |
| 1151 | 
                        + '',  | 
                    |
| 1152 | 
                        + ['--import', '-'],  | 
                    |
| 1153 | 
                        +                json.dumps({
                       | 
                    |
| 1154 | 
                        +                    'services': {
                       | 
                    |
| 1155 | 
                        + DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 1156 | 
                        +                        'weird entry name': {'phrase': 'Du\u0308sseldorf'},
                       | 
                    |
| 1157 | 
                        + }  | 
                    |
| 1158 | 
                        + }),  | 
                    |
| 1159 | 
                        + (  | 
                    |
| 1160 | 
                        + 'The $.services["weird entry name"] passphrase '  | 
                    |
| 1161 | 
                        + 'is not NFC-normalized'  | 
                    |
| 1162 | 
                        + ),  | 
                    |
| 1163 | 
                        + id='service-weird-name-NFC',  | 
                    |
| 1164 | 
                        + ),  | 
                    |
| 1165 | 
                        + pytest.param(  | 
                    |
| 1166 | 
                        + '',  | 
                    |
| 1167 | 
                        + ['--config', '-p', '--', DUMMY_SERVICE],  | 
                    |
| 1168 | 
                        + 'Du\u0308sseldorf',  | 
                    |
| 1169 | 
                        + (  | 
                    |
| 1170 | 
                        +                    f'The $.services.{DUMMY_SERVICE} passphrase '
                       | 
                    |
| 1171 | 
                        + f'is not NFC-normalized'  | 
                    |
| 1172 | 
                        + ),  | 
                    |
| 1173 | 
                        + id='config-NFC',  | 
                    |
| 1174 | 
                        + ),  | 
                    |
| 1175 | 
                        + pytest.param(  | 
                    |
| 1176 | 
                        + '',  | 
                    |
| 1177 | 
                        + ['-p', '--', DUMMY_SERVICE],  | 
                    |
| 1178 | 
                        + 'Du\u0308sseldorf',  | 
                    |
| 1179 | 
                        + 'The interactive input passphrase is not NFC-normalized',  | 
                    |
| 1180 | 
                        + id='direct-input-NFC',  | 
                    |
| 1181 | 
                        + ),  | 
                    |
| 1182 | 
                        + pytest.param(  | 
                    |
| 1183 | 
                        + textwrap.dedent(r"""  | 
                    |
| 1184 | 
                        + [vault]  | 
                    |
| 1185 | 
                        + default-unicode-normalization-form = 'NFD'  | 
                    |
| 1186 | 
                        + """),  | 
                    |
| 1187 | 
                        + ['--import', '-'],  | 
                    |
| 1188 | 
                        +                json.dumps({
                       | 
                    |
| 1189 | 
                        +                    'global': {
                       | 
                    |
| 1190 | 
                        + 'phrase': 'D\u00fcsseldorf',  | 
                    |
| 1191 | 
                        + },  | 
                    |
| 1192 | 
                        +                    'services': {},
                       | 
                    |
| 1193 | 
                        + }),  | 
                    |
| 1194 | 
                        + 'The $.global passphrase is not NFD-normalized',  | 
                    |
| 1195 | 
                        + id='global-NFD',  | 
                    |
| 1196 | 
                        + ),  | 
                    |
| 1197 | 
                        + pytest.param(  | 
                    |
| 1198 | 
                        + textwrap.dedent(r"""  | 
                    |
| 1199 | 
                        + [vault]  | 
                    |
| 1200 | 
                        + default-unicode-normalization-form = 'NFD'  | 
                    |
| 1201 | 
                        + """),  | 
                    |
| 1202 | 
                        + ['--import', '-'],  | 
                    |
| 1203 | 
                        +                json.dumps({
                       | 
                    |
| 1204 | 
                        +                    'services': {
                       | 
                    |
| 1205 | 
                        + DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 1206 | 
                        +                        'weird entry name': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 1207 | 
                        + },  | 
                    |
| 1208 | 
                        + }),  | 
                    |
| 1209 | 
                        + (  | 
                    |
| 1210 | 
                        + 'The $.services["weird entry name"] passphrase '  | 
                    |
| 1211 | 
                        + 'is not NFD-normalized'  | 
                    |
| 1212 | 
                        + ),  | 
                    |
| 1213 | 
                        + id='service-weird-name-NFD',  | 
                    |
| 1214 | 
                        + ),  | 
                    |
| 1215 | 
                        + pytest.param(  | 
                    |
| 1216 | 
                        + textwrap.dedent(r"""  | 
                    |
| 1217 | 
                        + [vault.unicode-normalization-form]  | 
                    |
| 1218 | 
                        + 'weird entry name 2' = 'NFKD'  | 
                    |
| 1219 | 
                        + """),  | 
                    |
| 1220 | 
                        + ['--import', '-'],  | 
                    |
| 1221 | 
                        +                json.dumps({
                       | 
                    |
| 1222 | 
                        +                    'services': {
                       | 
                    |
| 1223 | 
                        + DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 1224 | 
                        +                        'weird entry name 1': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 1225 | 
                        +                        'weird entry name 2': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 1226 | 
                        + },  | 
                    |
| 1227 | 
                        + }),  | 
                    |
| 1228 | 
                        + (  | 
                    |
| 1229 | 
                        + 'The $.services["weird entry name 2"] passphrase '  | 
                    |
| 1230 | 
                        + 'is not NFKD-normalized'  | 
                    |
| 1231 | 
                        + ),  | 
                    |
| 1232 | 
                        + id='service-weird-name-2-NFKD',  | 
                    |
| 1233 | 
                        + ),  | 
                    |
| 1234 | 
                        + ],  | 
                    |
| 1235 | 
                        + )  | 
                    |
| 1236 | 
                        +    CONFIG_SETTING_MODE = pytest.mark.parametrize('mode', ['config', 'import'])
                       | 
                    |
| 1237 | 
                        + NO_COLOR = pytest.mark.parametrize(  | 
                    |
| 1238 | 
                        + 'no_color',  | 
                    |
| 1239 | 
                        + [False, True],  | 
                    |
| 1240 | 
                        + ids=['yescolor', 'nocolor'],  | 
                    |
| 1241 | 
                        + )  | 
                    |
| 1242 | 
                        + VAULT_CHARSET_OPTION = pytest.mark.parametrize(  | 
                    |
| 1243 | 
                        + 'option',  | 
                    |
| 1244 | 
                        + [  | 
                    |
| 1245 | 
                        + '--lower',  | 
                    |
| 1246 | 
                        + '--upper',  | 
                    |
| 1247 | 
                        + '--number',  | 
                    |
| 1248 | 
                        + '--space',  | 
                    |
| 1249 | 
                        + '--dash',  | 
                    |
| 1250 | 
                        + '--symbol',  | 
                    |
| 1251 | 
                        + '--repeat',  | 
                    |
| 1252 | 
                        + '--length',  | 
                    |
| 1253 | 
                        + ],  | 
                    |
| 1254 | 
                        + )  | 
                    |
| 1255 | 
                        + OPTION_COMBINATIONS_INCOMPATIBLE = pytest.mark.parametrize(  | 
                    |
| 1256 | 
                        + ['options', 'service'],  | 
                    |
| 1257 | 
                        + [  | 
                    |
| 1258 | 
                        + pytest.param(o.options, o.needs_service, id=' '.join(o.options))  | 
                    |
| 1259 | 
                        + for o in INTERESTING_OPTION_COMBINATIONS  | 
                    |
| 1260 | 
                        + if o.incompatible  | 
                    |
| 1261 | 
                        + ],  | 
                    |
| 1262 | 
                        + )  | 
                    |
| 1263 | 
                        + OPTION_COMBINATIONS_SERVICE_NEEDED = pytest.mark.parametrize(  | 
                    |
| 1264 | 
                        + ['options', 'service', 'input', 'check_success'],  | 
                    |
| 1265 | 
                        + [  | 
                    |
| 1266 | 
                        + pytest.param(  | 
                    |
| 1267 | 
                        + o.options,  | 
                    |
| 1268 | 
                        + o.needs_service,  | 
                    |
| 1269 | 
                        + o.input,  | 
                    |
| 1270 | 
                        + o.check_success,  | 
                    |
| 1271 | 
                        + id=' '.join(o.options),  | 
                    |
| 1272 | 
                        + )  | 
                    |
| 1273 | 
                        + for o in INTERESTING_OPTION_COMBINATIONS  | 
                    |
| 1274 | 
                        + if not o.incompatible  | 
                    |
| 1275 | 
                        + ],  | 
                    |
| 1276 | 
                        + )  | 
                    |
| 1277 | 
                        + COMPLETABLE_ITEMS = pytest.mark.parametrize(  | 
                    |
| 1278 | 
                        + ['partial', 'is_completable'],  | 
                    |
| 1279 | 
                        + [  | 
                    |
| 1280 | 
                        +            ('', True),
                       | 
                    |
| 1281 | 
                        + (DUMMY_SERVICE, True),  | 
                    |
| 1282 | 
                        +            ('a\bn', False),
                       | 
                    |
| 1283 | 
                        +            ('\b', False),
                       | 
                    |
| 1284 | 
                        +            ('\x00', False),
                       | 
                    |
| 1285 | 
                        +            ('\x20', True),
                       | 
                    |
| 1286 | 
                        +            ('\x7f', False),
                       | 
                    |
| 1287 | 
                        +            ('service with spaces', True),
                       | 
                    |
| 1288 | 
                        +            ('service\nwith\nnewlines', False),
                       | 
                    |
| 1289 | 
                        + ],  | 
                    |
| 1290 | 
                        + )  | 
                    |
| 1291 | 
                        + SHELL_FORMATTER = pytest.mark.parametrize(  | 
                    |
| 1292 | 
                        + ['shell', 'format_func'],  | 
                    |
| 1293 | 
                        + [  | 
                    |
| 1294 | 
                        +            pytest.param('bash', bash_format, id='bash'),
                       | 
                    |
| 1295 | 
                        +            pytest.param('fish', fish_format, id='fish'),
                       | 
                    |
| 1296 | 
                        +            pytest.param('zsh', zsh_format, id='zsh'),
                       | 
                    |
| 1297 | 
                        + ],  | 
                    |
| 1298 | 
                        + )  | 
                    |
| 1299 | 
                        + TRY_RACE_FREE_IMPLEMENTATION = pytest.mark.parametrize(  | 
                    |
| 1300 | 
                        + 'try_race_free_implementation', [True, False]  | 
                    |
| 1301 | 
                        + )  | 
                    |
| 1302 | 
                        + VALIDATION_FUNCTION_INPUT = pytest.mark.parametrize(  | 
                    |
| 1303 | 
                        + ['vfunc', 'input'],  | 
                    |
| 1304 | 
                        + [  | 
                    |
| 1305 | 
                        + (cli_machinery.validate_occurrence_constraint, 20),  | 
                    |
| 1306 | 
                        + (cli_machinery.validate_length, 20),  | 
                    |
| 1307 | 
                        + ],  | 
                    |
| 1308 | 
                        + )  | 
                    |
| 1309 | 
                        +  | 
                    |
| 1310 | 
                        +  | 
                    |
| 297 | 1311 | 
                        class TestAllCLI:  | 
                    
| 298 | 1312 | 
                        """Tests uniformly for all command-line interfaces."""  | 
                    
| 299 | 1313 | 
                         | 
                    
| ... | ... | 
                      @@ -424,56 +1438,8 @@ class TestAllCLI:  | 
                  
| 424 | 1438 | 
                        empty_stderr=True, output='Use $VISUAL or $EDITOR to configure'  | 
                    
| 425 | 1439 | 
                        ), 'expected clean exit, and option group epilog in help text'  | 
                    
| 426 | 1440 | 
                         | 
                    
| 427 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 428 | 
                        - ['command', 'non_eager_arguments'],  | 
                    |
| 429 | 
                        - [  | 
                    |
| 430 | 
                        - pytest.param(  | 
                    |
| 431 | 
                        - [],  | 
                    |
| 432 | 
                        - [],  | 
                    |
| 433 | 
                        - id='top-nothing',  | 
                    |
| 434 | 
                        - ),  | 
                    |
| 435 | 
                        - pytest.param(  | 
                    |
| 436 | 
                        - [],  | 
                    |
| 437 | 
                        - ['export'],  | 
                    |
| 438 | 
                        - id='top-export',  | 
                    |
| 439 | 
                        - ),  | 
                    |
| 440 | 
                        - pytest.param(  | 
                    |
| 441 | 
                        - ['export'],  | 
                    |
| 442 | 
                        - [],  | 
                    |
| 443 | 
                        - id='export-nothing',  | 
                    |
| 444 | 
                        - ),  | 
                    |
| 445 | 
                        - pytest.param(  | 
                    |
| 446 | 
                        - ['export'],  | 
                    |
| 447 | 
                        - ['vault'],  | 
                    |
| 448 | 
                        - id='export-vault',  | 
                    |
| 449 | 
                        - ),  | 
                    |
| 450 | 
                        - pytest.param(  | 
                    |
| 451 | 
                        - ['export', 'vault'],  | 
                    |
| 452 | 
                        - [],  | 
                    |
| 453 | 
                        - id='export-vault-nothing',  | 
                    |
| 454 | 
                        - ),  | 
                    |
| 455 | 
                        - pytest.param(  | 
                    |
| 456 | 
                        - ['export', 'vault'],  | 
                    |
| 457 | 
                        - ['--format', 'this-format-doesnt-exist'],  | 
                    |
| 458 | 
                        - id='export-vault-args',  | 
                    |
| 459 | 
                        - ),  | 
                    |
| 460 | 
                        - pytest.param(  | 
                    |
| 461 | 
                        - ['vault'],  | 
                    |
| 462 | 
                        - [],  | 
                    |
| 463 | 
                        - id='vault-nothing',  | 
                    |
| 464 | 
                        - ),  | 
                    |
| 465 | 
                        - pytest.param(  | 
                    |
| 466 | 
                        - ['vault'],  | 
                    |
| 467 | 
                        - ['--export', './'],  | 
                    |
| 468 | 
                        - id='vault-args',  | 
                    |
| 469 | 
                        - ),  | 
                    |
| 470 | 
                        - ],  | 
                    |
| 471 | 
                        - )  | 
                    |
| 472 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 473 | 
                        - 'arguments',  | 
                    |
| 474 | 
                        - [['--help'], ['--version']],  | 
                    |
| 475 | 
                        - ids=['help', 'version'],  | 
                    |
| 476 | 
                        - )  | 
                    |
| 1441 | 
                        + @Parametrizations.COMMAND_NON_EAGER_ARGUMENTS.value  | 
                    |
| 1442 | 
                        + @Parametrizations.EAGER_ARGUMENTS.value  | 
                    |
| 477 | 1443 | 
                        def test_200_eager_options(  | 
                    
| 478 | 1444 | 
                        self,  | 
                    
| 479 | 1445 | 
                        command: list[str],  | 
                    
| ... | ... | 
                      @@ -501,31 +1467,10 @@ class TestAllCLI:  | 
                  
| 501 | 1467 | 
                        result = tests.ReadableResult.parse(result_)  | 
                    
| 502 | 1468 | 
                        assert result.clean_exit(empty_stderr=True), 'expected clean exit'  | 
                    
| 503 | 1469 | 
                         | 
                    
| 504 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 505 | 
                        - 'no_color',  | 
                    |
| 506 | 
                        - [False, True],  | 
                    |
| 507 | 
                        - ids=['yescolor', 'nocolor'],  | 
                    |
| 508 | 
                        - )  | 
                    |
| 509 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 510 | 
                        - 'force_color',  | 
                    |
| 511 | 
                        - [False, True],  | 
                    |
| 512 | 
                        - ids=['noforce', 'force'],  | 
                    |
| 513 | 
                        - )  | 
                    |
| 514 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 515 | 
                        - 'isatty',  | 
                    |
| 516 | 
                        - [False, True],  | 
                    |
| 517 | 
                        - ids=['notty', 'tty'],  | 
                    |
| 518 | 
                        - )  | 
                    |
| 519 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 520 | 
                        - ['command_line', 'input'],  | 
                    |
| 521 | 
                        - [  | 
                    |
| 522 | 
                        - (  | 
                    |
| 523 | 
                        - ['vault', '--import', '-'],  | 
                    |
| 524 | 
                        -                '{"services": {"": {"length": 20}}}',
                       | 
                    |
| 525 | 
                        - ),  | 
                    |
| 526 | 
                        - ],  | 
                    |
| 527 | 
                        - ids=['cmd'],  | 
                    |
| 528 | 
                        - )  | 
                    |
| 1470 | 
                        + @Parametrizations.NO_COLOR.value  | 
                    |
| 1471 | 
                        + @Parametrizations.FORCE_COLOR.value  | 
                    |
| 1472 | 
                        + @Parametrizations.ISATTY.value  | 
                    |
| 1473 | 
                        + @Parametrizations.COLORFUL_COMMAND_INPUT.value  | 
                    |
| 529 | 1474 | 
                        def test_201_no_color_force_color(  | 
                    
| 530 | 1475 | 
                        self,  | 
                    
| 531 | 1476 | 
                        no_color: bool,  | 
                    
| ... | ... | 
                      @@ -633,9 +1578,7 @@ class TestCLI:  | 
                  
| 633 | 1578 | 
                        'expected clean exit, and version in help text'  | 
                    
| 634 | 1579 | 
                        )  | 
                    
| 635 | 1580 | 
                         | 
                    
| 636 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 637 | 
                        - 'charset_name', ['lower', 'upper', 'number', 'space', 'dash', 'symbol']  | 
                    |
| 638 | 
                        - )  | 
                    |
| 1581 | 
                        + @Parametrizations.CHARSET_NAME.value  | 
                    |
| 639 | 1582 | 
                        def test_201_disable_character_set(  | 
                    
| 640 | 1583 | 
                        self,  | 
                    
| 641 | 1584 | 
                        charset_name: str,  | 
                    
| ... | ... | 
                      @@ -707,30 +1650,7 @@ class TestCLI:  | 
                  
| 707 | 1650 | 
                                         f'at position {i}: {result.output!r}'
                       | 
                    
| 708 | 1651 | 
                        )  | 
                    
| 709 | 1652 | 
                         | 
                    
| 710 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 711 | 
                        - 'config',  | 
                    |
| 712 | 
                        - [  | 
                    |
| 713 | 
                        - pytest.param(  | 
                    |
| 714 | 
                        -                {
                       | 
                    |
| 715 | 
                        -                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 716 | 
                        -                    'services': {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS},
                       | 
                    |
| 717 | 
                        - },  | 
                    |
| 718 | 
                        - id='global',  | 
                    |
| 719 | 
                        - ),  | 
                    |
| 720 | 
                        - pytest.param(  | 
                    |
| 721 | 
                        -                {
                       | 
                    |
| 722 | 
                        -                    'global': {'phrase': DUMMY_PASSPHRASE.rstrip('\n')},
                       | 
                    |
| 723 | 
                        -                    'services': {
                       | 
                    |
| 724 | 
                        -                        DUMMY_SERVICE: {
                       | 
                    |
| 725 | 
                        - 'key': DUMMY_KEY1_B64,  | 
                    |
| 726 | 
                        - **DUMMY_CONFIG_SETTINGS,  | 
                    |
| 727 | 
                        - }  | 
                    |
| 728 | 
                        - },  | 
                    |
| 729 | 
                        - },  | 
                    |
| 730 | 
                        - id='service',  | 
                    |
| 731 | 
                        - ),  | 
                    |
| 732 | 
                        - ],  | 
                    |
| 733 | 
                        - )  | 
                    |
| 1653 | 
                        + @Parametrizations.CONFIG_WITH_KEY.value  | 
                    |
| 734 | 1654 | 
                        def test_204a_key_from_config(  | 
                    
| 735 | 1655 | 
                        self,  | 
                    
| 736 | 1656 | 
                        config: _types.VaultConfig,  | 
                    
| ... | ... | 
                      @@ -811,30 +1731,8 @@ class TestCLI:  | 
                  
| 811 | 1731 | 
                        'expected known output'  | 
                    
| 812 | 1732 | 
                        )  | 
                    
| 813 | 1733 | 
                         | 
                    
| 814 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 815 | 
                        - 'config',  | 
                    |
| 816 | 
                        - [  | 
                    |
| 817 | 
                        - pytest.param(  | 
                    |
| 818 | 
                        -                {
                       | 
                    |
| 819 | 
                        -                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 820 | 
                        -                    'services': {DUMMY_SERVICE: {}},
                       | 
                    |
| 821 | 
                        - },  | 
                    |
| 822 | 
                        - id='global_config',  | 
                    |
| 823 | 
                        - ),  | 
                    |
| 824 | 
                        - pytest.param(  | 
                    |
| 825 | 
                        -                {'services': {DUMMY_SERVICE: {'key': DUMMY_KEY2_B64}}},
                       | 
                    |
| 826 | 
                        - id='service_config',  | 
                    |
| 827 | 
                        - ),  | 
                    |
| 828 | 
                        - pytest.param(  | 
                    |
| 829 | 
                        -                {
                       | 
                    |
| 830 | 
                        -                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 831 | 
                        -                    'services': {DUMMY_SERVICE: {'key': DUMMY_KEY2_B64}},
                       | 
                    |
| 832 | 
                        - },  | 
                    |
| 833 | 
                        - id='full_config',  | 
                    |
| 834 | 
                        - ),  | 
                    |
| 835 | 
                        - ],  | 
                    |
| 836 | 
                        - )  | 
                    |
| 837 | 
                        -    @pytest.mark.parametrize('key_index', [1, 2, 3], ids=lambda i: f'index{i}')
                       | 
                    |
| 1734 | 
                        + @Parametrizations.BASE_CONFIG_WITH_KEY_VARIATIONS.value  | 
                    |
| 1735 | 
                        + @Parametrizations.KEY_INDEX.value  | 
                    |
| 838 | 1736 | 
                        def test_204c_key_override_on_command_line(  | 
                    
| 839 | 1737 | 
                        self,  | 
                    
| 840 | 1738 | 
                        running_ssh_agent: tests.RunningSSHAgentInfo,  | 
                    
| ... | ... | 
                      @@ -920,39 +1818,7 @@ class TestCLI:  | 
                  
| 920 | 1818 | 
                        'expected known output'  | 
                    
| 921 | 1819 | 
                        )  | 
                    
| 922 | 1820 | 
                         | 
                    
| 923 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 924 | 
                        - ['config', 'command_line'],  | 
                    |
| 925 | 
                        - [  | 
                    |
| 926 | 
                        - pytest.param(  | 
                    |
| 927 | 
                        -                {
                       | 
                    |
| 928 | 
                        -                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 929 | 
                        -                    'services': {},
                       | 
                    |
| 930 | 
                        - },  | 
                    |
| 931 | 
                        - ['--config', '-p'],  | 
                    |
| 932 | 
                        - id='global',  | 
                    |
| 933 | 
                        - ),  | 
                    |
| 934 | 
                        - pytest.param(  | 
                    |
| 935 | 
                        -                {
                       | 
                    |
| 936 | 
                        -                    'services': {
                       | 
                    |
| 937 | 
                        -                        DUMMY_SERVICE: {
                       | 
                    |
| 938 | 
                        - 'key': DUMMY_KEY1_B64,  | 
                    |
| 939 | 
                        - **DUMMY_CONFIG_SETTINGS,  | 
                    |
| 940 | 
                        - },  | 
                    |
| 941 | 
                        - },  | 
                    |
| 942 | 
                        - },  | 
                    |
| 943 | 
                        - ['--config', '-p', '--', DUMMY_SERVICE],  | 
                    |
| 944 | 
                        - id='service',  | 
                    |
| 945 | 
                        - ),  | 
                    |
| 946 | 
                        - pytest.param(  | 
                    |
| 947 | 
                        -                {
                       | 
                    |
| 948 | 
                        -                    'global': {'key': DUMMY_KEY1_B64},
                       | 
                    |
| 949 | 
                        -                    'services': {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy()},
                       | 
                    |
| 950 | 
                        - },  | 
                    |
| 951 | 
                        - ['--config', '-p', '--', DUMMY_SERVICE],  | 
                    |
| 952 | 
                        - id='service-over-global',  | 
                    |
| 953 | 
                        - ),  | 
                    |
| 954 | 
                        - ],  | 
                    |
| 955 | 
                        - )  | 
                    |
| 1821 | 
                        + @Parametrizations.KEY_OVERRIDING_IN_CONFIG.value  | 
                    |
| 956 | 1822 | 
                        def test_206_setting_phrase_thus_overriding_key_in_config(  | 
                    
| 957 | 1823 | 
                        self,  | 
                    
| 958 | 1824 | 
                        running_ssh_agent: tests.RunningSSHAgentInfo,  | 
                    
| ... | ... | 
                      @@ -1003,19 +1869,7 @@ class TestCLI:  | 
                  
| 1003 | 1869 | 
                        map(is_harmless_config_import_warning, caplog.record_tuples)  | 
                    
| 1004 | 1870 | 
                        ), 'unexpected error output'  | 
                    
| 1005 | 1871 | 
                         | 
                    
| 1006 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1007 | 
                        - 'option',  | 
                    |
| 1008 | 
                        - [  | 
                    |
| 1009 | 
                        - '--lower',  | 
                    |
| 1010 | 
                        - '--upper',  | 
                    |
| 1011 | 
                        - '--number',  | 
                    |
| 1012 | 
                        - '--space',  | 
                    |
| 1013 | 
                        - '--dash',  | 
                    |
| 1014 | 
                        - '--symbol',  | 
                    |
| 1015 | 
                        - '--repeat',  | 
                    |
| 1016 | 
                        - '--length',  | 
                    |
| 1017 | 
                        - ],  | 
                    |
| 1018 | 
                        - )  | 
                    |
| 1872 | 
                        + @Parametrizations.VAULT_CHARSET_OPTION.value  | 
                    |
| 1019 | 1873 | 
                        def test_210_invalid_argument_range(  | 
                    
| 1020 | 1874 | 
                        self,  | 
                    
| 1021 | 1875 | 
                        option: str,  | 
                    
| ... | ... | 
                      @@ -1045,20 +1899,7 @@ class TestCLI:  | 
                  
| 1045 | 1899 | 
                        'expected error exit and known error message'  | 
                    
| 1046 | 1900 | 
                        )  | 
                    
| 1047 | 1901 | 
                         | 
                    
| 1048 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1049 | 
                        - ['options', 'service', 'input', 'check_success'],  | 
                    |
| 1050 | 
                        - [  | 
                    |
| 1051 | 
                        - pytest.param(  | 
                    |
| 1052 | 
                        - o.options,  | 
                    |
| 1053 | 
                        - o.needs_service,  | 
                    |
| 1054 | 
                        - o.input,  | 
                    |
| 1055 | 
                        - o.check_success,  | 
                    |
| 1056 | 
                        - id=' '.join(o.options),  | 
                    |
| 1057 | 
                        - )  | 
                    |
| 1058 | 
                        - for o in INTERESTING_OPTION_COMBINATIONS  | 
                    |
| 1059 | 
                        - if not o.incompatible  | 
                    |
| 1060 | 
                        - ],  | 
                    |
| 1061 | 
                        - )  | 
                    |
| 1902 | 
                        + @Parametrizations.OPTION_COMBINATIONS_SERVICE_NEEDED.value  | 
                    |
| 1062 | 1903 | 
                        def test_211_service_needed(  | 
                    
| 1063 | 1904 | 
                        self,  | 
                    
| 1064 | 1905 | 
                        options: list[str],  | 
                    
| ... | ... | 
                      @@ -1197,14 +2038,7 @@ class TestCLI:  | 
                  
| 1197 | 2038 | 
                                         'services': {'': {'length': 40}},
                       | 
                    
| 1198 | 2039 | 
                        }, 'requested configuration change was not applied'  | 
                    
| 1199 | 2040 | 
                         | 
                    
| 1200 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1201 | 
                        - ['options', 'service'],  | 
                    |
| 1202 | 
                        - [  | 
                    |
| 1203 | 
                        - pytest.param(o.options, o.needs_service, id=' '.join(o.options))  | 
                    |
| 1204 | 
                        - for o in INTERESTING_OPTION_COMBINATIONS  | 
                    |
| 1205 | 
                        - if o.incompatible  | 
                    |
| 1206 | 
                        - ],  | 
                    |
| 1207 | 
                        - )  | 
                    |
| 2041 | 
                        + @Parametrizations.OPTION_COMBINATIONS_INCOMPATIBLE.value  | 
                    |
| 1208 | 2042 | 
                        def test_212_incompatible_options(  | 
                    
| 1209 | 2043 | 
                        self,  | 
                    
| 1210 | 2044 | 
                        options: list[str],  | 
                    
| ... | ... | 
                      @@ -1234,14 +2068,7 @@ class TestCLI:  | 
                  
| 1234 | 2068 | 
                        'expected error exit and known error message'  | 
                    
| 1235 | 2069 | 
                        )  | 
                    
| 1236 | 2070 | 
                         | 
                    
| 1237 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1238 | 
                        - 'config',  | 
                    |
| 1239 | 
                        - [  | 
                    |
| 1240 | 
                        - conf.config  | 
                    |
| 1241 | 
                        - for conf in TEST_CONFIGS  | 
                    |
| 1242 | 
                        - if tests.is_valid_test_config(conf)  | 
                    |
| 1243 | 
                        - ],  | 
                    |
| 1244 | 
                        - )  | 
                    |
| 2071 | 
                        + @Parametrizations.VALID_TEST_CONFIGS.value  | 
                    |
| 1245 | 2072 | 
                        def test_213_import_config_success(  | 
                    
| 1246 | 2073 | 
                        self,  | 
                    
| 1247 | 2074 | 
                        caplog: pytest.LogCaptureFixture,  | 
                    
| ... | ... | 
                      @@ -1426,13 +2253,7 @@ class TestCLI:  | 
                  
| 1426 | 2253 | 
                        'expected error exit and known error message'  | 
                    
| 1427 | 2254 | 
                        )  | 
                    
| 1428 | 2255 | 
                         | 
                    
| 1429 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1430 | 
                        - 'export_options',  | 
                    |
| 1431 | 
                        - [  | 
                    |
| 1432 | 
                        - [],  | 
                    |
| 1433 | 
                        - ['--export-as=sh'],  | 
                    |
| 1434 | 
                        - ],  | 
                    |
| 1435 | 
                        - )  | 
                    |
| 2256 | 
                        + @Parametrizations.EXPORT_FORMAT_OPTIONS.value  | 
                    |
| 1436 | 2257 | 
                        def test_214_export_settings_no_stored_settings(  | 
                    
| 1437 | 2258 | 
                        self,  | 
                    
| 1438 | 2259 | 
                        export_options: list[str],  | 
                    
| ... | ... | 
                      @@ -1463,15 +2284,9 @@ class TestCLI:  | 
                  
| 1463 | 2284 | 
                        catch_exceptions=False,  | 
                    
| 1464 | 2285 | 
                        )  | 
                    
| 1465 | 2286 | 
                        result = tests.ReadableResult.parse(result_)  | 
                    
| 1466 | 
                        - assert result.clean_exit(empty_stderr=True), 'expected clean exit'  | 
                    |
| 1467 | 
                        -  | 
                    |
| 1468 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1469 | 
                        - 'export_options',  | 
                    |
| 1470 | 
                        - [  | 
                    |
| 1471 | 
                        - [],  | 
                    |
| 1472 | 
                        - ['--export-as=sh'],  | 
                    |
| 1473 | 
                        - ],  | 
                    |
| 1474 | 
                        - )  | 
                    |
| 2287 | 
                        + assert result.clean_exit(empty_stderr=True), 'expected clean exit'  | 
                    |
| 2288 | 
                        +  | 
                    |
| 2289 | 
                        + @Parametrizations.EXPORT_FORMAT_OPTIONS.value  | 
                    |
| 1475 | 2290 | 
                        def test_214a_export_settings_bad_stored_config(  | 
                    
| 1476 | 2291 | 
                        self,  | 
                    
| 1477 | 2292 | 
                        export_options: list[str],  | 
                    
| ... | ... | 
                      @@ -1501,13 +2316,7 @@ class TestCLI:  | 
                  
| 1501 | 2316 | 
                        'expected error exit and known error message'  | 
                    
| 1502 | 2317 | 
                        )  | 
                    
| 1503 | 2318 | 
                         | 
                    
| 1504 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1505 | 
                        - 'export_options',  | 
                    |
| 1506 | 
                        - [  | 
                    |
| 1507 | 
                        - [],  | 
                    |
| 1508 | 
                        - ['--export-as=sh'],  | 
                    |
| 1509 | 
                        - ],  | 
                    |
| 1510 | 
                        - )  | 
                    |
| 2319 | 
                        + @Parametrizations.EXPORT_FORMAT_OPTIONS.value  | 
                    |
| 1511 | 2320 | 
                        def test_214b_export_settings_not_a_file(  | 
                    
| 1512 | 2321 | 
                        self,  | 
                    
| 1513 | 2322 | 
                        export_options: list[str],  | 
                    
| ... | ... | 
                      @@ -1539,13 +2348,7 @@ class TestCLI:  | 
                  
| 1539 | 2348 | 
                        'expected error exit and known error message'  | 
                    
| 1540 | 2349 | 
                        )  | 
                    
| 1541 | 2350 | 
                         | 
                    
| 1542 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1543 | 
                        - 'export_options',  | 
                    |
| 1544 | 
                        - [  | 
                    |
| 1545 | 
                        - [],  | 
                    |
| 1546 | 
                        - ['--export-as=sh'],  | 
                    |
| 1547 | 
                        - ],  | 
                    |
| 1548 | 
                        - )  | 
                    |
| 2351 | 
                        + @Parametrizations.EXPORT_FORMAT_OPTIONS.value  | 
                    |
| 1549 | 2352 | 
                        def test_214c_export_settings_target_not_a_file(  | 
                    
| 1550 | 2353 | 
                        self,  | 
                    
| 1551 | 2354 | 
                        export_options: list[str],  | 
                    
| ... | ... | 
                      @@ -1575,13 +2378,7 @@ class TestCLI:  | 
                  
| 1575 | 2378 | 
                        'expected error exit and known error message'  | 
                    
| 1576 | 2379 | 
                        )  | 
                    
| 1577 | 2380 | 
                         | 
                    
| 1578 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1579 | 
                        - 'export_options',  | 
                    |
| 1580 | 
                        - [  | 
                    |
| 1581 | 
                        - [],  | 
                    |
| 1582 | 
                        - ['--export-as=sh'],  | 
                    |
| 1583 | 
                        - ],  | 
                    |
| 1584 | 
                        - )  | 
                    |
| 2381 | 
                        + @Parametrizations.EXPORT_FORMAT_OPTIONS.value  | 
                    |
| 1585 | 2382 | 
                        def test_214d_export_settings_settings_directory_not_a_directory(  | 
                    
| 1586 | 2383 | 
                        self,  | 
                    
| 1587 | 2384 | 
                        export_options: list[str],  | 
                    
| ... | ... | 
                      @@ -1759,53 +2556,7 @@ contents go here  | 
                  
| 1759 | 2556 | 
                        config = json.load(infile)  | 
                    
| 1760 | 2557 | 
                                     assert config == {'global': {'phrase': 'abc'}, 'services': {}}
                       | 
                    
| 1761 | 2558 | 
                         | 
                    
| 1762 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1763 | 
                        - ['command_line', 'input', 'result_config'],  | 
                    |
| 1764 | 
                        - [  | 
                    |
| 1765 | 
                        - pytest.param(  | 
                    |
| 1766 | 
                        - ['--phrase'],  | 
                    |
| 1767 | 
                        - 'my passphrase\n',  | 
                    |
| 1768 | 
                        -                {'global': {'phrase': 'my passphrase'}, 'services': {}},
                       | 
                    |
| 1769 | 
                        - id='phrase',  | 
                    |
| 1770 | 
                        - ),  | 
                    |
| 1771 | 
                        - pytest.param(  | 
                    |
| 1772 | 
                        - ['--key'],  | 
                    |
| 1773 | 
                        - '1\n',  | 
                    |
| 1774 | 
                        -                {
                       | 
                    |
| 1775 | 
                        -                    'global': {'key': DUMMY_KEY1_B64, 'phrase': 'abc'},
                       | 
                    |
| 1776 | 
                        -                    'services': {},
                       | 
                    |
| 1777 | 
                        - },  | 
                    |
| 1778 | 
                        - id='key',  | 
                    |
| 1779 | 
                        - ),  | 
                    |
| 1780 | 
                        - pytest.param(  | 
                    |
| 1781 | 
                        - ['--phrase', '--', 'sv'],  | 
                    |
| 1782 | 
                        - 'my passphrase\n',  | 
                    |
| 1783 | 
                        -                {
                       | 
                    |
| 1784 | 
                        -                    'global': {'phrase': 'abc'},
                       | 
                    |
| 1785 | 
                        -                    'services': {'sv': {'phrase': 'my passphrase'}},
                       | 
                    |
| 1786 | 
                        - },  | 
                    |
| 1787 | 
                        - id='phrase-sv',  | 
                    |
| 1788 | 
                        - ),  | 
                    |
| 1789 | 
                        - pytest.param(  | 
                    |
| 1790 | 
                        - ['--key', '--', 'sv'],  | 
                    |
| 1791 | 
                        - '1\n',  | 
                    |
| 1792 | 
                        -                {
                       | 
                    |
| 1793 | 
                        -                    'global': {'phrase': 'abc'},
                       | 
                    |
| 1794 | 
                        -                    'services': {'sv': {'key': DUMMY_KEY1_B64}},
                       | 
                    |
| 1795 | 
                        - },  | 
                    |
| 1796 | 
                        - id='key-sv',  | 
                    |
| 1797 | 
                        - ),  | 
                    |
| 1798 | 
                        - pytest.param(  | 
                    |
| 1799 | 
                        - ['--key', '--length', '15', '--', 'sv'],  | 
                    |
| 1800 | 
                        - '1\n',  | 
                    |
| 1801 | 
                        -                {
                       | 
                    |
| 1802 | 
                        -                    'global': {'phrase': 'abc'},
                       | 
                    |
| 1803 | 
                        -                    'services': {'sv': {'key': DUMMY_KEY1_B64, 'length': 15}},
                       | 
                    |
| 1804 | 
                        - },  | 
                    |
| 1805 | 
                        - id='key-length-sv',  | 
                    |
| 1806 | 
                        - ),  | 
                    |
| 1807 | 
                        - ],  | 
                    |
| 1808 | 
                        - )  | 
                    |
| 2559 | 
                        + @Parametrizations.CONFIG_EDITING_VIA_CONFIG_FLAG.value  | 
                    |
| 1809 | 2560 | 
                        def test_224_store_config_good(  | 
                    
| 1810 | 2561 | 
                        self,  | 
                    
| 1811 | 2562 | 
                        command_line: list[str],  | 
                    
| ... | ... | 
                      @@ -1845,35 +2596,7 @@ contents go here  | 
                  
| 1845 | 2596 | 
                        'stored config does not match expectation'  | 
                    
| 1846 | 2597 | 
                        )  | 
                    
| 1847 | 2598 | 
                         | 
                    
| 1848 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 1849 | 
                        - ['command_line', 'input', 'err_text'],  | 
                    |
| 1850 | 
                        - [  | 
                    |
| 1851 | 
                        - pytest.param(  | 
                    |
| 1852 | 
                        - [],  | 
                    |
| 1853 | 
                        - '',  | 
                    |
| 1854 | 
                        - 'Cannot update the global settings without any given settings',  | 
                    |
| 1855 | 
                        - id='None',  | 
                    |
| 1856 | 
                        - ),  | 
                    |
| 1857 | 
                        - pytest.param(  | 
                    |
| 1858 | 
                        - ['--', 'sv'],  | 
                    |
| 1859 | 
                        - '',  | 
                    |
| 1860 | 
                        - 'Cannot update the service-specific settings without any given settings',  | 
                    |
| 1861 | 
                        - id='None-sv',  | 
                    |
| 1862 | 
                        - ),  | 
                    |
| 1863 | 
                        - pytest.param(  | 
                    |
| 1864 | 
                        - ['--phrase', '--', 'sv'],  | 
                    |
| 1865 | 
                        - '',  | 
                    |
| 1866 | 
                        - 'No passphrase was given',  | 
                    |
| 1867 | 
                        - id='phrase-sv',  | 
                    |
| 1868 | 
                        - ),  | 
                    |
| 1869 | 
                        - pytest.param(  | 
                    |
| 1870 | 
                        - ['--key'],  | 
                    |
| 1871 | 
                        - '',  | 
                    |
| 1872 | 
                        - 'No SSH key was selected',  | 
                    |
| 1873 | 
                        - id='key-sv',  | 
                    |
| 1874 | 
                        - ),  | 
                    |
| 1875 | 
                        - ],  | 
                    |
| 1876 | 
                        - )  | 
                    |
| 2599 | 
                        + @Parametrizations.CONFIG_EDITING_VIA_CONFIG_FLAG_FAILURES.value  | 
                    |
| 1877 | 2600 | 
                        def test_225_store_config_fail(  | 
                    
| 1878 | 2601 | 
                        self,  | 
                    
| 1879 | 2602 | 
                        command_line: list[str],  | 
                    
| ... | ... | 
                      @@ -2000,7 +2723,7 @@ contents go here  | 
                  
| 2000 | 2723 | 
                        'expected error exit and known error message'  | 
                    
| 2001 | 2724 | 
                        )  | 
                    
| 2002 | 2725 | 
                         | 
                    
| 2003 | 
                        -    @pytest.mark.parametrize('try_race_free_implementation', [True, False])
                       | 
                    |
| 2726 | 
                        + @Parametrizations.TRY_RACE_FREE_IMPLEMENTATION.value  | 
                    |
| 2004 | 2727 | 
                        def test_225d_store_config_fail_manual_read_only_file(  | 
                    
| 2005 | 2728 | 
                        self,  | 
                    
| 2006 | 2729 | 
                        try_race_free_implementation: bool,  | 
                    
| ... | ... | 
                      @@ -2317,170 +3040,72 @@ contents go here  | 
                  
| 2317 | 3040 | 
                        # with-statements.  | 
                    
| 2318 | 3041 | 
                        # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9  | 
                    
| 2319 | 3042 | 
                        with contextlib.ExitStack() as stack:  | 
                    
| 2320 | 
                        - monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())  | 
                    |
| 2321 | 
                        - stack.enter_context(  | 
                    |
| 2322 | 
                        - tests.isolated_config(  | 
                    |
| 2323 | 
                        - monkeypatch=monkeypatch,  | 
                    |
| 2324 | 
                        - runner=runner,  | 
                    |
| 2325 | 
                        - )  | 
                    |
| 2326 | 
                        - )  | 
                    |
| 2327 | 
                        - save_config_ = cli_helpers.save_config  | 
                    |
| 2328 | 
                        -  | 
                    |
| 2329 | 
                        - def obstruct_config_saving(*args: Any, **kwargs: Any) -> Any:  | 
                    |
| 2330 | 
                        - config_dir = cli_helpers.config_filename(subsystem=None)  | 
                    |
| 2331 | 
                        - with contextlib.suppress(FileNotFoundError):  | 
                    |
| 2332 | 
                        - shutil.rmtree(config_dir)  | 
                    |
| 2333 | 
                        -                config_dir.write_text('Obstruction!!\n')
                       | 
                    |
| 2334 | 
                        - monkeypatch.setattr(cli_helpers, 'save_config', save_config_)  | 
                    |
| 2335 | 
                        - return save_config_(*args, **kwargs)  | 
                    |
| 2336 | 
                        -  | 
                    |
| 2337 | 
                        - monkeypatch.setattr(  | 
                    |
| 2338 | 
                        - cli_helpers, 'save_config', obstruct_config_saving  | 
                    |
| 2339 | 
                        - )  | 
                    |
| 2340 | 
                        - result_ = runner.invoke(  | 
                    |
| 2341 | 
                        - cli.derivepassphrase_vault,  | 
                    |
| 2342 | 
                        - ['--config', '-p'],  | 
                    |
| 2343 | 
                        - catch_exceptions=False,  | 
                    |
| 2344 | 
                        - input='abc\n',  | 
                    |
| 2345 | 
                        - )  | 
                    |
| 2346 | 
                        - result = tests.ReadableResult.parse(result_)  | 
                    |
| 2347 | 
                        - assert result.error_exit(error='Cannot store vault settings:'), (  | 
                    |
| 2348 | 
                        - 'expected error exit and known error message'  | 
                    |
| 2349 | 
                        - )  | 
                    |
| 2350 | 
                        -  | 
                    |
| 2351 | 
                        - def test_230b_store_config_custom_error(  | 
                    |
| 2352 | 
                        - self,  | 
                    |
| 2353 | 
                        - ) -> None:  | 
                    |
| 2354 | 
                        - """Storing the configuration reacts even to weird errors."""  | 
                    |
| 2355 | 
                        - runner = click.testing.CliRunner(mix_stderr=False)  | 
                    |
| 2356 | 
                        - # TODO(the-13th-letter): Rewrite using parenthesized  | 
                    |
| 2357 | 
                        - # with-statements.  | 
                    |
| 2358 | 
                        - # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9  | 
                    |
| 2359 | 
                        - with contextlib.ExitStack() as stack:  | 
                    |
| 2360 | 
                        - monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())  | 
                    |
| 2361 | 
                        - stack.enter_context(  | 
                    |
| 2362 | 
                        - tests.isolated_config(  | 
                    |
| 2363 | 
                        - monkeypatch=monkeypatch,  | 
                    |
| 2364 | 
                        - runner=runner,  | 
                    |
| 2365 | 
                        - )  | 
                    |
| 2366 | 
                        - )  | 
                    |
| 2367 | 
                        - custom_error = 'custom error message'  | 
                    |
| 2368 | 
                        -  | 
                    |
| 2369 | 
                        - def raiser(config: Any) -> None:  | 
                    |
| 2370 | 
                        - del config  | 
                    |
| 2371 | 
                        - raise RuntimeError(custom_error)  | 
                    |
| 2372 | 
                        -  | 
                    |
| 2373 | 
                        - monkeypatch.setattr(cli_helpers, 'save_config', raiser)  | 
                    |
| 2374 | 
                        - result_ = runner.invoke(  | 
                    |
| 2375 | 
                        - cli.derivepassphrase_vault,  | 
                    |
| 2376 | 
                        - ['--config', '-p'],  | 
                    |
| 2377 | 
                        - catch_exceptions=False,  | 
                    |
| 2378 | 
                        - input='abc\n',  | 
                    |
| 2379 | 
                        - )  | 
                    |
| 2380 | 
                        - result = tests.ReadableResult.parse(result_)  | 
                    |
| 2381 | 
                        - assert result.error_exit(error=custom_error), (  | 
                    |
| 2382 | 
                        - 'expected error exit and known error message'  | 
                    |
| 2383 | 
                        - )  | 
                    |
| 2384 | 
                        -  | 
                    |
| 2385 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 2386 | 
                        - ['main_config', 'command_line', 'input', 'warning_message'],  | 
                    |
| 2387 | 
                        - [  | 
                    |
| 2388 | 
                        - pytest.param(  | 
                    |
| 2389 | 
                        - '',  | 
                    |
| 2390 | 
                        - ['--import', '-'],  | 
                    |
| 2391 | 
                        -                json.dumps({
                       | 
                    |
| 2392 | 
                        -                    'global': {'phrase': 'Du\u0308sseldorf'},
                       | 
                    |
| 2393 | 
                        -                    'services': {},
                       | 
                    |
| 2394 | 
                        - }),  | 
                    |
| 2395 | 
                        - 'The $.global passphrase is not NFC-normalized',  | 
                    |
| 2396 | 
                        - id='global-NFC',  | 
                    |
| 2397 | 
                        - ),  | 
                    |
| 2398 | 
                        - pytest.param(  | 
                    |
| 2399 | 
                        - '',  | 
                    |
| 2400 | 
                        - ['--import', '-'],  | 
                    |
| 2401 | 
                        -                json.dumps({
                       | 
                    |
| 2402 | 
                        -                    'services': {
                       | 
                    |
| 2403 | 
                        - DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 2404 | 
                        -                        'weird entry name': {'phrase': 'Du\u0308sseldorf'},
                       | 
                    |
| 2405 | 
                        - }  | 
                    |
| 2406 | 
                        - }),  | 
                    |
| 2407 | 
                        - (  | 
                    |
| 2408 | 
                        - 'The $.services["weird entry name"] passphrase '  | 
                    |
| 2409 | 
                        - 'is not NFC-normalized'  | 
                    |
| 2410 | 
                        - ),  | 
                    |
| 2411 | 
                        - id='service-weird-name-NFC',  | 
                    |
| 2412 | 
                        - ),  | 
                    |
| 2413 | 
                        - pytest.param(  | 
                    |
| 2414 | 
                        - '',  | 
                    |
| 2415 | 
                        - ['--config', '-p', '--', DUMMY_SERVICE],  | 
                    |
| 2416 | 
                        - 'Du\u0308sseldorf',  | 
                    |
| 2417 | 
                        - (  | 
                    |
| 2418 | 
                        -                    f'The $.services.{DUMMY_SERVICE} passphrase '
                       | 
                    |
| 2419 | 
                        - f'is not NFC-normalized'  | 
                    |
| 2420 | 
                        - ),  | 
                    |
| 2421 | 
                        - id='config-NFC',  | 
                    |
| 2422 | 
                        - ),  | 
                    |
| 2423 | 
                        - pytest.param(  | 
                    |
| 2424 | 
                        - '',  | 
                    |
| 2425 | 
                        - ['-p', '--', DUMMY_SERVICE],  | 
                    |
| 2426 | 
                        - 'Du\u0308sseldorf',  | 
                    |
| 2427 | 
                        - 'The interactive input passphrase is not NFC-normalized',  | 
                    |
| 2428 | 
                        - id='direct-input-NFC',  | 
                    |
| 2429 | 
                        - ),  | 
                    |
| 2430 | 
                        - pytest.param(  | 
                    |
| 2431 | 
                        - textwrap.dedent(r"""  | 
                    |
| 2432 | 
                        - [vault]  | 
                    |
| 2433 | 
                        - default-unicode-normalization-form = 'NFD'  | 
                    |
| 2434 | 
                        - """),  | 
                    |
| 2435 | 
                        - ['--import', '-'],  | 
                    |
| 2436 | 
                        -                json.dumps({
                       | 
                    |
| 2437 | 
                        -                    'global': {
                       | 
                    |
| 2438 | 
                        - 'phrase': 'D\u00fcsseldorf',  | 
                    |
| 2439 | 
                        - },  | 
                    |
| 2440 | 
                        -                    'services': {},
                       | 
                    |
| 2441 | 
                        - }),  | 
                    |
| 2442 | 
                        - 'The $.global passphrase is not NFD-normalized',  | 
                    |
| 2443 | 
                        - id='global-NFD',  | 
                    |
| 2444 | 
                        - ),  | 
                    |
| 2445 | 
                        - pytest.param(  | 
                    |
| 2446 | 
                        - textwrap.dedent(r"""  | 
                    |
| 2447 | 
                        - [vault]  | 
                    |
| 2448 | 
                        - default-unicode-normalization-form = 'NFD'  | 
                    |
| 2449 | 
                        - """),  | 
                    |
| 2450 | 
                        - ['--import', '-'],  | 
                    |
| 2451 | 
                        -                json.dumps({
                       | 
                    |
| 2452 | 
                        -                    'services': {
                       | 
                    |
| 2453 | 
                        - DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 2454 | 
                        -                        'weird entry name': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 2455 | 
                        - },  | 
                    |
| 2456 | 
                        - }),  | 
                    |
| 2457 | 
                        - (  | 
                    |
| 2458 | 
                        - 'The $.services["weird entry name"] passphrase '  | 
                    |
| 2459 | 
                        - 'is not NFD-normalized'  | 
                    |
| 2460 | 
                        - ),  | 
                    |
| 2461 | 
                        - id='service-weird-name-NFD',  | 
                    |
| 2462 | 
                        - ),  | 
                    |
| 2463 | 
                        - pytest.param(  | 
                    |
| 2464 | 
                        - textwrap.dedent(r"""  | 
                    |
| 2465 | 
                        - [vault.unicode-normalization-form]  | 
                    |
| 2466 | 
                        - 'weird entry name 2' = 'NFKD'  | 
                    |
| 2467 | 
                        - """),  | 
                    |
| 2468 | 
                        - ['--import', '-'],  | 
                    |
| 2469 | 
                        -                json.dumps({
                       | 
                    |
| 2470 | 
                        -                    'services': {
                       | 
                    |
| 2471 | 
                        - DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 2472 | 
                        -                        'weird entry name 1': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 2473 | 
                        -                        'weird entry name 2': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 2474 | 
                        - },  | 
                    |
| 2475 | 
                        - }),  | 
                    |
| 2476 | 
                        - (  | 
                    |
| 2477 | 
                        - 'The $.services["weird entry name 2"] passphrase '  | 
                    |
| 2478 | 
                        - 'is not NFKD-normalized'  | 
                    |
| 2479 | 
                        - ),  | 
                    |
| 2480 | 
                        - id='service-weird-name-2-NFKD',  | 
                    |
| 2481 | 
                        - ),  | 
                    |
| 2482 | 
                        - ],  | 
                    |
| 3043 | 
                        + monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())  | 
                    |
| 3044 | 
                        + stack.enter_context(  | 
                    |
| 3045 | 
                        + tests.isolated_config(  | 
                    |
| 3046 | 
                        + monkeypatch=monkeypatch,  | 
                    |
| 3047 | 
                        + runner=runner,  | 
                    |
| 3048 | 
                        + )  | 
                    |
| 3049 | 
                        + )  | 
                    |
| 3050 | 
                        + save_config_ = cli_helpers.save_config  | 
                    |
| 3051 | 
                        +  | 
                    |
| 3052 | 
                        + def obstruct_config_saving(*args: Any, **kwargs: Any) -> Any:  | 
                    |
| 3053 | 
                        + config_dir = cli_helpers.config_filename(subsystem=None)  | 
                    |
| 3054 | 
                        + with contextlib.suppress(FileNotFoundError):  | 
                    |
| 3055 | 
                        + shutil.rmtree(config_dir)  | 
                    |
| 3056 | 
                        +                config_dir.write_text('Obstruction!!\n')
                       | 
                    |
| 3057 | 
                        + monkeypatch.setattr(cli_helpers, 'save_config', save_config_)  | 
                    |
| 3058 | 
                        + return save_config_(*args, **kwargs)  | 
                    |
| 3059 | 
                        +  | 
                    |
| 3060 | 
                        + monkeypatch.setattr(  | 
                    |
| 3061 | 
                        + cli_helpers, 'save_config', obstruct_config_saving  | 
                    |
| 3062 | 
                        + )  | 
                    |
| 3063 | 
                        + result_ = runner.invoke(  | 
                    |
| 3064 | 
                        + cli.derivepassphrase_vault,  | 
                    |
| 3065 | 
                        + ['--config', '-p'],  | 
                    |
| 3066 | 
                        + catch_exceptions=False,  | 
                    |
| 3067 | 
                        + input='abc\n',  | 
                    |
| 3068 | 
                        + )  | 
                    |
| 3069 | 
                        + result = tests.ReadableResult.parse(result_)  | 
                    |
| 3070 | 
                        + assert result.error_exit(error='Cannot store vault settings:'), (  | 
                    |
| 3071 | 
                        + 'expected error exit and known error message'  | 
                    |
| 3072 | 
                        + )  | 
                    |
| 3073 | 
                        +  | 
                    |
| 3074 | 
                        + def test_230b_store_config_custom_error(  | 
                    |
| 3075 | 
                        + self,  | 
                    |
| 3076 | 
                        + ) -> None:  | 
                    |
| 3077 | 
                        + """Storing the configuration reacts even to weird errors."""  | 
                    |
| 3078 | 
                        + runner = click.testing.CliRunner(mix_stderr=False)  | 
                    |
| 3079 | 
                        + # TODO(the-13th-letter): Rewrite using parenthesized  | 
                    |
| 3080 | 
                        + # with-statements.  | 
                    |
| 3081 | 
                        + # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9  | 
                    |
| 3082 | 
                        + with contextlib.ExitStack() as stack:  | 
                    |
| 3083 | 
                        + monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())  | 
                    |
| 3084 | 
                        + stack.enter_context(  | 
                    |
| 3085 | 
                        + tests.isolated_config(  | 
                    |
| 3086 | 
                        + monkeypatch=monkeypatch,  | 
                    |
| 3087 | 
                        + runner=runner,  | 
                    |
| 3088 | 
                        + )  | 
                    |
| 3089 | 
                        + )  | 
                    |
| 3090 | 
                        + custom_error = 'custom error message'  | 
                    |
| 3091 | 
                        +  | 
                    |
| 3092 | 
                        + def raiser(config: Any) -> None:  | 
                    |
| 3093 | 
                        + del config  | 
                    |
| 3094 | 
                        + raise RuntimeError(custom_error)  | 
                    |
| 3095 | 
                        +  | 
                    |
| 3096 | 
                        + monkeypatch.setattr(cli_helpers, 'save_config', raiser)  | 
                    |
| 3097 | 
                        + result_ = runner.invoke(  | 
                    |
| 3098 | 
                        + cli.derivepassphrase_vault,  | 
                    |
| 3099 | 
                        + ['--config', '-p'],  | 
                    |
| 3100 | 
                        + catch_exceptions=False,  | 
                    |
| 3101 | 
                        + input='abc\n',  | 
                    |
| 3102 | 
                        + )  | 
                    |
| 3103 | 
                        + result = tests.ReadableResult.parse(result_)  | 
                    |
| 3104 | 
                        + assert result.error_exit(error=custom_error), (  | 
                    |
| 3105 | 
                        + 'expected error exit and known error message'  | 
                    |
| 2483 | 3106 | 
                        )  | 
                    
| 3107 | 
                        +  | 
                    |
| 3108 | 
                        + @Parametrizations.UNICODE_NORMALIZATION_WARNING_INPUTS.value  | 
                    |
| 2484 | 3109 | 
                        def test_300_unicode_normalization_form_warning(  | 
                    
| 2485 | 3110 | 
                        self,  | 
                    
| 2486 | 3111 | 
                        caplog: pytest.LogCaptureFixture,  | 
                    
| ... | ... | 
                      @@ -2520,47 +3145,7 @@ contents go here  | 
                  
| 2520 | 3145 | 
                        'expected known warning message in stderr'  | 
                    
| 2521 | 3146 | 
                        )  | 
                    
| 2522 | 3147 | 
                         | 
                    
| 2523 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 2524 | 
                        - ['main_config', 'command_line', 'input', 'error_message'],  | 
                    |
| 2525 | 
                        - [  | 
                    |
| 2526 | 
                        - pytest.param(  | 
                    |
| 2527 | 
                        - textwrap.dedent(r"""  | 
                    |
| 2528 | 
                        - [vault]  | 
                    |
| 2529 | 
                        - default-unicode-normalization-form = 'XXX'  | 
                    |
| 2530 | 
                        - """),  | 
                    |
| 2531 | 
                        - ['--import', '-'],  | 
                    |
| 2532 | 
                        -                json.dumps({
                       | 
                    |
| 2533 | 
                        -                    'services': {
                       | 
                    |
| 2534 | 
                        - DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 2535 | 
                        -                        'with_normalization': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 2536 | 
                        - },  | 
                    |
| 2537 | 
                        - }),  | 
                    |
| 2538 | 
                        - (  | 
                    |
| 2539 | 
                        - "Invalid value 'XXX' for config key "  | 
                    |
| 2540 | 
                        - 'vault.default-unicode-normalization-form'  | 
                    |
| 2541 | 
                        - ),  | 
                    |
| 2542 | 
                        - id='global',  | 
                    |
| 2543 | 
                        - ),  | 
                    |
| 2544 | 
                        - pytest.param(  | 
                    |
| 2545 | 
                        - textwrap.dedent(r"""  | 
                    |
| 2546 | 
                        - [vault.unicode-normalization-form]  | 
                    |
| 2547 | 
                        - with_normalization = 'XXX'  | 
                    |
| 2548 | 
                        - """),  | 
                    |
| 2549 | 
                        - ['--import', '-'],  | 
                    |
| 2550 | 
                        -                json.dumps({
                       | 
                    |
| 2551 | 
                        -                    'services': {
                       | 
                    |
| 2552 | 
                        - DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 2553 | 
                        -                        'with_normalization': {'phrase': 'D\u00fcsseldorf'},
                       | 
                    |
| 2554 | 
                        - },  | 
                    |
| 2555 | 
                        - }),  | 
                    |
| 2556 | 
                        - (  | 
                    |
| 2557 | 
                        - "Invalid value 'XXX' for config key "  | 
                    |
| 2558 | 
                        - 'vault.with_normalization.unicode-normalization-form'  | 
                    |
| 2559 | 
                        - ),  | 
                    |
| 2560 | 
                        - id='service',  | 
                    |
| 2561 | 
                        - ),  | 
                    |
| 2562 | 
                        - ],  | 
                    |
| 2563 | 
                        - )  | 
                    |
| 3148 | 
                        + @Parametrizations.UNICODE_NORMALIZATION_ERROR_INPUTS.value  | 
                    |
| 2564 | 3149 | 
                        def test_301_unicode_normalization_form_error(  | 
                    
| 2565 | 3150 | 
                        self,  | 
                    
| 2566 | 3151 | 
                        main_config: str,  | 
                    
| ... | ... | 
                      @@ -2601,19 +3186,7 @@ contents go here  | 
                  
| 2601 | 3186 | 
                        'expected error exit and known error message'  | 
                    
| 2602 | 3187 | 
                        )  | 
                    
| 2603 | 3188 | 
                         | 
                    
| 2604 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 2605 | 
                        - 'command_line',  | 
                    |
| 2606 | 
                        - [  | 
                    |
| 2607 | 
                        - pytest.param(  | 
                    |
| 2608 | 
                        - ['--config', '--phrase'],  | 
                    |
| 2609 | 
                        - id='configure global passphrase',  | 
                    |
| 2610 | 
                        - ),  | 
                    |
| 2611 | 
                        - pytest.param(  | 
                    |
| 2612 | 
                        - ['--phrase', '--', DUMMY_SERVICE],  | 
                    |
| 2613 | 
                        - id='interactive passphrase',  | 
                    |
| 2614 | 
                        - ),  | 
                    |
| 2615 | 
                        - ],  | 
                    |
| 2616 | 
                        - )  | 
                    |
| 3189 | 
                        + @Parametrizations.UNICODE_NORMALIZATION_COMMAND_LINES.value  | 
                    |
| 2617 | 3190 | 
                        def test_301a_unicode_normalization_form_error_from_stored_config(  | 
                    
| 2618 | 3191 | 
                        self,  | 
                    
| 2619 | 3192 | 
                        command_line: list[str],  | 
                    
| ... | ... | 
                      @@ -2720,25 +3293,7 @@ contents go here  | 
                  
| 2720 | 3293 | 
                        class TestCLIUtils:  | 
                    
| 2721 | 3294 | 
                        """Tests for command-line utility functions."""  | 
                    
| 2722 | 3295 | 
                         | 
                    
| 2723 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 2724 | 
                        - 'config',  | 
                    |
| 2725 | 
                        - [  | 
                    |
| 2726 | 
                        -            {'global': {'phrase': 'my passphrase'}, 'services': {}},
                       | 
                    |
| 2727 | 
                        -            {'global': {'key': DUMMY_KEY1_B64}, 'services': {}},
                       | 
                    |
| 2728 | 
                        -            {
                       | 
                    |
| 2729 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 2730 | 
                        -                'services': {'sv': {'phrase': 'my passphrase'}},
                       | 
                    |
| 2731 | 
                        - },  | 
                    |
| 2732 | 
                        -            {
                       | 
                    |
| 2733 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 2734 | 
                        -                'services': {'sv': {'key': DUMMY_KEY1_B64}},
                       | 
                    |
| 2735 | 
                        - },  | 
                    |
| 2736 | 
                        -            {
                       | 
                    |
| 2737 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 2738 | 
                        -                'services': {'sv': {'key': DUMMY_KEY1_B64, 'length': 15}},
                       | 
                    |
| 2739 | 
                        - },  | 
                    |
| 2740 | 
                        - ],  | 
                    |
| 2741 | 
                        - )  | 
                    |
| 3296 | 
                        + @Parametrizations.BASE_CONFIG_VARIATIONS.value  | 
                    |
| 2742 | 3297 | 
                        def test_100_load_config(  | 
                    
| 2743 | 3298 | 
                        self,  | 
                    
| 2744 | 3299 | 
                        config: Any,  | 
                    
| ... | ... | 
                      @@ -3300,35 +3855,7 @@ Boo.  | 
                  
| 3300 | 3855 | 
                        assert _types.is_vault_config(config)  | 
                    
| 3301 | 3856 | 
                        return self.export_as_sh_helper(config)  | 
                    
| 3302 | 3857 | 
                         | 
                    
| 3303 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 3304 | 
                        - ['command_line', 'config', 'result_config'],  | 
                    |
| 3305 | 
                        - [  | 
                    |
| 3306 | 
                        - pytest.param(  | 
                    |
| 3307 | 
                        - ['--delete-globals'],  | 
                    |
| 3308 | 
                        -                {'global': {'phrase': 'abc'}, 'services': {}},
                       | 
                    |
| 3309 | 
                        -                {'services': {}},
                       | 
                    |
| 3310 | 
                        - id='globals',  | 
                    |
| 3311 | 
                        - ),  | 
                    |
| 3312 | 
                        - pytest.param(  | 
                    |
| 3313 | 
                        - ['--delete', '--', DUMMY_SERVICE],  | 
                    |
| 3314 | 
                        -                {
                       | 
                    |
| 3315 | 
                        -                    'global': {'phrase': 'abc'},
                       | 
                    |
| 3316 | 
                        -                    'services': {DUMMY_SERVICE: {'notes': '...'}},
                       | 
                    |
| 3317 | 
                        - },  | 
                    |
| 3318 | 
                        -                {'global': {'phrase': 'abc'}, 'services': {}},
                       | 
                    |
| 3319 | 
                        - id='service',  | 
                    |
| 3320 | 
                        - ),  | 
                    |
| 3321 | 
                        - pytest.param(  | 
                    |
| 3322 | 
                        - ['--clear'],  | 
                    |
| 3323 | 
                        -                {
                       | 
                    |
| 3324 | 
                        -                    'global': {'phrase': 'abc'},
                       | 
                    |
| 3325 | 
                        -                    'services': {DUMMY_SERVICE: {'notes': '...'}},
                       | 
                    |
| 3326 | 
                        - },  | 
                    |
| 3327 | 
                        -                {'services': {}},
                       | 
                    |
| 3328 | 
                        - id='all',  | 
                    |
| 3329 | 
                        - ),  | 
                    |
| 3330 | 
                        - ],  | 
                    |
| 3331 | 
                        - )  | 
                    |
| 3858 | 
                        + @Parametrizations.DELETE_CONFIG_INPUT.value  | 
                    |
| 3332 | 3859 | 
                        def test_203_repeated_config_deletion(  | 
                    
| 3333 | 3860 | 
                        self,  | 
                    
| 3334 | 3861 | 
                        command_line: list[str],  | 
                    
| ... | ... | 
                      @@ -3374,13 +3901,7 @@ Boo.  | 
                  
| 3374 | 3901 | 
                        == DUMMY_RESULT_KEY1  | 
                    
| 3375 | 3902 | 
                        )  | 
                    
| 3376 | 3903 | 
                         | 
                    
| 3377 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 3378 | 
                        - ['vfunc', 'input'],  | 
                    |
| 3379 | 
                        - [  | 
                    |
| 3380 | 
                        - (cli_machinery.validate_occurrence_constraint, 20),  | 
                    |
| 3381 | 
                        - (cli_machinery.validate_length, 20),  | 
                    |
| 3382 | 
                        - ],  | 
                    |
| 3383 | 
                        - )  | 
                    |
| 3904 | 
                        + @Parametrizations.VALIDATION_FUNCTION_INPUT.value  | 
                    |
| 3384 | 3905 | 
                        def test_210a_validate_constraints_manually(  | 
                    
| 3385 | 3906 | 
                        self,  | 
                    
| 3386 | 3907 | 
                        vfunc: Callable[[click.Context, click.Parameter, Any], int | None],  | 
                    
| ... | ... | 
                      @@ -3391,7 +3912,7 @@ Boo.  | 
                  
| 3391 | 3912 | 
                        param = cli.derivepassphrase_vault.params[0]  | 
                    
| 3392 | 3913 | 
                        assert vfunc(ctx, param, input) == input  | 
                    
| 3393 | 3914 | 
                         | 
                    
| 3394 | 
                        -    @pytest.mark.parametrize('conn_hint', ['none', 'socket', 'client'])
                       | 
                    |
| 3915 | 
                        + @Parametrizations.CONNECTION_HINTS.value  | 
                    |
| 3395 | 3916 | 
                        def test_227_get_suitable_ssh_keys(  | 
                    
| 3396 | 3917 | 
                        self,  | 
                    
| 3397 | 3918 | 
                        running_ssh_agent: tests.RunningSSHAgentInfo,  | 
                    
| ... | ... | 
                      @@ -3522,25 +4043,7 @@ Boo.  | 
                  
| 3522 | 4043 | 
                        class TestCLITransition:  | 
                    
| 3523 | 4044 | 
                        """Transition tests for the command-line interface up to v1.0."""  | 
                    
| 3524 | 4045 | 
                         | 
                    
| 3525 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 3526 | 
                        - 'config',  | 
                    |
| 3527 | 
                        - [  | 
                    |
| 3528 | 
                        -            {'global': {'phrase': 'my passphrase'}, 'services': {}},
                       | 
                    |
| 3529 | 
                        -            {'global': {'key': DUMMY_KEY1_B64}, 'services': {}},
                       | 
                    |
| 3530 | 
                        -            {
                       | 
                    |
| 3531 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3532 | 
                        -                'services': {'sv': {'phrase': 'my passphrase'}},
                       | 
                    |
| 3533 | 
                        - },  | 
                    |
| 3534 | 
                        -            {
                       | 
                    |
| 3535 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3536 | 
                        -                'services': {'sv': {'key': DUMMY_KEY1_B64}},
                       | 
                    |
| 3537 | 
                        - },  | 
                    |
| 3538 | 
                        -            {
                       | 
                    |
| 3539 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3540 | 
                        -                'services': {'sv': {'key': DUMMY_KEY1_B64, 'length': 15}},
                       | 
                    |
| 3541 | 
                        - },  | 
                    |
| 3542 | 
                        - ],  | 
                    |
| 3543 | 
                        - )  | 
                    |
| 4046 | 
                        + @Parametrizations.BASE_CONFIG_VARIATIONS.value  | 
                    |
| 3544 | 4047 | 
                        def test_110_load_config_backup(  | 
                    
| 3545 | 4048 | 
                        self,  | 
                    
| 3546 | 4049 | 
                        config: Any,  | 
                    
| ... | ... | 
                      @@ -3563,25 +4066,7 @@ class TestCLITransition:  | 
                  
| 3563 | 4066 | 
                        ).write_text(json.dumps(config, indent=2) + '\n', encoding='UTF-8')  | 
                    
| 3564 | 4067 | 
                        assert cli_helpers.migrate_and_load_old_config()[0] == config  | 
                    
| 3565 | 4068 | 
                         | 
                    
| 3566 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 3567 | 
                        - 'config',  | 
                    |
| 3568 | 
                        - [  | 
                    |
| 3569 | 
                        -            {'global': {'phrase': 'my passphrase'}, 'services': {}},
                       | 
                    |
| 3570 | 
                        -            {'global': {'key': DUMMY_KEY1_B64}, 'services': {}},
                       | 
                    |
| 3571 | 
                        -            {
                       | 
                    |
| 3572 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3573 | 
                        -                'services': {'sv': {'phrase': 'my passphrase'}},
                       | 
                    |
| 3574 | 
                        - },  | 
                    |
| 3575 | 
                        -            {
                       | 
                    |
| 3576 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3577 | 
                        -                'services': {'sv': {'key': DUMMY_KEY1_B64}},
                       | 
                    |
| 3578 | 
                        - },  | 
                    |
| 3579 | 
                        -            {
                       | 
                    |
| 3580 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3581 | 
                        -                'services': {'sv': {'key': DUMMY_KEY1_B64, 'length': 15}},
                       | 
                    |
| 3582 | 
                        - },  | 
                    |
| 3583 | 
                        - ],  | 
                    |
| 3584 | 
                        - )  | 
                    |
| 4069 | 
                        + @Parametrizations.BASE_CONFIG_VARIATIONS.value  | 
                    |
| 3585 | 4070 | 
                        def test_111_migrate_config(  | 
                    
| 3586 | 4071 | 
                        self,  | 
                    
| 3587 | 4072 | 
                        config: Any,  | 
                    
| ... | ... | 
                      @@ -3604,25 +4089,7 @@ class TestCLITransition:  | 
                  
| 3604 | 4089 | 
                        ).write_text(json.dumps(config, indent=2) + '\n', encoding='UTF-8')  | 
                    
| 3605 | 4090 | 
                        assert cli_helpers.migrate_and_load_old_config() == (config, None)  | 
                    
| 3606 | 4091 | 
                         | 
                    
| 3607 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 3608 | 
                        - 'config',  | 
                    |
| 3609 | 
                        - [  | 
                    |
| 3610 | 
                        -            {'global': {'phrase': 'my passphrase'}, 'services': {}},
                       | 
                    |
| 3611 | 
                        -            {'global': {'key': DUMMY_KEY1_B64}, 'services': {}},
                       | 
                    |
| 3612 | 
                        -            {
                       | 
                    |
| 3613 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3614 | 
                        -                'services': {'sv': {'phrase': 'my passphrase'}},
                       | 
                    |
| 3615 | 
                        - },  | 
                    |
| 3616 | 
                        -            {
                       | 
                    |
| 3617 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3618 | 
                        -                'services': {'sv': {'key': DUMMY_KEY1_B64}},
                       | 
                    |
| 3619 | 
                        - },  | 
                    |
| 3620 | 
                        -            {
                       | 
                    |
| 3621 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3622 | 
                        -                'services': {'sv': {'key': DUMMY_KEY1_B64, 'length': 15}},
                       | 
                    |
| 3623 | 
                        - },  | 
                    |
| 3624 | 
                        - ],  | 
                    |
| 3625 | 
                        - )  | 
                    |
| 4092 | 
                        + @Parametrizations.BASE_CONFIG_VARIATIONS.value  | 
                    |
| 3626 | 4093 | 
                        def test_112_migrate_config_error(  | 
                    
| 3627 | 4094 | 
                        self,  | 
                    
| 3628 | 4095 | 
                        config: Any,  | 
                    
| ... | ... | 
                      @@ -3634,42 +4101,24 @@ class TestCLITransition:  | 
                  
| 3634 | 4101 | 
                        # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9  | 
                    
| 3635 | 4102 | 
                        with contextlib.ExitStack() as stack:  | 
                    
| 3636 | 4103 | 
                        monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())  | 
                    
| 3637 | 
                        - stack.enter_context(  | 
                    |
| 3638 | 
                        - tests.isolated_config(  | 
                    |
| 3639 | 
                        - monkeypatch=monkeypatch,  | 
                    |
| 3640 | 
                        - runner=runner,  | 
                    |
| 3641 | 
                        - )  | 
                    |
| 3642 | 
                        - )  | 
                    |
| 3643 | 
                        - cli_helpers.config_filename(  | 
                    |
| 3644 | 
                        - subsystem='old settings.json'  | 
                    |
| 3645 | 
                        - ).write_text(json.dumps(config, indent=2) + '\n', encoding='UTF-8')  | 
                    |
| 3646 | 
                        - cli_helpers.config_filename(subsystem='vault').mkdir(  | 
                    |
| 3647 | 
                        - parents=True, exist_ok=True  | 
                    |
| 3648 | 
                        - )  | 
                    |
| 3649 | 
                        - config2, err = cli_helpers.migrate_and_load_old_config()  | 
                    |
| 3650 | 
                        - assert config2 == config  | 
                    |
| 3651 | 
                        - assert isinstance(err, OSError)  | 
                    |
| 3652 | 
                        - assert err.errno == errno.EISDIR  | 
                    |
| 3653 | 
                        -  | 
                    |
| 3654 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 3655 | 
                        - 'config',  | 
                    |
| 3656 | 
                        - [  | 
                    |
| 3657 | 
                        -            {'global': '', 'services': {}},
                       | 
                    |
| 3658 | 
                        -            {'global': 0, 'services': {}},
                       | 
                    |
| 3659 | 
                        -            {
                       | 
                    |
| 3660 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3661 | 
                        - 'services': False,  | 
                    |
| 3662 | 
                        - },  | 
                    |
| 3663 | 
                        -            {
                       | 
                    |
| 3664 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3665 | 
                        - 'services': True,  | 
                    |
| 3666 | 
                        - },  | 
                    |
| 3667 | 
                        -            {
                       | 
                    |
| 3668 | 
                        -                'global': {'phrase': 'abc'},
                       | 
                    |
| 3669 | 
                        - 'services': None,  | 
                    |
| 3670 | 
                        - },  | 
                    |
| 3671 | 
                        - ],  | 
                    |
| 4104 | 
                        + stack.enter_context(  | 
                    |
| 4105 | 
                        + tests.isolated_config(  | 
                    |
| 4106 | 
                        + monkeypatch=monkeypatch,  | 
                    |
| 4107 | 
                        + runner=runner,  | 
                    |
| 4108 | 
                        + )  | 
                    |
| 4109 | 
                        + )  | 
                    |
| 4110 | 
                        + cli_helpers.config_filename(  | 
                    |
| 4111 | 
                        + subsystem='old settings.json'  | 
                    |
| 4112 | 
                        + ).write_text(json.dumps(config, indent=2) + '\n', encoding='UTF-8')  | 
                    |
| 4113 | 
                        + cli_helpers.config_filename(subsystem='vault').mkdir(  | 
                    |
| 4114 | 
                        + parents=True, exist_ok=True  | 
                    |
| 3672 | 4115 | 
                        )  | 
                    
| 4116 | 
                        + config2, err = cli_helpers.migrate_and_load_old_config()  | 
                    |
| 4117 | 
                        + assert config2 == config  | 
                    |
| 4118 | 
                        + assert isinstance(err, OSError)  | 
                    |
| 4119 | 
                        + assert err.errno == errno.EISDIR  | 
                    |
| 4120 | 
                        +  | 
                    |
| 4121 | 
                        + @Parametrizations.BAD_CONFIGS.value  | 
                    |
| 3673 | 4122 | 
                        def test_113_migrate_config_error_bad_config_value(  | 
                    
| 3674 | 4123 | 
                        self,  | 
                    
| 3675 | 4124 | 
                        config: Any,  | 
                    
| ... | ... | 
                      @@ -3763,9 +4212,7 @@ class TestCLITransition:  | 
                  
| 3763 | 4212 | 
                        'expected error exit and known error type'  | 
                    
| 3764 | 4213 | 
                        )  | 
                    
| 3765 | 4214 | 
                         | 
                    
| 3766 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 3767 | 
                        - 'charset_name', ['lower', 'upper', 'number', 'space', 'dash', 'symbol']  | 
                    |
| 3768 | 
                        - )  | 
                    |
| 4215 | 
                        + @Parametrizations.CHARSET_NAME.value  | 
                    |
| 3769 | 4216 | 
                        def test_210_forward_vault_disable_character_set(  | 
                    
| 3770 | 4217 | 
                        self,  | 
                    
| 3771 | 4218 | 
                        caplog: pytest.LogCaptureFixture,  | 
                    
| ... | ... | 
                      @@ -4468,57 +4915,6 @@ TestConfigManagement = ConfigManagementStateMachine.TestCase  | 
                  
| 4468 | 4915 | 
                        """The [`unittest.TestCase`][] class that will actually be run."""  | 
                    
| 4469 | 4916 | 
                         | 
                    
| 4470 | 4917 | 
                         | 
                    
| 4471 | 
                        -def bash_format(item: click.shell_completion.CompletionItem) -> str:  | 
                    |
| 4472 | 
                        - """A formatter for `bash`-style shell completion items.  | 
                    |
| 4473 | 
                        -  | 
                    |
| 4474 | 
                        - The format is `type,value`, and is dictated by [`click`][].  | 
                    |
| 4475 | 
                        -  | 
                    |
| 4476 | 
                        - """  | 
                    |
| 4477 | 
                        - type, value = ( # noqa: A001  | 
                    |
| 4478 | 
                        - item.type,  | 
                    |
| 4479 | 
                        - item.value,  | 
                    |
| 4480 | 
                        - )  | 
                    |
| 4481 | 
                        -    return f'{type},{value}'
                       | 
                    |
| 4482 | 
                        -  | 
                    |
| 4483 | 
                        -  | 
                    |
| 4484 | 
                        -def fish_format(item: click.shell_completion.CompletionItem) -> str:  | 
                    |
| 4485 | 
                        - r"""A formatter for `fish`-style shell completion items.  | 
                    |
| 4486 | 
                        -  | 
                    |
| 4487 | 
                        - The format is `type,value<tab>help`, and is dictated by [`click`][].  | 
                    |
| 4488 | 
                        -  | 
                    |
| 4489 | 
                        - """  | 
                    |
| 4490 | 
                        - type, value, help = ( # noqa: A001  | 
                    |
| 4491 | 
                        - item.type,  | 
                    |
| 4492 | 
                        - item.value,  | 
                    |
| 4493 | 
                        - item.help,  | 
                    |
| 4494 | 
                        - )  | 
                    |
| 4495 | 
                        -    return f'{type},{value}\t{help}' if help else f'{type},{value}'
                       | 
                    |
| 4496 | 
                        -  | 
                    |
| 4497 | 
                        -  | 
                    |
| 4498 | 
                        -def zsh_format(item: click.shell_completion.CompletionItem) -> str:  | 
                    |
| 4499 | 
                        - r"""A formatter for `zsh`-style shell completion items.  | 
                    |
| 4500 | 
                        -  | 
                    |
| 4501 | 
                        - The format is `type<newline>value<newline>help<newline>`, and is  | 
                    |
| 4502 | 
                        - dictated by [`click`][]. Upstream `click` currently (v8.2.0) does  | 
                    |
| 4503 | 
                        - not deal with colons in the value correctly when the help text is  | 
                    |
| 4504 | 
                        - non-degenerate. Our formatter here does, provided the upstream  | 
                    |
| 4505 | 
                        - `zsh` completion script is used; see the  | 
                    |
| 4506 | 
                        - [`cli_machinery.ZshComplete`][] class. A request is underway to  | 
                    |
| 4507 | 
                        - merge this change into upstream `click`; see  | 
                    |
| 4508 | 
                        - [`pallets/click#2846`][PR2846].  | 
                    |
| 4509 | 
                        -  | 
                    |
| 4510 | 
                        - [PR2846]: https://github.com/pallets/click/pull/2846  | 
                    |
| 4511 | 
                        -  | 
                    |
| 4512 | 
                        - """  | 
                    |
| 4513 | 
                        - empty_help = '_'  | 
                    |
| 4514 | 
                        - help_, value = (  | 
                    |
| 4515 | 
                        -        (item.help, item.value.replace(':', r'\:'))
                       | 
                    |
| 4516 | 
                        - if item.help and item.help == empty_help  | 
                    |
| 4517 | 
                        - else (empty_help, item.value)  | 
                    |
| 4518 | 
                        - )  | 
                    |
| 4519 | 
                        -    return f'{item.type}\n{value}\n{help_}'
                       | 
                    |
| 4520 | 
                        -  | 
                    |
| 4521 | 
                        -  | 
                    |
| 4522 | 4918 | 
                        def completion_item(  | 
                    
| 4523 | 4919 | 
                        item: str | click.shell_completion.CompletionItem,  | 
                    
| 4524 | 4920 | 
                        ) -> click.shell_completion.CompletionItem:  | 
                    
| ... | ... | 
                      @@ -4581,20 +4977,7 @@ class TestShellCompletion:  | 
                  
| 4581 | 4977 | 
                        """Return the completion items' values, as a sequence."""  | 
                    
| 4582 | 4978 | 
                        return tuple(c.value for c in self())  | 
                    
| 4583 | 4979 | 
                         | 
                    
| 4584 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 4585 | 
                        - ['partial', 'is_completable'],  | 
                    |
| 4586 | 
                        - [  | 
                    |
| 4587 | 
                        -            ('', True),
                       | 
                    |
| 4588 | 
                        - (DUMMY_SERVICE, True),  | 
                    |
| 4589 | 
                        -            ('a\bn', False),
                       | 
                    |
| 4590 | 
                        -            ('\b', False),
                       | 
                    |
| 4591 | 
                        -            ('\x00', False),
                       | 
                    |
| 4592 | 
                        -            ('\x20', True),
                       | 
                    |
| 4593 | 
                        -            ('\x7f', False),
                       | 
                    |
| 4594 | 
                        -            ('service with spaces', True),
                       | 
                    |
| 4595 | 
                        -            ('service\nwith\nnewlines', False),
                       | 
                    |
| 4596 | 
                        - ],  | 
                    |
| 4597 | 
                        - )  | 
                    |
| 4980 | 
                        + @Parametrizations.COMPLETABLE_ITEMS.value  | 
                    |
| 4598 | 4981 | 
                        def test_100_is_completable_item(  | 
                    
| 4599 | 4982 | 
                        self,  | 
                    
| 4600 | 4983 | 
                        partial: str,  | 
                    
| ... | ... | 
                      @@ -4603,106 +4986,7 @@ class TestShellCompletion:  | 
                  
| 4603 | 4986 | 
                        """Our `_is_completable_item` predicate for service names works."""  | 
                    
| 4604 | 4987 | 
                        assert cli_helpers.is_completable_item(partial) == is_completable  | 
                    
| 4605 | 4988 | 
                         | 
                    
| 4606 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 4607 | 
                        - ['command_prefix', 'incomplete', 'completions'],  | 
                    |
| 4608 | 
                        - [  | 
                    |
| 4609 | 
                        - pytest.param(  | 
                    |
| 4610 | 
                        - (),  | 
                    |
| 4611 | 
                        - '-',  | 
                    |
| 4612 | 
                        -                frozenset({
                       | 
                    |
| 4613 | 
                        - '--help',  | 
                    |
| 4614 | 
                        - '-h',  | 
                    |
| 4615 | 
                        - '--version',  | 
                    |
| 4616 | 
                        - '--debug',  | 
                    |
| 4617 | 
                        - '--verbose',  | 
                    |
| 4618 | 
                        - '-v',  | 
                    |
| 4619 | 
                        - '--quiet',  | 
                    |
| 4620 | 
                        - '-q',  | 
                    |
| 4621 | 
                        - }),  | 
                    |
| 4622 | 
                        - id='derivepassphrase',  | 
                    |
| 4623 | 
                        - ),  | 
                    |
| 4624 | 
                        - pytest.param(  | 
                    |
| 4625 | 
                        -                ('export',),
                       | 
                    |
| 4626 | 
                        - '-',  | 
                    |
| 4627 | 
                        -                frozenset({
                       | 
                    |
| 4628 | 
                        - '--help',  | 
                    |
| 4629 | 
                        - '-h',  | 
                    |
| 4630 | 
                        - '--version',  | 
                    |
| 4631 | 
                        - '--debug',  | 
                    |
| 4632 | 
                        - '--verbose',  | 
                    |
| 4633 | 
                        - '-v',  | 
                    |
| 4634 | 
                        - '--quiet',  | 
                    |
| 4635 | 
                        - '-q',  | 
                    |
| 4636 | 
                        - }),  | 
                    |
| 4637 | 
                        - id='derivepassphrase-export',  | 
                    |
| 4638 | 
                        - ),  | 
                    |
| 4639 | 
                        - pytest.param(  | 
                    |
| 4640 | 
                        -                ('export', 'vault'),
                       | 
                    |
| 4641 | 
                        - '-',  | 
                    |
| 4642 | 
                        -                frozenset({
                       | 
                    |
| 4643 | 
                        - '--help',  | 
                    |
| 4644 | 
                        - '-h',  | 
                    |
| 4645 | 
                        - '--version',  | 
                    |
| 4646 | 
                        - '--debug',  | 
                    |
| 4647 | 
                        - '--verbose',  | 
                    |
| 4648 | 
                        - '-v',  | 
                    |
| 4649 | 
                        - '--quiet',  | 
                    |
| 4650 | 
                        - '-q',  | 
                    |
| 4651 | 
                        - '--format',  | 
                    |
| 4652 | 
                        - '-f',  | 
                    |
| 4653 | 
                        - '--key',  | 
                    |
| 4654 | 
                        - '-k',  | 
                    |
| 4655 | 
                        - }),  | 
                    |
| 4656 | 
                        - id='derivepassphrase-export-vault',  | 
                    |
| 4657 | 
                        - ),  | 
                    |
| 4658 | 
                        - pytest.param(  | 
                    |
| 4659 | 
                        -                ('vault',),
                       | 
                    |
| 4660 | 
                        - '-',  | 
                    |
| 4661 | 
                        -                frozenset({
                       | 
                    |
| 4662 | 
                        - '--help',  | 
                    |
| 4663 | 
                        - '-h',  | 
                    |
| 4664 | 
                        - '--version',  | 
                    |
| 4665 | 
                        - '--debug',  | 
                    |
| 4666 | 
                        - '--verbose',  | 
                    |
| 4667 | 
                        - '-v',  | 
                    |
| 4668 | 
                        - '--quiet',  | 
                    |
| 4669 | 
                        - '-q',  | 
                    |
| 4670 | 
                        - '--phrase',  | 
                    |
| 4671 | 
                        - '-p',  | 
                    |
| 4672 | 
                        - '--key',  | 
                    |
| 4673 | 
                        - '-k',  | 
                    |
| 4674 | 
                        - '--length',  | 
                    |
| 4675 | 
                        - '-l',  | 
                    |
| 4676 | 
                        - '--repeat',  | 
                    |
| 4677 | 
                        - '-r',  | 
                    |
| 4678 | 
                        - '--upper',  | 
                    |
| 4679 | 
                        - '--lower',  | 
                    |
| 4680 | 
                        - '--number',  | 
                    |
| 4681 | 
                        - '--space',  | 
                    |
| 4682 | 
                        - '--dash',  | 
                    |
| 4683 | 
                        - '--symbol',  | 
                    |
| 4684 | 
                        - '--config',  | 
                    |
| 4685 | 
                        - '-c',  | 
                    |
| 4686 | 
                        - '--notes',  | 
                    |
| 4687 | 
                        - '-n',  | 
                    |
| 4688 | 
                        - '--delete',  | 
                    |
| 4689 | 
                        - '-x',  | 
                    |
| 4690 | 
                        - '--delete-globals',  | 
                    |
| 4691 | 
                        - '--clear',  | 
                    |
| 4692 | 
                        - '-X',  | 
                    |
| 4693 | 
                        - '--export',  | 
                    |
| 4694 | 
                        - '-e',  | 
                    |
| 4695 | 
                        - '--import',  | 
                    |
| 4696 | 
                        - '-i',  | 
                    |
| 4697 | 
                        - '--overwrite-existing',  | 
                    |
| 4698 | 
                        - '--merge-existing',  | 
                    |
| 4699 | 
                        - '--unset',  | 
                    |
| 4700 | 
                        - '--export-as',  | 
                    |
| 4701 | 
                        - }),  | 
                    |
| 4702 | 
                        - id='derivepassphrase-vault',  | 
                    |
| 4703 | 
                        - ),  | 
                    |
| 4704 | 
                        - ],  | 
                    |
| 4705 | 
                        - )  | 
                    |
| 4989 | 
                        + @Parametrizations.COMPLETABLE_OPTIONS.value  | 
                    |
| 4706 | 4990 | 
                        def test_200_options(  | 
                    
| 4707 | 4991 | 
                        self,  | 
                    
| 4708 | 4992 | 
                        command_prefix: Sequence[str],  | 
                    
| ... | ... | 
                      @@ -4713,23 +4997,7 @@ class TestShellCompletion:  | 
                  
| 4713 | 4997 | 
                        comp = self.Completions(command_prefix, incomplete)  | 
                    
| 4714 | 4998 | 
                        assert frozenset(comp.get_words()) == completions  | 
                    
| 4715 | 4999 | 
                         | 
                    
| 4716 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 4717 | 
                        - ['command_prefix', 'incomplete', 'completions'],  | 
                    |
| 4718 | 
                        - [  | 
                    |
| 4719 | 
                        - pytest.param(  | 
                    |
| 4720 | 
                        - (),  | 
                    |
| 4721 | 
                        - '',  | 
                    |
| 4722 | 
                        -                frozenset({'export', 'vault'}),
                       | 
                    |
| 4723 | 
                        - id='derivepassphrase',  | 
                    |
| 4724 | 
                        - ),  | 
                    |
| 4725 | 
                        - pytest.param(  | 
                    |
| 4726 | 
                        -                ('export',),
                       | 
                    |
| 4727 | 
                        - '',  | 
                    |
| 4728 | 
                        -                frozenset({'vault'}),
                       | 
                    |
| 4729 | 
                        - id='derivepassphrase-export',  | 
                    |
| 4730 | 
                        - ),  | 
                    |
| 4731 | 
                        - ],  | 
                    |
| 4732 | 
                        - )  | 
                    |
| 5000 | 
                        + @Parametrizations.COMPLETABLE_SUBCOMMANDS.value  | 
                    |
| 4733 | 5001 | 
                        def test_201_subcommands(  | 
                    
| 4734 | 5002 | 
                        self,  | 
                    
| 4735 | 5003 | 
                        command_prefix: Sequence[str],  | 
                    
| ... | ... | 
                      @@ -4740,72 +5008,22 @@ class TestShellCompletion:  | 
                  
| 4740 | 5008 | 
                        comp = self.Completions(command_prefix, incomplete)  | 
                    
| 4741 | 5009 | 
                        assert frozenset(comp.get_words()) == completions  | 
                    
| 4742 | 5010 | 
                         | 
                    
| 4743 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 4744 | 
                        - 'command_prefix',  | 
                    |
| 4745 | 
                        - [  | 
                    |
| 4746 | 
                        - pytest.param(  | 
                    |
| 4747 | 
                        -                ('export', 'vault'),
                       | 
                    |
| 4748 | 
                        - id='derivepassphrase-export-vault',  | 
                    |
| 4749 | 
                        - ),  | 
                    |
| 4750 | 
                        - pytest.param(  | 
                    |
| 4751 | 
                        -                ('vault', '--export'),
                       | 
                    |
| 4752 | 
                        - id='derivepassphrase-vault--export',  | 
                    |
| 4753 | 
                        - ),  | 
                    |
| 4754 | 
                        - pytest.param(  | 
                    |
| 4755 | 
                        -                ('vault', '--import'),
                       | 
                    |
| 4756 | 
                        - id='derivepassphrase-vault--import',  | 
                    |
| 4757 | 
                        - ),  | 
                    |
| 4758 | 
                        - ],  | 
                    |
| 4759 | 
                        - )  | 
                    |
| 4760 | 
                        -    @pytest.mark.parametrize('incomplete', ['', 'partial'])
                       | 
                    |
| 5011 | 
                        + @Parametrizations.COMPLETABLE_PATH_ARGUMENT.value  | 
                    |
| 5012 | 
                        + @Parametrizations.INCOMPLETE.value  | 
                    |
| 4761 | 5013 | 
                        def test_202_paths(  | 
                    
| 4762 | 5014 | 
                        self,  | 
                    
| 4763 | 5015 | 
                        command_prefix: Sequence[str],  | 
                    
| 4764 | 
                        - incomplete: str,  | 
                    |
| 4765 | 
                        - ) -> None:  | 
                    |
| 4766 | 
                        - """Our completion machinery works for all commands' paths."""  | 
                    |
| 4767 | 
                        -        file = click.shell_completion.CompletionItem('', type='file')
                       | 
                    |
| 4768 | 
                        -        completions = frozenset({(file.type, file.value, file.help)})
                       | 
                    |
| 4769 | 
                        - comp = self.Completions(command_prefix, incomplete)  | 
                    |
| 4770 | 
                        - assert (  | 
                    |
| 4771 | 
                        - frozenset((x.type, x.value, x.help) for x in comp()) == completions  | 
                    |
| 4772 | 
                        - )  | 
                    |
| 4773 | 
                        -  | 
                    |
| 4774 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 4775 | 
                        - ['config', 'incomplete', 'completions'],  | 
                    |
| 4776 | 
                        - [  | 
                    |
| 4777 | 
                        - pytest.param(  | 
                    |
| 4778 | 
                        -                {'services': {}},
                       | 
                    |
| 4779 | 
                        - '',  | 
                    |
| 4780 | 
                        - frozenset(),  | 
                    |
| 4781 | 
                        - id='no_services',  | 
                    |
| 4782 | 
                        - ),  | 
                    |
| 4783 | 
                        - pytest.param(  | 
                    |
| 4784 | 
                        -                {'services': {}},
                       | 
                    |
| 4785 | 
                        - 'partial',  | 
                    |
| 4786 | 
                        - frozenset(),  | 
                    |
| 4787 | 
                        - id='no_services_partial',  | 
                    |
| 4788 | 
                        - ),  | 
                    |
| 4789 | 
                        - pytest.param(  | 
                    |
| 4790 | 
                        -                {'services': {DUMMY_SERVICE: {'length': 10}}},
                       | 
                    |
| 4791 | 
                        - '',  | 
                    |
| 4792 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 4793 | 
                        - id='one_service',  | 
                    |
| 4794 | 
                        - ),  | 
                    |
| 4795 | 
                        - pytest.param(  | 
                    |
| 4796 | 
                        -                {'services': {DUMMY_SERVICE: {'length': 10}}},
                       | 
                    |
| 4797 | 
                        - DUMMY_SERVICE[:4],  | 
                    |
| 4798 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 4799 | 
                        - id='one_service_partial',  | 
                    |
| 4800 | 
                        - ),  | 
                    |
| 4801 | 
                        - pytest.param(  | 
                    |
| 4802 | 
                        -                {'services': {DUMMY_SERVICE: {'length': 10}}},
                       | 
                    |
| 4803 | 
                        - DUMMY_SERVICE[-4:],  | 
                    |
| 4804 | 
                        - frozenset(),  | 
                    |
| 4805 | 
                        - id='one_service_partial_miss',  | 
                    |
| 4806 | 
                        - ),  | 
                    |
| 4807 | 
                        - ],  | 
                    |
| 5016 | 
                        + incomplete: str,  | 
                    |
| 5017 | 
                        + ) -> None:  | 
                    |
| 5018 | 
                        + """Our completion machinery works for all commands' paths."""  | 
                    |
| 5019 | 
                        +        file = click.shell_completion.CompletionItem('', type='file')
                       | 
                    |
| 5020 | 
                        +        completions = frozenset({(file.type, file.value, file.help)})
                       | 
                    |
| 5021 | 
                        + comp = self.Completions(command_prefix, incomplete)  | 
                    |
| 5022 | 
                        + assert (  | 
                    |
| 5023 | 
                        + frozenset((x.type, x.value, x.help) for x in comp()) == completions  | 
                    |
| 4808 | 5024 | 
                        )  | 
                    
| 5025 | 
                        +  | 
                    |
| 5026 | 
                        + @Parametrizations.COMPLETABLE_SERVICE_NAMES.value  | 
                    |
| 4809 | 5027 | 
                        def test_203_service_names(  | 
                    
| 4810 | 5028 | 
                        self,  | 
                    
| 4811 | 5029 | 
                        config: _types.VaultConfig,  | 
                    
| ... | ... | 
                      @@ -4829,107 +5047,8 @@ class TestShellCompletion:  | 
                  
| 4829 | 5047 | 
                        comp = self.Completions(['vault'], incomplete)  | 
                    
| 4830 | 5048 | 
                        assert frozenset(comp.get_words()) == completions  | 
                    
| 4831 | 5049 | 
                         | 
                    
| 4832 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 4833 | 
                        - ['shell', 'format_func'],  | 
                    |
| 4834 | 
                        - [  | 
                    |
| 4835 | 
                        -            pytest.param('bash', bash_format, id='bash'),
                       | 
                    |
| 4836 | 
                        -            pytest.param('fish', fish_format, id='fish'),
                       | 
                    |
| 4837 | 
                        -            pytest.param('zsh', zsh_format, id='zsh'),
                       | 
                    |
| 4838 | 
                        - ],  | 
                    |
| 4839 | 
                        - )  | 
                    |
| 4840 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 4841 | 
                        - ['config', 'comp_func', 'args', 'incomplete', 'results'],  | 
                    |
| 4842 | 
                        - [  | 
                    |
| 4843 | 
                        - pytest.param(  | 
                    |
| 4844 | 
                        -                {'services': {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy()}},
                       | 
                    |
| 4845 | 
                        - cli_helpers.shell_complete_service,  | 
                    |
| 4846 | 
                        - ['vault'],  | 
                    |
| 4847 | 
                        - '',  | 
                    |
| 4848 | 
                        - [DUMMY_SERVICE],  | 
                    |
| 4849 | 
                        - id='base_config-service',  | 
                    |
| 4850 | 
                        - ),  | 
                    |
| 4851 | 
                        - pytest.param(  | 
                    |
| 4852 | 
                        -                {'services': {}},
                       | 
                    |
| 4853 | 
                        - cli_helpers.shell_complete_service,  | 
                    |
| 4854 | 
                        - ['vault'],  | 
                    |
| 4855 | 
                        - '',  | 
                    |
| 4856 | 
                        - [],  | 
                    |
| 4857 | 
                        - id='empty_config-service',  | 
                    |
| 4858 | 
                        - ),  | 
                    |
| 4859 | 
                        - pytest.param(  | 
                    |
| 4860 | 
                        -                {
                       | 
                    |
| 4861 | 
                        -                    'services': {
                       | 
                    |
| 4862 | 
                        - DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4863 | 
                        - 'newline\nin\nname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4864 | 
                        - }  | 
                    |
| 4865 | 
                        - },  | 
                    |
| 4866 | 
                        - cli_helpers.shell_complete_service,  | 
                    |
| 4867 | 
                        - ['vault'],  | 
                    |
| 4868 | 
                        - '',  | 
                    |
| 4869 | 
                        - [DUMMY_SERVICE],  | 
                    |
| 4870 | 
                        - id='incompletable_newline_config-service',  | 
                    |
| 4871 | 
                        - ),  | 
                    |
| 4872 | 
                        - pytest.param(  | 
                    |
| 4873 | 
                        -                {
                       | 
                    |
| 4874 | 
                        -                    'services': {
                       | 
                    |
| 4875 | 
                        - DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4876 | 
                        - 'backspace\bin\bname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4877 | 
                        - }  | 
                    |
| 4878 | 
                        - },  | 
                    |
| 4879 | 
                        - cli_helpers.shell_complete_service,  | 
                    |
| 4880 | 
                        - ['vault'],  | 
                    |
| 4881 | 
                        - '',  | 
                    |
| 4882 | 
                        - [DUMMY_SERVICE],  | 
                    |
| 4883 | 
                        - id='incompletable_backspace_config-service',  | 
                    |
| 4884 | 
                        - ),  | 
                    |
| 4885 | 
                        - pytest.param(  | 
                    |
| 4886 | 
                        -                {
                       | 
                    |
| 4887 | 
                        -                    'services': {
                       | 
                    |
| 4888 | 
                        - DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4889 | 
                        - 'colon:in:name': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4890 | 
                        - }  | 
                    |
| 4891 | 
                        - },  | 
                    |
| 4892 | 
                        - cli_helpers.shell_complete_service,  | 
                    |
| 4893 | 
                        - ['vault'],  | 
                    |
| 4894 | 
                        - '',  | 
                    |
| 4895 | 
                        - sorted([DUMMY_SERVICE, 'colon:in:name']),  | 
                    |
| 4896 | 
                        - id='brittle_colon_config-service',  | 
                    |
| 4897 | 
                        - ),  | 
                    |
| 4898 | 
                        - pytest.param(  | 
                    |
| 4899 | 
                        -                {
                       | 
                    |
| 4900 | 
                        -                    'services': {
                       | 
                    |
| 4901 | 
                        - DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4902 | 
                        - 'colon:in:name': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4903 | 
                        - 'newline\nin\nname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4904 | 
                        - 'backspace\bin\bname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4905 | 
                        - 'nul\x00in\x00name': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4906 | 
                        - 'del\x7fin\x7fname': DUMMY_CONFIG_SETTINGS.copy(),  | 
                    |
| 4907 | 
                        - }  | 
                    |
| 4908 | 
                        - },  | 
                    |
| 4909 | 
                        - cli_helpers.shell_complete_service,  | 
                    |
| 4910 | 
                        - ['vault'],  | 
                    |
| 4911 | 
                        - '',  | 
                    |
| 4912 | 
                        - sorted([DUMMY_SERVICE, 'colon:in:name']),  | 
                    |
| 4913 | 
                        - id='brittle_incompletable_multi_config-service',  | 
                    |
| 4914 | 
                        - ),  | 
                    |
| 4915 | 
                        - pytest.param(  | 
                    |
| 4916 | 
                        -                {'services': {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy()}},
                       | 
                    |
| 4917 | 
                        - cli_helpers.shell_complete_path,  | 
                    |
| 4918 | 
                        - ['vault', '--import'],  | 
                    |
| 4919 | 
                        - '',  | 
                    |
| 4920 | 
                        -                [click.shell_completion.CompletionItem('', type='file')],
                       | 
                    |
| 4921 | 
                        - id='base_config-path',  | 
                    |
| 4922 | 
                        - ),  | 
                    |
| 4923 | 
                        - pytest.param(  | 
                    |
| 4924 | 
                        -                {'services': {}},
                       | 
                    |
| 4925 | 
                        - cli_helpers.shell_complete_path,  | 
                    |
| 4926 | 
                        - ['vault', '--import'],  | 
                    |
| 4927 | 
                        - '',  | 
                    |
| 4928 | 
                        -                [click.shell_completion.CompletionItem('', type='file')],
                       | 
                    |
| 4929 | 
                        - id='empty_config-path',  | 
                    |
| 4930 | 
                        - ),  | 
                    |
| 4931 | 
                        - ],  | 
                    |
| 4932 | 
                        - )  | 
                    |
| 5050 | 
                        + @Parametrizations.SHELL_FORMATTER.value  | 
                    |
| 5051 | 
                        + @Parametrizations.COMPLETION_FUNCTION_INPUTS.value  | 
                    |
| 4933 | 5052 | 
                        def test_300_shell_completion_formatting(  | 
                    
| 4934 | 5053 | 
                        self,  | 
                    
| 4935 | 5054 | 
                        shell: str,  | 
                    
| ... | ... | 
                      @@ -4993,156 +5112,8 @@ class TestShellCompletion:  | 
                  
| 4993 | 5112 | 
                        assert actual_items == expected_items  | 
                    
| 4994 | 5113 | 
                        assert actual_string == expected_string  | 
                    
| 4995 | 5114 | 
                         | 
                    
| 4996 | 
                        -    @pytest.mark.parametrize('mode', ['config', 'import'])
                       | 
                    |
| 4997 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 4998 | 
                        - ['config', 'key', 'incomplete', 'completions'],  | 
                    |
| 4999 | 
                        - [  | 
                    |
| 5000 | 
                        - pytest.param(  | 
                    |
| 5001 | 
                        -                {
                       | 
                    |
| 5002 | 
                        -                    'services': {
                       | 
                    |
| 5003 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5004 | 
                        -                        'newline\nin\nname': {'length': 10},
                       | 
                    |
| 5005 | 
                        - },  | 
                    |
| 5006 | 
                        - },  | 
                    |
| 5007 | 
                        - 'newline\nin\nname',  | 
                    |
| 5008 | 
                        - '',  | 
                    |
| 5009 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 5010 | 
                        - id='newline',  | 
                    |
| 5011 | 
                        - ),  | 
                    |
| 5012 | 
                        - pytest.param(  | 
                    |
| 5013 | 
                        -                {
                       | 
                    |
| 5014 | 
                        -                    'services': {
                       | 
                    |
| 5015 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5016 | 
                        -                        'newline\nin\nname': {'length': 10},
                       | 
                    |
| 5017 | 
                        - },  | 
                    |
| 5018 | 
                        - },  | 
                    |
| 5019 | 
                        - 'newline\nin\nname',  | 
                    |
| 5020 | 
                        - 'serv',  | 
                    |
| 5021 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 5022 | 
                        - id='newline_partial_other',  | 
                    |
| 5023 | 
                        - ),  | 
                    |
| 5024 | 
                        - pytest.param(  | 
                    |
| 5025 | 
                        -                {
                       | 
                    |
| 5026 | 
                        -                    'services': {
                       | 
                    |
| 5027 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5028 | 
                        -                        'newline\nin\nname': {'length': 10},
                       | 
                    |
| 5029 | 
                        - },  | 
                    |
| 5030 | 
                        - },  | 
                    |
| 5031 | 
                        - 'newline\nin\nname',  | 
                    |
| 5032 | 
                        - 'newline',  | 
                    |
| 5033 | 
                        -                frozenset({}),
                       | 
                    |
| 5034 | 
                        - id='newline_partial_specific',  | 
                    |
| 5035 | 
                        - ),  | 
                    |
| 5036 | 
                        - pytest.param(  | 
                    |
| 5037 | 
                        -                {
                       | 
                    |
| 5038 | 
                        -                    'services': {
                       | 
                    |
| 5039 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5040 | 
                        -                        'nul\x00in\x00name': {'length': 10},
                       | 
                    |
| 5041 | 
                        - },  | 
                    |
| 5042 | 
                        - },  | 
                    |
| 5043 | 
                        - 'nul\x00in\x00name',  | 
                    |
| 5044 | 
                        - '',  | 
                    |
| 5045 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 5046 | 
                        - id='nul',  | 
                    |
| 5047 | 
                        - ),  | 
                    |
| 5048 | 
                        - pytest.param(  | 
                    |
| 5049 | 
                        -                {
                       | 
                    |
| 5050 | 
                        -                    'services': {
                       | 
                    |
| 5051 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5052 | 
                        -                        'nul\x00in\x00name': {'length': 10},
                       | 
                    |
| 5053 | 
                        - },  | 
                    |
| 5054 | 
                        - },  | 
                    |
| 5055 | 
                        - 'nul\x00in\x00name',  | 
                    |
| 5056 | 
                        - 'serv',  | 
                    |
| 5057 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 5058 | 
                        - id='nul_partial_other',  | 
                    |
| 5059 | 
                        - ),  | 
                    |
| 5060 | 
                        - pytest.param(  | 
                    |
| 5061 | 
                        -                {
                       | 
                    |
| 5062 | 
                        -                    'services': {
                       | 
                    |
| 5063 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5064 | 
                        -                        'nul\x00in\x00name': {'length': 10},
                       | 
                    |
| 5065 | 
                        - },  | 
                    |
| 5066 | 
                        - },  | 
                    |
| 5067 | 
                        - 'nul\x00in\x00name',  | 
                    |
| 5068 | 
                        - 'nul',  | 
                    |
| 5069 | 
                        -                frozenset({}),
                       | 
                    |
| 5070 | 
                        - id='nul_partial_specific',  | 
                    |
| 5071 | 
                        - ),  | 
                    |
| 5072 | 
                        - pytest.param(  | 
                    |
| 5073 | 
                        -                {
                       | 
                    |
| 5074 | 
                        -                    'services': {
                       | 
                    |
| 5075 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5076 | 
                        -                        'backspace\bin\bname': {'length': 10},
                       | 
                    |
| 5077 | 
                        - },  | 
                    |
| 5078 | 
                        - },  | 
                    |
| 5079 | 
                        - 'backspace\bin\bname',  | 
                    |
| 5080 | 
                        - '',  | 
                    |
| 5081 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 5082 | 
                        - id='backspace',  | 
                    |
| 5083 | 
                        - ),  | 
                    |
| 5084 | 
                        - pytest.param(  | 
                    |
| 5085 | 
                        -                {
                       | 
                    |
| 5086 | 
                        -                    'services': {
                       | 
                    |
| 5087 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5088 | 
                        -                        'backspace\bin\bname': {'length': 10},
                       | 
                    |
| 5089 | 
                        - },  | 
                    |
| 5090 | 
                        - },  | 
                    |
| 5091 | 
                        - 'backspace\bin\bname',  | 
                    |
| 5092 | 
                        - 'serv',  | 
                    |
| 5093 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 5094 | 
                        - id='backspace_partial_other',  | 
                    |
| 5095 | 
                        - ),  | 
                    |
| 5096 | 
                        - pytest.param(  | 
                    |
| 5097 | 
                        -                {
                       | 
                    |
| 5098 | 
                        -                    'services': {
                       | 
                    |
| 5099 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5100 | 
                        -                        'backspace\bin\bname': {'length': 10},
                       | 
                    |
| 5101 | 
                        - },  | 
                    |
| 5102 | 
                        - },  | 
                    |
| 5103 | 
                        - 'backspace\bin\bname',  | 
                    |
| 5104 | 
                        - 'back',  | 
                    |
| 5105 | 
                        -                frozenset({}),
                       | 
                    |
| 5106 | 
                        - id='backspace_partial_specific',  | 
                    |
| 5107 | 
                        - ),  | 
                    |
| 5108 | 
                        - pytest.param(  | 
                    |
| 5109 | 
                        -                {
                       | 
                    |
| 5110 | 
                        -                    'services': {
                       | 
                    |
| 5111 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5112 | 
                        -                        'del\x7fin\x7fname': {'length': 10},
                       | 
                    |
| 5113 | 
                        - },  | 
                    |
| 5114 | 
                        - },  | 
                    |
| 5115 | 
                        - 'del\x7fin\x7fname',  | 
                    |
| 5116 | 
                        - '',  | 
                    |
| 5117 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 5118 | 
                        - id='del',  | 
                    |
| 5119 | 
                        - ),  | 
                    |
| 5120 | 
                        - pytest.param(  | 
                    |
| 5121 | 
                        -                {
                       | 
                    |
| 5122 | 
                        -                    'services': {
                       | 
                    |
| 5123 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5124 | 
                        -                        'del\x7fin\x7fname': {'length': 10},
                       | 
                    |
| 5125 | 
                        - },  | 
                    |
| 5126 | 
                        - },  | 
                    |
| 5127 | 
                        - 'del\x7fin\x7fname',  | 
                    |
| 5128 | 
                        - 'serv',  | 
                    |
| 5129 | 
                        -                frozenset({DUMMY_SERVICE}),
                       | 
                    |
| 5130 | 
                        - id='del_partial_other',  | 
                    |
| 5131 | 
                        - ),  | 
                    |
| 5132 | 
                        - pytest.param(  | 
                    |
| 5133 | 
                        -                {
                       | 
                    |
| 5134 | 
                        -                    'services': {
                       | 
                    |
| 5135 | 
                        -                        DUMMY_SERVICE: {'length': 10},
                       | 
                    |
| 5136 | 
                        -                        'del\x7fin\x7fname': {'length': 10},
                       | 
                    |
| 5137 | 
                        - },  | 
                    |
| 5138 | 
                        - },  | 
                    |
| 5139 | 
                        - 'del\x7fin\x7fname',  | 
                    |
| 5140 | 
                        - 'del',  | 
                    |
| 5141 | 
                        -                frozenset({}),
                       | 
                    |
| 5142 | 
                        - id='del_partial_specific',  | 
                    |
| 5143 | 
                        - ),  | 
                    |
| 5144 | 
                        - ],  | 
                    |
| 5145 | 
                        - )  | 
                    |
| 5115 | 
                        + @Parametrizations.CONFIG_SETTING_MODE.value  | 
                    |
| 5116 | 
                        + @Parametrizations.SERVICE_NAME_COMPLETION_INPUTS.value  | 
                    |
| 5146 | 5117 | 
                        def test_400_incompletable_service_names(  | 
                    
| 5147 | 5118 | 
                        self,  | 
                    
| 5148 | 5119 | 
                        caplog: pytest.LogCaptureFixture,  | 
                    
| ... | ... | 
                      @@ -5220,7 +5191,7 @@ class TestShellCompletion:  | 
                  
| 5220 | 5191 | 
                        '',  | 
                    
| 5221 | 5192 | 
                        )  | 
                    
| 5222 | 5193 | 
                         | 
                    
| 5223 | 
                        -    @pytest.mark.parametrize('exc_type', [RuntimeError, KeyError, ValueError])
                       | 
                    |
| 5194 | 
                        + @Parametrizations.SERVICE_NAME_EXCEPTIONS.value  | 
                    |
| 5224 | 5195 | 
                        def test_410b_service_name_exceptions_custom_error(  | 
                    
| 5225 | 5196 | 
                        self,  | 
                    
| 5226 | 5197 | 
                        exc_type: type[Exception],  | 
                    
| ... | ... | 
                      @@ -6,6 +6,7 @@ from __future__ import annotations  | 
                  
| 6 | 6 | 
                         | 
                    
| 7 | 7 | 
                        import base64  | 
                    
| 8 | 8 | 
                        import contextlib  | 
                    
| 9 | 
                        +import enum  | 
                    |
| 9 | 10 | 
                        import json  | 
                    
| 10 | 11 | 
                        import pathlib  | 
                    
| 11 | 12 | 
                        from typing import TYPE_CHECKING  | 
                    
| ... | ... | 
                      @@ -39,6 +40,170 @@ if TYPE_CHECKING:  | 
                  
| 39 | 40 | 
                        from typing_extensions import Buffer, Literal  | 
                    
| 40 | 41 | 
                         | 
                    
| 41 | 42 | 
                         | 
                    
| 43 | 
                        +class Parametrizations(enum.Enum):  | 
                    |
| 44 | 
                        + BAD_CONFIG = pytest.mark.parametrize(  | 
                    |
| 45 | 
                        +        'config', ['xxx', 'null', '{"version": 255}']
                       | 
                    |
| 46 | 
                        + )  | 
                    |
| 47 | 
                        + # TODO(the-13th-letter): Rename "result" to "config_data".  | 
                    |
| 48 | 
                        + VAULT_NATIVE_CONFIG_DATA = pytest.mark.parametrize(  | 
                    |
| 49 | 
                        + ['config', 'format', 'result'],  | 
                    |
| 50 | 
                        + [  | 
                    |
| 51 | 
                        + pytest.param(  | 
                    |
| 52 | 
                        + tests.VAULT_V02_CONFIG,  | 
                    |
| 53 | 
                        + 'v0.2',  | 
                    |
| 54 | 
                        + tests.VAULT_V02_CONFIG_DATA,  | 
                    |
| 55 | 
                        + id='V02_CONFIG-v0.2',  | 
                    |
| 56 | 
                        + ),  | 
                    |
| 57 | 
                        + pytest.param(  | 
                    |
| 58 | 
                        + tests.VAULT_V02_CONFIG,  | 
                    |
| 59 | 
                        + 'v0.3',  | 
                    |
| 60 | 
                        + exporter.NotAVaultConfigError,  | 
                    |
| 61 | 
                        + id='V02_CONFIG-v0.3',  | 
                    |
| 62 | 
                        + ),  | 
                    |
| 63 | 
                        + pytest.param(  | 
                    |
| 64 | 
                        + tests.VAULT_V03_CONFIG,  | 
                    |
| 65 | 
                        + 'v0.2',  | 
                    |
| 66 | 
                        + exporter.NotAVaultConfigError,  | 
                    |
| 67 | 
                        + id='V03_CONFIG-v0.2',  | 
                    |
| 68 | 
                        + ),  | 
                    |
| 69 | 
                        + pytest.param(  | 
                    |
| 70 | 
                        + tests.VAULT_V03_CONFIG,  | 
                    |
| 71 | 
                        + 'v0.3',  | 
                    |
| 72 | 
                        + tests.VAULT_V03_CONFIG_DATA,  | 
                    |
| 73 | 
                        + id='V03_CONFIG-v0.3',  | 
                    |
| 74 | 
                        + ),  | 
                    |
| 75 | 
                        + ],  | 
                    |
| 76 | 
                        + )  | 
                    |
| 77 | 
                        + BAD_MASTER_KEYS_DATA = pytest.mark.parametrize(  | 
                    |
| 78 | 
                        + ['data', 'err_msg'],  | 
                    |
| 79 | 
                        + [  | 
                    |
| 80 | 
                        + pytest.param(  | 
                    |
| 81 | 
                        +                '{"version": 255}',
                       | 
                    |
| 82 | 
                        + 'bad or unsupported keys version header',  | 
                    |
| 83 | 
                        + id='v255',  | 
                    |
| 84 | 
                        + ),  | 
                    |
| 85 | 
                        + pytest.param(  | 
                    |
| 86 | 
                        +                '{"version": 1}\nAAAA\nAAAA',
                       | 
                    |
| 87 | 
                        + 'trailing data; cannot make sense',  | 
                    |
| 88 | 
                        + id='trailing-data',  | 
                    |
| 89 | 
                        + ),  | 
                    |
| 90 | 
                        + pytest.param(  | 
                    |
| 91 | 
                        +                '{"version": 1}\nAAAA',
                       | 
                    |
| 92 | 
                        + 'cannot handle version 0 encrypted keys',  | 
                    |
| 93 | 
                        + id='v0-keys',  | 
                    |
| 94 | 
                        + ),  | 
                    |
| 95 | 
                        + ],  | 
                    |
| 96 | 
                        + )  | 
                    |
| 97 | 
                        + # TODO(the-13th-letter): Consolidate with  | 
                    |
| 98 | 
                        + # test_derivepassphrase_exporter.Parametrizations.VAULT_CONFIG_FORMATS_DATA.  | 
                    |
| 99 | 
                        + # TODO(the-13th-letter): Reorder as "config", "format", "config_data".  | 
                    |
| 100 | 
                        + VAULT_CONFIG_FORMATS_DATA = pytest.mark.parametrize(  | 
                    |
| 101 | 
                        + ['format', 'config', 'config_data'],  | 
                    |
| 102 | 
                        + [  | 
                    |
| 103 | 
                        + pytest.param(  | 
                    |
| 104 | 
                        + 'v0.2',  | 
                    |
| 105 | 
                        + tests.VAULT_V02_CONFIG,  | 
                    |
| 106 | 
                        + tests.VAULT_V02_CONFIG_DATA,  | 
                    |
| 107 | 
                        + id='0.2',  | 
                    |
| 108 | 
                        + ),  | 
                    |
| 109 | 
                        + pytest.param(  | 
                    |
| 110 | 
                        + 'v0.3',  | 
                    |
| 111 | 
                        + tests.VAULT_V03_CONFIG,  | 
                    |
| 112 | 
                        + tests.VAULT_V03_CONFIG_DATA,  | 
                    |
| 113 | 
                        + id='0.3',  | 
                    |
| 114 | 
                        + ),  | 
                    |
| 115 | 
                        + pytest.param(  | 
                    |
| 116 | 
                        + 'storeroom',  | 
                    |
| 117 | 
                        + tests.VAULT_STOREROOM_CONFIG_ZIPPED,  | 
                    |
| 118 | 
                        + tests.VAULT_STOREROOM_CONFIG_DATA,  | 
                    |
| 119 | 
                        + id='storeroom',  | 
                    |
| 120 | 
                        + ),  | 
                    |
| 121 | 
                        + ],  | 
                    |
| 122 | 
                        + )  | 
                    |
| 123 | 
                        + STOREROOM_HANDLER = pytest.mark.parametrize(  | 
                    |
| 124 | 
                        + 'handler',  | 
                    |
| 125 | 
                        + [  | 
                    |
| 126 | 
                        + pytest.param(storeroom.export_storeroom_data, id='handler'),  | 
                    |
| 127 | 
                        + pytest.param(exporter.export_vault_config_data, id='dispatcher'),  | 
                    |
| 128 | 
                        + ],  | 
                    |
| 129 | 
                        + )  | 
                    |
| 130 | 
                        + VAULT_NATIVE_HANDLER = pytest.mark.parametrize(  | 
                    |
| 131 | 
                        + 'handler',  | 
                    |
| 132 | 
                        + [  | 
                    |
| 133 | 
                        + pytest.param(vault_native.export_vault_native_data, id='handler'),  | 
                    |
| 134 | 
                        + pytest.param(exporter.export_vault_config_data, id='dispatcher'),  | 
                    |
| 135 | 
                        + ],  | 
                    |
| 136 | 
                        + )  | 
                    |
| 137 | 
                        + VAULT_NATIVE_PBKDF2_RESULT = pytest.mark.parametrize(  | 
                    |
| 138 | 
                        + ['iterations', 'result'],  | 
                    |
| 139 | 
                        + [  | 
                    |
| 140 | 
                        + pytest.param(100, b'6ede361e81e9c061efcdd68aeb768b80', id='100'),  | 
                    |
| 141 | 
                        + pytest.param(200, b'bcc7d01e075b9ffb69e702bf701187c1', id='200'),  | 
                    |
| 142 | 
                        + ],  | 
                    |
| 143 | 
                        + )  | 
                    |
| 144 | 
                        + KEY_FORMATS = pytest.mark.parametrize(  | 
                    |
| 145 | 
                        + 'key',  | 
                    |
| 146 | 
                        + [  | 
                    |
| 147 | 
                        + None,  | 
                    |
| 148 | 
                        + pytest.param(tests.VAULT_MASTER_KEY, id='str'),  | 
                    |
| 149 | 
                        +            pytest.param(tests.VAULT_MASTER_KEY.encode('ascii'), id='bytes'),
                       | 
                    |
| 150 | 
                        + pytest.param(  | 
                    |
| 151 | 
                        +                bytearray(tests.VAULT_MASTER_KEY.encode('ascii')),
                       | 
                    |
| 152 | 
                        + id='bytearray',  | 
                    |
| 153 | 
                        + ),  | 
                    |
| 154 | 
                        + pytest.param(  | 
                    |
| 155 | 
                        +                memoryview(tests.VAULT_MASTER_KEY.encode('ascii')),
                       | 
                    |
| 156 | 
                        + id='memoryview',  | 
                    |
| 157 | 
                        + ),  | 
                    |
| 158 | 
                        + ],  | 
                    |
| 159 | 
                        + )  | 
                    |
| 160 | 
                        + # TODO(the-13th-letter): Reorder and rename to "config", "parser_class",  | 
                    |
| 161 | 
                        + # "config_data".  | 
                    |
| 162 | 
                        + VAULT_NATIVE_PARSER_CLASS_DATA = pytest.mark.parametrize(  | 
                    |
| 163 | 
                        + ['parser_class', 'config', 'result'],  | 
                    |
| 164 | 
                        + [  | 
                    |
| 165 | 
                        + pytest.param(  | 
                    |
| 166 | 
                        + vault_native.VaultNativeV02ConfigParser,  | 
                    |
| 167 | 
                        + tests.VAULT_V02_CONFIG,  | 
                    |
| 168 | 
                        + tests.VAULT_V02_CONFIG_DATA,  | 
                    |
| 169 | 
                        + id='0.2',  | 
                    |
| 170 | 
                        + ),  | 
                    |
| 171 | 
                        + pytest.param(  | 
                    |
| 172 | 
                        + vault_native.VaultNativeV03ConfigParser,  | 
                    |
| 173 | 
                        + tests.VAULT_V03_CONFIG,  | 
                    |
| 174 | 
                        + tests.VAULT_V03_CONFIG_DATA,  | 
                    |
| 175 | 
                        + id='0.3',  | 
                    |
| 176 | 
                        + ),  | 
                    |
| 177 | 
                        + ],  | 
                    |
| 178 | 
                        + )  | 
                    |
| 179 | 
                        +    PATH = pytest.mark.parametrize('path', ['.vault', None])
                       | 
                    |
| 180 | 
                        + BAD_STOREROOM_CONFIG_DATA = pytest.mark.parametrize(  | 
                    |
| 181 | 
                        + ['zipped_config', 'error_text'],  | 
                    |
| 182 | 
                        + [  | 
                    |
| 183 | 
                        + pytest.param(  | 
                    |
| 184 | 
                        + tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED,  | 
                    |
| 185 | 
                        + 'Object key mismatch',  | 
                    |
| 186 | 
                        + id='VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED',  | 
                    |
| 187 | 
                        + ),  | 
                    |
| 188 | 
                        + pytest.param(  | 
                    |
| 189 | 
                        + tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2,  | 
                    |
| 190 | 
                        + 'Directory index is not actually an index',  | 
                    |
| 191 | 
                        + id='VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2',  | 
                    |
| 192 | 
                        + ),  | 
                    |
| 193 | 
                        + pytest.param(  | 
                    |
| 194 | 
                        + tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3,  | 
                    |
| 195 | 
                        + 'Directory index is not actually an index',  | 
                    |
| 196 | 
                        + id='VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3',  | 
                    |
| 197 | 
                        + ),  | 
                    |
| 198 | 
                        + pytest.param(  | 
                    |
| 199 | 
                        + tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4,  | 
                    |
| 200 | 
                        + 'Object key mismatch',  | 
                    |
| 201 | 
                        + id='VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4',  | 
                    |
| 202 | 
                        + ),  | 
                    |
| 203 | 
                        + ],  | 
                    |
| 204 | 
                        + )  | 
                    |
| 205 | 
                        +  | 
                    |
| 206 | 
                        +  | 
                    |
| 42 | 207 | 
                        class TestCLI:  | 
                    
| 43 | 208 | 
                        """Test the command-line interface for `derivepassphrase export vault`."""  | 
                    
| 44 | 209 | 
                         | 
                    
| ... | ... | 
                      @@ -96,29 +261,7 @@ class TestCLI:  | 
                  
| 96 | 261 | 
                        assert result.clean_exit(empty_stderr=True), 'expected clean exit'  | 
                    
| 97 | 262 | 
                        assert json.loads(result.output) == tests.VAULT_V03_CONFIG_DATA  | 
                    
| 98 | 263 | 
                         | 
                    
| 99 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 100 | 
                        - ['format', 'config', 'config_data'],  | 
                    |
| 101 | 
                        - [  | 
                    |
| 102 | 
                        - pytest.param(  | 
                    |
| 103 | 
                        - 'v0.2',  | 
                    |
| 104 | 
                        - tests.VAULT_V02_CONFIG,  | 
                    |
| 105 | 
                        - tests.VAULT_V02_CONFIG_DATA,  | 
                    |
| 106 | 
                        - id='0.2',  | 
                    |
| 107 | 
                        - ),  | 
                    |
| 108 | 
                        - pytest.param(  | 
                    |
| 109 | 
                        - 'v0.3',  | 
                    |
| 110 | 
                        - tests.VAULT_V03_CONFIG,  | 
                    |
| 111 | 
                        - tests.VAULT_V03_CONFIG_DATA,  | 
                    |
| 112 | 
                        - id='0.3',  | 
                    |
| 113 | 
                        - ),  | 
                    |
| 114 | 
                        - pytest.param(  | 
                    |
| 115 | 
                        - 'storeroom',  | 
                    |
| 116 | 
                        - tests.VAULT_STOREROOM_CONFIG_ZIPPED,  | 
                    |
| 117 | 
                        - tests.VAULT_STOREROOM_CONFIG_DATA,  | 
                    |
| 118 | 
                        - id='storeroom',  | 
                    |
| 119 | 
                        - ),  | 
                    |
| 120 | 
                        - ],  | 
                    |
| 121 | 
                        - )  | 
                    |
| 264 | 
                        + @Parametrizations.VAULT_CONFIG_FORMATS_DATA.value  | 
                    |
| 122 | 265 | 
                        def test_210_load_vault_v02_v03_storeroom(  | 
                    
| 123 | 266 | 
                        self,  | 
                    
| 124 | 267 | 
                        format: str,  | 
                    
| ... | ... | 
                      @@ -324,30 +467,9 @@ class TestCLI:  | 
                  
| 324 | 467 | 
                        class TestStoreroom:  | 
                    
| 325 | 468 | 
                        """Test the "storeroom" handler and handler machinery."""  | 
                    
| 326 | 469 | 
                         | 
                    
| 327 | 
                        -    @pytest.mark.parametrize('path', ['.vault', None])
                       | 
                    |
| 328 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 329 | 
                        - 'key',  | 
                    |
| 330 | 
                        - [  | 
                    |
| 331 | 
                        - None,  | 
                    |
| 332 | 
                        - pytest.param(tests.VAULT_MASTER_KEY, id='str'),  | 
                    |
| 333 | 
                        -            pytest.param(tests.VAULT_MASTER_KEY.encode('ascii'), id='bytes'),
                       | 
                    |
| 334 | 
                        - pytest.param(  | 
                    |
| 335 | 
                        -                bytearray(tests.VAULT_MASTER_KEY.encode('ascii')),
                       | 
                    |
| 336 | 
                        - id='bytearray',  | 
                    |
| 337 | 
                        - ),  | 
                    |
| 338 | 
                        - pytest.param(  | 
                    |
| 339 | 
                        -                memoryview(tests.VAULT_MASTER_KEY.encode('ascii')),
                       | 
                    |
| 340 | 
                        - id='memoryview',  | 
                    |
| 341 | 
                        - ),  | 
                    |
| 342 | 
                        - ],  | 
                    |
| 343 | 
                        - )  | 
                    |
| 344 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 345 | 
                        - 'handler',  | 
                    |
| 346 | 
                        - [  | 
                    |
| 347 | 
                        - pytest.param(storeroom.export_storeroom_data, id='handler'),  | 
                    |
| 348 | 
                        - pytest.param(exporter.export_vault_config_data, id='dispatcher'),  | 
                    |
| 349 | 
                        - ],  | 
                    |
| 350 | 
                        - )  | 
                    |
| 470 | 
                        + @Parametrizations.PATH.value  | 
                    |
| 471 | 
                        + @Parametrizations.KEY_FORMATS.value  | 
                    |
| 472 | 
                        + @Parametrizations.STOREROOM_HANDLER.value  | 
                    |
| 351 | 473 | 
                        def test_200_export_data_path_and_keys_type(  | 
                    
| 352 | 474 | 
                        self,  | 
                    
| 353 | 475 | 
                        path: str | None,  | 
                    
| ... | ... | 
                      @@ -392,7 +514,7 @@ class TestStoreroom:  | 
                  
| 392 | 514 | 
                        with pytest.raises(ValueError, match='Cannot handle version 255'):  | 
                    
| 393 | 515 | 
                        storeroom._decrypt_bucket_item(bucket_item, master_keys)  | 
                    
| 394 | 516 | 
                         | 
                    
| 395 | 
                        -    @pytest.mark.parametrize('config', ['xxx', 'null', '{"version": 255}'])
                       | 
                    |
| 517 | 
                        + @Parametrizations.BAD_CONFIG.value  | 
                    |
| 396 | 518 | 
                        def test_401_decrypt_bucket_file_bad_json_or_version(  | 
                    
| 397 | 519 | 
                        self,  | 
                    
| 398 | 520 | 
                        config: str,  | 
                    
| ... | ... | 
                      @@ -427,33 +549,8 @@ class TestStoreroom:  | 
                  
| 427 | 549 | 
                        with pytest.raises(ValueError, match='Invalid bucket file: '):  | 
                    
| 428 | 550 | 
                        list(storeroom._decrypt_bucket_file(p, master_keys))  | 
                    
| 429 | 551 | 
                         | 
                    
| 430 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 431 | 
                        - ['data', 'err_msg'],  | 
                    |
| 432 | 
                        - [  | 
                    |
| 433 | 
                        - pytest.param(  | 
                    |
| 434 | 
                        -                '{"version": 255}',
                       | 
                    |
| 435 | 
                        - 'bad or unsupported keys version header',  | 
                    |
| 436 | 
                        - id='v255',  | 
                    |
| 437 | 
                        - ),  | 
                    |
| 438 | 
                        - pytest.param(  | 
                    |
| 439 | 
                        -                '{"version": 1}\nAAAA\nAAAA',
                       | 
                    |
| 440 | 
                        - 'trailing data; cannot make sense',  | 
                    |
| 441 | 
                        - id='trailing-data',  | 
                    |
| 442 | 
                        - ),  | 
                    |
| 443 | 
                        - pytest.param(  | 
                    |
| 444 | 
                        -                '{"version": 1}\nAAAA',
                       | 
                    |
| 445 | 
                        - 'cannot handle version 0 encrypted keys',  | 
                    |
| 446 | 
                        - id='v0-keys',  | 
                    |
| 447 | 
                        - ),  | 
                    |
| 448 | 
                        - ],  | 
                    |
| 449 | 
                        - )  | 
                    |
| 450 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 451 | 
                        - 'handler',  | 
                    |
| 452 | 
                        - [  | 
                    |
| 453 | 
                        - pytest.param(storeroom.export_storeroom_data, id='handler'),  | 
                    |
| 454 | 
                        - pytest.param(exporter.export_vault_config_data, id='dispatcher'),  | 
                    |
| 455 | 
                        - ],  | 
                    |
| 456 | 
                        - )  | 
                    |
| 552 | 
                        + @Parametrizations.BAD_MASTER_KEYS_DATA.value  | 
                    |
| 553 | 
                        + @Parametrizations.STOREROOM_HANDLER.value  | 
                    |
| 457 | 554 | 
                        def test_402_export_storeroom_data_bad_master_keys_file(  | 
                    
| 458 | 555 | 
                        self,  | 
                    
| 459 | 556 | 
                        data: str,  | 
                    
| ... | ... | 
                      @@ -485,38 +582,8 @@ class TestStoreroom:  | 
                  
| 485 | 582 | 
                        with pytest.raises(RuntimeError, match=err_msg):  | 
                    
| 486 | 583 | 
                        handler(format='storeroom')  | 
                    
| 487 | 584 | 
                         | 
                    
| 488 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 489 | 
                        - ['zipped_config', 'error_text'],  | 
                    |
| 490 | 
                        - [  | 
                    |
| 491 | 
                        - pytest.param(  | 
                    |
| 492 | 
                        - tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED,  | 
                    |
| 493 | 
                        - 'Object key mismatch',  | 
                    |
| 494 | 
                        - id='VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED',  | 
                    |
| 495 | 
                        - ),  | 
                    |
| 496 | 
                        - pytest.param(  | 
                    |
| 497 | 
                        - tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2,  | 
                    |
| 498 | 
                        - 'Directory index is not actually an index',  | 
                    |
| 499 | 
                        - id='VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED2',  | 
                    |
| 500 | 
                        - ),  | 
                    |
| 501 | 
                        - pytest.param(  | 
                    |
| 502 | 
                        - tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3,  | 
                    |
| 503 | 
                        - 'Directory index is not actually an index',  | 
                    |
| 504 | 
                        - id='VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED3',  | 
                    |
| 505 | 
                        - ),  | 
                    |
| 506 | 
                        - pytest.param(  | 
                    |
| 507 | 
                        - tests.VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4,  | 
                    |
| 508 | 
                        - 'Object key mismatch',  | 
                    |
| 509 | 
                        - id='VAULT_STOREROOM_BROKEN_DIR_CONFIG_ZIPPED4',  | 
                    |
| 510 | 
                        - ),  | 
                    |
| 511 | 
                        - ],  | 
                    |
| 512 | 
                        - )  | 
                    |
| 513 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 514 | 
                        - 'handler',  | 
                    |
| 515 | 
                        - [  | 
                    |
| 516 | 
                        - pytest.param(storeroom.export_storeroom_data, id='handler'),  | 
                    |
| 517 | 
                        - pytest.param(exporter.export_vault_config_data, id='dispatcher'),  | 
                    |
| 518 | 
                        - ],  | 
                    |
| 519 | 
                        - )  | 
                    |
| 585 | 
                        + @Parametrizations.BAD_STOREROOM_CONFIG_DATA.value  | 
                    |
| 586 | 
                        + @Parametrizations.STOREROOM_HANDLER.value  | 
                    |
| 520 | 587 | 
                        def test_403_export_storeroom_data_bad_directory_listing(  | 
                    
| 521 | 588 | 
                        self,  | 
                    
| 522 | 589 | 
                        zipped_config: bytes,  | 
                    
| ... | ... | 
                      @@ -634,13 +701,7 @@ class TestStoreroom:  | 
                  
| 634 | 701 | 
                        class TestVaultNativeConfig:  | 
                    
| 635 | 702 | 
                        """Test the vault-native handler and handler machinery."""  | 
                    
| 636 | 703 | 
                         | 
                    
| 637 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 638 | 
                        - ['iterations', 'result'],  | 
                    |
| 639 | 
                        - [  | 
                    |
| 640 | 
                        - pytest.param(100, b'6ede361e81e9c061efcdd68aeb768b80', id='100'),  | 
                    |
| 641 | 
                        - pytest.param(200, b'bcc7d01e075b9ffb69e702bf701187c1', id='200'),  | 
                    |
| 642 | 
                        - ],  | 
                    |
| 643 | 
                        - )  | 
                    |
| 704 | 
                        + @Parametrizations.VAULT_NATIVE_PBKDF2_RESULT.value  | 
                    |
| 644 | 705 | 
                        def test_200_pbkdf2_manually(self, iterations: int, result: bytes) -> None:  | 
                    
| 645 | 706 | 
                        """The PBKDF2 helper function works."""  | 
                    
| 646 | 707 | 
                        assert (  | 
                    
| ... | ... | 
                      @@ -650,42 +711,8 @@ class TestVaultNativeConfig:  | 
                  
| 650 | 711 | 
                        == result  | 
                    
| 651 | 712 | 
                        )  | 
                    
| 652 | 713 | 
                         | 
                    
| 653 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 654 | 
                        - ['config', 'format', 'result'],  | 
                    |
| 655 | 
                        - [  | 
                    |
| 656 | 
                        - pytest.param(  | 
                    |
| 657 | 
                        - tests.VAULT_V02_CONFIG,  | 
                    |
| 658 | 
                        - 'v0.2',  | 
                    |
| 659 | 
                        - tests.VAULT_V02_CONFIG_DATA,  | 
                    |
| 660 | 
                        - id='V02_CONFIG-v0.2',  | 
                    |
| 661 | 
                        - ),  | 
                    |
| 662 | 
                        - pytest.param(  | 
                    |
| 663 | 
                        - tests.VAULT_V02_CONFIG,  | 
                    |
| 664 | 
                        - 'v0.3',  | 
                    |
| 665 | 
                        - exporter.NotAVaultConfigError,  | 
                    |
| 666 | 
                        - id='V02_CONFIG-v0.3',  | 
                    |
| 667 | 
                        - ),  | 
                    |
| 668 | 
                        - pytest.param(  | 
                    |
| 669 | 
                        - tests.VAULT_V03_CONFIG,  | 
                    |
| 670 | 
                        - 'v0.2',  | 
                    |
| 671 | 
                        - exporter.NotAVaultConfigError,  | 
                    |
| 672 | 
                        - id='V03_CONFIG-v0.2',  | 
                    |
| 673 | 
                        - ),  | 
                    |
| 674 | 
                        - pytest.param(  | 
                    |
| 675 | 
                        - tests.VAULT_V03_CONFIG,  | 
                    |
| 676 | 
                        - 'v0.3',  | 
                    |
| 677 | 
                        - tests.VAULT_V03_CONFIG_DATA,  | 
                    |
| 678 | 
                        - id='V03_CONFIG-v0.3',  | 
                    |
| 679 | 
                        - ),  | 
                    |
| 680 | 
                        - ],  | 
                    |
| 681 | 
                        - )  | 
                    |
| 682 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 683 | 
                        - 'handler',  | 
                    |
| 684 | 
                        - [  | 
                    |
| 685 | 
                        - pytest.param(vault_native.export_vault_native_data, id='handler'),  | 
                    |
| 686 | 
                        - pytest.param(exporter.export_vault_config_data, id='dispatcher'),  | 
                    |
| 687 | 
                        - ],  | 
                    |
| 688 | 
                        - )  | 
                    |
| 714 | 
                        + @Parametrizations.VAULT_NATIVE_CONFIG_DATA.value  | 
                    |
| 715 | 
                        + @Parametrizations.VAULT_NATIVE_HANDLER.value  | 
                    |
| 689 | 716 | 
                        def test_201_export_vault_native_data_explicit_version(  | 
                    
| 690 | 717 | 
                        self,  | 
                    
| 691 | 718 | 
                        config: str,  | 
                    
| ... | ... | 
                      @@ -724,30 +751,9 @@ class TestVaultNativeConfig:  | 
                  
| 724 | 751 | 
                        parsed_config = handler(None, format=format)  | 
                    
| 725 | 752 | 
                        assert parsed_config == result  | 
                    
| 726 | 753 | 
                         | 
                    
| 727 | 
                        -    @pytest.mark.parametrize('path', ['.vault', None])
                       | 
                    |
| 728 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 729 | 
                        - 'key',  | 
                    |
| 730 | 
                        - [  | 
                    |
| 731 | 
                        - None,  | 
                    |
| 732 | 
                        - pytest.param(tests.VAULT_MASTER_KEY, id='str'),  | 
                    |
| 733 | 
                        -            pytest.param(tests.VAULT_MASTER_KEY.encode('ascii'), id='bytes'),
                       | 
                    |
| 734 | 
                        - pytest.param(  | 
                    |
| 735 | 
                        -                bytearray(tests.VAULT_MASTER_KEY.encode('ascii')),
                       | 
                    |
| 736 | 
                        - id='bytearray',  | 
                    |
| 737 | 
                        - ),  | 
                    |
| 738 | 
                        - pytest.param(  | 
                    |
| 739 | 
                        -                memoryview(tests.VAULT_MASTER_KEY.encode('ascii')),
                       | 
                    |
| 740 | 
                        - id='memoryview',  | 
                    |
| 741 | 
                        - ),  | 
                    |
| 742 | 
                        - ],  | 
                    |
| 743 | 
                        - )  | 
                    |
| 744 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 745 | 
                        - 'handler',  | 
                    |
| 746 | 
                        - [  | 
                    |
| 747 | 
                        - pytest.param(vault_native.export_vault_native_data, id='handler'),  | 
                    |
| 748 | 
                        - pytest.param(exporter.export_vault_config_data, id='dispatcher'),  | 
                    |
| 749 | 
                        - ],  | 
                    |
| 750 | 
                        - )  | 
                    |
| 754 | 
                        + @Parametrizations.PATH.value  | 
                    |
| 755 | 
                        + @Parametrizations.KEY_FORMATS.value  | 
                    |
| 756 | 
                        + @Parametrizations.VAULT_NATIVE_HANDLER.value  | 
                    |
| 751 | 757 | 
                        def test_202_export_data_path_and_keys_type(  | 
                    
| 752 | 758 | 
                        self,  | 
                    
| 753 | 759 | 
                        path: str | None,  | 
                    
| ... | ... | 
                      @@ -779,23 +785,7 @@ class TestVaultNativeConfig:  | 
                  
| 779 | 785 | 
                        == tests.VAULT_V03_CONFIG_DATA  | 
                    
| 780 | 786 | 
                        )  | 
                    
| 781 | 787 | 
                         | 
                    
| 782 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 783 | 
                        - ['parser_class', 'config', 'result'],  | 
                    |
| 784 | 
                        - [  | 
                    |
| 785 | 
                        - pytest.param(  | 
                    |
| 786 | 
                        - vault_native.VaultNativeV02ConfigParser,  | 
                    |
| 787 | 
                        - tests.VAULT_V02_CONFIG,  | 
                    |
| 788 | 
                        - tests.VAULT_V02_CONFIG_DATA,  | 
                    |
| 789 | 
                        - id='0.2',  | 
                    |
| 790 | 
                        - ),  | 
                    |
| 791 | 
                        - pytest.param(  | 
                    |
| 792 | 
                        - vault_native.VaultNativeV03ConfigParser,  | 
                    |
| 793 | 
                        - tests.VAULT_V03_CONFIG,  | 
                    |
| 794 | 
                        - tests.VAULT_V03_CONFIG_DATA,  | 
                    |
| 795 | 
                        - id='0.3',  | 
                    |
| 796 | 
                        - ),  | 
                    |
| 797 | 
                        - ],  | 
                    |
| 798 | 
                        - )  | 
                    |
| 788 | 
                        + @Parametrizations.VAULT_NATIVE_PARSER_CLASS_DATA.value  | 
                    |
| 799 | 789 | 
                        def test_300_result_caching(  | 
                    
| 800 | 790 | 
                        self,  | 
                    
| 801 | 791 | 
                        parser_class: type[vault_native.VaultNativeConfigParser],  | 
                    
| ... | ... | 
                      @@ -5,6 +5,7 @@  | 
                  
| 5 | 5 | 
                        from __future__ import annotations  | 
                    
| 6 | 6 | 
                         | 
                    
| 7 | 7 | 
                        import contextlib  | 
                    
| 8 | 
                        +import enum  | 
                    |
| 8 | 9 | 
                        import operator  | 
                    
| 9 | 10 | 
                        import os  | 
                    
| 10 | 11 | 
                        import pathlib  | 
                    
| ... | ... | 
                      @@ -23,6 +24,59 @@ if TYPE_CHECKING:  | 
                  
| 23 | 24 | 
                        from typing_extensions import Buffer  | 
                    
| 24 | 25 | 
                         | 
                    
| 25 | 26 | 
                         | 
                    
| 27 | 
                        +class Parametrizations(enum.Enum):  | 
                    |
| 28 | 
                        + EXPECTED_VAULT_PATH = pytest.mark.parametrize(  | 
                    |
| 29 | 
                        + ['expected', 'path'],  | 
                    |
| 30 | 
                        + [  | 
                    |
| 31 | 
                        +            (pathlib.Path('/tmp'), pathlib.Path('/tmp')),
                       | 
                    |
| 32 | 
                        +            (pathlib.Path('~'), pathlib.Path()),
                       | 
                    |
| 33 | 
                        +            (pathlib.Path('~/.vault'), None),
                       | 
                    |
| 34 | 
                        + ],  | 
                    |
| 35 | 
                        + )  | 
                    |
| 36 | 
                        + # TODO(the-13th-letter): Consolidate with  | 
                    |
| 37 | 
                        + # test_derivepassphrase_cli_export_vault.Parametrizations.VAULT_CONFIG_FORMATS_DATA.  | 
                    |
| 38 | 
                        + # TODO(the-13th-letter): Reorder as "config", "format", "config_data".  | 
                    |
| 39 | 
                        + VAULT_CONFIG_FORMATS_DATA = pytest.mark.parametrize(  | 
                    |
| 40 | 
                        + ['format', 'config', 'config_data'],  | 
                    |
| 41 | 
                        + [  | 
                    |
| 42 | 
                        + pytest.param(  | 
                    |
| 43 | 
                        + 'v0.2',  | 
                    |
| 44 | 
                        + tests.VAULT_V02_CONFIG,  | 
                    |
| 45 | 
                        + tests.VAULT_V02_CONFIG_DATA,  | 
                    |
| 46 | 
                        + id='0.2',  | 
                    |
| 47 | 
                        + ),  | 
                    |
| 48 | 
                        + pytest.param(  | 
                    |
| 49 | 
                        + 'v0.3',  | 
                    |
| 50 | 
                        + tests.VAULT_V03_CONFIG,  | 
                    |
| 51 | 
                        + tests.VAULT_V03_CONFIG_DATA,  | 
                    |
| 52 | 
                        + id='0.3',  | 
                    |
| 53 | 
                        + ),  | 
                    |
| 54 | 
                        + pytest.param(  | 
                    |
| 55 | 
                        + 'storeroom',  | 
                    |
| 56 | 
                        + tests.VAULT_STOREROOM_CONFIG_ZIPPED,  | 
                    |
| 57 | 
                        + tests.VAULT_STOREROOM_CONFIG_DATA,  | 
                    |
| 58 | 
                        + id='storeroom',  | 
                    |
| 59 | 
                        + ),  | 
                    |
| 60 | 
                        + ],  | 
                    |
| 61 | 
                        + )  | 
                    |
| 62 | 
                        + EXPORT_VAULT_CONFIG_DATA_HANDLER_NAMELISTS = pytest.mark.parametrize(  | 
                    |
| 63 | 
                        + ['namelist', 'err_pat'],  | 
                    |
| 64 | 
                        + [  | 
                    |
| 65 | 
                        + pytest.param((), '[Nn]o names given', id='empty'),  | 
                    |
| 66 | 
                        + pytest.param(  | 
                    |
| 67 | 
                        +                ('name1', '', 'name2'),
                       | 
                    |
| 68 | 
                        + '[Uu]nder an empty name',  | 
                    |
| 69 | 
                        + id='empty-string',  | 
                    |
| 70 | 
                        + ),  | 
                    |
| 71 | 
                        + pytest.param(  | 
                    |
| 72 | 
                        +                ('dummy', 'name1', 'name2'),
                       | 
                    |
| 73 | 
                        + '[Aa]lready registered',  | 
                    |
| 74 | 
                        + id='existing',  | 
                    |
| 75 | 
                        + ),  | 
                    |
| 76 | 
                        + ],  | 
                    |
| 77 | 
                        + )  | 
                    |
| 78 | 
                        +  | 
                    |
| 79 | 
                        +  | 
                    |
| 26 | 80 | 
                        class Test001ExporterUtils:  | 
                    
| 27 | 81 | 
                        """Test the utility functions in the `exporter` subpackage."""  | 
                    
| 28 | 82 | 
                         | 
                    
| ... | ... | 
                      @@ -194,14 +248,7 @@ class Test001ExporterUtils:  | 
                  
| 194 | 248 | 
                        monkeypatch.setenv(key, value)  | 
                    
| 195 | 249 | 
                        assert os.fsdecode(exporter.get_vault_key()) == expected  | 
                    
| 196 | 250 | 
                         | 
                    
| 197 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 198 | 
                        - ['expected', 'path'],  | 
                    |
| 199 | 
                        - [  | 
                    |
| 200 | 
                        -            (pathlib.Path('/tmp'), pathlib.Path('/tmp')),
                       | 
                    |
| 201 | 
                        -            (pathlib.Path('~'), pathlib.Path()),
                       | 
                    |
| 202 | 
                        -            (pathlib.Path('~/.vault'), None),
                       | 
                    |
| 203 | 
                        - ],  | 
                    |
| 204 | 
                        - )  | 
                    |
| 251 | 
                        + @Parametrizations.EXPECTED_VAULT_PATH.value  | 
                    |
| 205 | 252 | 
                        def test_210_get_vault_path(  | 
                    
| 206 | 253 | 
                        self,  | 
                    
| 207 | 254 | 
                        expected: pathlib.Path,  | 
                    
| ... | ... | 
                      @@ -304,22 +351,7 @@ class Test001ExporterUtils:  | 
                  
| 304 | 351 | 
                        ):  | 
                    
| 305 | 352 | 
                        exporter.get_vault_path()  | 
                    
| 306 | 353 | 
                         | 
                    
| 307 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 308 | 
                        - ['namelist', 'err_pat'],  | 
                    |
| 309 | 
                        - [  | 
                    |
| 310 | 
                        - pytest.param((), '[Nn]o names given', id='empty'),  | 
                    |
| 311 | 
                        - pytest.param(  | 
                    |
| 312 | 
                        -                ('name1', '', 'name2'),
                       | 
                    |
| 313 | 
                        - '[Uu]nder an empty name',  | 
                    |
| 314 | 
                        - id='empty-string',  | 
                    |
| 315 | 
                        - ),  | 
                    |
| 316 | 
                        - pytest.param(  | 
                    |
| 317 | 
                        -                ('dummy', 'name1', 'name2'),
                       | 
                    |
| 318 | 
                        - '[Aa]lready registered',  | 
                    |
| 319 | 
                        - id='existing',  | 
                    |
| 320 | 
                        - ),  | 
                    |
| 321 | 
                        - ],  | 
                    |
| 322 | 
                        - )  | 
                    |
| 354 | 
                        + @Parametrizations.EXPORT_VAULT_CONFIG_DATA_HANDLER_NAMELISTS.value  | 
                    |
| 323 | 355 | 
                        def test_320_register_export_vault_config_data_handler_errors(  | 
                    
| 324 | 356 | 
                        self,  | 
                    
| 325 | 357 | 
                        namelist: tuple[str, ...],  | 
                    
| ... | ... | 
                      @@ -398,29 +430,7 @@ class Test002CLI:  | 
                  
| 398 | 430 | 
                        )  | 
                    
| 399 | 431 | 
                         | 
                    
| 400 | 432 | 
                        @tests.skip_if_cryptography_support  | 
                    
| 401 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 402 | 
                        - ['format', 'config', 'key'],  | 
                    |
| 403 | 
                        - [  | 
                    |
| 404 | 
                        - pytest.param(  | 
                    |
| 405 | 
                        - 'v0.2',  | 
                    |
| 406 | 
                        - tests.VAULT_V02_CONFIG,  | 
                    |
| 407 | 
                        - tests.VAULT_MASTER_KEY,  | 
                    |
| 408 | 
                        - id='v0.2',  | 
                    |
| 409 | 
                        - ),  | 
                    |
| 410 | 
                        - pytest.param(  | 
                    |
| 411 | 
                        - 'v0.3',  | 
                    |
| 412 | 
                        - tests.VAULT_V03_CONFIG,  | 
                    |
| 413 | 
                        - tests.VAULT_MASTER_KEY,  | 
                    |
| 414 | 
                        - id='v0.3',  | 
                    |
| 415 | 
                        - ),  | 
                    |
| 416 | 
                        - pytest.param(  | 
                    |
| 417 | 
                        - 'storeroom',  | 
                    |
| 418 | 
                        - tests.VAULT_STOREROOM_CONFIG_ZIPPED,  | 
                    |
| 419 | 
                        - tests.VAULT_MASTER_KEY,  | 
                    |
| 420 | 
                        - id='storeroom',  | 
                    |
| 421 | 
                        - ),  | 
                    |
| 422 | 
                        - ],  | 
                    |
| 423 | 
                        - )  | 
                    |
| 433 | 
                        + @Parametrizations.VAULT_CONFIG_FORMATS_DATA.value  | 
                    |
| 424 | 434 | 
                        def test_999_no_cryptography_error_message(  | 
                    
| 425 | 435 | 
                        self,  | 
                    
| 426 | 436 | 
                        caplog: pytest.LogCaptureFixture,  | 
                    
| ... | ... | 
                      @@ -8,6 +8,7 @@ from __future__ import annotations  | 
                  
| 8 | 8 | 
                         | 
                    
| 9 | 9 | 
                        import collections  | 
                    
| 10 | 10 | 
                        import contextlib  | 
                    
| 11 | 
                        +import enum  | 
                    |
| 11 | 12 | 
                        import functools  | 
                    
| 12 | 13 | 
                        import math  | 
                    
| 13 | 14 | 
                        import operator  | 
                    
| ... | ... | 
                      @@ -54,6 +55,29 @@ def bitseq(string: str) -> list[int]:  | 
                  
| 54 | 55 | 
                        return [int(char, 2) for char in string]  | 
                    
| 55 | 56 | 
                         | 
                    
| 56 | 57 | 
                         | 
                    
| 58 | 
                        +class Parametrizations(enum.Enum):  | 
                    |
| 59 | 
                        + BIG_ENDIAN_NUMBER_EXCEPTIONS = pytest.mark.parametrize(  | 
                    |
| 60 | 
                        + ['exc_type', 'exc_pattern', 'sequence', 'base'],  | 
                    |
| 61 | 
                        + [  | 
                    |
| 62 | 
                        + (ValueError, 'invalid base 3 digit:', [-1], 3),  | 
                    |
| 63 | 
                        + (ValueError, 'invalid base:', [0], 1),  | 
                    |
| 64 | 
                        + (TypeError, 'not an integer:', [0.0, 1.0, 0.0, 1.0], 2),  | 
                    |
| 65 | 
                        + ],  | 
                    |
| 66 | 
                        + )  | 
                    |
| 67 | 
                        + INVALID_SEQUIN_INPUTS = pytest.mark.parametrize(  | 
                    |
| 68 | 
                        + ['sequence', 'is_bitstring', 'exc_type', 'exc_pattern'],  | 
                    |
| 69 | 
                        + [  | 
                    |
| 70 | 
                        + (  | 
                    |
| 71 | 
                        + [0, 1, 2, 3, 4, 5, 6, 7],  | 
                    |
| 72 | 
                        + True,  | 
                    |
| 73 | 
                        + ValueError,  | 
                    |
| 74 | 
                        + 'sequence item out of range',  | 
                    |
| 75 | 
                        + ),  | 
                    |
| 76 | 
                        +            ('こんにちは。', False, ValueError, 'sequence item out of range'),
                       | 
                    |
| 77 | 
                        + ],  | 
                    |
| 78 | 
                        + )  | 
                    |
| 79 | 
                        +  | 
                    |
| 80 | 
                        +  | 
                    |
| 57 | 81 | 
                        class TestStaticFunctionality:  | 
                    
| 58 | 82 | 
                        """Test the static functionality in the `sequin` module."""  | 
                    
| 59 | 83 | 
                         | 
                    
| ... | ... | 
                      @@ -175,14 +199,7 @@ class TestStaticFunctionality:  | 
                  
| 175 | 199 | 
                        sequin.Sequin._big_endian_number(sequence, base=base)  | 
                    
| 176 | 200 | 
                        ) == expected  | 
                    
| 177 | 201 | 
                         | 
                    
| 178 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 179 | 
                        - ['exc_type', 'exc_pattern', 'sequence', 'base'],  | 
                    |
| 180 | 
                        - [  | 
                    |
| 181 | 
                        - (ValueError, 'invalid base 3 digit:', [-1], 3),  | 
                    |
| 182 | 
                        - (ValueError, 'invalid base:', [0], 1),  | 
                    |
| 183 | 
                        - (TypeError, 'not an integer:', [0.0, 1.0, 0.0, 1.0], 2),  | 
                    |
| 184 | 
                        - ],  | 
                    |
| 185 | 
                        - )  | 
                    |
| 202 | 
                        + @Parametrizations.BIG_ENDIAN_NUMBER_EXCEPTIONS.value  | 
                    |
| 186 | 203 | 
                        def test_300_big_endian_number_exceptions(  | 
                    
| 187 | 204 | 
                        self,  | 
                    
| 188 | 205 | 
                        exc_type: type[Exception],  | 
                    
| ... | ... | 
                      @@ -524,18 +541,7 @@ class TestSequin:  | 
                  
| 524 | 541 | 
                                             f'After step {i}, the bit sequence is not exhausted yet'
                       | 
                    
| 525 | 542 | 
                        )  | 
                    
| 526 | 543 | 
                         | 
                    
| 527 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 528 | 
                        - ['sequence', 'is_bitstring', 'exc_type', 'exc_pattern'],  | 
                    |
| 529 | 
                        - [  | 
                    |
| 530 | 
                        - (  | 
                    |
| 531 | 
                        - [0, 1, 2, 3, 4, 5, 6, 7],  | 
                    |
| 532 | 
                        - True,  | 
                    |
| 533 | 
                        - ValueError,  | 
                    |
| 534 | 
                        - 'sequence item out of range',  | 
                    |
| 535 | 
                        - ),  | 
                    |
| 536 | 
                        -            ('こんにちは。', False, ValueError, 'sequence item out of range'),
                       | 
                    |
| 537 | 
                        - ],  | 
                    |
| 538 | 
                        - )  | 
                    |
| 544 | 
                        + @Parametrizations.INVALID_SEQUIN_INPUTS.value  | 
                    |
| 539 | 545 | 
                        def test_300_constructor_exceptions(  | 
                    
| 540 | 546 | 
                        self,  | 
                    
| 541 | 547 | 
                        sequence: list[int] | str,  | 
                    
| ... | ... | 
                      @@ -8,6 +8,7 @@ from __future__ import annotations  | 
                  
| 8 | 8 | 
                         | 
                    
| 9 | 9 | 
                        import base64  | 
                    
| 10 | 10 | 
                        import contextlib  | 
                    
| 11 | 
                        +import enum  | 
                    |
| 11 | 12 | 
                        import io  | 
                    
| 12 | 13 | 
                        import re  | 
                    
| 13 | 14 | 
                        import socket  | 
                    
| ... | ... | 
                      @@ -29,74 +30,125 @@ if TYPE_CHECKING:  | 
                  
| 29 | 30 | 
                        from typing_extensions import Any, Buffer  | 
                    
| 30 | 31 | 
                         | 
                    
| 31 | 32 | 
                         | 
                    
| 32 | 
                        -class TestStaticFunctionality:  | 
                    |
| 33 | 
                        - """Test the static functionality of the `ssh_agent` module."""  | 
                    |
| 34 | 
                        -  | 
                    |
| 35 | 
                        - @staticmethod  | 
                    |
| 36 | 
                        - def as_ssh_string(bytestring: bytes) -> bytes:  | 
                    |
| 37 | 
                        - """Return an encoded SSH string from a bytestring.  | 
                    |
| 38 | 
                        -  | 
                    |
| 39 | 
                        - This is a helper function for hypothesis data generation.  | 
                    |
| 40 | 
                        -  | 
                    |
| 41 | 
                        - """  | 
                    |
| 42 | 
                        - return int.to_bytes(len(bytestring), 4, 'big') + bytestring  | 
                    |
| 43 | 
                        -  | 
                    |
| 44 | 
                        - @staticmethod  | 
                    |
| 45 | 
                        - def canonicalize1(data: bytes) -> bytes:  | 
                    |
| 46 | 
                        - """Return an encoded SSH string from a bytestring.  | 
                    |
| 47 | 
                        -  | 
                    |
| 48 | 
                        - This is a helper function for hypothesis testing.  | 
                    |
| 49 | 
                        -  | 
                    |
| 50 | 
                        - References:  | 
                    |
| 51 | 
                        -  | 
                    |
| 52 | 
                        - * [David R. MacIver: Another invariant to test for  | 
                    |
| 53 | 
                        - encoders][DECODE_ENCODE]  | 
                    |
| 54 | 
                        -  | 
                    |
| 55 | 
                        - [DECODE_ENCODE]: https://hypothesis.works/articles/canonical-serialization/  | 
                    |
| 56 | 
                        -  | 
                    |
| 57 | 
                        - """  | 
                    |
| 58 | 
                        - return ssh_agent.SSHAgentClient.string(  | 
                    |
| 59 | 
                        - ssh_agent.SSHAgentClient.unstring(data)  | 
                    |
| 33 | 
                        +class Parametrizations(enum.Enum):  | 
                    |
| 34 | 
                        + SSH_STRING_EXCEPTIONS = pytest.mark.parametrize(  | 
                    |
| 35 | 
                        + ['input', 'exc_type', 'exc_pattern'],  | 
                    |
| 36 | 
                        + [  | 
                    |
| 37 | 
                        + pytest.param(  | 
                    |
| 38 | 
                        + 'some string', TypeError, 'invalid payload type', id='str'  | 
                    |
| 39 | 
                        + ),  | 
                    |
| 40 | 
                        + ],  | 
                    |
| 60 | 41 | 
                        )  | 
                    
| 61 | 
                        -  | 
                    |
| 62 | 
                        - @staticmethod  | 
                    |
| 63 | 
                        - def canonicalize2(data: bytes) -> bytes:  | 
                    |
| 64 | 
                        - """Return an encoded SSH string from a bytestring.  | 
                    |
| 65 | 
                        -  | 
                    |
| 66 | 
                        - This is a helper function for hypothesis testing.  | 
                    |
| 67 | 
                        -  | 
                    |
| 68 | 
                        - References:  | 
                    |
| 69 | 
                        -  | 
                    |
| 70 | 
                        - * [David R. MacIver: Another invariant to test for  | 
                    |
| 71 | 
                        - encoders][DECODE_ENCODE]  | 
                    |
| 72 | 
                        -  | 
                    |
| 73 | 
                        - [DECODE_ENCODE]: https://hypothesis.works/articles/canonical-serialization/  | 
                    |
| 74 | 
                        -  | 
                    |
| 75 | 
                        - """  | 
                    |
| 76 | 
                        - unstringed, trailer = ssh_agent.SSHAgentClient.unstring_prefix(data)  | 
                    |
| 77 | 
                        - assert not trailer  | 
                    |
| 78 | 
                        - return ssh_agent.SSHAgentClient.string(unstringed)  | 
                    |
| 79 | 
                        -  | 
                    |
| 80 | 
                        - # TODO(the-13th-letter): Re-evaluate if this check is worth keeping.  | 
                    |
| 81 | 
                        - # It cannot provide true tamper-resistence, but probably appears to.  | 
                    |
| 82 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 83 | 
                        - ['public_key', 'public_key_data'],  | 
                    |
| 42 | 
                        + SSH_UNSTRING_EXCEPTIONS = pytest.mark.parametrize(  | 
                    |
| 43 | 
                        + ['input', 'exc_type', 'exc_pattern', 'has_trailer', 'parts'],  | 
                    |
| 84 | 44 | 
                        [  | 
                    
| 85 | 
                        - (val.public_key, val.public_key_data)  | 
                    |
| 86 | 
                        - for val in tests.SUPPORTED_KEYS.values()  | 
                    |
| 45 | 
                        + pytest.param(  | 
                    |
| 46 | 
                        + b'ssh',  | 
                    |
| 47 | 
                        + ValueError,  | 
                    |
| 48 | 
                        + 'malformed SSH byte string',  | 
                    |
| 49 | 
                        + False,  | 
                    |
| 50 | 
                        + None,  | 
                    |
| 51 | 
                        + id='unencoded',  | 
                    |
| 52 | 
                        + ),  | 
                    |
| 53 | 
                        + pytest.param(  | 
                    |
| 54 | 
                        + b'\x00\x00\x00\x08ssh-rsa',  | 
                    |
| 55 | 
                        + ValueError,  | 
                    |
| 56 | 
                        + 'malformed SSH byte string',  | 
                    |
| 57 | 
                        + False,  | 
                    |
| 58 | 
                        + None,  | 
                    |
| 59 | 
                        + id='truncated',  | 
                    |
| 60 | 
                        + ),  | 
                    |
| 61 | 
                        + pytest.param(  | 
                    |
| 62 | 
                        + b'\x00\x00\x00\x04XXX trailing text',  | 
                    |
| 63 | 
                        + ValueError,  | 
                    |
| 64 | 
                        + 'malformed SSH byte string',  | 
                    |
| 65 | 
                        + True,  | 
                    |
| 66 | 
                        + (b'XXX ', b'trailing text'),  | 
                    |
| 67 | 
                        + id='trailing-data',  | 
                    |
| 68 | 
                        + ),  | 
                    |
| 87 | 69 | 
                        ],  | 
                    
| 88 | 
                        - ids=list(tests.SUPPORTED_KEYS.keys()),  | 
                    |
| 89 | 70 | 
                        )  | 
                    
| 90 | 
                        - def test_100_key_decoding(  | 
                    |
| 91 | 
                        - self, public_key: bytes, public_key_data: bytes  | 
                    |
| 92 | 
                        - ) -> None:  | 
                    |
| 93 | 
                        - """The [`tests.ALL_KEYS`][] public key data looks sane."""  | 
                    |
| 94 | 
                        - keydata = base64.b64decode(public_key.split(None, 2)[1])  | 
                    |
| 95 | 
                        - assert keydata == public_key_data, (  | 
                    |
| 96 | 
                        - "recorded public key data doesn't match"  | 
                    |
| 71 | 
                        + SSH_STRING_INPUT = pytest.mark.parametrize(  | 
                    |
| 72 | 
                        + ['input', 'expected'],  | 
                    |
| 73 | 
                        + [  | 
                    |
| 74 | 
                        + pytest.param(  | 
                    |
| 75 | 
                        + b'ssh-rsa',  | 
                    |
| 76 | 
                        + b'\x00\x00\x00\x07ssh-rsa',  | 
                    |
| 77 | 
                        + id='ssh-rsa',  | 
                    |
| 78 | 
                        + ),  | 
                    |
| 79 | 
                        + pytest.param(  | 
                    |
| 80 | 
                        + b'ssh-ed25519',  | 
                    |
| 81 | 
                        + b'\x00\x00\x00\x0bssh-ed25519',  | 
                    |
| 82 | 
                        + id='ssh-ed25519',  | 
                    |
| 83 | 
                        + ),  | 
                    |
| 84 | 
                        + pytest.param(  | 
                    |
| 85 | 
                        + ssh_agent.SSHAgentClient.string(b'ssh-ed25519'),  | 
                    |
| 86 | 
                        + b'\x00\x00\x00\x0f\x00\x00\x00\x0bssh-ed25519',  | 
                    |
| 87 | 
                        + id='string(ssh-ed25519)',  | 
                    |
| 88 | 
                        + ),  | 
                    |
| 89 | 
                        + ],  | 
                    |
| 97 | 90 | 
                        )  | 
                    
| 98 | 
                        -  | 
                    |
| 99 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 91 | 
                        + SSH_UNSTRING_INPUT = pytest.mark.parametrize(  | 
                    |
| 92 | 
                        + ['input', 'expected'],  | 
                    |
| 93 | 
                        + [  | 
                    |
| 94 | 
                        + pytest.param(  | 
                    |
| 95 | 
                        + b'\x00\x00\x00\x07ssh-rsa',  | 
                    |
| 96 | 
                        + b'ssh-rsa',  | 
                    |
| 97 | 
                        + id='ssh-rsa',  | 
                    |
| 98 | 
                        + ),  | 
                    |
| 99 | 
                        + pytest.param(  | 
                    |
| 100 | 
                        + ssh_agent.SSHAgentClient.string(b'ssh-ed25519'),  | 
                    |
| 101 | 
                        + b'ssh-ed25519',  | 
                    |
| 102 | 
                        + id='ssh-ed25519',  | 
                    |
| 103 | 
                        + ),  | 
                    |
| 104 | 
                        + ],  | 
                    |
| 105 | 
                        + )  | 
                    |
| 106 | 
                        + UINT32_INPUT = pytest.mark.parametrize(  | 
                    |
| 107 | 
                        + ['input', 'expected'],  | 
                    |
| 108 | 
                        + [  | 
                    |
| 109 | 
                        + pytest.param(16777216, b'\x01\x00\x00\x00', id='16777216'),  | 
                    |
| 110 | 
                        + ],  | 
                    |
| 111 | 
                        + )  | 
                    |
| 112 | 
                        + SIGN_ERROR_RESPONSES = pytest.mark.parametrize(  | 
                    |
| 113 | 
                        + [  | 
                    |
| 114 | 
                        + 'key',  | 
                    |
| 115 | 
                        + 'check',  | 
                    |
| 116 | 
                        + 'response_code',  | 
                    |
| 117 | 
                        + 'response',  | 
                    |
| 118 | 
                        + 'exc_type',  | 
                    |
| 119 | 
                        + 'exc_pattern',  | 
                    |
| 120 | 
                        + ],  | 
                    |
| 121 | 
                        + [  | 
                    |
| 122 | 
                        + pytest.param(  | 
                    |
| 123 | 
                        + b'invalid-key',  | 
                    |
| 124 | 
                        + True,  | 
                    |
| 125 | 
                        + _types.SSH_AGENT.FAILURE,  | 
                    |
| 126 | 
                        + b'',  | 
                    |
| 127 | 
                        + KeyError,  | 
                    |
| 128 | 
                        + 'target SSH key not loaded into agent',  | 
                    |
| 129 | 
                        + id='key-not-loaded',  | 
                    |
| 130 | 
                        + ),  | 
                    |
| 131 | 
                        + pytest.param(  | 
                    |
| 132 | 
                        + tests.SUPPORTED_KEYS['ed25519'].public_key_data,  | 
                    |
| 133 | 
                        + True,  | 
                    |
| 134 | 
                        + _types.SSH_AGENT.FAILURE,  | 
                    |
| 135 | 
                        + b'',  | 
                    |
| 136 | 
                        + ssh_agent.SSHAgentFailedError,  | 
                    |
| 137 | 
                        + 'failed to complete the request',  | 
                    |
| 138 | 
                        + id='failed-to-complete',  | 
                    |
| 139 | 
                        + ),  | 
                    |
| 140 | 
                        + ],  | 
                    |
| 141 | 
                        + )  | 
                    |
| 142 | 
                        + SSH_KEY_SELECTION = pytest.mark.parametrize(  | 
                    |
| 143 | 
                        + ['key', 'single'],  | 
                    |
| 144 | 
                        + [  | 
                    |
| 145 | 
                        + (value.public_key_data, False)  | 
                    |
| 146 | 
                        + for value in tests.SUPPORTED_KEYS.values()  | 
                    |
| 147 | 
                        + ]  | 
                    |
| 148 | 
                        + + [(tests.list_keys_singleton()[0].key, True)],  | 
                    |
| 149 | 
                        + ids=[*tests.SUPPORTED_KEYS.keys(), 'singleton'],  | 
                    |
| 150 | 
                        + )  | 
                    |
| 151 | 
                        + SH_EXPORT_LINES = pytest.mark.parametrize(  | 
                    |
| 100 | 152 | 
                        ['line', 'env_name', 'value'],  | 
                    
| 101 | 153 | 
                        [  | 
                    
| 102 | 154 | 
                        pytest.param(  | 
                    
| ... | ... | 
                      @@ -167,6 +219,175 @@ class TestStaticFunctionality:  | 
                  
| 167 | 219 | 
                        ),  | 
                    
| 168 | 220 | 
                        ],  | 
                    
| 169 | 221 | 
                        )  | 
                    
| 222 | 
                        + # TODO(the-13th-letter): Modify receiver to receive the whole struct  | 
                    |
| 223 | 
                        + # directly.  | 
                    |
| 224 | 
                        + PUBLIC_KEY_DATA = pytest.mark.parametrize(  | 
                    |
| 225 | 
                        + ['public_key', 'public_key_data'],  | 
                    |
| 226 | 
                        + [  | 
                    |
| 227 | 
                        + (val.public_key, val.public_key_data)  | 
                    |
| 228 | 
                        + for val in tests.SUPPORTED_KEYS.values()  | 
                    |
| 229 | 
                        + ],  | 
                    |
| 230 | 
                        + ids=list(tests.SUPPORTED_KEYS.keys()),  | 
                    |
| 231 | 
                        + )  | 
                    |
| 232 | 
                        + REQUEST_ERROR_RESPONSES = pytest.mark.parametrize(  | 
                    |
| 233 | 
                        + ['request_code', 'response_code', 'exc_type', 'exc_pattern'],  | 
                    |
| 234 | 
                        + [  | 
                    |
| 235 | 
                        + pytest.param(  | 
                    |
| 236 | 
                        + _types.SSH_AGENTC.REQUEST_IDENTITIES,  | 
                    |
| 237 | 
                        + _types.SSH_AGENT.SUCCESS,  | 
                    |
| 238 | 
                        + ssh_agent.SSHAgentFailedError,  | 
                    |
| 239 | 
                        + re.escape(  | 
                    |
| 240 | 
                        +                    f'[Code {_types.SSH_AGENT.IDENTITIES_ANSWER.value}]'
                       | 
                    |
| 241 | 
                        + ),  | 
                    |
| 242 | 
                        + id='REQUEST_IDENTITIES-expect-SUCCESS',  | 
                    |
| 243 | 
                        + ),  | 
                    |
| 244 | 
                        + ],  | 
                    |
| 245 | 
                        + )  | 
                    |
| 246 | 
                        + TRUNCATED_AGENT_RESPONSES = pytest.mark.parametrize(  | 
                    |
| 247 | 
                        + 'response',  | 
                    |
| 248 | 
                        + [  | 
                    |
| 249 | 
                        + b'\x00\x00',  | 
                    |
| 250 | 
                        + b'\x00\x00\x00\x1f some bytes missing',  | 
                    |
| 251 | 
                        + ],  | 
                    |
| 252 | 
                        + ids=['in-header', 'in-body'],  | 
                    |
| 253 | 
                        + )  | 
                    |
| 254 | 
                        + LIST_KEYS_ERROR_RESPONSES = pytest.mark.parametrize(  | 
                    |
| 255 | 
                        + ['response_code', 'response', 'exc_type', 'exc_pattern'],  | 
                    |
| 256 | 
                        + [  | 
                    |
| 257 | 
                        + pytest.param(  | 
                    |
| 258 | 
                        + _types.SSH_AGENT.FAILURE,  | 
                    |
| 259 | 
                        + b'',  | 
                    |
| 260 | 
                        + ssh_agent.SSHAgentFailedError,  | 
                    |
| 261 | 
                        + 'failed to complete the request',  | 
                    |
| 262 | 
                        + id='failed-to-complete',  | 
                    |
| 263 | 
                        + ),  | 
                    |
| 264 | 
                        + pytest.param(  | 
                    |
| 265 | 
                        + _types.SSH_AGENT.IDENTITIES_ANSWER,  | 
                    |
| 266 | 
                        + b'\x00\x00\x00\x01',  | 
                    |
| 267 | 
                        + EOFError,  | 
                    |
| 268 | 
                        + 'truncated response',  | 
                    |
| 269 | 
                        + id='truncated-response',  | 
                    |
| 270 | 
                        + ),  | 
                    |
| 271 | 
                        + pytest.param(  | 
                    |
| 272 | 
                        + _types.SSH_AGENT.IDENTITIES_ANSWER,  | 
                    |
| 273 | 
                        + b'\x00\x00\x00\x00abc',  | 
                    |
| 274 | 
                        + ssh_agent.TrailingDataError,  | 
                    |
| 275 | 
                        + 'Overlong response',  | 
                    |
| 276 | 
                        + id='overlong-response',  | 
                    |
| 277 | 
                        + ),  | 
                    |
| 278 | 
                        + ],  | 
                    |
| 279 | 
                        + )  | 
                    |
| 280 | 
                        + QUERY_EXTENSIONS_MALFORMED_RESPONSES = pytest.mark.parametrize(  | 
                    |
| 281 | 
                        + 'response_data',  | 
                    |
| 282 | 
                        + [  | 
                    |
| 283 | 
                        + pytest.param(b'\xde\xad\xbe\xef', id='truncated'),  | 
                    |
| 284 | 
                        + pytest.param(  | 
                    |
| 285 | 
                        + b'\x00\x00\x00\x0fwrong extension', id='wrong-extension'  | 
                    |
| 286 | 
                        + ),  | 
                    |
| 287 | 
                        + pytest.param(  | 
                    |
| 288 | 
                        + b'\x00\x00\x00\x05query\xde\xad\xbe\xef', id='with-trailer'  | 
                    |
| 289 | 
                        + ),  | 
                    |
| 290 | 
                        + pytest.param(  | 
                    |
| 291 | 
                        + b'\x00\x00\x00\x05query\x00\x00\x00\x04ext1\x00\x00',  | 
                    |
| 292 | 
                        + id='with-extra-fields',  | 
                    |
| 293 | 
                        + ),  | 
                    |
| 294 | 
                        + ],  | 
                    |
| 295 | 
                        + )  | 
                    |
| 296 | 
                        + # TODO(the-13th-letter): Also yield the key type, for reporting purposes.  | 
                    |
| 297 | 
                        + SUPPORTED_SSH_TEST_KEYS = pytest.mark.parametrize(  | 
                    |
| 298 | 
                        + 'ssh_test_key',  | 
                    |
| 299 | 
                        + list(tests.SUPPORTED_KEYS.values()),  | 
                    |
| 300 | 
                        + ids=tests.SUPPORTED_KEYS.keys(),  | 
                    |
| 301 | 
                        + )  | 
                    |
| 302 | 
                        + # TODO(the-13th-letter): Also yield the key type, for reporting purposes.  | 
                    |
| 303 | 
                        + UNSUITABLE_SSH_TEST_KEYS = pytest.mark.parametrize(  | 
                    |
| 304 | 
                        + 'ssh_test_key',  | 
                    |
| 305 | 
                        + list(tests.UNSUITABLE_KEYS.values()),  | 
                    |
| 306 | 
                        + ids=tests.UNSUITABLE_KEYS.keys(),  | 
                    |
| 307 | 
                        + )  | 
                    |
| 308 | 
                        + # TODO(the-13th-letter): Rename "value" to "input".  | 
                    |
| 309 | 
                        + UINT32_EXCEPTIONS = pytest.mark.parametrize(  | 
                    |
| 310 | 
                        + ['value', 'exc_type', 'exc_pattern'],  | 
                    |
| 311 | 
                        + [  | 
                    |
| 312 | 
                        + pytest.param(  | 
                    |
| 313 | 
                        + 10000000000000000,  | 
                    |
| 314 | 
                        + OverflowError,  | 
                    |
| 315 | 
                        + 'int too big to convert',  | 
                    |
| 316 | 
                        + id='10000000000000000',  | 
                    |
| 317 | 
                        + ),  | 
                    |
| 318 | 
                        + pytest.param(  | 
                    |
| 319 | 
                        + -1,  | 
                    |
| 320 | 
                        + OverflowError,  | 
                    |
| 321 | 
                        + "can't convert negative int to unsigned",  | 
                    |
| 322 | 
                        + id='-1',  | 
                    |
| 323 | 
                        + ),  | 
                    |
| 324 | 
                        + ],  | 
                    |
| 325 | 
                        + )  | 
                    |
| 326 | 
                        +  | 
                    |
| 327 | 
                        +  | 
                    |
| 328 | 
                        +class TestStaticFunctionality:  | 
                    |
| 329 | 
                        + """Test the static functionality of the `ssh_agent` module."""  | 
                    |
| 330 | 
                        +  | 
                    |
| 331 | 
                        + @staticmethod  | 
                    |
| 332 | 
                        + def as_ssh_string(bytestring: bytes) -> bytes:  | 
                    |
| 333 | 
                        + """Return an encoded SSH string from a bytestring.  | 
                    |
| 334 | 
                        +  | 
                    |
| 335 | 
                        + This is a helper function for hypothesis data generation.  | 
                    |
| 336 | 
                        +  | 
                    |
| 337 | 
                        + """  | 
                    |
| 338 | 
                        + return int.to_bytes(len(bytestring), 4, 'big') + bytestring  | 
                    |
| 339 | 
                        +  | 
                    |
| 340 | 
                        + @staticmethod  | 
                    |
| 341 | 
                        + def canonicalize1(data: bytes) -> bytes:  | 
                    |
| 342 | 
                        + """Return an encoded SSH string from a bytestring.  | 
                    |
| 343 | 
                        +  | 
                    |
| 344 | 
                        + This is a helper function for hypothesis testing.  | 
                    |
| 345 | 
                        +  | 
                    |
| 346 | 
                        + References:  | 
                    |
| 347 | 
                        +  | 
                    |
| 348 | 
                        + * [David R. MacIver: Another invariant to test for  | 
                    |
| 349 | 
                        + encoders][DECODE_ENCODE]  | 
                    |
| 350 | 
                        +  | 
                    |
| 351 | 
                        + [DECODE_ENCODE]: https://hypothesis.works/articles/canonical-serialization/  | 
                    |
| 352 | 
                        +  | 
                    |
| 353 | 
                        + """  | 
                    |
| 354 | 
                        + return ssh_agent.SSHAgentClient.string(  | 
                    |
| 355 | 
                        + ssh_agent.SSHAgentClient.unstring(data)  | 
                    |
| 356 | 
                        + )  | 
                    |
| 357 | 
                        +  | 
                    |
| 358 | 
                        + @staticmethod  | 
                    |
| 359 | 
                        + def canonicalize2(data: bytes) -> bytes:  | 
                    |
| 360 | 
                        + """Return an encoded SSH string from a bytestring.  | 
                    |
| 361 | 
                        +  | 
                    |
| 362 | 
                        + This is a helper function for hypothesis testing.  | 
                    |
| 363 | 
                        +  | 
                    |
| 364 | 
                        + References:  | 
                    |
| 365 | 
                        +  | 
                    |
| 366 | 
                        + * [David R. MacIver: Another invariant to test for  | 
                    |
| 367 | 
                        + encoders][DECODE_ENCODE]  | 
                    |
| 368 | 
                        +  | 
                    |
| 369 | 
                        + [DECODE_ENCODE]: https://hypothesis.works/articles/canonical-serialization/  | 
                    |
| 370 | 
                        +  | 
                    |
| 371 | 
                        + """  | 
                    |
| 372 | 
                        + unstringed, trailer = ssh_agent.SSHAgentClient.unstring_prefix(data)  | 
                    |
| 373 | 
                        + assert not trailer  | 
                    |
| 374 | 
                        + return ssh_agent.SSHAgentClient.string(unstringed)  | 
                    |
| 375 | 
                        +  | 
                    |
| 376 | 
                        + # TODO(the-13th-letter): Re-evaluate if this check is worth keeping.  | 
                    |
| 377 | 
                        + # It cannot provide true tamper-resistence, but probably appears to.  | 
                    |
| 378 | 
                        + # TODO(the-13th-letter): Modify parametrization to work directly on the  | 
                    |
| 379 | 
                        + # struct.  | 
                    |
| 380 | 
                        + @Parametrizations.PUBLIC_KEY_DATA.value  | 
                    |
| 381 | 
                        + def test_100_key_decoding(  | 
                    |
| 382 | 
                        + self, public_key: bytes, public_key_data: bytes  | 
                    |
| 383 | 
                        + ) -> None:  | 
                    |
| 384 | 
                        + """The [`tests.ALL_KEYS`][] public key data looks sane."""  | 
                    |
| 385 | 
                        + keydata = base64.b64decode(public_key.split(None, 2)[1])  | 
                    |
| 386 | 
                        + assert keydata == public_key_data, (  | 
                    |
| 387 | 
                        + "recorded public key data doesn't match"  | 
                    |
| 388 | 
                        + )  | 
                    |
| 389 | 
                        +  | 
                    |
| 390 | 
                        + @Parametrizations.SH_EXPORT_LINES.value  | 
                    |
| 170 | 391 | 
                        def test_190_sh_export_line_parsing(  | 
                    
| 171 | 392 | 
                        self, line: str, env_name: str, value: str | None  | 
                    
| 172 | 393 | 
                        ) -> None:  | 
                    
| ... | ... | 
                      @@ -190,12 +411,7 @@ class TestStaticFunctionality:  | 
                  
| 190 | 411 | 
                        ):  | 
                    
| 191 | 412 | 
                        ssh_agent.SSHAgentClient()  | 
                    
| 192 | 413 | 
                         | 
                    
| 193 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 194 | 
                        - ['input', 'expected'],  | 
                    |
| 195 | 
                        - [  | 
                    |
| 196 | 
                        - pytest.param(16777216, b'\x01\x00\x00\x00', id='16777216'),  | 
                    |
| 197 | 
                        - ],  | 
                    |
| 198 | 
                        - )  | 
                    |
| 414 | 
                        + @Parametrizations.UINT32_INPUT.value  | 
                    |
| 199 | 415 | 
                        def test_210_uint32(self, input: int, expected: bytes | bytearray) -> None:  | 
                    
| 200 | 416 | 
                        """`uint32` encoding works."""  | 
                    
| 201 | 417 | 
                        uint32 = ssh_agent.SSHAgentClient.uint32  | 
                    
| ... | ... | 
                      @@ -220,26 +436,7 @@ class TestStaticFunctionality:  | 
                  
| 220 | 436 | 
                        == bytestring  | 
                    
| 221 | 437 | 
                        )  | 
                    
| 222 | 438 | 
                         | 
                    
| 223 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 224 | 
                        - ['input', 'expected'],  | 
                    |
| 225 | 
                        - [  | 
                    |
| 226 | 
                        - pytest.param(  | 
                    |
| 227 | 
                        - b'ssh-rsa',  | 
                    |
| 228 | 
                        - b'\x00\x00\x00\x07ssh-rsa',  | 
                    |
| 229 | 
                        - id='ssh-rsa',  | 
                    |
| 230 | 
                        - ),  | 
                    |
| 231 | 
                        - pytest.param(  | 
                    |
| 232 | 
                        - b'ssh-ed25519',  | 
                    |
| 233 | 
                        - b'\x00\x00\x00\x0bssh-ed25519',  | 
                    |
| 234 | 
                        - id='ssh-ed25519',  | 
                    |
| 235 | 
                        - ),  | 
                    |
| 236 | 
                        - pytest.param(  | 
                    |
| 237 | 
                        - ssh_agent.SSHAgentClient.string(b'ssh-ed25519'),  | 
                    |
| 238 | 
                        - b'\x00\x00\x00\x0f\x00\x00\x00\x0bssh-ed25519',  | 
                    |
| 239 | 
                        - id='string(ssh-ed25519)',  | 
                    |
| 240 | 
                        - ),  | 
                    |
| 241 | 
                        - ],  | 
                    |
| 242 | 
                        - )  | 
                    |
| 439 | 
                        + @Parametrizations.SSH_STRING_INPUT.value  | 
                    |
| 243 | 440 | 
                        def test_211_string(  | 
                    
| 244 | 441 | 
                        self, input: bytes | bytearray, expected: bytes | bytearray  | 
                    
| 245 | 442 | 
                        ) -> None:  | 
                    
| ... | ... | 
                      @@ -258,21 +455,7 @@ class TestStaticFunctionality:  | 
                  
| 258 | 455 | 
                        assert int.from_bytes(res[:4], 'big', signed=False) == len(bytestring)  | 
                    
| 259 | 456 | 
                        assert res[4:] == bytestring  | 
                    
| 260 | 457 | 
                         | 
                    
| 261 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 262 | 
                        - ['input', 'expected'],  | 
                    |
| 263 | 
                        - [  | 
                    |
| 264 | 
                        - pytest.param(  | 
                    |
| 265 | 
                        - b'\x00\x00\x00\x07ssh-rsa',  | 
                    |
| 266 | 
                        - b'ssh-rsa',  | 
                    |
| 267 | 
                        - id='ssh-rsa',  | 
                    |
| 268 | 
                        - ),  | 
                    |
| 269 | 
                        - pytest.param(  | 
                    |
| 270 | 
                        - ssh_agent.SSHAgentClient.string(b'ssh-ed25519'),  | 
                    |
| 271 | 
                        - b'ssh-ed25519',  | 
                    |
| 272 | 
                        - id='ssh-ed25519',  | 
                    |
| 273 | 
                        - ),  | 
                    |
| 274 | 
                        - ],  | 
                    |
| 275 | 
                        - )  | 
                    |
| 458 | 
                        + @Parametrizations.SSH_UNSTRING_INPUT.value  | 
                    |
| 276 | 459 | 
                        def test_212_unstring(  | 
                    
| 277 | 460 | 
                        self, input: bytes | bytearray, expected: bytes | bytearray  | 
                    
| 278 | 461 | 
                        ) -> None:  | 
                    
| ... | ... | 
                      @@ -337,23 +520,8 @@ class TestStaticFunctionality:  | 
                  
| 337 | 520 | 
                        assert canon1(encoded) == canon2(encoded)  | 
                    
| 338 | 521 | 
                        assert canon1(canon2(encoded)) == canon1(encoded)  | 
                    
| 339 | 522 | 
                         | 
                    
| 340 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 341 | 
                        - ['value', 'exc_type', 'exc_pattern'],  | 
                    |
| 342 | 
                        - [  | 
                    |
| 343 | 
                        - pytest.param(  | 
                    |
| 344 | 
                        - 10000000000000000,  | 
                    |
| 345 | 
                        - OverflowError,  | 
                    |
| 346 | 
                        - 'int too big to convert',  | 
                    |
| 347 | 
                        - id='10000000000000000',  | 
                    |
| 348 | 
                        - ),  | 
                    |
| 349 | 
                        - pytest.param(  | 
                    |
| 350 | 
                        - -1,  | 
                    |
| 351 | 
                        - OverflowError,  | 
                    |
| 352 | 
                        - "can't convert negative int to unsigned",  | 
                    |
| 353 | 
                        - id='-1',  | 
                    |
| 354 | 
                        - ),  | 
                    |
| 355 | 
                        - ],  | 
                    |
| 356 | 
                        - )  | 
                    |
| 523 | 
                        + # TODO(the-13th-letter): Rename "value" to "input".  | 
                    |
| 524 | 
                        + @Parametrizations.UINT32_EXCEPTIONS.value  | 
                    |
| 357 | 525 | 
                        def test_310_uint32_exceptions(  | 
                    
| 358 | 526 | 
                        self, value: int, exc_type: type[Exception], exc_pattern: str  | 
                    
| 359 | 527 | 
                        ) -> None:  | 
                    
| ... | ... | 
                      @@ -362,14 +530,7 @@ class TestStaticFunctionality:  | 
                  
| 362 | 530 | 
                        with pytest.raises(exc_type, match=exc_pattern):  | 
                    
| 363 | 531 | 
                        uint32(value)  | 
                    
| 364 | 532 | 
                         | 
                    
| 365 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 366 | 
                        - ['input', 'exc_type', 'exc_pattern'],  | 
                    |
| 367 | 
                        - [  | 
                    |
| 368 | 
                        - pytest.param(  | 
                    |
| 369 | 
                        - 'some string', TypeError, 'invalid payload type', id='str'  | 
                    |
| 370 | 
                        - ),  | 
                    |
| 371 | 
                        - ],  | 
                    |
| 372 | 
                        - )  | 
                    |
| 533 | 
                        + @Parametrizations.SSH_STRING_EXCEPTIONS.value  | 
                    |
| 373 | 534 | 
                        def test_311_string_exceptions(  | 
                    
| 374 | 535 | 
                        self, input: Any, exc_type: type[Exception], exc_pattern: str  | 
                    
| 375 | 536 | 
                        ) -> None:  | 
                    
| ... | ... | 
                      @@ -378,35 +539,7 @@ class TestStaticFunctionality:  | 
                  
| 378 | 539 | 
                        with pytest.raises(exc_type, match=exc_pattern):  | 
                    
| 379 | 540 | 
                        string(input)  | 
                    
| 380 | 541 | 
                         | 
                    
| 381 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 382 | 
                        - ['input', 'exc_type', 'exc_pattern', 'has_trailer', 'parts'],  | 
                    |
| 383 | 
                        - [  | 
                    |
| 384 | 
                        - pytest.param(  | 
                    |
| 385 | 
                        - b'ssh',  | 
                    |
| 386 | 
                        - ValueError,  | 
                    |
| 387 | 
                        - 'malformed SSH byte string',  | 
                    |
| 388 | 
                        - False,  | 
                    |
| 389 | 
                        - None,  | 
                    |
| 390 | 
                        - id='unencoded',  | 
                    |
| 391 | 
                        - ),  | 
                    |
| 392 | 
                        - pytest.param(  | 
                    |
| 393 | 
                        - b'\x00\x00\x00\x08ssh-rsa',  | 
                    |
| 394 | 
                        - ValueError,  | 
                    |
| 395 | 
                        - 'malformed SSH byte string',  | 
                    |
| 396 | 
                        - False,  | 
                    |
| 397 | 
                        - None,  | 
                    |
| 398 | 
                        - id='truncated',  | 
                    |
| 399 | 
                        - ),  | 
                    |
| 400 | 
                        - pytest.param(  | 
                    |
| 401 | 
                        - b'\x00\x00\x00\x04XXX trailing text',  | 
                    |
| 402 | 
                        - ValueError,  | 
                    |
| 403 | 
                        - 'malformed SSH byte string',  | 
                    |
| 404 | 
                        - True,  | 
                    |
| 405 | 
                        - (b'XXX ', b'trailing text'),  | 
                    |
| 406 | 
                        - id='trailing-data',  | 
                    |
| 407 | 
                        - ),  | 
                    |
| 408 | 
                        - ],  | 
                    |
| 409 | 
                        - )  | 
                    |
| 542 | 
                        + @Parametrizations.SSH_UNSTRING_EXCEPTIONS.value  | 
                    |
| 410 | 543 | 
                        def test_312_unstring_exceptions(  | 
                    
| 411 | 544 | 
                        self,  | 
                    
| 412 | 545 | 
                        input: bytes | bytearray,  | 
                    
| ... | ... | 
                      @@ -433,11 +566,7 @@ class TestAgentInteraction:  | 
                  
| 433 | 566 | 
                        # TODO(the-13th-letter): Convert skip into xfail, and include the  | 
                    
| 434 | 567 | 
                        # key type in the skip/xfail message. This means the key type needs  | 
                    
| 435 | 568 | 
                        # to be passed to the test function as well.  | 
                    
| 436 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 437 | 
                        - 'ssh_test_key',  | 
                    |
| 438 | 
                        - list(tests.SUPPORTED_KEYS.values()),  | 
                    |
| 439 | 
                        - ids=tests.SUPPORTED_KEYS.keys(),  | 
                    |
| 440 | 
                        - )  | 
                    |
| 569 | 
                        + @Parametrizations.SUPPORTED_SSH_TEST_KEYS.value  | 
                    |
| 441 | 570 | 
                        def test_200_sign_data_via_agent(  | 
                    
| 442 | 571 | 
                        self,  | 
                    
| 443 | 572 | 
                        ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient,  | 
                    
| ... | ... | 
                      @@ -475,11 +604,7 @@ class TestAgentInteraction:  | 
                  
| 475 | 604 | 
                        # TODO(the-13th-letter): Include the key type in the skip message.  | 
                    
| 476 | 605 | 
                        # This means the key type needs to be passed to the test function as  | 
                    
| 477 | 606 | 
                        # well.  | 
                    
| 478 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 479 | 
                        - 'ssh_test_key',  | 
                    |
| 480 | 
                        - list(tests.UNSUITABLE_KEYS.values()),  | 
                    |
| 481 | 
                        - ids=tests.UNSUITABLE_KEYS.keys(),  | 
                    |
| 482 | 
                        - )  | 
                    |
| 607 | 
                        + @Parametrizations.UNSUITABLE_SSH_TEST_KEYS.value  | 
                    |
| 483 | 608 | 
                        def test_201_sign_data_via_agent_unsupported(  | 
                    
| 484 | 609 | 
                        self,  | 
                    
| 485 | 610 | 
                        ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient,  | 
                    
| ... | ... | 
                      @@ -507,15 +632,7 @@ class TestAgentInteraction:  | 
                  
| 507 | 632 | 
                        with pytest.raises(ValueError, match='unsuitable SSH key'):  | 
                    
| 508 | 633 | 
                        vault.Vault.phrase_from_key(public_key_data, conn=client)  | 
                    
| 509 | 634 | 
                         | 
                    
| 510 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 511 | 
                        - ['key', 'single'],  | 
                    |
| 512 | 
                        - [  | 
                    |
| 513 | 
                        - (value.public_key_data, False)  | 
                    |
| 514 | 
                        - for value in tests.SUPPORTED_KEYS.values()  | 
                    |
| 515 | 
                        - ]  | 
                    |
| 516 | 
                        - + [(tests.list_keys_singleton()[0].key, True)],  | 
                    |
| 517 | 
                        - ids=[*tests.SUPPORTED_KEYS.keys(), 'singleton'],  | 
                    |
| 518 | 
                        - )  | 
                    |
| 635 | 
                        + @Parametrizations.SSH_KEY_SELECTION.value  | 
                    |
| 519 | 636 | 
                        def test_210_ssh_key_selector(  | 
                    
| 520 | 637 | 
                        self,  | 
                    
| 521 | 638 | 
                        monkeypatch: pytest.MonkeyPatch,  | 
                    
| ... | ... | 
                      @@ -616,14 +733,7 @@ class TestAgentInteraction:  | 
                  
| 616 | 733 | 
                        ):  | 
                    
| 617 | 734 | 
                        ssh_agent.SSHAgentClient()  | 
                    
| 618 | 735 | 
                         | 
                    
| 619 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 620 | 
                        - 'response',  | 
                    |
| 621 | 
                        - [  | 
                    |
| 622 | 
                        - b'\x00\x00',  | 
                    |
| 623 | 
                        - b'\x00\x00\x00\x1f some bytes missing',  | 
                    |
| 624 | 
                        - ],  | 
                    |
| 625 | 
                        - ids=['in-header', 'in-body'],  | 
                    |
| 626 | 
                        - )  | 
                    |
| 736 | 
                        + @Parametrizations.TRUNCATED_AGENT_RESPONSES.value  | 
                    |
| 627 | 737 | 
                        def test_310_truncated_server_response(  | 
                    
| 628 | 738 | 
                        self,  | 
                    
| 629 | 739 | 
                        running_ssh_agent: tests.RunningSSHAgentInfo,  | 
                    
| ... | ... | 
                      @@ -647,32 +757,7 @@ class TestAgentInteraction:  | 
                  
| 647 | 757 | 
                        with pytest.raises(EOFError):  | 
                    
| 648 | 758 | 
                        client.request(255, b'')  | 
                    
| 649 | 759 | 
                         | 
                    
| 650 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 651 | 
                        - ['response_code', 'response', 'exc_type', 'exc_pattern'],  | 
                    |
| 652 | 
                        - [  | 
                    |
| 653 | 
                        - pytest.param(  | 
                    |
| 654 | 
                        - _types.SSH_AGENT.FAILURE,  | 
                    |
| 655 | 
                        - b'',  | 
                    |
| 656 | 
                        - ssh_agent.SSHAgentFailedError,  | 
                    |
| 657 | 
                        - 'failed to complete the request',  | 
                    |
| 658 | 
                        - id='failed-to-complete',  | 
                    |
| 659 | 
                        - ),  | 
                    |
| 660 | 
                        - pytest.param(  | 
                    |
| 661 | 
                        - _types.SSH_AGENT.IDENTITIES_ANSWER,  | 
                    |
| 662 | 
                        - b'\x00\x00\x00\x01',  | 
                    |
| 663 | 
                        - EOFError,  | 
                    |
| 664 | 
                        - 'truncated response',  | 
                    |
| 665 | 
                        - id='truncated-response',  | 
                    |
| 666 | 
                        - ),  | 
                    |
| 667 | 
                        - pytest.param(  | 
                    |
| 668 | 
                        - _types.SSH_AGENT.IDENTITIES_ANSWER,  | 
                    |
| 669 | 
                        - b'\x00\x00\x00\x00abc',  | 
                    |
| 670 | 
                        - ssh_agent.TrailingDataError,  | 
                    |
| 671 | 
                        - 'Overlong response',  | 
                    |
| 672 | 
                        - id='overlong-response',  | 
                    |
| 673 | 
                        - ),  | 
                    |
| 674 | 
                        - ],  | 
                    |
| 675 | 
                        - )  | 
                    |
| 760 | 
                        + @Parametrizations.LIST_KEYS_ERROR_RESPONSES.value  | 
                    |
| 676 | 761 | 
                        def test_320_list_keys_error_responses(  | 
                    
| 677 | 762 | 
                        self,  | 
                    
| 678 | 763 | 
                        running_ssh_agent: tests.RunningSSHAgentInfo,  | 
                    
| ... | ... | 
                      @@ -695,6 +780,8 @@ class TestAgentInteraction:  | 
                  
| 695 | 780 | 
                         | 
                    
| 696 | 781 | 
                        passed_response_code = response_code  | 
                    
| 697 | 782 | 
                         | 
                    
| 783 | 
                        + # TODO(the-13th-letter): Extract this mock function into a common  | 
                    |
| 784 | 
                        + # top-level "request" mock function.  | 
                    |
| 698 | 785 | 
                        def request(  | 
                    
| 699 | 786 | 
                        request_code: int | _types.SSH_AGENTC,  | 
                    
| 700 | 787 | 
                        payload: bytes | bytearray,  | 
                    
| ... | ... | 
                      @@ -730,36 +817,7 @@ class TestAgentInteraction:  | 
                  
| 730 | 817 | 
                        with pytest.raises(exc_type, match=exc_pattern):  | 
                    
| 731 | 818 | 
                        client.list_keys()  | 
                    
| 732 | 819 | 
                         | 
                    
| 733 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 734 | 
                        - [  | 
                    |
| 735 | 
                        - 'key',  | 
                    |
| 736 | 
                        - 'check',  | 
                    |
| 737 | 
                        - 'response_code',  | 
                    |
| 738 | 
                        - 'response',  | 
                    |
| 739 | 
                        - 'exc_type',  | 
                    |
| 740 | 
                        - 'exc_pattern',  | 
                    |
| 741 | 
                        - ],  | 
                    |
| 742 | 
                        - [  | 
                    |
| 743 | 
                        - pytest.param(  | 
                    |
| 744 | 
                        - b'invalid-key',  | 
                    |
| 745 | 
                        - True,  | 
                    |
| 746 | 
                        - _types.SSH_AGENT.FAILURE,  | 
                    |
| 747 | 
                        - b'',  | 
                    |
| 748 | 
                        - KeyError,  | 
                    |
| 749 | 
                        - 'target SSH key not loaded into agent',  | 
                    |
| 750 | 
                        - id='key-not-loaded',  | 
                    |
| 751 | 
                        - ),  | 
                    |
| 752 | 
                        - pytest.param(  | 
                    |
| 753 | 
                        - tests.SUPPORTED_KEYS['ed25519'].public_key_data,  | 
                    |
| 754 | 
                        - True,  | 
                    |
| 755 | 
                        - _types.SSH_AGENT.FAILURE,  | 
                    |
| 756 | 
                        - b'',  | 
                    |
| 757 | 
                        - ssh_agent.SSHAgentFailedError,  | 
                    |
| 758 | 
                        - 'failed to complete the request',  | 
                    |
| 759 | 
                        - id='failed-to-complete',  | 
                    |
| 760 | 
                        - ),  | 
                    |
| 761 | 
                        - ],  | 
                    |
| 762 | 
                        - )  | 
                    |
| 820 | 
                        + @Parametrizations.SIGN_ERROR_RESPONSES.value  | 
                    |
| 763 | 821 | 
                        def test_330_sign_error_responses(  | 
                    
| 764 | 822 | 
                        self,  | 
                    
| 765 | 823 | 
                        running_ssh_agent: tests.RunningSSHAgentInfo,  | 
                    
| ... | ... | 
                      @@ -782,6 +840,8 @@ class TestAgentInteraction:  | 
                  
| 782 | 840 | 
                        del running_ssh_agent  | 
                    
| 783 | 841 | 
                        passed_response_code = response_code  | 
                    
| 784 | 842 | 
                         | 
                    
| 843 | 
                        + # TODO(the-13th-letter): Extract this mock function into a common  | 
                    |
| 844 | 
                        + # top-level "request" mock function.  | 
                    |
| 785 | 845 | 
                        def request(  | 
                    
| 786 | 846 | 
                        request_code: int | _types.SSH_AGENTC,  | 
                    
| 787 | 847 | 
                        payload: bytes | bytearray,  | 
                    
| ... | ... | 
                      @@ -826,20 +886,7 @@ class TestAgentInteraction:  | 
                  
| 826 | 886 | 
                        with pytest.raises(exc_type, match=exc_pattern):  | 
                    
| 827 | 887 | 
                        client.sign(key, b'abc', check_if_key_loaded=check)  | 
                    
| 828 | 888 | 
                         | 
                    
| 829 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 830 | 
                        - ['request_code', 'response_code', 'exc_type', 'exc_pattern'],  | 
                    |
| 831 | 
                        - [  | 
                    |
| 832 | 
                        - pytest.param(  | 
                    |
| 833 | 
                        - _types.SSH_AGENTC.REQUEST_IDENTITIES,  | 
                    |
| 834 | 
                        - _types.SSH_AGENT.SUCCESS,  | 
                    |
| 835 | 
                        - ssh_agent.SSHAgentFailedError,  | 
                    |
| 836 | 
                        - re.escape(  | 
                    |
| 837 | 
                        -                    f'[Code {_types.SSH_AGENT.IDENTITIES_ANSWER.value}]'
                       | 
                    |
| 838 | 
                        - ),  | 
                    |
| 839 | 
                        - id='REQUEST_IDENTITIES-expect-SUCCESS',  | 
                    |
| 840 | 
                        - ),  | 
                    |
| 841 | 
                        - ],  | 
                    |
| 842 | 
                        - )  | 
                    |
| 889 | 
                        + @Parametrizations.REQUEST_ERROR_RESPONSES.value  | 
                    |
| 843 | 890 | 
                        def test_340_request_error_responses(  | 
                    
| 844 | 891 | 
                        self,  | 
                    
| 845 | 892 | 
                        running_ssh_agent: tests.RunningSSHAgentInfo,  | 
                    
| ... | ... | 
                      @@ -867,22 +914,7 @@ class TestAgentInteraction:  | 
                  
| 867 | 914 | 
                        client = stack.enter_context(ssh_agent.SSHAgentClient())  | 
                    
| 868 | 915 | 
                        client.request(request_code, b'', response_code=response_code)  | 
                    
| 869 | 916 | 
                         | 
                    
| 870 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 871 | 
                        - 'response_data',  | 
                    |
| 872 | 
                        - [  | 
                    |
| 873 | 
                        - pytest.param(b'\xde\xad\xbe\xef', id='truncated'),  | 
                    |
| 874 | 
                        - pytest.param(  | 
                    |
| 875 | 
                        - b'\x00\x00\x00\x0fwrong extension', id='wrong-extension'  | 
                    |
| 876 | 
                        - ),  | 
                    |
| 877 | 
                        - pytest.param(  | 
                    |
| 878 | 
                        - b'\x00\x00\x00\x05query\xde\xad\xbe\xef', id='with-trailer'  | 
                    |
| 879 | 
                        - ),  | 
                    |
| 880 | 
                        - pytest.param(  | 
                    |
| 881 | 
                        - b'\x00\x00\x00\x05query\x00\x00\x00\x04ext1\x00\x00',  | 
                    |
| 882 | 
                        - id='with-extra-fields',  | 
                    |
| 883 | 
                        - ),  | 
                    |
| 884 | 
                        - ],  | 
                    |
| 885 | 
                        - )  | 
                    |
| 917 | 
                        + @Parametrizations.QUERY_EXTENSIONS_MALFORMED_RESPONSES.value  | 
                    |
| 886 | 918 | 
                        def test_350_query_extensions_malformed_responses(  | 
                    
| 887 | 919 | 
                        self,  | 
                    
| 888 | 920 | 
                        monkeypatch: pytest.MonkeyPatch,  | 
                    
| ... | ... | 
                      @@ -892,6 +924,9 @@ class TestAgentInteraction:  | 
                  
| 892 | 924 | 
                        """Fail on malformed responses while querying extensions."""  | 
                    
| 893 | 925 | 
                        del running_ssh_agent  | 
                    
| 894 | 926 | 
                         | 
                    
| 927 | 
                        + # TODO(the-13th-letter): Extract this mock function into a common  | 
                    |
| 928 | 
                        + # top-level "request" mock function after removing the  | 
                    |
| 929 | 
                        + # payload-specific parts.  | 
                    |
| 895 | 930 | 
                        def request(  | 
                    
| 896 | 931 | 
                        code: int | _types.SSH_AGENTC,  | 
                    
| 897 | 932 | 
                        payload: Buffer,  | 
                    
| ... | ... | 
                      @@ -5,6 +5,7 @@  | 
                  
| 5 | 5 | 
                        from __future__ import annotations  | 
                    
| 6 | 6 | 
                         | 
                    
| 7 | 7 | 
                        import copy  | 
                    
| 8 | 
                        +import enum  | 
                    |
| 8 | 9 | 
                        import math  | 
                    
| 9 | 10 | 
                         | 
                    
| 10 | 11 | 
                        import hypothesis  | 
                    
| ... | ... | 
                      @@ -66,6 +67,21 @@ def js_nested_strategy(draw: strategies.DrawFn) -> Any:  | 
                  
| 66 | 67 | 
                        )  | 
                    
| 67 | 68 | 
                         | 
                    
| 68 | 69 | 
                         | 
                    
| 70 | 
                        +class Parametrizations(enum.Enum):  | 
                    |
| 71 | 
                        + VALID_VAULT_TEST_CONFIGS = pytest.mark.parametrize(  | 
                    |
| 72 | 
                        + 'test_config',  | 
                    |
| 73 | 
                        + [  | 
                    |
| 74 | 
                        + conf  | 
                    |
| 75 | 
                        + for conf in tests.TEST_CONFIGS  | 
                    |
| 76 | 
                        +            if conf.validation_settings in {None, (True,)}
                       | 
                    |
| 77 | 
                        + ],  | 
                    |
| 78 | 
                        + ids=tests._test_config_ids,  | 
                    |
| 79 | 
                        + )  | 
                    |
| 80 | 
                        + VAULT_TEST_CONFIGS = pytest.mark.parametrize(  | 
                    |
| 81 | 
                        + 'test_config', tests.TEST_CONFIGS, ids=tests._test_config_ids  | 
                    |
| 82 | 
                        + )  | 
                    |
| 83 | 
                        +  | 
                    |
| 84 | 
                        +  | 
                    |
| 69 | 85 | 
                        @hypothesis.given(value=js_nested_strategy())  | 
                    
| 70 | 86 | 
                         @hypothesis.example(float('nan'))
                       | 
                    
| 71 | 87 | 
                        def test_100_js_truthiness(value: Any) -> None:  | 
                    
| ... | ... | 
                      @@ -85,15 +101,7 @@ def test_100_js_truthiness(value: Any) -> None:  | 
                  
| 85 | 101 | 
                        assert _types.js_truthiness(value) == expected  | 
                    
| 86 | 102 | 
                         | 
                    
| 87 | 103 | 
                         | 
                    
| 88 | 
                        -@pytest.mark.parametrize(  | 
                    |
| 89 | 
                        - 'test_config',  | 
                    |
| 90 | 
                        - [  | 
                    |
| 91 | 
                        - conf  | 
                    |
| 92 | 
                        - for conf in tests.TEST_CONFIGS  | 
                    |
| 93 | 
                        -        if conf.validation_settings in {None, (True,)}
                       | 
                    |
| 94 | 
                        - ],  | 
                    |
| 95 | 
                        - ids=tests._test_config_ids,  | 
                    |
| 96 | 
                        -)  | 
                    |
| 104 | 
                        +@Parametrizations.VALID_VAULT_TEST_CONFIGS.value  | 
                    |
| 97 | 105 | 
                        def test_200_is_vault_config(test_config: tests.VaultTestConfig) -> None:  | 
                    
| 98 | 106 | 
                        """Is this vault configuration recognized as valid/invalid?  | 
                    
| 99 | 107 | 
                         | 
                    
| ... | ... | 
                      @@ -148,9 +156,7 @@ def test_200a_is_vault_config_smudged(  | 
                  
| 148 | 156 | 
                        )  | 
                    
| 149 | 157 | 
                         | 
                    
| 150 | 158 | 
                         | 
                    
| 151 | 
                        -@pytest.mark.parametrize(  | 
                    |
| 152 | 
                        - 'test_config', tests.TEST_CONFIGS, ids=tests._test_config_ids  | 
                    |
| 153 | 
                        -)  | 
                    |
| 159 | 
                        +@Parametrizations.VAULT_TEST_CONFIGS.value  | 
                    |
| 154 | 160 | 
                        def test_400_validate_vault_config(test_config: tests.VaultTestConfig) -> None:  | 
                    
| 155 | 161 | 
                        """Validate this vault configuration.  | 
                    
| 156 | 162 | 
                         | 
                    
| ... | ... | 
                      @@ -7,6 +7,7 @@  | 
                  
| 7 | 7 | 
                        from __future__ import annotations  | 
                    
| 8 | 8 | 
                         | 
                    
| 9 | 9 | 
                        import array  | 
                    
| 10 | 
                        +import enum  | 
                    |
| 10 | 11 | 
                        import hashlib  | 
                    
| 11 | 12 | 
                        import math  | 
                    
| 12 | 13 | 
                        from typing import TYPE_CHECKING  | 
                    
| ... | ... | 
                      @@ -27,6 +28,59 @@ if TYPE_CHECKING:  | 
                  
| 27 | 28 | 
                        BLOCK_SIZE = hashlib.sha1().block_size  | 
                    
| 28 | 29 | 
                        DIGEST_SIZE = hashlib.sha1().digest_size  | 
                    
| 29 | 30 | 
                         | 
                    
| 31 | 
                        +PHRASE = b'She cells C shells bye the sea shoars'  | 
                    |
| 32 | 
                        +"""The standard passphrase from <i>vault</i>(1)'s test suite."""  | 
                    |
| 33 | 
                        +GOOGLE_PHRASE = rb': 4TVH#5:aZl8LueOT\{'
                       | 
                    |
| 34 | 
                        +"""  | 
                    |
| 35 | 
                        +The standard derived passphrase for the "google" service, from  | 
                    |
| 36 | 
                        +<i>vault</i>(1)'s test suite.  | 
                    |
| 37 | 
                        +"""  | 
                    |
| 38 | 
                        +TWITTER_PHRASE = rb"[ (HN_N:lI&<ro=)3'g9"  | 
                    |
| 39 | 
                        +"""  | 
                    |
| 40 | 
                        +The standard derived passphrase for the "twitter" service, from  | 
                    |
| 41 | 
                        +<i>vault</i>(1)'s test suite.  | 
                    |
| 42 | 
                        +"""  | 
                    |
| 43 | 
                        +  | 
                    |
| 44 | 
                        +  | 
                    |
| 45 | 
                        +class Parametrizations(enum.Enum):  | 
                    |
| 46 | 
                        + ENTROPY_RESULTS = pytest.mark.parametrize(  | 
                    |
| 47 | 
                        + ['length', 'settings', 'entropy'],  | 
                    |
| 48 | 
                        + [  | 
                    |
| 49 | 
                        +            (20, {}, math.log2(math.factorial(20)) + 20 * math.log2(94)),
                       | 
                    |
| 50 | 
                        + (  | 
                    |
| 51 | 
                        + 20,  | 
                    |
| 52 | 
                        +                {'upper': 0, 'number': 0, 'space': 0, 'symbol': 0},
                       | 
                    |
| 53 | 
                        + math.log2(math.factorial(20)) + 20 * math.log2(26),  | 
                    |
| 54 | 
                        + ),  | 
                    |
| 55 | 
                        +            (0, {}, float('-inf')),
                       | 
                    |
| 56 | 
                        + (  | 
                    |
| 57 | 
                        + 0,  | 
                    |
| 58 | 
                        +                {'lower': 0, 'number': 0, 'space': 0, 'symbol': 0},
                       | 
                    |
| 59 | 
                        +                float('-inf'),
                       | 
                    |
| 60 | 
                        + ),  | 
                    |
| 61 | 
                        +            (1, {}, math.log2(94)),
                       | 
                    |
| 62 | 
                        +            (1, {'upper': 0, 'lower': 0, 'number': 0, 'symbol': 0}, 0.0),
                       | 
                    |
| 63 | 
                        + ],  | 
                    |
| 64 | 
                        + )  | 
                    |
| 65 | 
                        + BINARY_STRINGS = pytest.mark.parametrize(  | 
                    |
| 66 | 
                        + 's',  | 
                    |
| 67 | 
                        + [  | 
                    |
| 68 | 
                        + 'ñ',  | 
                    |
| 69 | 
                        + 'Düsseldorf',  | 
                    |
| 70 | 
                        + 'liberté, egalité, fraternité',  | 
                    |
| 71 | 
                        + 'ASCII',  | 
                    |
| 72 | 
                        + b'D\xc3\xbcsseldorf',  | 
                    |
| 73 | 
                        + bytearray([2, 3, 5, 7, 11, 13]),  | 
                    |
| 74 | 
                        + ],  | 
                    |
| 75 | 
                        + )  | 
                    |
| 76 | 
                        + SAMPLE_SERVICES_AND_PHRASES = pytest.mark.parametrize(  | 
                    |
| 77 | 
                        + ['service', 'expected'],  | 
                    |
| 78 | 
                        + [  | 
                    |
| 79 | 
                        + (b'google', GOOGLE_PHRASE),  | 
                    |
| 80 | 
                        +            ('twitter', TWITTER_PHRASE),
                       | 
                    |
| 81 | 
                        + ],  | 
                    |
| 82 | 
                        + )  | 
                    |
| 83 | 
                        +  | 
                    |
| 30 | 84 | 
                         | 
                    
| 31 | 85 | 
                        def phrases_are_interchangable(  | 
                    
| 32 | 86 | 
                        phrase1: Buffer | str,  | 
                    
| ... | ... | 
                      @@ -65,18 +119,7 @@ def phrases_are_interchangable(  | 
                  
| 65 | 119 | 
                        class TestVault:  | 
                    
| 66 | 120 | 
                        """Test passphrase derivation with the "vault" scheme."""  | 
                    
| 67 | 121 | 
                         | 
                    
| 68 | 
                        - phrase = b'She cells C shells bye the sea shoars'  | 
                    |
| 69 | 
                        - """The standard passphrase from <i>vault</i>(1)'s test suite."""  | 
                    |
| 70 | 
                        -    google_phrase = rb': 4TVH#5:aZl8LueOT\{'
                       | 
                    |
| 71 | 
                        - """  | 
                    |
| 72 | 
                        - The standard derived passphrase for the "google" service, from  | 
                    |
| 73 | 
                        - <i>vault</i>(1)'s test suite.  | 
                    |
| 74 | 
                        - """  | 
                    |
| 75 | 
                        - twitter_phrase = rb"[ (HN_N:lI&<ro=)3'g9"  | 
                    |
| 76 | 
                        - """  | 
                    |
| 77 | 
                        - The standard derived passphrase for the "twitter" service, from  | 
                    |
| 78 | 
                        - <i>vault</i>(1)'s test suite.  | 
                    |
| 79 | 
                        - """  | 
                    |
| 122 | 
                        + phrase = PHRASE  | 
                    |
| 80 | 123 | 
                         | 
                    
| 81 | 124 | 
                        @hypothesis.given(  | 
                    
| 82 | 125 | 
                        phrases=strategies.lists(  | 
                    
| ... | ... | 
                      @@ -275,13 +318,7 @@ class TestVault:  | 
                  
| 275 | 318 | 
                        phrase=phrases[0], service=service  | 
                    
| 276 | 319 | 
                        ) == vault.Vault.create_hash(phrase=phrases[1], service=service)  | 
                    
| 277 | 320 | 
                         | 
                    
| 278 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 279 | 
                        - ['service', 'expected'],  | 
                    |
| 280 | 
                        - [  | 
                    |
| 281 | 
                        - (b'google', google_phrase),  | 
                    |
| 282 | 
                        -            ('twitter', twitter_phrase),
                       | 
                    |
| 283 | 
                        - ],  | 
                    |
| 284 | 
                        - )  | 
                    |
| 321 | 
                        + @Parametrizations.SAMPLE_SERVICES_AND_PHRASES.value  | 
                    |
| 285 | 322 | 
                        def test_200_basic_configuration(  | 
                    
| 286 | 323 | 
                        self, service: bytes | str, expected: bytes  | 
                    
| 287 | 324 | 
                        ) -> None:  | 
                    
| ... | ... | 
                      @@ -761,25 +798,7 @@ class TestVault:  | 
                  
| 761 | 798 | 
                        """Removing allowed characters internally works."""  | 
                    
| 762 | 799 | 
                        assert vault.Vault._subtract(b'be', b'abcdef') == bytearray(b'acdf')  | 
                    
| 763 | 800 | 
                         | 
                    
| 764 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 765 | 
                        - ['length', 'settings', 'entropy'],  | 
                    |
| 766 | 
                        - [  | 
                    |
| 767 | 
                        -            (20, {}, math.log2(math.factorial(20)) + 20 * math.log2(94)),
                       | 
                    |
| 768 | 
                        - (  | 
                    |
| 769 | 
                        - 20,  | 
                    |
| 770 | 
                        -                {'upper': 0, 'number': 0, 'space': 0, 'symbol': 0},
                       | 
                    |
| 771 | 
                        - math.log2(math.factorial(20)) + 20 * math.log2(26),  | 
                    |
| 772 | 
                        - ),  | 
                    |
| 773 | 
                        -            (0, {}, float('-inf')),
                       | 
                    |
| 774 | 
                        - (  | 
                    |
| 775 | 
                        - 0,  | 
                    |
| 776 | 
                        -                {'lower': 0, 'number': 0, 'space': 0, 'symbol': 0},
                       | 
                    |
| 777 | 
                        -                float('-inf'),
                       | 
                    |
| 778 | 
                        - ),  | 
                    |
| 779 | 
                        -            (1, {}, math.log2(94)),
                       | 
                    |
| 780 | 
                        -            (1, {'upper': 0, 'lower': 0, 'number': 0, 'symbol': 0}, 0.0),
                       | 
                    |
| 781 | 
                        - ],  | 
                    |
| 782 | 
                        - )  | 
                    |
| 801 | 
                        + @Parametrizations.ENTROPY_RESULTS.value  | 
                    |
| 783 | 802 | 
                        def test_221_entropy(  | 
                    
| 784 | 803 | 
                        self, length: int, settings: dict[str, int], entropy: int  | 
                    
| 785 | 804 | 
                        ) -> None:  | 
                    
| ... | ... | 
                      @@ -809,13 +828,7 @@ class TestVault:  | 
                  
| 809 | 828 | 
                        assert v._entropy() == 0.0  | 
                    
| 810 | 829 | 
                        assert v._estimate_sufficient_hash_length() > 0  | 
                    
| 811 | 830 | 
                         | 
                    
| 812 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 813 | 
                        - ['service', 'expected'],  | 
                    |
| 814 | 
                        - [  | 
                    |
| 815 | 
                        - (b'google', google_phrase),  | 
                    |
| 816 | 
                        -            ('twitter', twitter_phrase),
                       | 
                    |
| 817 | 
                        - ],  | 
                    |
| 818 | 
                        - )  | 
                    |
| 831 | 
                        + @Parametrizations.SAMPLE_SERVICES_AND_PHRASES.value  | 
                    |
| 819 | 832 | 
                        def test_223_hash_length_expansion(  | 
                    
| 820 | 833 | 
                        self,  | 
                    
| 821 | 834 | 
                        monkeypatch: pytest.MonkeyPatch,  | 
                    
| ... | ... | 
                      @@ -834,17 +847,7 @@ class TestVault:  | 
                  
| 834 | 847 | 
                        assert v._estimate_sufficient_hash_length() < len(self.phrase)  | 
                    
| 835 | 848 | 
                        assert v.generate(service) == expected  | 
                    
| 836 | 849 | 
                         | 
                    
| 837 | 
                        - @pytest.mark.parametrize(  | 
                    |
| 838 | 
                        - 's',  | 
                    |
| 839 | 
                        - [  | 
                    |
| 840 | 
                        - 'ñ',  | 
                    |
| 841 | 
                        - 'Düsseldorf',  | 
                    |
| 842 | 
                        - 'liberté, egalité, fraternité',  | 
                    |
| 843 | 
                        - 'ASCII',  | 
                    |
| 844 | 
                        - b'D\xc3\xbcsseldorf',  | 
                    |
| 845 | 
                        - bytearray([2, 3, 5, 7, 11, 13]),  | 
                    |
| 846 | 
                        - ],  | 
                    |
| 847 | 
                        - )  | 
                    |
| 850 | 
                        + @Parametrizations.BINARY_STRINGS.value  | 
                    |
| 848 | 851 | 
                        def test_224_binary_strings(self, s: str | bytes | bytearray) -> None:  | 
                    
| 849 | 852 | 
                        """Byte string conversion is idempotent."""  | 
                    
| 850 | 853 | 
                        binstr = vault.Vault._get_binary_string  | 
                    
| ... | ... | 
                      @@ -7,6 +7,7 @@  | 
                  
| 7 | 7 | 
                        from __future__ import annotations  | 
                    
| 8 | 8 | 
                         | 
                    
| 9 | 9 | 
                        import contextlib  | 
                    
| 10 | 
                        +import enum  | 
                    |
| 10 | 11 | 
                        import errno  | 
                    
| 11 | 12 | 
                        import gettext  | 
                    
| 12 | 13 | 
                        import os  | 
                    
| ... | ... | 
                      @@ -23,6 +24,13 @@ from derivepassphrase._internals import cli_messages as msg  | 
                  
| 23 | 24 | 
                        if TYPE_CHECKING:  | 
                    
| 24 | 25 | 
                        from collections.abc import Iterator  | 
                    
| 25 | 26 | 
                         | 
                    
| 27 | 
                        +  | 
                    |
| 28 | 
                        +class Parametrizations(enum.Enum):  | 
                    |
| 29 | 
                        + MAYBE_FORMAT_STRINGS = pytest.mark.parametrize(  | 
                    |
| 30 | 
                        +        's', ['{spam}', '{spam}abc', '{', '}', '{{{']
                       | 
                    |
| 31 | 
                        + )  | 
                    |
| 32 | 
                        +  | 
                    |
| 33 | 
                        +  | 
                    |
| 26 | 34 | 
                        all_translatable_strings_dict: dict[  | 
                    
| 27 | 35 | 
                        msg.TranslatableString,  | 
                    
| 28 | 36 | 
                        msg.MsgTemplate,  | 
                    
| ... | ... | 
                      @@ -203,7 +211,7 @@ class TestL10nMachineryWithDebugTranslations:  | 
                  
| 203 | 211 | 
                        assert ts0 != ts1  | 
                    
| 204 | 212 | 
                                     assert len({ts0, ts1}) == 2
                       | 
                    
| 205 | 213 | 
                         | 
                    
| 206 | 
                        -    @pytest.mark.parametrize('s', ['{spam}', '{spam}abc', '{', '}', '{{{'])
                       | 
                    |
| 214 | 
                        + @Parametrizations.MAYBE_FORMAT_STRINGS.value  | 
                    |
| 207 | 215 | 
                        def test_102_translated_strings_suppressed_interpolation_fail(  | 
                    
| 208 | 216 | 
                        self,  | 
                    
| 209 | 217 | 
                        s: str,  | 
                    
| 210 | 218 |