diff --git a/src/_repobee/cli.py b/src/_repobee/cli.py
index bdb7779..ae02f32 100644
--- a/src/_repobee/cli.py
+++ b/src/_repobee/cli.py
@@ -598,7 +598,7 @@ def create_parser_for_docs():
     """
     daiquiri.setup(level=logging.FATAL)
     # load default plugins
-    plugin.initialize_plugins()
+    plugin.initialize_plugins([_repobee.constants.DEFAULT_PLUGIN])
     ext_commands = plug.manager.hook.create_extension_command()
     return _create_parser(show_all_opts=True, ext_commands=ext_commands)
 
diff --git a/src/_repobee/constants.py b/src/_repobee/constants.py
index bc74930..6a4ce12 100644
--- a/src/_repobee/constants.py
+++ b/src/_repobee/constants.py
@@ -32,3 +32,5 @@ ORDERED_CONFIGURABLE_ARGS = (
     "plugins",
 )
 CONFIGURABLE_ARGS = set(ORDERED_CONFIGURABLE_ARGS)
+
+DEFAULT_PLUGIN = "defaults"
diff --git a/src/_repobee/main.py b/src/_repobee/main.py
index 3dbdd89..35b2f3e 100644
--- a/src/_repobee/main.py
+++ b/src/_repobee/main.py
@@ -15,6 +15,7 @@ import repobee_plug as plug
 from _repobee import cli
 from _repobee import plugin
 from _repobee import exception
+from _repobee import constants
 
 LOGGER = daiquiri.getLogger(__file__)
 
@@ -56,9 +57,16 @@ def main(sys_args: List[str]):
 
         if parsed_preparser_args.no_plugins:
             LOGGER.info("Non-default plugins disabled")
-            plugin.initialize_plugins()
+            plugin.initialize_plugins([constants.DEFAULT_PLUGIN])
         else:
-            plugin.initialize_plugins(parsed_preparser_args.plug or [])
+            plugin_names = plugin.resolve_plugin_names(
+                parsed_preparser_args.plug, constants.DEFAULT_CONFIG_FILE
+            )
+            # IMPORTANT: the default plugin MUST be loaded last to ensure that
+            # any user-defined plugins override the firstresult hooks
+            plugin.initialize_plugins(
+                plugin_names + [constants.DEFAULT_PLUGIN]
+            )
 
         ext_commands = plug.manager.hook.create_extension_command()
         parsed_args, api = cli.parse_args(
diff --git a/src/_repobee/plugin.py b/src/_repobee/plugin.py
index 7677abf..a6d6108 100644
--- a/src/_repobee/plugin.py
+++ b/src/_repobee/plugin.py
@@ -11,7 +11,7 @@ Module containing plugin system utility functions and classes.
 import pathlib
 import importlib
 from types import ModuleType
-from typing import Union, List, Optional, Iterable
+from typing import List, Optional, Iterable
 
 import daiquiri
 
@@ -35,21 +35,13 @@ def _external_plugin_qualname(plugin_name):
     )
 
 
-DEFAULT_PLUGIN = "defaults"
+def load_plugin_modules(plugin_names: Iterable[str]) -> List[ModuleType]:
+    """Load the given plugins. Plugins are loaded such that they are executed
+    in the same order that they are specified in the plugin_names list.
 
-
-def load_plugin_modules(
-    config_file: Union[str, pathlib.Path] = constants.DEFAULT_CONFIG_FILE,
-    plugin_names: Iterable[str] = None,
-) -> List[ModuleType]:
-    """Load plugins that are specified in the config, as well as default
-    plugins. Note that default plugins are always loaded first, such that
-    they are either run last or overridden by plugins with firstresult=True
-    (such as the default_peer_review plugin).
-
-    Try to import first from :py:mod:`_repobee.ext`, and then from
-    ``repobee_<plugin>``. For example, if ``javac`` is listed as a plugin, the
-    following imports will be attempted:
+    When loading a plugin, tries to import first from :py:mod:`_repobee.ext`,
+    and then from ``repobee_<plugin>``. For example, if ``javac`` is listed as
+    a plugin, the following imports will be attempted:
 
     .. code-block:: python
 
@@ -60,23 +52,13 @@ def load_plugin_modules(
         from repobee_javac import javac
 
     Args:
-        config_file: Path to the configuration file.
         plugin_names: A list of plugin names. Overrides the config file.
 
     Returns:
         a list of loaded modules.
     """
     loaded_modules = []
