Regroup the CLI and SSH agent tests into smaller groups
Marco Ricci

Marco Ricci commited on 2025-08-11 21:50:42
Zeige 6 geänderte Dateien mit 409 Einfügungen und 156 Löschungen.


Beyond the five main groups that reflect the original five test classes
for the command-line interface, introduce further classes/groupings for
related tests, and trim the test names appropriately.  In particular,
remove the ordinal in the test name until the new order (due to the new
class names and trimmed test names) sufficiently settles.  Do the same
for the SSH agent tests.  (This could have been done for all tests, but
with diminishing returns.)

The grouping currently (mostly) reflects thematic similarity, but in the
long run, we intend for it to reflect both thematic similarity and
common test environments, with respect to setup and teardown: tests of
the same group should look similar to each other too, and common code
among tests of the same group should be factored out if possible.

The grouping is inserted, syntactically, between the existing test
functions.  In particular, to keep the diff readable, we do not relocate
or reorder any test functions yet in this commit.  We add some TODOs to
indicate where further reordering or rewriting is necessary, beyond just
introducing new groups.

The intended (but not yet implemented) sharing of test setup and
teardown code will probably entail using fixtures, pytest's preferred
mechanism for shared setup and teardown.  However, as of this writing,
it is still an open question how to deal with `hypothesis`-based tests
in this scenario: they do not work well together with per-function test
fixtures, but we also do *not* want to have to (re-)implement all
setup/teardown pairs once as a fixture and once as a context manager.
... ...
@@ -2,6 +2,8 @@
2 2
 #
3 3
 # SPDX-License-Identifier: Zlib
4 4
 
5
+"""Tests for the `derivepassphrase vault` command-line interface."""
6
+
5 7
 from __future__ import annotations
6 8
 
7 9
 import contextlib
... ...
@@ -671,10 +673,10 @@ class Parametrize(types.SimpleNamespace):
671 673
     )
672 674
 
673 675
 
674
-class TestCLI:
675
-    """Tests for the `derivepassphrase vault` command-line interface."""
676
+class TestHelp:
677
+    """Tests related to help output."""
676 678
 
677
-    def test_200_help_output(
679
+    def test_help_output(
678 680
         self,
679 681
     ) -> None:
680 682
         """The `--help` option emits help text."""
... ...
@@ -702,8 +704,12 @@ class TestCLI:
702 704
             empty_stderr=True, output="Use $VISUAL or $EDITOR to configure"
703 705
         ), "expected clean exit, and option group epilog in help text"
704 706
 
707
+
708
+class TestDerivedPassphraseConstraints:
709
+    """Tests for (derived) passphrase constraints."""
710
+
705 711
     @Parametrize.CHARSET_NAME
706
-    def test_201_disable_character_set(
712
+    def test_disable_character_set(
707 713
         self,
708 714
         charset_name: str,
709 715
     ) -> None:
... ...
@@ -739,7 +745,7 @@ class TestCLI:
739 745
                 f"derived password contains forbidden character {c!r}"
740 746
             )
741 747
 
742
-    def test_202_disable_repetition(
748
+    def test_disable_repetition(
743 749
         self,
744 750
     ) -> None:
745 751
         """Character repetition can be disabled on the command-line."""
... ...
@@ -776,8 +782,40 @@ class TestCLI:
776 782
                 f"at position {i}: {result.stdout!r}"
777 783
             )
778 784
 
785
+
786
+class TestPhraseBasic:
787
+    """Tests for master passphrase configuration: basic."""
788
+
789
+    @pytest.mark.xfail(
790
+        True,
791
+        reason='not implemented yet',
792
+        raises=NotImplementedError,
793
+        strict=True,
794
+    )
795
+    def test_phrase_from_config(
796
+        self,
797
+    ) -> None:
798
+        """A stored configured master passphrase will be used."""
799
+        raise NotImplementedError
800
+
801
+    @pytest.mark.xfail(
802
+        True,
803
+        reason='not implemented yet',
804
+        raises=NotImplementedError,
805
+        strict=True,
806
+    )
807
+    def test_phrase_from_command_line(
808
+        self,
809
+    ) -> None:
810
+        """A master passphrase requested on the command-line will be used."""
811
+        raise NotImplementedError
812
+
813
+
814
+class TestKeyBasic:
815
+    """Tests for SSH key configuration: basic."""
816
+
779 817
     @Parametrize.CONFIG_WITH_KEY
780
-    def test_204a_key_from_config(
818
+    def test_key_from_config(
781 819
         self,
782 820
         running_ssh_agent: data.RunningSSHAgentInfo,
783 821
         config: _types.VaultConfig,
... ...
@@ -819,7 +857,7 @@ class TestCLI:
819 857
             result.stdout.rstrip("\n").encode("UTF-8") == DUMMY_RESULT_KEY1
820 858
         ), "expected known output"
821 859
 
822
-    def test_204b_key_from_command_line(
860
+    def test_key_from_command_line(
823 861
         self,
824 862
         running_ssh_agent: data.RunningSSHAgentInfo,
825 863
     ) -> None:
... ...
@@ -866,9 +904,13 @@ class TestCLI:
866 904
             "expected known output"
867 905
         )
868 906
 
907
+
908
+class TestPhraseAndKeyOverriding:
909
+    """Tests for master passphrase and SSH key configuration: overriding."""
910
+
869 911
     @Parametrize.BASE_CONFIG_WITH_KEY_VARIATIONS
870 912
     @Parametrize.KEY_INDEX
871
-    def test_204c_key_override_on_command_line(
913
+    def test_key_override_on_command_line(
872 914
         self,
873 915
         running_ssh_agent: data.RunningSSHAgentInfo,
874 916
         config: dict[str, Any],
... ...
@@ -909,7 +951,7 @@ class TestCLI:
909 951
             "expected no error messages on stderr"
910 952
         )
911 953
 
912
-    def test_205_service_phrase_if_key_in_global_config(
954
+    def test_service_phrase_if_key_in_global_config(
913 955
         self,
914 956
         running_ssh_agent: data.RunningSSHAgentInfo,
915 957
     ) -> None:
... ...
@@ -960,7 +1002,7 @@ class TestCLI:
960 1002
         )
961 1003
 
962 1004
     @Parametrize.KEY_OVERRIDING_IN_CONFIG
963
-    def test_206_setting_phrase_thus_overriding_key_in_config(
1005
+    def test_setting_phrase_thus_overriding_key_in_config(
964 1006
         self,
965 1007
         running_ssh_agent: data.RunningSSHAgentInfo,
966 1008
         caplog: pytest.LogCaptureFixture,
... ...
@@ -1013,6 +1055,11 @@ class TestCLI:
1013 1055
             map(is_harmless_config_import_warning, caplog.record_tuples)
1014 1056
         ), "unexpected error output"
1015 1057
 
1058
+
1059
+# TODO(the-13th-letter): Assimilate into its descendant.
1060
+class TestNotesPrinting000:
1061
+    """Tests concerning printing the service notes: group 000."""
1062
+
1016 1063
     @hypothesis.given(
1017 1064
         notes=strategies.text(
1018 1065
             strategies.characters(
... ...
@@ -1069,8 +1116,13 @@ class TestCLI:
1069 1116
             "expected known stderr contents"
1070 1117
         )
1071 1118
 
1119
+
1120
+# TODO(the-13th-letter): Assimilate the descendants.
1121
+class TestInvalidCommandLines:
1122
+    """Tests concerning invalid command-lines."""
1123
+
1072 1124
     @Parametrize.VAULT_CHARSET_OPTION
1073
-    def test_210_invalid_argument_range(
1125
+    def test_invalid_argument_range(
1074 1126
         self,
1075 1127
         option: str,
1076 1128
     ) -> None:
... ...
@@ -1099,7 +1151,7 @@ class TestCLI:
1099 1151
                 )
1100 1152
 
1101 1153
     @Parametrize.OPTION_COMBINATIONS_SERVICE_NEEDED
1102
-    def test_211_service_needed(
1154
+    def test_service_needed(
1103 1155
         self,
1104 1156
         options: list[str],
1105 1157
         service: bool | None,
... ...
@@ -1173,7 +1225,7 @@ class TestCLI:
1173 1225
                 )
1174 1226
             assert result.clean_exit(empty_stderr=True), "expected clean exit"
1175 1227
 
1176
-    def test_211a_empty_service_name_causes_warning(
1228
+    def test_empty_service_name_causes_warning(
1177 1229
         self,
1178 1230
         caplog: pytest.LogCaptureFixture,
1179 1231
     ) -> None:
... ...
@@ -1240,7 +1292,7 @@ class TestCLI:
1240 1292
             }, "requested configuration change was not applied"
1241 1293
 
1242 1294
     @Parametrize.OPTION_COMBINATIONS_INCOMPATIBLE
1243
-    def test_212_incompatible_options(
1295
+    def test_incompatible_options(
1244 1296
         self,
1245 1297
         options: list[str],
1246 1298
         service: bool | None,
... ...
@@ -1268,8 +1320,12 @@ class TestCLI:
1268 1320
             "expected error exit and known error message"
1269 1321
         )
1270 1322
 
1323
+
1324
+class TestImportConfigValid:
1325
+    """Tests concerning `vault` configuration imports: valid imports."""
1326
+
1271 1327
     @Parametrize.VALID_TEST_CONFIGS
1272
-    def test_213_import_config_success(
1328
+    def test_import_config(
1273 1329
         self,
1274 1330
         caplog: pytest.LogCaptureFixture,
1275 1331
         config: Any,
... ...
@@ -1318,7 +1374,7 @@ class TestCLI:
1318 1374
             ])
1319 1375
         )
1320 1376
     )
1321
-    def test_213a_import_config_success(
1377
+    def test_import_smudged_config(
1322 1378
         self,
1323 1379
         caplog: pytest.LogCaptureFixture,
1324 1380
         conf: data.VaultTestConfig,
... ...
@@ -1363,7 +1419,11 @@ class TestCLI:
1363 1419
         ), "unexpected error output"
1364 1420
         assert_vault_config_is_indented_and_line_broken(config_txt)
1365 1421
 
1366
-    def test_213b_import_bad_config_not_vault_config(
1422
+
1423
+class TestImportConfigInvalid:
1424
+    """Tests concerning `vault` configuration imports: invalid imports."""
1425
+
1426
+    def test_import_config_not_a_vault_config(
1367 1427
         self,
1368 1428
     ) -> None:
1369 1429
         """Importing an invalid config fails."""
... ...
@@ -1389,7 +1449,7 @@ class TestCLI:
1389 1449
             "expected error exit and known error message"
1390 1450
         )
1391 1451
 
1392
-    def test_213c_import_bad_config_not_json_data(
1452
+    def test_import_config_not_json_data(
1393 1453
         self,
1394 1454
     ) -> None:
1395 1455
         """Importing an invalid config fails."""
... ...
@@ -1415,7 +1475,7 @@ class TestCLI:
1415 1475
             "expected error exit and known error message"
1416 1476
         )
1417 1477
 
1418
-    def test_213d_import_bad_config_not_a_file(
1478
+    def test_import_config_not_a_file(
1419 1479
         self,
1420 1480
     ) -> None:
1421 1481
         """Importing an invalid config fails."""
... ...
@@ -1453,8 +1513,12 @@ class TestCLI:
1453 1513
             "expected error exit and known error message"
1454 1514
         )
1455 1515
 
1516
+
1517
+class TestExportConfigValid:
1518
+    """Tests concerning `vault` configuration exports: valid exports."""
1519
+
1456 1520
     @Parametrize.VALID_TEST_CONFIGS
1457
-    def test_214_export_config_success(
1521
+    def test_export_config_success(
1458 1522
         self,
1459 1523
         caplog: pytest.LogCaptureFixture,
1460 1524
         config: Any,
... ...
@@ -1494,8 +1558,20 @@ class TestCLI:
1494 1558
         ), "unexpected error output"
1495 1559
         assert_vault_config_is_indented_and_line_broken(result.stdout)
1496 1560
 
1561
+    @pytest.mark.xfail(
1562
+        True,
1563
+        reason='not implemented yet',
1564
+        raises=NotImplementedError,
1565
+        strict=True,
1566
+    )
1567
+    def test_export_smudged_config(
1568
+        self,
1569
+    ) -> None:
1570
+        """Exporting a smudged configuration works."""
1571
+        raise NotImplementedError
1572
+
1497 1573
     @Parametrize.EXPORT_FORMAT_OPTIONS
1498
-    def test_214a_export_settings_no_stored_settings(
1574
+    def test_export_config_no_stored_settings(
1499 1575
         self,
1500 1576
         export_options: list[str],
1501 1577
     ) -> None:
... ...
@@ -1526,8 +1602,12 @@ class TestCLI:
1526 1602
             )
1527 1603
         assert result.clean_exit(empty_stderr=True), "expected clean exit"
1528 1604
 
1605
+
1606
+class TestExportConfigInvalid:
1607
+    """Tests concerning `vault` configuration exports: invalid exports."""
1608
+
1529 1609
     @Parametrize.EXPORT_FORMAT_OPTIONS
1530
-    def test_214b_export_settings_bad_stored_config(
1610
+    def test_export_config_bad_stored_config(
1531 1611
         self,
1532 1612
         export_options: list[str],
1533 1613
     ) -> None:
... ...
@@ -1556,7 +1636,7 @@ class TestCLI:
1556 1636
         )
1557 1637
 
1558 1638
     @Parametrize.EXPORT_FORMAT_OPTIONS
1559
-    def test_214c_export_settings_not_a_file(
1639
+    def test_export_config_not_a_file(
1560 1640
         self,
1561 1641
         export_options: list[str],
1562 1642
     ) -> None:
... ...
@@ -1587,7 +1667,7 @@ class TestCLI:
1587 1667
         )
1588 1668
 
1589 1669
     @Parametrize.EXPORT_FORMAT_OPTIONS
1590
-    def test_214d_export_settings_target_not_a_file(
1670
+    def test_export_config_target_not_a_file(
1591 1671
         self,
1592 1672
         export_options: list[str],
1593 1673
     ) -> None:
... ...
@@ -1617,7 +1697,7 @@ class TestCLI:
1617 1697
 
1618 1698
     @pytest_machinery.skip_if_on_the_annoying_os
1619 1699
     @Parametrize.EXPORT_FORMAT_OPTIONS
1620
-    def test_214e_export_settings_settings_directory_not_a_directory(
1700
+    def test_export_config_settings_directory_not_a_directory(
1621 1701
         self,
1622 1702
         export_options: list[str],
1623 1703
     ) -> None:
... ...
@@ -1650,6 +1730,11 @@ class TestCLI:
1650 1730
             "expected error exit and known error message"
1651 1731
         )
1652 1732
 
1733
+
1734
+# TODO(the-13th-letter): Assimilate the parent.
1735
+class TestNotesPrinting001:
1736
+    """Tests concerning printing the service notes: group 001."""
1737
+
1653 1738
     @Parametrize.NOTES_PLACEMENT
1654 1739
     @hypothesis.given(
1655 1740
         notes=strategies.text(
... ...
@@ -1660,7 +1745,7 @@ class TestCLI:
1660 1745
             max_size=512,
1661 1746
         ).filter(str.strip),
1662 1747
     )
1663
-    def test_215_notes_placement(
1748
+    def test_notes_placement(
1664 1749
         self,
1665 1750
         notes_placement: Literal["before", "after"],
1666 1751
         placement_args: list[str],
... ...
@@ -1700,6 +1785,10 @@ class TestCLI:
1700 1785
             )
1701 1786
             assert result.clean_exit(output=expected), "expected clean exit"
1702 1787
 
1788
+
1789
+class TestNotesEditingValid:
1790
+    """Tests concerning editing service notes: valid calls."""
1791
+
1703 1792
     @Parametrize.MODERN_EDITOR_INTERFACE
1704 1793
     @hypothesis.settings(
1705 1794
         suppress_health_check=[
... ...
@@ -1716,7 +1805,7 @@ class TestCLI:
1716 1805
             max_size=512,
1717 1806
         ).filter(str.strip),
1718 1807
     )
1719
-    def test_220_edit_notes_successfully(
1808
+    def test_successful_edit(
1720 1809
         self,
1721 1810
         caplog: pytest.LogCaptureFixture,
1722 1811
         modern_editor_interface: bool,
... ...
@@ -1806,7 +1895,7 @@ class TestCLI:
1806 1895
             max_size=512,
1807 1896
         ).filter(str.strip),
1808 1897
     )
1809
-    def test_221_edit_notes_noop(
1898
+    def test_noop_edit(
1810 1899
         self,
1811 1900
         edit_func_name: Literal["empty", "space"],
1812 1901
         modern_editor_interface: bool,
... ...
@@ -1895,7 +1984,7 @@ class TestCLI:
1895 1984
             max_size=512,
1896 1985
         ).filter(str.strip),
1897 1986
     )
1898
-    def test_222_edit_notes_marker_removed(
1987
+    def test_marker_removed(
1899 1988
         self,
1900 1989
         caplog: pytest.LogCaptureFixture,
1901 1990
         modern_editor_interface: bool,
... ...
@@ -1971,6 +2060,10 @@ class TestCLI:
1971 2060
                 "services": {"sv": {"notes": notes.strip()}},
1972 2061
             }
1973 2062
 
2063
+
2064
+class TestNotesEditingInvalid:
2065
+    """Tests concerning editing service notes: invalid/error calls."""
2066
+
1974 2067
     @hypothesis.given(
1975 2068
         notes=strategies.text(
1976 2069
             strategies.characters(
... ...
@@ -1980,7 +2073,7 @@ class TestCLI:
1980 2073
             max_size=512,
1981 2074
         ).filter(str.strip),
1982 2075
     )
1983
-    def test_223_edit_notes_abort(
2076
+    def test_abort(
1984 2077
         self,
1985 2078
         notes: str,
1986 2079
     ) -> None:
... ...
@@ -2029,7 +2122,7 @@ class TestCLI:
2029 2122
                 "services": {"sv": {"notes": notes.strip()}},
2030 2123
             }
2031 2124
 
2032
-    def test_223a_edit_empty_notes_abort(
2125
+    def test_abort_no_prior_notes(
2033 2126
         self,
2034 2127
     ) -> None:
2035 2128
         """Aborting editing notes works even if no notes are stored yet.
... ...
@@ -2092,7 +2185,7 @@ class TestCLI:
2092 2185
             max_size=512,
2093 2186
         ),
2094 2187
     )
2095
-    def test_223b_edit_notes_fail_config_option_missing(
2188
+    def test_fail_on_config_option_missing(
2096 2189
         self,
2097 2190
         caplog: pytest.LogCaptureFixture,
2098 2191
         modern_editor_interface: bool,
... ...
@@ -2172,8 +2265,13 @@ class TestCLI:
2172 2265
                 config = json.load(infile)
2173 2266
             assert config == vault_config
2174 2267
 
2268
+
2269
+# TODO(the-13th-letter): Assimilate the descendants.
2270
+class TestStoringConfigurationSuccesses:
2271
+    """Tests concerning storing the configuration: successes."""
2272
+
2175 2273
     @Parametrize.CONFIG_EDITING_VIA_CONFIG_FLAG
2176
-    def test_224_store_config_good(
2274
+    def test_store_good_config(
2177 2275
         self,
2178 2276
         command_line: list[str],
2179 2277
         input: str,
... ...
@@ -2219,8 +2317,13 @@ class TestCLI:
2219 2317
             )
2220 2318
             assert_vault_config_is_indented_and_line_broken(config_txt)
2221 2319
 
2320
+
2321
+# TODO(the-13th-letter): Assimilate the descendants.
2322
+class TestStoringConfigurationFailures:
2323
+    """Tests concerning storing the configuration: failures."""
2324
+
2222 2325
     @Parametrize.CONFIG_EDITING_VIA_CONFIG_FLAG_FAILURES
2223
-    def test_225_store_config_fail(
2326
+    def test_store_bad_config(
2224 2327
         self,
2225 2328
         command_line: list[str],
2226 2329
         input: str,
... ...
@@ -2255,7 +2358,7 @@ class TestCLI:
2255 2358
             "expected error exit and known error message"
2256 2359
         )
2257 2360
 
2258
-    def test_225a_store_config_fail_manual_no_ssh_key_selection(
2361
+    def test_fail_because_no_ssh_key_selection(
2259 2362
         self,
2260 2363
         running_ssh_agent: data.RunningSSHAgentInfo,
2261 2364
     ) -> None:
... ...
@@ -2297,7 +2400,7 @@ class TestCLI:
2297 2400
             "expected error exit and known error message"
2298 2401
         )
2299 2402
 
2300
-    def test_225b_store_config_fail_manual_no_ssh_agent(
2403
+    def test_fail_because_no_ssh_agent(
2301 2404
         self,
2302 2405
         running_ssh_agent: data.RunningSSHAgentInfo,
2303 2406
     ) -> None:
... ...
@@ -2326,7 +2429,7 @@ class TestCLI:
2326 2429
             "expected error exit and known error message"
2327 2430
         )
2328 2431
 
2329
-    def test_225c_store_config_fail_manual_bad_ssh_agent_connection(
2432
+    def test_fail_because_bad_ssh_agent_connection(
2330 2433
         self,
2331 2434
         running_ssh_agent: data.RunningSSHAgentInfo,
2332 2435
     ) -> None:
... ...
@@ -2357,7 +2460,7 @@ class TestCLI:
2357 2460
         )
2358 2461
 
2359 2462
     @Parametrize.TRY_RACE_FREE_IMPLEMENTATION
2360
-    def test_225d_store_config_fail_manual_read_only_file(
2463
+    def test_fail_because_read_only_file(
2361 2464
         self,
2362 2465
         try_race_free_implementation: bool,
2363 2466
     ) -> None:
... ...
@@ -2388,10 +2491,10 @@ class TestCLI:
2388 2491
             "expected error exit and known error message"
2389 2492
         )
2390 2493
 
2391
-    def test_225e_store_config_fail_manual_custom_error(
2494
+    def test_fail_because_of_custom_error(
2392 2495
         self,
2393 2496
     ) -> None:
2394
-        """OS-erroring with `--config` fails."""
2497
+        """Triggering internal errors during `--config` leads to failure."""
2395 2498
         runner = machinery.CliRunner(mix_stderr=False)
2396 2499
         # TODO(the-13th-letter): Rewrite using parenthesized
2397 2500
         # with-statements.
... ...
@@ -2421,7 +2524,7 @@ class TestCLI:
2421 2524
             "expected error exit and known error message"
2422 2525
         )
2423 2526
 
2424
-    def test_225f_store_config_fail_unset_and_set_same_settings(
2527
+    def test_fail_because_unsetting_and_setting_same_settings(
2425 2528
         self,
2426 2529
     ) -> None:
2427 2530
         """Issuing conflicting settings to `--config` fails."""
... ...
@@ -2453,7 +2556,7 @@ class TestCLI:
2453 2556
             error="Attempted to unset and set --length at the same time."
2454 2557
         ), "expected error exit and known error message"
2455 2558
 
2456
-    def test_225g_store_config_fail_manual_ssh_agent_no_keys_loaded(
2559
+    def test_fail_because_ssh_agent_has_no_keys_loaded(
2457 2560
         self,
2458 2561
         running_ssh_agent: data.RunningSSHAgentInfo,
2459 2562
     ) -> None:
... ...
@@ -2489,11 +2592,11 @@ class TestCLI:
2489 2592
             "expected error exit and known error message"
2490 2593
         )
2491 2594
 
2492
-    def test_225h_store_config_fail_manual_ssh_agent_runtime_error(
2595
+    def test_store_config_fail_manual_ssh_agent_runtime_error(
2493 2596
         self,
2494 2597
         running_ssh_agent: data.RunningSSHAgentInfo,
2495 2598
     ) -> None:
2496
-        """The SSH agent erroring during `--config --key` fails."""
2599
+        """Triggering an error in the SSH agent during `--config --key` leads to failure."""
2497 2600
         del running_ssh_agent
2498 2601
         runner = machinery.CliRunner(mix_stderr=False)
2499 2602
         # TODO(the-13th-letter): Rewrite using parenthesized
... ...
@@ -2522,11 +2625,11 @@ class TestCLI:
2522 2625
             error="violates the communication protocol."
2523 2626
         ), "expected error exit and known error message"
2524 2627
 
2525
-    def test_225i_store_config_fail_manual_ssh_agent_refuses(
2628
+    def test_store_config_fail_manual_ssh_agent_refuses(
2526 2629
         self,
2527 2630
         running_ssh_agent: data.RunningSSHAgentInfo,
2528 2631
     ) -> None:
2529
-        """The SSH agent refusing during `--config --key` fails."""
2632
+        """The SSH agent refusing during `--config --key` leads to failure."""
2530 2633
         del running_ssh_agent
2531 2634
         runner = machinery.CliRunner(mix_stderr=False)
2532 2635
         # TODO(the-13th-letter): Rewrite using parenthesized
... ...
@@ -2557,7 +2660,12 @@ class TestCLI:
2557 2660
             "expected error exit and known error message"
2558 2661
         )
2559 2662
 
2560
-    def test_226_no_arguments(self) -> None:
2663
+
2664
+# TODO(the-13th-letter): Assimilate the parent.
2665
+class TestInvalidCommandLines001(TestInvalidCommandLines):
2666
+    """Tests concerning invalid command-lines: group 001."""
2667
+
2668
+    def test_no_arguments(self) -> None:
2561 2669
         """Calling `derivepassphrase vault` without any arguments fails."""
2562 2670
         runner = machinery.CliRunner(mix_stderr=False)
2563 2671
         # TODO(the-13th-letter): Rewrite using parenthesized
... ...
@@ -2578,7 +2686,7 @@ class TestCLI:
2578 2686
             error="Deriving a passphrase requires a SERVICE"
2579 2687
         ), "expected error exit and known error message"
2580 2688
 
2581
-    def test_226a_no_passphrase_or_key(
2689
+    def test_no_passphrase_or_key(
2582 2690
         self,
2583 2691
     ) -> None:
2584 2692
         """Deriving a passphrase without a passphrase or key fails."""
... ...
@@ -2603,12 +2711,20 @@ class TestCLI:
2603 2711
             "expected error exit and known error message"
2604 2712
         )
2605 2713
 
2606
-    def test_230_config_directory_nonexistant(
2714
+
2715
+# TODO(the-13th-letter): Assimilate into the parent.
2716
+class TestStoringConfigurationSuccesses001:
2717
+    """Tests concerning storing the configuration: successes, group 001."""
2718
+
2719
+    def test_config_directory_nonexistant(
2607 2720
         self,
2608 2721
     ) -> None:
2609 2722
         """Running without an existing config directory works.
2610 2723
 
2611 2724
         This is a regression test; see [issue\u00a0#6][] for context.
2725
+        See also
2726
+        [TestStoringConfigurationFailures001.test_config_directory_not_a_file][]
2727
+        for a related aspect of this.
2612 2728
 
2613 2729
         [issue #6]: https://github.com/the-13th-letter/derivepassphrase/issues/6
2614 2730
 
... ...
@@ -2646,7 +2762,12 @@ class TestCLI:
2646 2762
                 "services": {},
2647 2763
             }, "config mismatch"
2648 2764
 
2649
-    def test_230a_config_directory_not_a_file(
2765
+
2766
+# TODO(the-13th-letter): Assimilate into the parent.
2767
+class TestStoringConfigurationFailures001:
2768
+    """Tests concerning storing the configuration: failures, group 001."""
2769
+
2770
+    def test_config_directory_not_a_file(
2650 2771
         self,
2651 2772
     ) -> None:
2652 2773
         """Erroring without an existing config directory errors normally.
... ...
@@ -2655,6 +2776,9 @@ class TestCLI:
2655 2776
         errors by itself.
2656 2777
 
2657 2778
         This is a regression test; see [issue\u00a0#6][] for context.
2779
+        See also
2780
+        [TestStoringConfigurationSuccesses001.test_config_directory_nonexistant][]
2781
+        for a related aspect of this.
2658 2782
 
2659 2783
         [issue #6]: https://github.com/the-13th-letter/derivepassphrase/issues/6
2660 2784
 
... ...
@@ -2694,7 +2818,10 @@ class TestCLI:
2694 2818
                 "expected error exit and known error message"
2695 2819
             )
2696 2820
 
2697
-    def test_230b_store_config_custom_error(
2821
+    # TODO(the-13th-letter): Remove this test, because it is basically
2822
+    # the same as
2823
+    # TestStoringConfigurationFailures.test_fail_because_of_custom_error.
2824
+    def test_store_config_custom_error(
2698 2825
         self,
2699 2826
     ) -> None:
2700 2827
         """Storing the configuration reacts even to weird errors."""
... ...
@@ -2727,8 +2854,12 @@ class TestCLI:
2727 2854
                 "expected error exit and known error message"
2728 2855
             )
2729 2856
 
2857
+
2858
+class TestPassphraseUnicodeNormalization:
2859
+    """Tests concerning the Unicode normalization of passphrases."""
2860
+
2730 2861
     @Parametrize.UNICODE_NORMALIZATION_WARNING_INPUTS
2731
-    def test_300_unicode_normalization_form_warning(
2862
+    def test_warning(
2732 2863
         self,
2733 2864
         caplog: pytest.LogCaptureFixture,
2734 2865
         main_config: str,
... ...
@@ -2767,7 +2898,7 @@ class TestCLI:
2767 2898
         ), "expected known warning message in stderr"
2768 2899
 
2769 2900
     @Parametrize.UNICODE_NORMALIZATION_ERROR_INPUTS
2770
-    def test_301_unicode_normalization_form_error(
2901
+    def test_error(
2771 2902
         self,
2772 2903
         main_config: str,
2773 2904
         command_line: list[str],
... ...
@@ -2807,7 +2938,7 @@ class TestCLI:
2807 2938
         )
2808 2939
 
2809 2940
     @Parametrize.UNICODE_NORMALIZATION_COMMAND_LINES
2810
-    def test_301a_unicode_normalization_form_error_from_stored_config(
2941
+    def test_error_from_stored_config(
2811 2942
         self,
2812 2943
         command_line: list[str],
2813 2944
     ) -> None:
... ...
@@ -2848,7 +2979,11 @@ class TestCLI:
2848 2979
                 ),
2849 2980
             ), "expected error exit and known error message"
2850 2981
 
2851
-    def test_310_bad_user_config_file(
2982
+
2983
+class TestUserConfigurationFileOther:
2984
+    """Other tests concerning the user configuration file."""
2985
+
2986
+    def test_bad_user_config_file(
2852 2987
         self,
2853 2988
     ) -> None:
2854 2989
         """Loading a user configuration file in an invalid format fails."""
... ...
@@ -2876,10 +3011,10 @@ class TestCLI:
2876 3011
                 "expected error exit and known error message"
2877 3012
             )
2878 3013
 
2879
-    def test_311_bad_user_config_is_a_directory(
3014
+    def test_user_config_is_a_directory(
2880 3015
         self,
2881 3016
     ) -> None:
2882
-        """Loading a user configuration file in an invalid format fails."""
3017
+        """Loading a user configuration non-file fails."""
2883 3018
         runner = machinery.CliRunner(mix_stderr=False)
2884 3019
         # TODO(the-13th-letter): Rewrite using parenthesized
2885 3020
         # with-statements.
... ...
@@ -2909,7 +3044,11 @@ class TestCLI:
2909 3044
                 "expected error exit and known error message"
2910 3045
             )
2911 3046
 
2912
-    def test_400_missing_af_unix_support(
3047
+
3048
+class TestSSHAgentAvailability:
3049
+    """Tests concerning the availability of the SSH agent."""
3050
+
3051
+    def test_missing_af_unix_support(
2913 3052
         self,
2914 3053
         caplog: pytest.LogCaptureFixture,
2915 3054
     ) -> None:
... ...
@@ -349,13 +349,13 @@ def parse_version_output(  # noqa: C901
349 349
     )
350 350
 
351 351
 
352
-class TestAllCLI:
353
-    """Tests uniformly for all command-line interfaces."""
352
+class Test001VersionOutputParser:
353
+    """Tests for the `--version` output parser."""
354 354
 
355 355
     @Parametrize.MASK_PROG_NAME
356 356
     @Parametrize.MASK_VERSION
357 357
     @Parametrize.VERSION_OUTPUT_DATA
358
-    def test_001_parse_version_output(
358
+    def test_parse_version_output(
359 359
         self,
360 360
         version_output: str,
361 361
         prog_name: str | None,
... ...
@@ -374,6 +374,10 @@ class TestAllCLI:
374 374
             == expected_parse
375 375
         )
376 376
 
377
+
378
+class TestHelpOutput:
379
+    """Tests for all command-line interfaces' `--help` output."""
380
+
377 381
     # TODO(the-13th-letter): Do we actually need this?  What should we
378 382
     # check for?
379 383
     def test_100_help_output(self) -> None:
... ...
@@ -569,7 +573,11 @@ class TestAllCLI:
569 573
             "Expected no color, but found an ANSI control sequence"
570 574
         )
571 575
 
572
-    def test_202a_derivepassphrase_version_option_output(
576
+
577
+class TestVersionOutput:
578
+    """Tests for all command-line interfaces' `--version` output."""
579
+
580
+    def test_derivepassphrase_version_option_output(
573 581
         self,
574 582
     ) -> None:
575 583
         """The version output states supported features.
... ...
@@ -614,7 +622,7 @@ class TestAllCLI:
614 622
         assert not version_data.features
615 623
         assert not version_data.extras
616 624
 
617
-    def test_202b_export_version_option_output(
625
+    def test_export_version_option_output(
618 626
         self,
619 627
     ) -> None:
620 628
         """The version output states supported features.
... ...
@@ -666,7 +674,7 @@ class TestAllCLI:
666 674
         assert not version_data.features
667 675
         assert not version_data.extras
668 676
 
669
-    def test_202c_export_vault_version_option_output(
677
+    def test_export_vault_version_option_output(
670 678
         self,
671 679
     ) -> None:
672 680
         """The version output states supported features.
... ...
@@ -726,7 +734,7 @@ class TestAllCLI:
726 734
         assert not version_data.features
727 735
         assert version_data.extras == actually_enabled_extras
728 736
 
729
-    def test_202d_vault_version_option_output(
737
+    def test_vault_version_option_output(
730 738
         self,
731 739
     ) -> None:
732 740
         """The version output states supported features.
... ...
@@ -589,8 +589,12 @@ class TestShellCompletion:
589 589
             """Return the completion items' values, as a sequence."""
590 590
             return tuple(c.value for c in self())
591 591
 
592
+
593
+class TestCompletableItems:
594
+    """Tests for completablility of items."""
595
+
592 596
     @Parametrize.COMPLETABLE_ITEMS
593
-    def test_100_is_completable_item(
597
+    def test_is_completable_item(
594 598
         self,
595 599
         partial: str,
596 600
         is_completable: bool,
... ...
@@ -598,8 +602,12 @@ class TestShellCompletion:
598 602
         """Our `_is_completable_item` predicate for service names works."""
599 603
         assert cli_helpers.is_completable_item(partial) == is_completable
600 604
 
605
+
606
+class TestCompletableItemClasses(TestShellCompletion):
607
+    """Tests for the different classes of completable items."""
608
+
601 609
     @Parametrize.COMPLETABLE_OPTIONS
602
-    def test_200_options(
610
+    def test_options(
603 611
         self,
604 612
         command_prefix: Sequence[str],
605 613
         incomplete: str,
... ...
@@ -610,7 +618,7 @@ class TestShellCompletion:
610 618
         assert frozenset(comp.get_words()) == completions
611 619
 
612 620
     @Parametrize.COMPLETABLE_SUBCOMMANDS
613
-    def test_201_subcommands(
621
+    def test_subcommands(
614 622
         self,
615 623
         command_prefix: Sequence[str],
616 624
         incomplete: str,
... ...
@@ -622,7 +630,7 @@ class TestShellCompletion:
622 630
 
623 631
     @Parametrize.COMPLETABLE_PATH_ARGUMENT
624 632
     @Parametrize.INCOMPLETE
625
-    def test_202_paths(
633
+    def test_paths(
626 634
         self,
627 635
         command_prefix: Sequence[str],
628 636
         incomplete: str,
... ...
@@ -636,7 +644,7 @@ class TestShellCompletion:
636 644
         )
637 645
 
638 646
     @Parametrize.COMPLETABLE_SERVICE_NAMES
639
-    def test_203_service_names(
647
+    def test_service_names(
640 648
         self,
641 649
         config: _types.VaultConfig,
642 650
         incomplete: str,
... ...
@@ -659,9 +667,13 @@ class TestShellCompletion:
659 667
             comp = self.Completions(["vault"], incomplete)
660 668
             assert frozenset(comp.get_words()) == completions
661 669
 
670
+
671
+class TestCompletionFormatting:
672
+    """Tests for the formatting of completable items."""
673
+
662 674
     @Parametrize.SHELL_FORMATTER
663 675
     @Parametrize.COMPLETION_FUNCTION_INPUTS
664
-    def test_300_shell_completion_formatting(
676
+    def test_shell_completion_formatting(
665 677
         self,
666 678
         shell: str,
667 679
         format_func: Callable[[click.shell_completion.CompletionItem], str],
... ...
@@ -724,9 +736,13 @@ class TestShellCompletion:
724 736
             assert actual_items == expected_items
725 737
             assert actual_string == expected_string
726 738
 
739
+
740
+class TestCLICompletabilityHandling(TestShellCompletion):
741
+    """Tests for how the command-line interface handles completability."""
742
+
727 743
     @Parametrize.CONFIG_SETTING_MODE
728 744
     @Parametrize.SERVICE_NAME_COMPLETION_INPUTS
729
-    def test_400_incompletable_service_names(
745
+    def test_cli_warns_and_completion_skips_incompletable_service_names(
730 746
         self,
731 747
         caplog: pytest.LogCaptureFixture,
732 748
         mode: Literal["config", "import"],
... ...
@@ -774,7 +790,7 @@ class TestShellCompletion:
774 790
             comp = self.Completions(["vault"], incomplete)
775 791
             assert frozenset(comp.get_words()) == completions
776 792
 
777
-    def test_410a_service_name_exceptions_not_found(
793
+    def test_handling_nonexistant_service_names(
778 794
         self,
779 795
     ) -> None:
780 796
         """Service name completion quietly fails on missing configuration."""
... ...
@@ -803,7 +819,7 @@ class TestShellCompletion:
803 819
             )
804 820
 
805 821
     @Parametrize.SERVICE_NAME_EXCEPTIONS
806
-    def test_410b_service_name_exceptions_custom_error(
822
+    def test_handling_unexpected_exceptions(
807 823
         self,
808 824
         exc_type: type[Exception],
809 825
     ) -> None:
... ...
@@ -56,11 +56,12 @@ class Parametrize(test_000_basic.Parametrize, test_utils.Parametrize):
56 56
     )
57 57
 
58 58
 
59
-class TestCLITransition:
60
-    """Transition tests for the command-line interface up to v1.0."""
59
+# TODO(the-13th-letter): Assimilate the descendants.
60
+class TestConfigMigrationMachinery:
61
+    """Tests for the configuration file migration machinery."""
61 62
 
62 63
     @Parametrize.BASE_CONFIG_VARIATIONS
63
-    def test_110_load_config_backup(
64
+    def test_load_config_backup(
64 65
         self,
65 66
         config: Any,
66 67
     ) -> None:
... ...
@@ -83,7 +84,7 @@ class TestCLITransition:
83 84
             assert cli_helpers.migrate_and_load_old_config()[0] == config
84 85
 
85 86
     @Parametrize.BASE_CONFIG_VARIATIONS
86
-    def test_111_migrate_config(
87
+    def test_migration(
87 88
         self,
88 89
         config: Any,
89 90
     ) -> None:
... ...
@@ -106,7 +107,7 @@ class TestCLITransition:
106 107
             assert cli_helpers.migrate_and_load_old_config() == (config, None)
107 108
 
108 109
     @Parametrize.BASE_CONFIG_VARIATIONS
109
-    def test_112_migrate_config_error(
110
+    def test_migration_error(
110 111
         self,
111 112
         config: Any,
112 113
     ) -> None:
... ...
@@ -136,7 +137,7 @@ class TestCLITransition:
136 137
             assert err.errno in {errno.EISDIR, errno.EEXIST}
137 138
 
138 139
     @Parametrize.BAD_CONFIGS
139
-    def test_113_migrate_config_error_bad_config_value(
140
+    def test_bad_config_migration(
140 141
         self,
141 142
         config: Any,
142 143
     ) -> None:
... ...
@@ -161,7 +162,11 @@ class TestCLITransition:
161 162
             ):
162 163
                 cli_helpers.migrate_and_load_old_config()
163 164
 
164
-    def test_200_forward_export_vault_path_parameter(
165
+
166
+class TestArgumentForwarding:
167
+    """Tests for the argument forwarding up to v1.0."""
168
+
169
+    def test_forward_export_vault_path_parameter(
165 170
         self,
166 171
         caplog: pytest.LogCaptureFixture,
167 172
     ) -> None:
... ...
@@ -195,7 +200,7 @@ class TestCLITransition:
195 200
         )
196 201
         assert json.loads(result.stdout) == data.VAULT_V03_CONFIG_DATA
197 202
 
198
-    def test_201_forward_export_vault_empty_commandline(
203
+    def test_forward_export_vault_empty_commandline(
199 204
         self,
200 205
         caplog: pytest.LogCaptureFixture,
201 206
     ) -> None:
... ...
@@ -228,7 +233,7 @@ class TestCLITransition:
228 233
         )
229 234
 
230 235
     @Parametrize.CHARSET_NAME
231
-    def test_210_forward_vault_disable_character_set(
236
+    def test_forward_vault_disable_character_set(
232 237
         self,
233 238
         caplog: pytest.LogCaptureFixture,
234 239
         charset_name: str,
... ...
@@ -271,7 +276,7 @@ class TestCLITransition:
271 276
                 f"derived password contains forbidden character {c!r}"
272 277
             )
273 278
 
274
-    def test_211_forward_vault_empty_command_line(
279
+    def test_forward_vault_empty_command_line(
275 280
         self,
276 281
         caplog: pytest.LogCaptureFixture,
277 282
     ) -> None:
... ...
@@ -304,7 +309,11 @@ class TestCLITransition:
304 309
             error="Deriving a passphrase requires a SERVICE."
305 310
         ), "expected error exit and known error type"
306 311
 
307
-    def test_300_export_using_old_config_file(
312
+
313
+class TestConfigMigration:
314
+    """Tests for the configuration file migration up to v1.0."""
315
+
316
+    def test_export_and_automatic_migration(
308 317
         self,
309 318
         caplog: pytest.LogCaptureFixture,
310 319
     ) -> None:
... ...
@@ -345,7 +354,7 @@ class TestCLITransition:
345 354
             "Successfully migrated to ", caplog.record_tuples
346 355
         ), "expected known warning message in stderr"
347 356
 
348
-    def test_300a_export_using_old_config_file_migration_error(
357
+    def test_export_and_automatic_migration_with_errors(
349 358
         self,
350 359
         caplog: pytest.LogCaptureFixture,
351 360
     ) -> None:
... ...
@@ -395,7 +404,12 @@ class TestCLITransition:
395 404
             "Failed to migrate to ", caplog.record_tuples
396 405
         ), "expected known warning message in stderr"
397 406
 
398
-    def test_400_completion_service_name_old_config_file(
407
+
408
+# TODO(the-13th-letter): Assimilate into the parent.
409
+class TestConfigMigrationMachinery001(TestConfigMigrationMachinery):
410
+    """Tests for the configuration file migration machinery, group 001."""
411
+
412
+    def test_completion_supports_old_config_file(
399 413
         self,
400 414
     ) -> None:
401 415
         """Completing service names from the old settings file works."""
... ...
@@ -526,11 +526,11 @@ class Parametrize(types.SimpleNamespace):
526 526
     )
527 527
 
528 528
 
529
-class TestCLIUtils:
530
-    """Tests for command-line utility functions."""
529
+class TestConfigLoadingSaving:
530
+    """Tests for the config loading and saving utility functions."""
531 531
 
532 532
     @Parametrize.BASE_CONFIG_VARIATIONS
533
-    def test_100_load_config(
533
+    def test_load(
534 534
         self,
535 535
         config: Any,
536 536
     ) -> None:
... ...
@@ -553,7 +553,7 @@ class TestCLIUtils:
553 553
                 assert json.load(fileobj) == config
554 554
             assert cli_helpers.load_config() == config
555 555
 
556
-    def test_110_save_bad_config(
556
+    def test_save_bad_config(
557 557
         self,
558 558
     ) -> None:
559 559
         """[`cli_helpers.save_config`][] fails for bad configurations."""
... ...
@@ -575,7 +575,11 @@ class TestCLIUtils:
575 575
             )
576 576
             cli_helpers.save_config(None)  # type: ignore[arg-type]
577 577
 
578
-    def test_111_prompt_for_selection_multiple(self) -> None:
578
+
579
+class TestPrompting:
580
+    """Tests for the prompting utility functions."""
581
+
582
+    def test_selection_multiple(self) -> None:
579 583
         """[`cli_helpers.prompt_for_selection`][] works in the "multiple" case."""
580 584
 
581 585
         @click.command()
... ...
@@ -687,7 +691,7 @@ Your selection? (1-10, leave empty to abort):\x20
687 691
 Your selection? (1-10, leave empty to abort): """,
688 692
         }, "expected known output"
689 693
 
690
-    def test_112_prompt_for_selection_single(self) -> None:
694
+    def test_selection_single(self) -> None:
691 695
         """[`cli_helpers.prompt_for_selection`][] works in the "single" case."""
692 696
 
693 697
         @click.command()
... ...
@@ -755,7 +759,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
755 759
 """,
756 760
         }, "expected known output"
757 761
 
758
-    def test_113_prompt_for_passphrase(
762
+    def test_passphrase(
759 763
         self,
760 764
     ) -> None:
761 765
         """[`cli_helpers.prompt_for_passphrase`][] works."""
... ...
@@ -775,7 +779,11 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
775 779
         assert res["kwargs"].get("err"), err_msg
776 780
         assert res["kwargs"].get("hide_input"), err_msg
777 781
 
778
-    def test_120_standard_logging_context_manager(
782
+
783
+class TestLoggingMachinery:
784
+    """Tests for the logging utility functions."""
785
+
786
+    def test_standard_logging_context_manager(
779 787
         self,
780 788
         caplog: pytest.LogCaptureFixture,
781 789
         capsys: pytest.CaptureFixture[str],
... ...
@@ -837,7 +845,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
837 845
                 (package_name, logging.WARNING, "message 3"),
838 846
             ]
839 847
 
840
-    def test_121_standard_logging_warnings_context_manager(
848
+    def test_warnings_context_manager(
841 849
         self,
842 850
         caplog: pytest.LogCaptureFixture,
843 851
         capsys: pytest.CaptureFixture[str],
... ...
@@ -896,6 +904,10 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
896 904
             assert f"FutureWarning: {THE_FUTURE}" in record_tuples[1][2]
897 905
             assert f"UserWarning: {JUST_TESTING}" in record_tuples[2][2]
898 906
 
907
+
908
+class TestExportConfigAsShellScript:
909
+    """Tests the utility functions for exporting configs in `sh` format."""
910
+
899 911
     def export_as_sh_helper(
900 912
         self,
901 913
         config: Any,
... ...
@@ -958,7 +970,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
958 970
             },
959 971
         ),
960 972
     )
961
-    def test_130a_export_as_sh_global(
973
+    def test_export_as_sh_global(
962 974
         self,
963 975
         global_config_settable: _types.VaultConfigServicesSettings,
964 976
         global_config_importable: _types.VaultConfigServicesSettings,
... ...
@@ -1001,7 +1013,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1001 1013
             },
1002 1014
         ),
1003 1015
     )
1004
-    def test_130b_export_as_sh_global_only_imports(
1016
+    def test_export_as_sh_global_only_imports(
1005 1017
         self,
1006 1018
         global_config_importable: _types.VaultConfigServicesSettings,
1007 1019
     ) -> None:
... ...
@@ -1061,7 +1073,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1061 1073
             },
1062 1074
         ),
1063 1075
     )
1064
-    def test_130c_export_as_sh_service(
1076
+    def test_export_as_sh_service(
1065 1077
         self,
1066 1078
         service_name: str,
1067 1079
         service_config_settable: _types.VaultConfigServicesSettings,
... ...
@@ -1124,7 +1136,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1124 1136
             },
1125 1137
         ),
1126 1138
     )
1127
-    def test_130d_export_as_sh_service_only_imports(
1139
+    def test_export_as_sh_service_only_imports(
1128 1140
         self,
1129 1141
         service_name: str,
1130 1142
         service_config_importable: _types.VaultConfigServicesSettings,
... ...
@@ -1146,6 +1158,10 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1146 1158
         assert _types.is_vault_config(config)
1147 1159
         return self.export_as_sh_helper(config)
1148 1160
 
1161
+
1162
+class TestTempdir:
1163
+    """Tests for the temporary directory handling utility functions."""
1164
+
1149 1165
     # The Annoying OS appears to silently truncate spaces at the end of
1150 1166
     # filenames.
1151 1167
     @hypothesis.given(
... ...
@@ -1165,7 +1181,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1165 1181
         ),
1166 1182
     )
1167 1183
     @hypothesis.example(env_var="", suffix=".")
1168
-    def test_140a_get_tempdir(
1184
+    def test_get_tempdir(
1169 1185
         self,
1170 1186
         env_var: str,
1171 1187
         suffix: str,
... ...
@@ -1228,7 +1244,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1228 1244
             )
1229 1245
         assert not temp_path.exists(), f"temp path {temp_path} not cleaned up!"
1230 1246
 
1231
-    def test_140b_get_tempdir_force_default(self) -> None:
1247
+    def test_get_tempdir_force_default(self) -> None:
1232 1248
         """[`cli_helpers.get_tempdir`][] returns a temporary directory.
1233 1249
 
1234 1250
         If all candidates are mocked to fail for the standard temporary
... ...
@@ -1282,8 +1298,14 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1282 1298
             monkeypatch.setattr(pathlib.Path, "is_dir", is_dir_error)
1283 1299
             assert cli_helpers.get_tempdir() == config_dir
1284 1300
 
1301
+
1302
+# TODO(the-13th-letter): Use a better class name, or consider keeping them
1303
+# as top-level functions.
1304
+class TestMisc:
1305
+    """Miscellaneous tests for the command-line utility functions."""
1306
+
1285 1307
     @Parametrize.DELETE_CONFIG_INPUT
1286
-    def test_203_repeated_config_deletion(
1308
+    def test_repeated_config_deletion(
1287 1309
         self,
1288 1310
         command_line: list[str],
1289 1311
         config: _types.VaultConfig,
... ...
@@ -1318,7 +1340,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1318 1340
                     config_readback = json.load(infile)
1319 1341
                 assert config_readback == result_config
1320 1342
 
1321
-    def test_204_phrase_from_key_manually(self) -> None:
1343
+    def test_phrase_from_key_manually(self) -> None:
1322 1344
         """The dummy service, key and config settings are consistent."""
1323 1345
         assert (
1324 1346
             vault.Vault(
... ...
@@ -1328,7 +1350,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1328 1350
         )
1329 1351
 
1330 1352
     @Parametrize.VALIDATION_FUNCTION_INPUT
1331
-    def test_210a_validate_constraints_manually(
1353
+    def test_validate_constraints_manually(
1332 1354
         self,
1333 1355
         vfunc: Callable[[click.Context, click.Parameter, Any], int | None],
1334 1356
         input: int,
... ...
@@ -1339,7 +1361,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1339 1361
         assert vfunc(ctx, param, input) == input
1340 1362
 
1341 1363
     @Parametrize.CONNECTION_HINTS
1342
-    def test_227_get_suitable_ssh_keys(
1364
+    def test_get_suitable_ssh_keys(
1343 1365
         self,
1344 1366
         running_ssh_agent: data.RunningSSHAgentInfo,
1345 1367
         conn_hint: str,
... ...
@@ -1384,7 +1406,7 @@ Will replace with spam, okay? (Please say "y" or "n".): Boo.
1384 1406
                 )
1385 1407
 
1386 1408
     @Parametrize.KEY_TO_PHRASE_SETTINGS
1387
-    def test_400_key_to_phrase(
1409
+    def test_key_to_phrase(
1388 1410
         self,
1389 1411
         ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient,
1390 1412
         list_keys_action: ListKeysAction | None,
... ...
@@ -507,10 +507,10 @@ class Parametrize(types.SimpleNamespace):
507 507
     )
508 508
 
509 509
 
510
-class TestTestingMachineryStubbedSSHAgentSocket:
511
-    """Test the stubbed SSH agent socket for the `ssh_agent` module tests."""
510
+class TestStubbedSSHAgentSocketRequests:
511
+    """Test the stubbed SSH agent socket: normal requests."""
512 512
 
513
-    def test_100a_query_extensions_base(self) -> None:
513
+    def test_query_extensions_base(self) -> None:
514 514
         """The base agent implements no extensions."""
515 515
         with contextlib.ExitStack() as stack:
516 516
             monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())
... ...
@@ -539,7 +539,7 @@ class TestTestingMachineryStubbedSSHAgentSocket:
539 539
             agent.sendall(query_request)
540 540
             assert agent.recv(1000) == query_response
541 541
 
542
-    def test_100b_query_extensions_extended(self) -> None:
542
+    def test_query_extensions_extended(self) -> None:
543 543
         """The extended agent implements a known list of extensions."""
544 544
         with contextlib.ExitStack() as stack:
545 545
             monkeypatch = stack.enter_context(pytest.MonkeyPatch.context())
... ...
@@ -575,7 +575,7 @@ class TestTestingMachineryStubbedSSHAgentSocket:
575 575
             agent.sendall(query_request)
576 576
             assert agent.recv(1000) == query_response
577 577
 
578
-    def test_101_request_identities(self) -> None:
578
+    def test_request_identities(self) -> None:
579 579
         """The agent implements a known list of identities."""
580 580
         unstring_prefix = ssh_agent.SSHAgentClient.unstring_prefix
581 581
         with machinery.StubbedSSHAgentSocket() as agent:
... ...
@@ -606,7 +606,7 @@ class TestTestingMachineryStubbedSSHAgentSocket:
606 606
             assert not message
607 607
 
608 608
     @Parametrize.SUPPORTED_SSH_TEST_KEYS
609
-    def test_102_sign(
609
+    def test_sign(
610 610
         self,
611 611
         ssh_test_key_type: str,
612 612
         ssh_test_key: data.SSHTestKey,
... ...
@@ -636,7 +636,11 @@ class TestTestingMachineryStubbedSSHAgentSocket:
636 636
             agent.sendall(query_request)
637 637
             assert agent.recv(1000) == query_response
638 638
 
639
-    def test_120_close_multiple(self) -> None:
639
+
640
+class TestStubbedSSHAgentSocketProperOperations:
641
+    """Test the stubbed SSH agent socket: proper use and misuse."""
642
+
643
+    def test_close_multiple(self) -> None:
640 644
         """The agent can be closed repeatedly."""
641 645
         with machinery.StubbedSSHAgentSocket() as agent:
642 646
             pass
... ...
@@ -644,7 +648,7 @@ class TestTestingMachineryStubbedSSHAgentSocket:
644 648
             pass
645 649
         del agent
646 650
 
647
-    def test_121_closed_agents_cannot_be_interacted_with(self) -> None:
651
+    def test_closed_agents_cannot_be_interacted_with(self) -> None:
648 652
         """The agent can be closed repeatedly."""
649 653
         with machinery.StubbedSSHAgentSocket() as agent:
650 654
             pass
... ...
@@ -664,7 +668,7 @@ class TestTestingMachineryStubbedSSHAgentSocket:
664 668
             agent.sendall(query_request)
665 669
         assert agent.recv(100) == query_response
666 670
 
667
-    def test_122_no_recv_without_sendall(self) -> None:
671
+    def test_no_recv_without_sendall(self) -> None:
668 672
         """The agent requires a message before sending a response."""
669 673
         with machinery.StubbedSSHAgentSocket() as agent:  # noqa: SIM117
670 674
             with pytest.raises(
... ...
@@ -676,7 +680,7 @@ class TestTestingMachineryStubbedSSHAgentSocket:
676 680
                 agent.recv(100)
677 681
 
678 682
     @Parametrize.INVALID_SSH_AGENT_MESSAGES
679
-    def test_123_invalid_ssh_agent_messages(
683
+    def test_invalid_ssh_agent_messages(
680 684
         self,
681 685
         message: Buffer,
682 686
     ) -> None:
... ...
@@ -691,8 +695,12 @@ class TestTestingMachineryStubbedSSHAgentSocket:
691 695
             agent.sendall(message)
692 696
             assert agent.recv(100) == query_response
693 697
 
698
+
699
+class TestStubbedSSHAgentSocketSupportedAndUnsupportedFeatures:
700
+    """Test the stubbed SSH agent socket: supported/unsupported features."""
701
+
694 702
     @Parametrize.UNSUPPORTED_SSH_AGENT_MESSAGES
695
-    def test_124_unsupported_ssh_agent_messages(
703
+    def test_unsupported_ssh_agent_messages(
696 704
         self,
697 705
         message: Buffer,
698 706
     ) -> None:
... ...
@@ -708,7 +716,7 @@ class TestTestingMachineryStubbedSSHAgentSocket:
708 716
             assert agent.recv(100) == query_response
709 717
 
710 718
     @Parametrize.STUBBED_AGENT_ADDRESSES
711
-    def test_125_addresses(
719
+    def test_addresses(
712 720
         self,
713 721
         address: str | None,
714 722
         exception: type[Exception] | None,
... ...
@@ -776,8 +784,12 @@ class TestStaticFunctionality:
776 784
         assert not trailer
777 785
         return ssh_agent.SSHAgentClient.string(unstringed)
778 786
 
779
-    # TODO(the-13th-letter): Re-evaluate if this check is worth keeping.
780
-    # It cannot provide true tamper-resistence, but probably appears to.
787
+
788
+class TestObsolete001:
789
+    """Obsolete tests: group 001."""
790
+
791
+    # TODO(the-13th-letter): Remove this test. The test key data has its
792
+    # own set of tests now.
781 793
     @Parametrize.PUBLIC_KEY_DATA
782 794
     def test_100_key_decoding(
783 795
         self,
... ...
@@ -791,8 +803,12 @@ class TestStaticFunctionality:
791 803
             "recorded public key data doesn't match"
792 804
         )
793 805
 
806
+
807
+class TestShellExportScriptParsing:
808
+    """Test the shell export script parsing utility function."""
809
+
794 810
     @Parametrize.SH_EXPORT_LINES
795
-    def test_190_sh_export_line_parsing(
811
+    def test_sh_export_line_parsing(
796 812
         self, line: str, env_name: str, value: str | None
797 813
     ) -> None:
798 814
         """[`tests.parse_sh_export_line`][] works."""
... ...
@@ -805,6 +821,10 @@ class TestStaticFunctionality:
805 821
             with pytest.raises(ValueError, match="Cannot parse sh line:"):
806 822
                 callables.parse_sh_export_line(line, env_name=env_name)
807 823
 
824
+
825
+class TestGracefulFailureForSSHAuthSock:
826
+    """Test the `posix` socket provider if `SSH_AUTH_SOCK` is unset."""
827
+
808 828
     def test_200_constructor_posix_no_ssh_auth_sock(
809 829
         self,
810 830
         skip_if_no_af_unix_support: None,
... ...
@@ -819,15 +839,19 @@ class TestStaticFunctionality:
819 839
             ):
820 840
                 posix_handler()
821 841
 
842
+
843
+class TestSSHProtocolDatatypes(TestStaticFunctionality):
844
+    """Tests for the utility functions for the SSH protocol datatypes."""
845
+
822 846
     @Parametrize.UINT32_INPUT
823
-    def test_210_uint32(self, input: int, expected: bytes | bytearray) -> None:
847
+    def test_uint32(self, input: int, expected: bytes | bytearray) -> None:
824 848
         """`uint32` encoding works."""
825 849
         uint32 = ssh_agent.SSHAgentClient.uint32
826 850
         assert uint32(input) == expected
827 851
 
828 852
     @hypothesis.given(strategies.integers(min_value=0, max_value=0xFFFFFFFF))
829 853
     @hypothesis.example(0xDEADBEEF).via("manual, pre-hypothesis example")
830
-    def test_210a_uint32_from_number(self, num: int) -> None:
854
+    def test_uint32_from_number(self, num: int) -> None:
831 855
         """`uint32` encoding works, starting from numbers."""
832 856
         uint32 = ssh_agent.SSHAgentClient.uint32
833 857
         assert int.from_bytes(uint32(num), "big", signed=False) == num
... ...
@@ -836,7 +860,7 @@ class TestStaticFunctionality:
836 860
     @hypothesis.example(b"\xde\xad\xbe\xef").via(
837 861
         "manual, pre-hypothesis example"
838 862
     )
839
-    def test_210b_uint32_from_bytestring(self, bytestring: bytes) -> None:
863
+    def test_uint32_from_bytestring(self, bytestring: bytes) -> None:
840 864
         """`uint32` encoding works, starting from length four byte strings."""
841 865
         uint32 = ssh_agent.SSHAgentClient.uint32
842 866
         assert (
... ...
@@ -845,7 +869,7 @@ class TestStaticFunctionality:
845 869
         )
846 870
 
847 871
     @Parametrize.SSH_STRING_INPUT
848
-    def test_211_string(
872
+    def test_string(
849 873
         self, input: bytes | bytearray, expected: bytes | bytearray
850 874
     ) -> None:
851 875
         """SSH string encoding works."""
... ...
@@ -856,7 +880,7 @@ class TestStaticFunctionality:
856 880
     @hypothesis.example(b"DEADBEEF" * 10000).via(
857 881
         "manual, pre-hypothesis example with highest order bit set"
858 882
     )
859
-    def test_211a_string_from_bytestring(self, bytestring: bytes) -> None:
883
+    def test_string_from_bytestring(self, bytestring: bytes) -> None:
860 884
         """SSH string encoding works, starting from a byte string."""
861 885
         res = ssh_agent.SSHAgentClient.string(bytestring)
862 886
         assert res.startswith((b"\x00\x00", b"\x00\x01"))
... ...
@@ -864,7 +888,7 @@ class TestStaticFunctionality:
864 888
         assert res[4:] == bytestring
865 889
 
866 890
     @Parametrize.SSH_UNSTRING_INPUT
867
-    def test_212_unstring(
891
+    def test_unstring(
868 892
         self, input: bytes | bytearray, expected: bytes | bytearray
869 893
     ) -> None:
870 894
         """SSH string decoding works."""
... ...
@@ -883,7 +907,7 @@ class TestStaticFunctionality:
883 907
     @hypothesis.example(b"\x00\x00\x00\x01").via(
884 908
         "detect no-op encoding via ill-formed SSH string"
885 909
     )
886
-    def test_212a_unstring_of_string_of_data(self, bytestring: bytes) -> None:
910
+    def test_unstring_of_string_of_data(self, bytestring: bytes) -> None:
887 911
         """SSH string decoding of encoded SSH strings works.
888 912
 
889 913
         References:
... ...
@@ -911,7 +935,7 @@ class TestStaticFunctionality:
911 935
             lambda x: TestStaticFunctionality.as_ssh_string(x)  # noqa: PLW0108
912 936
         ),
913 937
     )
914
-    def test_212b_string_of_unstring_of_data(self, encoded: bytes) -> None:
938
+    def test_string_of_unstring_of_data(self, encoded: bytes) -> None:
915 939
         """SSH string decoding of encoded SSH strings works.
916 940
 
917 941
         References:
... ...
@@ -928,7 +952,11 @@ class TestStaticFunctionality:
928 952
                 assert canon1(encoded) == canon2(encoded)
929 953
                 assert canon1(canon2(encoded)) == canon1(encoded)
930 954
 
931
-    def test_220_registry_resolve(
955
+
956
+class TestSSHAgentSocketProviderRegistry:
957
+    """Tests for the SSH agent socket provider registry."""
958
+
959
+    def test_resolve(
932 960
         self,
933 961
     ) -> None:
934 962
         """Resolving entries in the socket provider registry works."""
... ...
@@ -944,7 +972,7 @@ class TestStaticFunctionality:
944 972
                 resolve("stub_agent")
945 973
 
946 974
     @Parametrize.RESOLVE_CHAINS
947
-    def test_221_registry_resolve_chains(
975
+    def test_resolve_chains(
948 976
         self,
949 977
         terminal: Literal["unimplemented", "alias", "callable"],
950 978
         chain: list[str],
... ...
@@ -1003,7 +1031,7 @@ class TestStaticFunctionality:
1003 1031
             unique=True,
1004 1032
         ),
1005 1033
     )
1006
-    def test_221a_registry_resolve_chains(
1034
+    def test_resolve_chains2(
1007 1035
         self,
1008 1036
         terminal: Literal["unimplemented", "alias", "callable"],
1009 1037
         chain: list[str],
... ...
@@ -1041,7 +1069,7 @@ class TestStaticFunctionality:
1041 1069
                     assert resolve(link) == implementation
1042 1070
 
1043 1071
     @Parametrize.GOOD_ENTRY_POINTS
1044
-    def test_230_find_all_socket_providers(
1072
+    def test_find_all_socket_providers(
1045 1073
         self,
1046 1074
         additional_entry_points: list[importlib.metadata.EntryPoint],
1047 1075
     ) -> None:
... ...
@@ -1060,7 +1088,7 @@ class TestStaticFunctionality:
1060 1088
                 }
1061 1089
 
1062 1090
     @Parametrize.BAD_ENTRY_POINTS
1063
-    def test_231_find_all_socket_providers_errors(
1091
+    def test_find_all_socket_providers_errors(
1064 1092
         self,
1065 1093
         additional_entry_points: list[importlib.metadata.EntryPoint],
1066 1094
     ) -> None:
... ...
@@ -1074,8 +1102,13 @@ class TestStaticFunctionality:
1074 1102
             stack.enter_context(pytest.raises(AssertionError))
1075 1103
             socketprovider.SocketProvider._find_all_ssh_agent_socket_providers()
1076 1104
 
1105
+
1106
+# TODO(the-13th-letter): Assimilate into base class.
1107
+class TestSSHProtocolDatatypes001(TestSSHProtocolDatatypes):
1108
+    """More tests for the utility functions for the SSH protocol datatypes."""
1109
+
1077 1110
     @Parametrize.UINT32_EXCEPTIONS
1078
-    def test_310_uint32_exceptions(
1111
+    def test_uint32_exceptions(
1079 1112
         self, input: int, exc_type: type[Exception], exc_pattern: str
1080 1113
     ) -> None:
1081 1114
         """`uint32` encoding fails for out-of-bound values."""
... ...
@@ -1084,7 +1117,7 @@ class TestStaticFunctionality:
1084 1117
             uint32(input)
1085 1118
 
1086 1119
     @Parametrize.SSH_STRING_EXCEPTIONS
1087
-    def test_311_string_exceptions(
1120
+    def test_string_exceptions(
1088 1121
         self, input: Any, exc_type: type[Exception], exc_pattern: str
1089 1122
     ) -> None:
1090 1123
         """SSH string encoding fails for non-strings."""
... ...
@@ -1093,7 +1126,7 @@ class TestStaticFunctionality:
1093 1126
             string(input)
1094 1127
 
1095 1128
     @Parametrize.SSH_UNSTRING_EXCEPTIONS
1096
-    def test_312_unstring_exceptions(
1129
+    def test_unstring_exceptions(
1097 1130
         self,
1098 1131
         input: bytes | bytearray,
1099 1132
         exc_type: type[Exception],
... ...
@@ -1112,7 +1145,12 @@ class TestStaticFunctionality:
1112 1145
             with pytest.raises(exc_type, match=exc_pattern):
1113 1146
                 unstring_prefix(input)
1114 1147
 
1115
-    def test_320_registry_already_registered(
1148
+
1149
+# TODO(the-13th-letter): Assimilate into base class.
1150
+class TestSSHAgentSocketProviderRegistry001(TestSSHAgentSocketProviderRegistry):
1151
+    """More tests for the SSH agent socket provider registry."""
1152
+
1153
+    def test_already_registered(
1116 1154
         self,
1117 1155
     ) -> None:
1118 1156
         """The registry forbids overwriting entries."""
... ...
@@ -1136,7 +1174,7 @@ class TestStaticFunctionality:
1136 1174
             with pytest.raises(ValueError, match="already registered"):
1137 1175
                 register("the_annoying_os", "unix_domain")(the_annoying_os)
1138 1176
 
1139
-    def test_321_registry_resolve_non_existant_entries(
1177
+    def test_resolve_non_existant_entries(
1140 1178
         self,
1141 1179
     ) -> None:
1142 1180
         """Resolving a non-existant entry fails."""
... ...
@@ -1153,7 +1191,7 @@ class TestStaticFunctionality:
1153 1191
             with pytest.raises(socketprovider.NoSuchProviderError):
1154 1192
                 socketprovider.SocketProvider.resolve("native")
1155 1193
 
1156
-    def test_322_registry_register_new_entry(
1194
+    def test_register_new_entry(
1157 1195
         self,
1158 1196
     ) -> None:
1159 1197
         """Registering new entries works."""
... ...
@@ -1188,7 +1226,7 @@ class TestStaticFunctionality:
1188 1226
             ])
1189 1227
 
1190 1228
     @Parametrize.EXISTING_REGISTRY_ENTRIES
1191
-    def test_323_registry_register_old_entry(
1229
+    def test_register_old_entry(
1192 1230
         self,
1193 1231
         existing: str,
1194 1232
     ) -> None:
... ...
@@ -1230,11 +1268,11 @@ class TestStaticFunctionality:
1230 1268
             ])
1231 1269
 
1232 1270
 
1233
-class TestAgentInteraction:
1234
-    """Test actually talking to the SSH agent."""
1271
+class TestAgentSigning:
1272
+    """Test actually talking to the SSH agent: signing data."""
1235 1273
 
1236 1274
     @Parametrize.SUPPORTED_SSH_TEST_KEYS
1237
-    def test_200_sign_data_via_agent(
1275
+    def test_sign_data_via_agent(
1238 1276
         self,
1239 1277
         ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient,
1240 1278
         ssh_test_key_type: str,
... ...
@@ -1279,7 +1317,7 @@ class TestAgentInteraction:
1279 1317
         ), f"SSH signature mismatch ({ssh_test_key_type})"
1280 1318
 
1281 1319
     @Parametrize.UNSUITABLE_SSH_TEST_KEYS
1282
-    def test_201_sign_data_via_agent_unsupported(
1320
+    def test_sign_data_via_agent_unsupported(
1283 1321
         self,
1284 1322
         ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient,
1285 1323
         ssh_test_key_type: str,
... ...
@@ -1309,8 +1347,12 @@ class TestAgentInteraction:
1309 1347
         with pytest.raises(ValueError, match="unsuitable SSH key"):
1310 1348
             vault.Vault.phrase_from_key(public_key_data, conn=client)
1311 1349
 
1350
+
1351
+class TestSuitableKeys:
1352
+    """Test actually talking to the SSH agent: determining suitable keys."""
1353
+
1312 1354
     @Parametrize.SSH_KEY_SELECTION
1313
-    def test_210_ssh_key_selector(
1355
+    def test_ssh_key_selector(
1314 1356
         self,
1315 1357
         monkeypatch: pytest.MonkeyPatch,
1316 1358
         ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient,
... ...
@@ -1389,7 +1431,11 @@ class TestAgentInteraction:
1389 1431
         for snippet in ("Suitable SSH keys:\n", text, f"\n{b64_key}\n"):
1390 1432
             assert result.clean_exit(output=snippet), "expected clean exit"
1391 1433
 
1392
-    def test_300_constructor_bad_running_agent(
1434
+
1435
+class TestConstructorFailures:
1436
+    """Test actually talking to the SSH agent: constructor failures."""
1437
+
1438
+    def test_constructor_bad_running_agent(
1393 1439
         self,
1394 1440
         running_ssh_agent: data.RunningSSHAgentInfo,
1395 1441
     ) -> None:
... ...
@@ -1404,7 +1450,7 @@ class TestAgentInteraction:
1404 1450
             with pytest.raises(OSError):  # noqa: PT011
1405 1451
                 ssh_agent.SSHAgentClient()
1406 1452
 
1407
-    def test_301_constructor_no_af_unix_support(self) -> None:
1453
+    def test_constructor_no_af_unix_support(self) -> None:
1408 1454
         """Fail without [`socket.AF_UNIX`][] support."""
1409 1455
         assert "posix" in socketprovider.SocketProvider.registry
1410 1456
         with pytest.MonkeyPatch.context() as monkeypatch:
... ...
@@ -1416,7 +1462,7 @@ class TestAgentInteraction:
1416 1462
             ):
1417 1463
                 ssh_agent.SSHAgentClient(socket="posix")
1418 1464
 
1419
-    def test_302_no_ssh_agent_socket_provider_available(
1465
+    def test_no_ssh_agent_socket_provider_available(
1420 1466
         self,
1421 1467
     ) -> None:
1422 1468
         """Fail if no SSH agent socket provider is available."""
... ...
@@ -1433,15 +1479,23 @@ class TestAgentInteraction:
1433 1479
                 for e in excinfo.value.exceptions
1434 1480
             ])
1435 1481
 
1436
-    def test_303_explicit_socket(
1482
+
1483
+class TestOtherConstructorFeatures:
1484
+    """Test actually talking to the SSH agent: other constructor features."""
1485
+
1486
+    def test_explicit_socket(
1437 1487
         self,
1438 1488
         spawn_ssh_agent: data.SpawnedSSHAgentInfo,
1439 1489
     ) -> None:
1440 1490
         conn = spawn_ssh_agent.client._connection
1441 1491
         ssh_agent.SSHAgentClient(socket=conn)
1442 1492
 
1493
+
1494
+class TestAgentErrorResponses:
1495
+    """Test actually talking to the SSH agent: errors from the SSH agent."""
1496
+
1443 1497
     @Parametrize.TRUNCATED_AGENT_RESPONSES
1444
-    def test_310_truncated_server_response(
1498
+    def test_truncated_server_response(
1445 1499
         self,
1446 1500
         running_ssh_agent: data.RunningSSHAgentInfo,
1447 1501
         response: bytes,
... ...
@@ -1465,7 +1519,7 @@ class TestAgentInteraction:
1465 1519
                 client.request(255, b"")
1466 1520
 
1467 1521
     @Parametrize.LIST_KEYS_ERROR_RESPONSES
1468
-    def test_320_list_keys_error_responses(
1522
+    def test_list_keys_error_responses(
1469 1523
         self,
1470 1524
         running_ssh_agent: data.RunningSSHAgentInfo,
1471 1525
         response_code: _types.SSH_AGENT,
... ...
@@ -1525,7 +1579,7 @@ class TestAgentInteraction:
1525 1579
                 client.list_keys()
1526 1580
 
1527 1581
     @Parametrize.SIGN_ERROR_RESPONSES
1528
-    def test_330_sign_error_responses(
1582
+    def test_sign_error_responses(
1529 1583
         self,
1530 1584
         running_ssh_agent: data.RunningSSHAgentInfo,
1531 1585
         key: bytes | bytearray,
... ...
@@ -1594,7 +1648,7 @@ class TestAgentInteraction:
1594 1648
                 client.sign(key, b"abc", check_if_key_loaded=check)
1595 1649
 
1596 1650
     @Parametrize.REQUEST_ERROR_RESPONSES
1597
-    def test_340_request_error_responses(
1651
+    def test_request_error_responses(
1598 1652
         self,
1599 1653
         running_ssh_agent: data.RunningSSHAgentInfo,
1600 1654
         request_code: _types.SSH_AGENTC,
... ...
@@ -1622,7 +1676,7 @@ class TestAgentInteraction:
1622 1676
             client.request(request_code, b"", response_code=response_code)
1623 1677
 
1624 1678
     @Parametrize.QUERY_EXTENSIONS_MALFORMED_RESPONSES
1625
-    def test_350_query_extensions_malformed_responses(
1679
+    def test_query_extensions_malformed_responses(
1626 1680
         self,
1627 1681
         monkeypatch: pytest.MonkeyPatch,
1628 1682
         running_ssh_agent: data.RunningSSHAgentInfo,
1629 1683