-    plugin_names = [
-        *(plugin_names or config.get_plugin_names(config_file) or []),
-        # default plugin last so hooks are overridden by user-specified hooks
-        DEFAULT_PLUGIN,
-    ]
     LOGGER.info("Loading plugins: " + ", ".join(plugin_names))
-    if plugin_names == [DEFAULT_PLUGIN]:
-        from .ext import defaults
-
-        return [defaults]
 
     for name in plugin_names:
         plug_mod = _try_load_module(
@@ -135,3 +117,19 @@ def initialize_plugins(plugin_names: List[str] = None):
     """
     plug_modules = load_plugin_modules(plugin_names=plugin_names)
     register_plugins(plug_modules)
+
+
+def resolve_plugin_names(
+    plugin_names: Optional[List[str]] = None,
+    config_file: pathlib.Path = constants.DEFAULT_CONFIG_FILE,
+) -> List[str]:
+    """Return a list of plugin names to load into RepoBee given a list of
+    externally specified plugin names, and a path to a configuration file.
+
+    Args:
+        plugin_names: A list of names of plugins.
+        config_file: A configuration file.
+    Returns:
+        A list of plugin names that should be loaded.
+    """
+    return [*(plugin_names or config.get_plugin_names(config_file) or [])]

--no-plugins does not exclude config file plugins
Even if you specify `--no-plugins`, plugins specced in the config file will be loaded.
diff --git a/tests/unit_tests/test_cli.py b/tests/unit_tests/test_cli.py
index 932031c..e6ac806 100644
--- a/tests/unit_tests/test_cli.py
+++ b/tests/unit_tests/test_cli.py
@@ -1,18 +1,17 @@
 import os
-import argparse
 import pathlib
 import argparse
 from unittest.mock import MagicMock
 from unittest import mock
 
 import pytest
-import repobee_plug as plug
 
 import repobee_plug as plug
 
 import _repobee
 import _repobee.ext
 import _repobee.ext.github
+import _repobee.constants
 import _repobee.plugin
 from _repobee import cli
 from _repobee import exception
@@ -82,7 +81,9 @@ def api_class_mock(mocker, api_instance_mock):
 @pytest.fixture(autouse=True)
 def load_default_plugins(api_instance_mock):
     """Load the default plugins after mocking the GitHubAPI."""
-    loaded = _repobee.plugin.load_plugin_modules()
+    loaded = _repobee.plugin.load_plugin_modules(
+        [_repobee.constants.DEFAULT_PLUGIN]
+    )
     _repobee.plugin.register_plugins(loaded)
 
 
@@ -1075,8 +1076,6 @@ class TestCloneParser:
             args=mock.ANY
         )
 
-    # def test_no_plugins_option_drops_plugins()
-
     @pytest.mark.parametrize(
         "parser, extra_args",
         [
diff --git a/tests/unit_tests/test_command.py b/tests/unit_tests/test_command.py
index 86a1586..dd2747c 100644
--- a/tests/unit_tests/test_command.py
+++ b/tests/unit_tests/test_command.py
@@ -482,8 +482,11 @@ class TestCloneRepos:
     """Tests for clone_repos."""
 
     @pytest.fixture
-    def register_default_plugins(self, config_mock):
-        modules = plugin.load_plugin_modules(str(config_mock))
+    def register_default_plugins(self):
+        plugin_names = plugin.resolve_plugin_names(
+            plugin_names=constants.PLUGINS
+        )
+        modules = plugin.load_plugin_modules(plugin_names)
         plugin.register_plugins(modules)
 
     @pytest.fixture
@@ -519,7 +522,10 @@ class TestCloneRepos:
             "_repobee.ext.pylint.act_on_cloned_repo", act_hook_func
         )
 
-        modules = plugin.load_plugin_modules(str(config_mock))
+        plugin_names = plugin.resolve_plugin_names(
+            plugin_names=constants.PLUGINS
+        )
+        modules = plugin.load_plugin_modules(plugin_names)
         plugin.register_plugins(modules)
 
         return javac_hook, pylint_hook
@@ -662,7 +668,9 @@ def test_purge_review_teams(master_names, students, api_mock):
 class TestAssignPeerReviewers:
     @pytest.fixture(autouse=True)
     def load_default_plugins(self):
-        modules = plugin.load_plugin_modules()
+        modules = plugin.load_plugin_modules(
+            [_repobee.constants.DEFAULT_PLUGIN]
+        )
         plugin.register_plugins(modules)
 
     @pytest.mark.parametrize(
diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py
index 1b9230c..1d036b7 100644
--- a/tests/unit_tests/test_main.py
+++ b/tests/unit_tests/test_main.py
@@ -5,6 +5,7 @@ from collections import namedtuple
 import pytest
 
 from functions import raise_
+import _repobee.constants
 from _repobee import cli
 from _repobee import main
 from _repobee import plugin
@@ -131,7 +132,9 @@ def test_plugins_args(
 
     main.main(sys_args)
 
-    init_plugins_mock.assert_called_once_with(["javac", "pylint"])
+    init_plugins_mock.assert_called_once_with(
+        ["javac", "pylint", _repobee.constants.DEFAULT_PLUGIN]
+    )
     parse_args_mock.assert_called_once_with(
         CLONE_ARGS, show_all_opts=False, ext_commands=[]
     )
@@ -141,13 +144,48 @@ def test_no_plugins_arg(
     parse_args_mock, dispatch_command_mock, init_plugins_mock
 ):
     """Default plugins still need to be loaded, so initialize_plugins should be
-    called without arguments.
+    called only with the default plugin.
+    """
+    sys_args = ["repobee", "--no-plugins", *CLONE_ARGS]
+
+    main.main(sys_args)
+
+    init_plugins_mock.assert_called_once_with(
+        [_repobee.constants.DEFAULT_PLUGIN]
+    )
+    parse_args_mock.assert_called_once_with(
+        CLONE_ARGS, show_all_opts=False, ext_commands=[]
+    )
+
+
+def test_no_plugins_with_configured_plugins(
+    parse_args_mock, dispatch_command_mock, init_plugins_mock, config_mock
+):
+    """Test that --no-plugins causes any plugins listed in the config file to
+    NOT be loaded.
     """
     sys_args = ["repobee", "--no-plugins", *CLONE_ARGS]
 
     main.main(sys_args)
 
-    init_plugins_mock.assert_called_once_with()
+    init_plugins_mock.assert_called_once_with(
+        [_repobee.constants.DEFAULT_PLUGIN]
+    )
+    parse_args_mock.assert_called_once_with(
+        CLONE_ARGS, show_all_opts=False, ext_commands=[]
+    )
+
+
+def test_configured_plugins_are_loaded(
+    parse_args_mock, dispatch_command_mock, init_plugins_mock, config_mock
+):
+    sys_args = ["repobee", *CLONE_ARGS]
+
+    main.main(sys_args)
+
+    init_plugins_mock.assert_called_once_with(
+        [*constants.PLUGINS, _repobee.constants.DEFAULT_PLUGIN]
+    )
     parse_args_mock.assert_called_once_with(
         CLONE_ARGS, show_all_opts=False, ext_commands=[]
     )
@@ -160,7 +198,9 @@ def test_plugin_with_subparser_name(
 
     main.main(sys_args)
 
-    init_plugins_mock.assert_called_once_with(["javac", "clone"])
+    init_plugins_mock.assert_called_once_with(
+        ["javac", "clone", _repobee.constants.DEFAULT_PLUGIN]
+    )
     parse_args_mock.assert_called_once_with(
         CLONE_ARGS, show_all_opts=False, ext_commands=[]
     )
@@ -185,8 +225,7 @@ def test_invalid_plug_options(dispatch_command_mock, init_plugins_mock):
     Note that the default plugins must be loaded for this test to work.
     """
     # load default plugins
-    loaded = plugin.load_plugin_modules()
-    print(loaded)
+    loaded = plugin.load_plugin_modules([_repobee.constants.DEFAULT_PLUGIN])
     plugin.register_plugins(loaded)
 
     sys_args = ["repobee", "-p", "javac", "-f", *CLONE_ARGS]
@@ -194,7 +233,9 @@ def test_invalid_plug_options(dispatch_command_mock, init_plugins_mock):
     with pytest.raises(SystemExit):
         main.main(sys_args)
 
-    init_plugins_mock.assert_called_once_with(["javac"])
+    init_plugins_mock.assert_called_once_with(
+        ["javac", _repobee.constants.DEFAULT_PLUGIN]
+    )
     assert not dispatch_command_mock.called
 
 
diff --git a/tests/unit_tests/test_plugin.py b/tests/unit_tests/test_plugin.py
index 84b199b..770e5b9 100644
--- a/tests/unit_tests/test_plugin.py
+++ b/tests/unit_tests/test_plugin.py
@@ -6,99 +6,66 @@
         installed any other plugins, tests in here may fail unexpectedly
         without anything actually being wrong.
 """
-import os
 from unittest.mock import call, MagicMock
 
 import pytest
 
+import _repobee.constants
 from _repobee import plugin
 from _repobee import exception
 
-from _repobee.plugin import DEFAULT_PLUGIN
-from _repobee.ext import javac, pylint, defaults
+from _repobee.ext import javac, pylint
 
 import constants
 
 PLUGINS = constants.PLUGINS
 
 
-class TestLoadPluginModules:
-    """Tests for load_plugin_modules.
-
-    Note that the default plugin _repobee.ext.defaults is always loaded.
-    """
-
-    def test_load_all_bundled_plugins(self, config_mock):
-        """Test load the bundled plugins, i.e. the ones listed in
-        constants.PLUGINS.
-        """
-        expected_names = list(
-            map(plugin._plugin_qualname, [*PLUGINS, DEFAULT_PLUGIN])
-        )
-
-        modules = plugin.load_plugin_modules(str(config_mock))
-        module_names = [mod.__name__ for mod in modules]
-
-        assert module_names == expected_names
+class TestResolvePluginNames:
+    """Tests for resolve_plugin_names."""
 
     def test_plugin_names_override_config_file(self, config_mock, mocker):
         """Test that the plugin_names argument override the configuration
         file."""
         plugin_names = ["awesome", "the_slarse_plugin", "ric_easter_egg"]
-        expected_calls = [
-            call(plug)
-            for plug in map(
-                plugin._plugin_qualname, plugin_names + [DEFAULT_PLUGIN]
-            )
-        ]
-
-        class module:
-            pass
-
-        load_module_mock = mocker.patch(
-            "_repobee.plugin._try_load_module", return_value=module
-        )
 
-        plugin.load_plugin_modules(
+        actual_plugin_names = plugin.resolve_plugin_names(
             config_file=str(config_mock), plugin_names=plugin_names
         )
 
-        load_module_mock.assert_has_calls(expected_calls)
+        assert actual_plugin_names == plugin_names
 
-    def test_load_no_plugins(self, no_config_mock):
-        """Test calling load plugins when no plugins are specified results in
-        only the default being loaded.
-        """
-        modules = plugin.load_plugin_modules()
 
-        assert modules == [defaults]
+class TestLoadPluginModules:
+    """Tests for load_plugin_modules."""
 
-    def test_specify_single_plugin(self, empty_config_mock):
-        plugin_name = "javac"
-        config_contents = os.linesep.join(
-            ["[DEFAULTS]", "plugins = {}".format(plugin_name)]
-        )
-        empty_config_mock.write(config_contents)
+    def test_load_all_bundled_plugins(self):
+        """Test load the bundled plugins, i.e. the ones listed in
+        constants.PLUGINS.
+        """
+        plugin_names = [*PLUGINS, _repobee.constants.DEFAULT_PLUGIN]
+        plugin_qualnames = list(map(plugin._plugin_qualname, plugin_names))
 
-        modules = plugin.load_plugin_modules(str(empty_config_mock))
+        modules = plugin.load_plugin_modules(plugin_names)
         module_names = [mod.__name__ for mod in modules]
 
-        assert module_names == list(
-            map(plugin._plugin_qualname, [plugin_name, DEFAULT_PLUGIN])
-        )
+        assert module_names == plugin_qualnames
+
+    def test_load_no_plugins(self, no_config_mock):
+        """Test calling load plugins when no plugins are specified results in
+        no plugins being loaded."""
+        modules = plugin.load_plugin_modules([])
+
+        assert modules == []
 
     def test_raises_when_loading_invalid_module(self, empty_config_mock):
         """Test that PluginError is raised when when the plugin specified
         does not exist.
         """
         plugin_name = "this_plugin_does_not_exist"
-        config_contents = os.linesep.join(
-            ["[DEFAULTS]", "plugins = {}".format(plugin_name)]
-        )
-        empty_config_mock.write(config_contents)
 
         with pytest.raises(exception.PluginError) as exc_info:
-            plugin.load_plugin_modules(str(empty_config_mock))
+            plugin.load_plugin_modules([plugin_name])
 
         assert "failed to load plugin module " + plugin_name in str(
             exc_info.value

pip install -e .[TEST]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-mock codecov
apt-get update apt-get install -y gcc
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
codecov==2.1.13
colored==1.4.4
coverage==7.2.7
cryptography==44.0.2
daiquiri==3.2.1
Deprecated==1.2.18
exceptiongroup==1.2.2
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
packaging==24.0
pluggy==1.2.0
pycparser==2.21
PyGithub==2.3.0
PyJWT==2.8.0
PyNaCl==1.5.0
pytest==7.4.4
pytest-cov==4.1.0
pytest-mock==3.11.1
python-gitlab==1.8.0
python-json-logger==3.0.1
-e git+https://github.com/repobee/repobee.git@8b0bdf68bddc6f7fb868249402e3639d82092f6d#egg=repobee
repobee-plug==0.7.0
requests==2.31.0
six==1.17.0
tomli==2.0.1
typing_extensions==4.7.1
urllib3==2.0.7
wrapt==1.16.0
zipp==3.15.0

name: repobee
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - codecov==2.1.13
      - colored==1.4.4
      - coverage==7.2.7
      - cryptography==44.0.2
      - daiquiri==3.2.1
      - deprecated==1.2.18
      - exceptiongroup==1.2.2
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - packaging==24.0
      - pluggy==1.2.0
      - pycparser==2.21
      - pygithub==2.3.0
      - pyjwt==2.8.0
      - pynacl==1.5.0
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - python-gitlab==1.8.0
      - python-json-logger==3.0.1
      - repobee==2.0a1
      - repobee-plug==0.7.0
      - requests==2.31.0
      - six==1.17.0
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - wrapt==1.16.0
      - zipp==3.15.0
prefix: /opt/conda/envs/repobee

tests/unit_tests/test_cli.py::TestDispatchCommand::test_raises_on_invalid_subparser_value tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[setup] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[update] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[clone] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[migrate] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[open-issues] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[close-issues] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[list-issues] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[verify-settings] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[assign-reviews] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[purge-review-teams] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[show-config] tests/unit_tests/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[check-reviews] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[setup-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[setup-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[setup-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[setup-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[update-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[update-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[update-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[update-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[clone-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[clone-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[clone-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[clone-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[migrate-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[migrate-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[migrate-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[migrate-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[open-issues-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[open-issues-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[open-issues-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[open-issues-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[close-issues-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[close-issues-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[close-issues-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[close-issues-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[list-issues-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[list-issues-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[list-issues-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[list-issues-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[verify-settings-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[verify-settings-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[verify-settings-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[verify-settings-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[assign-reviews-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[assign-reviews-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[assign-reviews-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[assign-reviews-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[purge-review-teams-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[purge-review-teams-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[purge-review-teams-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[purge-review-teams-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[show-config-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[show-config-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[show-config-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[show-config-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[check-reviews-command_all_raise_mock0] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[check-reviews-command_all_raise_mock1] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[check-reviews-command_all_raise_mock2] tests/unit_tests/test_cli.py::TestDispatchCommand::test_expected_exception_results_in_system_exit[check-reviews-command_all_raise_mock3] tests/unit_tests/test_cli.py::TestDispatchCommand::test_setup_student_repos_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_update_student_repos_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_open_issue_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_close_issue_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_migrate_repos_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_clone_repos_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_verify_settings_called_with_correct_args tests/unit_tests/test_cli.py::TestDispatchCommand::test_verify_settings_called_with_master_org_name tests/unit_tests/test_cli.py::test_help_calls_add_arguments[setup] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[update] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[clone] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[migrate] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[open-issues] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[close-issues] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[list-issues] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[verify-settings] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[assign-reviews] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[purge-review-teams] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[show-config] tests/unit_tests/test_cli.py::test_help_calls_add_arguments[check-reviews] tests/unit_tests/test_cli.py::test_create_parser_for_docs tests/unit_tests/test_cli.py::TestBaseParsing::test_show_all_opts_true_shows_configured_args tests/unit_tests/test_cli.py::TestBaseParsing::test_show_all_opts_false_hides_configured_args tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_invalid_org tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_bad_credentials tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_invalid_base_url tests/unit_tests/test_cli.py::TestBaseParsing::test_master_org_overrides_target_org_for_master_repos[setup] tests/unit_tests/test_cli.py::TestBaseParsing::test_master_org_overrides_target_org_for_master_repos[update] tests/unit_tests/test_cli.py::TestBaseParsing::test_master_org_name_defaults_to_org_name[setup] tests/unit_tests/test_cli.py::TestBaseParsing::test_master_org_name_defaults_to_org_name[update] tests/unit_tests/test_cli.py::TestBaseParsing::test_token_env_variable_picked_up[setup] tests/unit_tests/test_cli.py::TestBaseParsing::test_token_env_variable_picked_up[update] tests/unit_tests/test_cli.py::TestBaseParsing::test_token_cli_arg_picked_up[setup] tests/unit_tests/test_cli.py::TestBaseParsing::test_token_cli_arg_picked_up[update] tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[http://some_enterprise_host/api/v3] tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[ftp://some_enterprise_host/api/v3] tests/unit_tests/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[some_enterprise_host/api/v3] tests/unit_tests/test_cli.py::TestExtensionCommands::test_parse_ext_command_that_does_not_require_api tests/unit_tests/test_cli.py::TestExtensionCommands::test_parse_ext_command_that_requires_api tests/unit_tests/test_cli.py::TestExtensionCommands::test_dispatch_ext_command_that_does_not_require_api tests/unit_tests/test_cli.py::TestExtensionCommands::test_dispatch_ext_command_that_requires_api tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_listing_students[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_listing_students[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_listing_students[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_listing_students[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_student_file[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_student_file[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_student_file[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parser_student_file[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[open-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[setup|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[update|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[close-issues|['-mn', tests/unit_tests/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[open-issues|['-mn', tests/unit_tests/test_cli.py::TestConfig::test_full_config[setup-extra_args0] tests/unit_tests/test_cli.py::TestConfig::test_full_config[update-extra_args1] tests/unit_tests/test_cli.py::TestConfig::test_full_config[open-issues-extra_args2] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-bu] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-u] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-sf] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-o] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_is_required[-mo] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-bu] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-u] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-sf] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-o] tests/unit_tests/test_cli.py::TestConfig::test_missing_option_can_be_specified[-mo] tests/unit_tests/test_cli.py::TestSetupAndUpdateParsers::test_happy_path[setup] tests/unit_tests/test_cli.py::TestSetupAndUpdateParsers::test_happy_path[update] tests/unit_tests/test_cli.py::TestSetupAndUpdateParsers::test_finds_local_repo[setup] tests/unit_tests/test_cli.py::TestSetupAndUpdateParsers::test_finds_local_repo[update] tests/unit_tests/test_cli.py::TestMigrateParser::test_happy_path tests/unit_tests/test_cli.py::TestVerifyParser::test_happy_path tests/unit_tests/test_cli.py::TestCloneParser::test_happy_path tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[setup-extra_args0] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[update-extra_args1] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[open-issues-extra_args2] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[close-issues-extra_args3] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[verify-settings-extra_args4] tests/unit_tests/test_cli.py::TestCloneParser::test_no_other_parser_gets_parse_hook[migrate-extra_args5] tests/unit_tests/test_cli.py::TestShowConfigParser::test_happy_path tests/unit_tests/test_cli.py::TestCommandDeprecation::test_deprecated_commands_parsed_to_current_commands[assign-peer-reviews-assign-reviews-sys_args0] tests/unit_tests/test_cli.py::TestCommandDeprecation::test_deprecated_commands_parsed_to_current_commands[purge-peer-review-teams-purge-review-teams-sys_args1] tests/unit_tests/test_cli.py::TestCommandDeprecation::test_deprecated_commands_parsed_to_current_commands[check-peer-review-progress-check-reviews-sys_args2] tests/unit_tests/test_command.py::TestCloneRepos::test_executes_act_hooks tests/unit_tests/test_command.py::TestAssignPeerReviewers::test_too_few_students_raises[3-3] tests/unit_tests/test_command.py::TestAssignPeerReviewers::test_too_few_students_raises[3-4] tests/unit_tests/test_command.py::TestAssignPeerReviewers::test_zero_reviews_raises tests/unit_tests/test_command.py::TestAssignPeerReviewers::test_happy_path tests/unit_tests/test_main.py::test_plugins_args tests/unit_tests/test_main.py::test_no_plugins_arg tests/unit_tests/test_main.py::test_no_plugins_with_configured_plugins tests/unit_tests/test_main.py::test_configured_plugins_are_loaded tests/unit_tests/test_main.py::test_plugin_with_subparser_name tests/unit_tests/test_main.py::test_invalid_plug_options tests/unit_tests/test_plugin.py::TestResolvePluginNames::test_plugin_names_override_config_file tests/unit_tests/test_plugin.py::TestLoadPluginModules::test_load_all_bundled_plugins tests/unit_tests/test_plugin.py::TestLoadPluginModules::test_load_no_plugins tests/unit_tests/test_plugin.py::TestLoadPluginModules::test_raises_when_loading_invalid_module
tests/unit_tests/test_command.py::TestSetupStudentRepos::test_raises_on_clone_failure tests/unit_tests/test_command.py::TestSetupStudentRepos::test_raises_on_duplicate_master_urls tests/unit_tests/test_command.py::TestSetupStudentRepos::test_happy_path tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_raises_on_duplicate_master_urls tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_happy_path tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_issues_on_exceptions[issue0] tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_issues_on_exceptions[None] tests/unit_tests/test_command.py::TestUpdateStudentRepos::test_issues_arent_opened_on_exceptions_if_unspeficied tests/unit_tests/test_command.py::TestOpenIssue::test_happy_path tests/unit_tests/test_command.py::TestCloseIssue::test_happy_path tests/unit_tests/test_command.py::TestCloneRepos::test_happy_path tests/unit_tests/test_command.py::TestMigrateRepo::test_happy_path tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True--IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True--IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True--IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True-^.*$-IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True-^.*$-IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-True-^.*$-IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False--IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False--IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False--IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False-^.*$-IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False-^.*$-IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[None-False-^.*$-IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True--IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True--IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True--IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True-^.*$-IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True-^.*$-IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-True-^.*$-IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False--IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False--IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False--IssueState.ALL] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False-^.*$-IssueState.OPEN] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False-^.*$-IssueState.CLOSED] tests/unit_tests/test_command.py::TestListIssues::test_happy_path[slarse-False-^.*$-IssueState.ALL] tests/unit_tests/test_command.py::test_purge_review_teams tests/unit_tests/test_command.py::TestCheckPeerReviewProgress::test_happy_path tests/unit_tests/test_main.py::test_happy_path tests/unit_tests/test_main.py::test_does_not_raise_on_exception_in_parsing tests/unit_tests/test_main.py::test_does_not_raise_on_exception_in_handling_parsed_args tests/unit_tests/test_main.py::test_plug_arg_incompatible_with_no_plugins tests/unit_tests/test_main.py::test_does_not_raise_on_exception_in_dispatch tests/unit_tests/test_main.py::test_logs_traceback_on_exception_in_dispatch_if_traceback tests/unit_tests/test_main.py::test_show_all_opts_correctly_separated tests/unit_tests/test_plugin.py::TestRegisterPlugins::test_register_module tests/unit_tests/test_plugin.py::TestRegisterPlugins::test_register_module_with_plugin_class tests/unit_tests/test_plugin.py::TestRegisterPlugins::test_register_modules_and_classes
src/_repobee/main.py src/_repobee/constants.py src/_repobee/cli.py src/_repobee/plugin.py
src/_repobee/main.py src/_repobee/constants.py src/_repobee/cli.py src/_repobee/plugin.py