codecov[bot]: # [Codecov](https://codecov.io/gh/repobee/repobee/pull/538?src=pr&el=h1) Report
> Merging [#538](https://codecov.io/gh/repobee/repobee/pull/538?src=pr&el=desc) into [master](https://codecov.io/gh/repobee/repobee/commit/2d8eecab831a65edf4f838e8ee47a8b687b24406&el=desc) will **decrease** coverage by `0.06%`.
> The diff coverage is `77.14%`.

[![Impacted file tree graph](https://codecov.io/gh/repobee/repobee/pull/538/graphs/tree.svg?width=650&height=150&src=pr&token=5vOKgkphZe)](https://codecov.io/gh/repobee/repobee/pull/538?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #538      +/-   ##
==========================================
- Coverage   88.24%   88.18%   -0.07%     
==========================================
  Files          47       47              
  Lines        2476     2462      -14     
  Branches      406      405       -1     
==========================================
- Hits         2185     2171      -14     
  Misses        241      241              
  Partials       50       50              
```


| [Impacted Files](https://codecov.io/gh/repobee/repobee/pull/538?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/\_repobee/exception.py](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4Y2VwdGlvbi5weQ==) | `100.00% <ø> (ø)` | |
| [src/repobee\_plug/\_apimeta.py](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree#diff-c3JjL3JlcG9iZWVfcGx1Zy9fYXBpbWV0YS5weQ==) | `97.16% <ø> (ø)` | |
| [src/\_repobee/ext/gitlab.py](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9naXRsYWIucHk=) | `63.86% <54.54%> (-0.40%)` | :arrow_down: |
| [src/\_repobee/ext/defaults/github.py](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9kZWZhdWx0cy9naXRodWIucHk=) | `90.26% <76.92%> (-0.15%)` | :arrow_down: |
| [src/\_repobee/cli/parsing.py](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NsaS9wYXJzaW5nLnB5) | `86.57% <100.00%> (ø)` | |
| [src/repobee\_plug/\_\_init\_\_.py](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree#diff-c3JjL3JlcG9iZWVfcGx1Zy9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/repobee\_plug/\_exceptions.py](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree#diff-c3JjL3JlcG9iZWVfcGx1Zy9fZXhjZXB0aW9ucy5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/repobee\_plug/cli/\_corecommand.py](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree#diff-c3JjL3JlcG9iZWVfcGx1Zy9jbGkvX2NvcmVjb21tYW5kLnB5) | `92.30% <0.00%> (-0.20%)` | :arrow_down: |
| [src/\_repobee/main.py](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL21haW4ucHk=) | `90.69% <0.00%> (-0.11%)` | :arrow_down: |
| ... and [5 more](https://codecov.io/gh/repobee/repobee/pull/538/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/repobee/repobee/pull/538?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/repobee/repobee/pull/538?src=pr&el=footer). Last update [2d8eeca...1b67332](https://codecov.io/gh/repobee/repobee/pull/538?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

slarse: Failure due to flaky integration tests :(
diff --git a/src/_repobee/cli/parsing.py b/src/_repobee/cli/parsing.py
index ee7cddf..8022cc5 100644
--- a/src/_repobee/cli/parsing.py
+++ b/src/_repobee/cli/parsing.py
@@ -251,9 +251,9 @@ def _connect_to_api(
     api_class = plug.manager.hook.get_api_class()
     try:
         return api_class(**kwargs)
-    except exception.NotFoundError:
+    except plug.NotFoundError:
         # more informative message
-        raise exception.NotFoundError(
+        raise plug.NotFoundError(
             "either organization {} could not be found, "
             "or the base url '{}' is incorrect".format(org_name, base_url)
         )
diff --git a/src/_repobee/exception.py b/src/_repobee/exception.py
index f1ed370..0e32040 100644
--- a/src/_repobee/exception.py
+++ b/src/_repobee/exception.py
@@ -36,32 +36,6 @@ class FileError(RepoBeeException):
     """Raise when reading or writing to a file errors out."""
 
 
-class APIError(RepoBeeException):
-    """An exception raised when the API responds with an error code."""
-
-    def __init__(self, msg="", status=None):
-        super().__init__(msg)
-        self.status = status
-
-
-class NotFoundError(APIError):
-    """An exception raised when a platform API fails to find a resource."""
-
-
-class ServiceNotFoundError(APIError):
-    """Raise if the base url can't be located."""
-
-
-class BadCredentials(APIError):
-    """Raise when credentials are rejected."""
-
-
-class UnexpectedException(APIError):
-    """An exception raised when an API request raises an unexpected
-    exception.
-    """
-
-
 class GitError(RepoBeeException):
     """A generic error to raise when a git command exits with a non-zero exit
     status.
diff --git a/src/_repobee/ext/defaults/github.py b/src/_repobee/ext/defaults/github.py
index 3a3b576..53ae769 100644
--- a/src/_repobee/ext/defaults/github.py
+++ b/src/_repobee/ext/defaults/github.py
@@ -22,8 +22,6 @@ import github
 
 import repobee_plug as plug
 
-from _repobee import exception
-
 REQUIRED_TOKEN_SCOPES = {"admin:org", "repo"}
 ISSUE_GENERATOR = Generator[plug.Issue, None, None]
 
@@ -54,15 +52,15 @@ DEFAULT_REVIEW_ISSUE = plug.Issue(
 @contextlib.contextmanager
 def _convert_404_to_not_found_error(msg):
     """Catch a github.GithubException with status 404 and convert to
-    exception.NotFoundError with the provided message. If the GithubException
-    does not have status 404, instead raise exception.UnexpectedException.
+    plug.NotFoundError with the provided message. If the GithubException
+    does not have status 404, instead raise plug.UnexpectedException.
     """
     try:
         yield
     except github.GithubException as exc:
         if exc.status == 404:
-            raise exception.NotFoundError(msg)
-        raise exception.UnexpectedException(
+            raise plug.NotFoundError(msg)
+        raise plug.UnexpectedException(
             "An unexpected exception occured. {.__name__}: {}".format(
                 type(exc), str(exc)
             )
@@ -78,11 +76,11 @@ def _try_api_request(ignore_statuses: Optional[Iterable[int]] = None):
         applicable if the exception is a github.GithubException).
 
     Raises:
-        exception.NotFoundError
-        exception.BadCredentials
-        exception.APIError
-        exception.ServiceNotFoundError
-        exception.UnexpectedException
+        plug.NotFoundError
+        plug.BadCredentials
+        plug.APIError
+        plug.ServiceNotFoundError
+        plug.UnexpectedException
     """
     try:
         yield
@@ -91,20 +89,20 @@ def _try_api_request(ignore_statuses: Optional[Iterable[int]] = None):
             return
 
         if e.status == 404:
-            raise exception.NotFoundError(str(e), status=404)
+            raise plug.NotFoundError(str(e), status=404)
         elif e.status == 401:
-            raise exception.BadCredentials(
+            raise plug.BadCredentials(
                 "credentials rejected, verify that token has correct access.",
                 status=401,
             )
         else:
-            raise exception.APIError(str(e), status=e.status)
+            raise plug.APIError(str(e), status=e.status)
     except gaierror:
-        raise exception.ServiceNotFoundError(
+        raise plug.ServiceNotFoundError(
             "GitHub service could not be found, check the url"
         )
     except Exception as e:
-        raise exception.UnexpectedException(
+        raise plug.UnexpectedException(
             "a {} occured unexpectedly: {}".format(type(e).__name__, str(e))
         )
 
@@ -222,7 +220,7 @@ class GitHubAPI(plug.API):
                 existing_users.append(self._github.get_user(name))
             except github.GithubException as exc:
                 if exc.status != 404:
-                    raise exception.APIError(
+                    raise plug.APIError(
                         "Got unexpected response code from the GitHub API",
                         status=exc.status,
                     )
@@ -268,7 +266,7 @@ class GitHubAPI(plug.API):
             A list of Team namedtuples representing the teams corresponding to
             the provided team_names.
         Raises:
-            exception.UnexpectedException if anything but a 422 (team already
+            plug.UnexpectedException if anything but a 422 (team already
             exists) is raised when trying to create a team.
         """
         existing_teams = list(self._org.get_teams())
@@ -632,7 +630,7 @@ class GitHubAPI(plug.API):
         """See :py:meth:`repobee_plug.API.verify_settings`."""
         LOGGER.info("Verifying settings ...")
         if not token:
-            raise exception.BadCredentials(
+            raise plug.BadCredentials(
                 msg="token is empty. Check that REPOBEE_TOKEN environment "
                 "variable is properly set, or supply the `--token` option."
             )
@@ -656,13 +654,13 @@ class GitHubAPI(plug.API):
                     "{} Possible reasons: bad api url that points to a "
                     "GitHub instance, but not to the api endpoint."
                 ).format(msg)
-                raise exception.UnexpectedException(msg=msg)
+                raise plug.UnexpectedException(msg=msg)
             elif user_.login != user:
                 msg = (
                     "{} Possible reasons: unknown, rerun with -tb and open an "
                     "issue on GitHub.".format(msg)
                 )
-                raise exception.UnexpectedException(msg=msg)
+                raise plug.UnexpectedException(msg=msg)
         LOGGER.info(
             "SUCCESS: found user {}, "
             "user exists and base url looks okay".format(user)
@@ -671,7 +669,7 @@ class GitHubAPI(plug.API):
         LOGGER.info("Verifying access token scopes ...")
         scopes = g.oauth_scopes
         if not REQUIRED_TOKEN_SCOPES.issubset(scopes):
-            raise exception.BadCredentials(
+            raise plug.BadCredentials(
                 "missing one or more access token scopes. "
                 "Actual: {}. Required {}".format(scopes, REQUIRED_TOKEN_SCOPES)
             )
@@ -705,7 +703,7 @@ class GitHubAPI(plug.API):
             owner.login for owner in org.get_members(role="admin")
         )
         if user not in owner_usernames:
-            raise exception.BadCredentials(
+            raise plug.BadCredentials(
                 "user {} is not an owner of organization {}".format(
                     user, org_name
                 )
diff --git a/src/_repobee/ext/gitlab.py b/src/_repobee/ext/gitlab.py
index fa9a41d..76b3530 100644
--- a/src/_repobee/ext/gitlab.py
+++ b/src/_repobee/ext/gitlab.py
@@ -52,8 +52,8 @@ def _convert_404_to_not_found_error(msg):
         yield
     except gitlab.exceptions.GitlabError as exc:
         if exc.response_code == 404:
-            raise exception.NotFoundError(msg)
-        raise exception.UnexpectedException(
+            raise plug.NotFoundError(msg)
+        raise plug.UnexpectedException(
             f"An unexpected exception occured. {type(exc).__name__}: {exc}"
         )
 
@@ -81,18 +81,18 @@ def _try_api_request(ignore_statuses: Optional[Iterable[int]] = None):
             return
 
         if e.response_code == 404:
-            raise exception.NotFoundError(str(e), status=404) from e
+            raise plug.NotFoundError(str(e), status=404) from e
         elif e.response_code == 401:
-            raise exception.BadCredentials(
+            raise plug.BadCredentials(
                 "credentials rejected, verify that token has correct access.",
                 status=401,
             ) from e
         else:
-            raise exception.APIError(str(e), status=e.response_code) from e
+            raise plug.APIError(str(e), status=e.response_code) from e
     except (exception.RepoBeeException, plug.PlugError):
         raise
     except Exception as e:
-        raise exception.UnexpectedException(
+        raise plug.UnexpectedException(
             f"a {type(e).__name__} occured unexpectedly: {str(e)}"
         ) from e
 
@@ -203,7 +203,7 @@ class GitLabAPI(plug.API):
         ]
 
         if not matches:
-            raise exception.NotFoundError(org_name, status=404)
+            raise plug.NotFoundError(org_name, status=404)
 
         return matches[0]
 
@@ -258,7 +258,7 @@ class GitLabAPI(plug.API):
             A list of Team namedtuples representing the teams corresponding to
             the provided team_names.
         Raises:
-            exception.UnexpectedException if anything but a 422 (team already
+            plug.UnexpectedException if anything but a 422 (team already
             exists) is raised when trying to create a team.
         """
         existing_teams = list(self._gitlab.groups.list(id=self._group.id))
@@ -386,7 +386,7 @@ class GitLabAPI(plug.API):
         if missing:
             msg = f"Can't find repos: {', '.join(missing)}"
             if strict:
-                raise exception.NotFoundError(msg)
+                raise plug.NotFoundError(msg)
             LOGGER.warning(msg)
 
     def delete_teams(self, team_names: Iterable[str]) -> None:
@@ -612,7 +612,7 @@ class GitLabAPI(plug.API):
         """See :py:meth:`repobee_plug.API.verify_settings`."""
         LOGGER.info("GitLabAPI is verifying settings ...")
         if not token:
-            raise exception.BadCredentials(
+            raise plug.BadCredentials(
                 msg="Token is empty. Check that REPOBEE_TOKEN environment "
                 "variable is properly set, or supply the `--token` option."
             )
@@ -624,11 +624,11 @@ class GitLabAPI(plug.API):
         LOGGER.info(f"Authenticating connection to {base_url}...")
         with _convert_error(
             gitlab.exceptions.GitlabAuthenticationError,
-            exception.BadCredentials,
+            plug.BadCredentials,
             "Could not authenticate token",
         ), _convert_error(
             requests.exceptions.ConnectionError,
-            exception.APIError,
+            plug.APIError,
             f"Could not connect to {base_url}, please check the URL",
         ):
             gl.auth()
@@ -654,7 +654,7 @@ class GitLabAPI(plug.API):
             if group.path == group_name
         ]
         if not slug_matched:
-            raise exception.NotFoundError(
+            raise plug.NotFoundError(
                 f"Could not find group with slug {group_name}. Verify that "
                 f"you have access to the group, and that you've provided "
                 f"the slug (the name in the address bar)."
@@ -672,7 +672,7 @@ class GitLabAPI(plug.API):
             and member.access_level == gitlab.OWNER_ACCESS
         ]
         if not matching_members:
-            raise exception.BadCredentials(
+            raise plug.BadCredentials(
                 f"User {user} is not an owner of {group_name}"
             )
         LOGGER.info(f"SUCCESS: User {user} is an owner of group {group_name}")
diff --git a/src/repobee_plug/__init__.py b/src/repobee_plug/__init__.py
index f30f81a..5c1cfc6 100644
--- a/src/repobee_plug/__init__.py
+++ b/src/repobee_plug/__init__.py
@@ -46,7 +46,15 @@ from repobee_plug._apimeta import (
 )
 
 # Exceptions
-from repobee_plug._exceptions import HookNameError, PlugError
+from repobee_plug._exceptions import (
+    HookNameError,
+    PlugError,
+    APIError,
+    NotFoundError,
+    ServiceNotFoundError,
+    BadCredentials,
+    UnexpectedException,
+)
 
 manager = pluggy.PluginManager(__package__)
 manager.add_hookspecs(_clone_hook)
@@ -78,6 +86,11 @@ __all__ = [
     # Exceptions
     "HookNameError",
     "PlugError",
+    "APIError",
+    "NotFoundError",
+    "ServiceNotFoundError",
+    "BadCredentials",
+    "UnexpectedException",
     # Helpers
     "json_to_result_mapping",
     "result_mapping_to_json",
diff --git a/src/repobee_plug/_apimeta.py b/src/repobee_plug/_apimeta.py
index e61a94d..2f3431a 100644
--- a/src/repobee_plug/_apimeta.py
+++ b/src/repobee_plug/_apimeta.py
@@ -381,7 +381,7 @@ class APISpec:
         organization member list and checking roles)
 
         Should raise an appropriate subclass of
-        :py:class:`_repobee.exception.APIError` when a problem is encountered.
+        :py:class:`_repobee.plug.APIError` when a problem is encountered.
 
         Args:
             user: The username to try to fetch.
@@ -392,7 +392,7 @@ class APISpec:
         Returns:
             True if the connection is well formed.
         Raises:
-            :py:class:`_repobee.exception.APIError`
+            :py:class:`_repobee.plug.APIError`
         """
         _not_implemented()
 
@@ -482,7 +482,7 @@ class API(APISpec, metaclass=APIMeta):
 
     1. Public methods *must* override one of the public methods of
        :py:class:`APISpec`. If an inheriting class defines any other public
-       method, an :py:class:`~_repobee.exception.APIError` is raised when the
+       method, an :py:class:`~_repobee.plug.APIError` is raised when the
        class is defined.
     2. All public methods in :py:class:`APISpec` have a default implementation
        that simply raise a :py:class:`NotImplementedError`. There is no
diff --git a/src/repobee_plug/_exceptions.py b/src/repobee_plug/_exceptions.py
index 6d65571..26249e6 100644
--- a/src/repobee_plug/_exceptions.py
+++ b/src/repobee_plug/_exceptions.py
@@ -51,3 +51,29 @@ class HookNameError(PlugError):
 
 class APIImplementationError(PlugError):
     """Raise when an API is defined incorrectly."""
+
+
+class APIError(PlugError):
+    """An exception raised when the API responds with an error code."""
+
+    def __init__(self, msg="", status=None):
+        super().__init__(msg)
+        self.status = status
+
+
+class NotFoundError(APIError):
+    """An exception raised when a platform API fails to find a resource."""
+
+
+class ServiceNotFoundError(APIError):
+    """Raise if the base url can't be located."""
+
+
+class BadCredentials(APIError):
+    """Raise when credentials are rejected."""
+
+
+class UnexpectedException(APIError):
+    """An exception raised when an API request raises an unexpected
+    exception.
+    """

APIError should be moved to repobee_plug
The `APIError`, and all of its descendant errors, should be moved to repobee_plug, as APIs are plugins nowadays. As `repobee_plug` is now in this repository, this should be fairly easy.

Currently, `APIError` is located in `_repobee.exception`, and currently extends the `RepoBeeException` exception. It should be moved to `repobee_plug._exceptions` and instead extend `repobee_plug._exceptions.PlugError` `APIError` should then be exposed in `repobee_plug`'s top level (i.e. in `src/repobee_plug/__init__.py`).

The intended use would then be:

```python
import repobee_plug as plug

raise plug.APIError("msg", status=404)
```
diff --git a/tests/unit_tests/repobee/plugin_tests/test_github.py b/tests/unit_tests/repobee/plugin_tests/test_github.py
index a8a6b47..d1005cf 100644
--- a/tests/unit_tests/repobee/plugin_tests/test_github.py
+++ b/tests/unit_tests/repobee/plugin_tests/test_github.py
@@ -8,7 +8,6 @@ import github
 import repobee_plug as plug
 
 import _repobee.ext.defaults.github as github_plugin
-from _repobee import exception
 from _repobee.ext.defaults.github import REQUIRED_TOKEN_SCOPES
 
 import constants
@@ -389,9 +388,9 @@ class TestEnsureTeamsAndMembers:
     @pytest.mark.parametrize(
         "unexpected_exc",
         [
-            exception.APIError("", 404),
-            exception.APIError("", 400),
-            exception.APIError("", 500),
+            plug.APIError("", 404),
+            plug.APIError("", 400),
+            plug.APIError("", 500),
         ],
     )
     def test_raises_on_non_422_exception(
@@ -406,7 +405,7 @@ class TestEnsureTeamsAndMembers:
 
         organization.create_team.side_effect = raise_
 
-        with pytest.raises(exception.UnexpectedException):
+        with pytest.raises(plug.UnexpectedException):
             api.ensure_teams_and_members(team_wrappers)
 
     def test_running_twice_has_no_ill_effects(
@@ -472,7 +471,7 @@ class TestCreateRepos:
         side_effect_github_exception = [unexpected_exception] + side_effect[1:]
 
         create_repo_mock.side_effect = side_effect_github_exception
-        with pytest.raises(exception.APIError):
+        with pytest.raises(plug.APIError):
             api.create_repos(repo_infos)
 
     def test_returns_all_urls(self, mocker, repos, repo_infos, api):
@@ -834,7 +833,7 @@ class TestVerifySettings:
     def test_empty_token_raises_bad_credentials(
         self, happy_github, monkeypatch, api
     ):
-        with pytest.raises(exception.BadCredentials) as exc_info:
+        with pytest.raises(plug.BadCredentials) as exc_info:
             github_plugin.GitHubAPI.verify_settings(
                 USER, ORG_NAME, BASE_URL, ""
             )
@@ -842,7 +841,7 @@ class TestVerifySettings:
         assert "token is empty" in str(exc_info.value)
 
     def test_incorrect_info_raises_not_found_error(self, github_bad_info, api):
-        with pytest.raises(exception.NotFoundError):
+        with pytest.raises(plug.NotFoundError):
             github_plugin.GitHubAPI.verify_settings(
                 USER, ORG_NAME, BASE_URL, TOKEN
             )
@@ -850,14 +849,14 @@ class TestVerifySettings:
     def test_bad_token_scope_raises(self, happy_github, api):
         type(happy_github).oauth_scopes = PropertyMock(return_value=["repo"])
 
-        with pytest.raises(exception.BadCredentials) as exc_info:
+        with pytest.raises(plug.BadCredentials) as exc_info:
             github_plugin.GitHubAPI.verify_settings(
                 USER, ORG_NAME, BASE_URL, TOKEN
             )
         assert "missing one or more access token scopes" in str(exc_info.value)
 
     def test_not_owner_raises(self, happy_github, organization, api):
-        with pytest.raises(exception.BadCredentials) as exc_info:
+        with pytest.raises(plug.BadCredentials) as exc_info:
             github_plugin.GitHubAPI.verify_settings(
                 NOT_OWNER, ORG_NAME, BASE_URL, TOKEN
             )
@@ -870,7 +869,7 @@ class TestVerifySettings:
         self, happy_github, api
     ):
         happy_github.get_user.side_effect = SERVER_ERROR
-        with pytest.raises(exception.UnexpectedException):
+        with pytest.raises(plug.UnexpectedException):
             api.verify_settings(USER, ORG_NAME, BASE_URL, TOKEN)
 
     def test_none_user_raises(self, happy_github, organization, api):
@@ -880,7 +879,7 @@ class TestVerifySettings:
         """
         happy_github.get_user.side_effect = lambda _: User(login=None)
 
-        with pytest.raises(exception.UnexpectedException) as exc_info:
+        with pytest.raises(plug.UnexpectedException) as exc_info:
             github_plugin.GitHubAPI.verify_settings(
                 USER, ORG_NAME, BASE_URL, TOKEN
             )
@@ -904,7 +903,7 @@ class TestVerifySettings:
             "Possible reasons: unknown",
         ]
 
-        with pytest.raises(exception.UnexpectedException) as exc_info:
+        with pytest.raises(plug.UnexpectedException) as exc_info:
             github_plugin.GitHubAPI.verify_settings(
                 USER, ORG_NAME, BASE_URL, TOKEN
             )
diff --git a/tests/unit_tests/repobee/plugin_tests/test_gitlab.py b/tests/unit_tests/repobee/plugin_tests/test_gitlab.py
index e17876c..2396c63 100644
--- a/tests/unit_tests/repobee/plugin_tests/test_gitlab.py
+++ b/tests/unit_tests/repobee/plugin_tests/test_gitlab.py
@@ -6,7 +6,6 @@ import gitlab
 import repobee_plug as plug
 
 import _repobee
-from _repobee import exception
 
 import constants
 
@@ -302,7 +301,7 @@ class TestInit:
     """Tests for the GitLabAPI constructor."""
 
     def test_raises_api_error_when_target_group_cant_be_found(self):
-        with pytest.raises(exception.NotFoundError):
+        with pytest.raises(plug.NotFoundError):
             _repobee.ext.gitlab.GitLabAPI(BASE_URL, TOKEN, "fake-name")
 
 
@@ -312,16 +311,13 @@ class TestEnsureTeamsAndMembers:
         [
             (
                 gitlab.exceptions.GitlabAuthenticationError(response_code=401),
-                exception.BadCredentials,
+                plug.BadCredentials,
             ),
             (
                 gitlab.exceptions.GitlabGetError(response_code=404),
-                exception.NotFoundError,
-            ),
-            (
-                gitlab.exceptions.GitlabError(response_code=500),
-                exception.APIError,
+                plug.NotFoundError,
             ),
+            (gitlab.exceptions.GitlabError(response_code=500), plug.APIError,),
         ],
     )
     def test_converts_gitlab_errors_to_repobee_errors(
@@ -577,13 +573,13 @@ class TestCreateRepos:
 
 class TestVerifySettings:
     def test_raises_if_token_is_empty(self):
-        with pytest.raises(exception.BadCredentials):
+        with pytest.raises(plug.BadCredentials):
             _repobee.ext.gitlab.GitLabAPI.verify_settings(
                 user=None, org_name=TARGET_GROUP, base_url=BASE_URL, token=""
             )
 
     def test_raises_on_failed_connection(self):
-        with pytest.raises(exception.APIError) as exc_info:
+        with pytest.raises(plug.APIError) as exc_info:
             _repobee.ext.gitlab.GitLabAPI.verify_settings(
                 user=None,
                 org_name=TARGET_GROUP,
@@ -594,7 +590,7 @@ class TestVerifySettings:
         assert "please check the URL" in str(exc_info.value)
 
     def test_raises_on_bad_token(self):
-        with pytest.raises(exception.BadCredentials) as exc_info:
+        with pytest.raises(plug.BadCredentials) as exc_info:
             _repobee.ext.gitlab.GitLabAPI.verify_settings(
                 user=None,
                 org_name=TARGET_GROUP,
@@ -606,7 +602,7 @@ class TestVerifySettings:
 
     def test_raises_if_group_cant_be_found(self):
         non_existing_group = "some-garbage-group"
-        with pytest.raises(exception.NotFoundError) as exc_info:
+        with pytest.raises(plug.NotFoundError) as exc_info:
             _repobee.ext.gitlab.GitLabAPI.verify_settings(
                 user=None,
                 org_name=non_existing_group,
@@ -620,7 +616,7 @@ class TestVerifySettings:
 
     def test_raises_if_master_group_cant_be_found(self):
         non_existing_group = "some-garbage-group"
-        with pytest.raises(exception.NotFoundError) as exc_info:
+        with pytest.raises(plug.NotFoundError) as exc_info:
             _repobee.ext.gitlab.GitLabAPI.verify_settings(
                 user=None,
                 org_name=TARGET_GROUP,
diff --git a/tests/unit_tests/repobee/test_cli.py b/tests/unit_tests/repobee/test_cli.py
index 39a842d..2f6b3f7 100644
--- a/tests/unit_tests/repobee/test_cli.py
+++ b/tests/unit_tests/repobee/test_cli.py
@@ -156,7 +156,7 @@ def parsed_args_all_subparsers(request):
         exception.PushFailedError("some message", 128, b"error", "someurl"),
         exception.CloneFailedError("some message", 128, b"error", "someurl"),
         exception.GitError("some message", 128, b"error"),
-        exception.APIError("some message"),
+        plug.APIError("some message"),
     ],
 )
 def command_all_raise_mock(command_mock, api_class_mock, request):
@@ -526,11 +526,11 @@ class TestBaseParsing:
         """
 
         def raise_(*args, **kwargs):
-            raise exception.NotFoundError("Couldn't find the organization.")
+            raise plug.NotFoundError("Couldn't find the organization.")
 
         api_class_mock.side_effect = raise_
 
-        with pytest.raises(exception.NotFoundError) as exc_info:
+        with pytest.raises(plug.NotFoundError) as exc_info:
             _repobee.cli.parsing.handle_args(
                 [
                     *repobee_plug.cli.CoreCommand.repos.setup.as_name_tuple(),
@@ -546,11 +546,11 @@ class TestBaseParsing:
 
     def test_raises_on_bad_credentials(self, api_class_mock, students_file):
         def raise_(*args, **kwargs):
-            raise exception.BadCredentials("bad credentials")
+            raise plug.BadCredentials("bad credentials")
 
         api_class_mock.side_effect = raise_
 
-        with pytest.raises(exception.BadCredentials) as exc_info:
+        with pytest.raises(plug.BadCredentials) as exc_info:
             _repobee.cli.parsing.handle_args(
                 [
                     *repobee_plug.cli.CoreCommand.repos.setup.as_name_tuple(),
@@ -564,13 +564,13 @@ class TestBaseParsing:
 
     def test_raises_on_invalid_base_url(self, api_class_mock, students_file):
         def raise_(*args, **kwargs):
-            raise exception.ServiceNotFoundError(
+            raise plug.ServiceNotFoundError(
                 "GitHub service could not be found, check the url"
             )
 
         api_class_mock.side_effect = raise_
 
-        with pytest.raises(exception.ServiceNotFoundError) as exc_info:
+        with pytest.raises(plug.ServiceNotFoundError) as exc_info:
             _repobee.cli.parsing.handle_args(
                 [
                     *repobee_plug.cli.CoreCommand.repos.setup.as_name_tuple(),

pip install -e .[TEST]
parse_log_pytest
requirements.txt
pytest>=5.0.0 pytest-cov>=2.6.1 pytest-mock codecov bandit>=1.6.2 flake8>=3.8.3 black pytest
apt-get update apt-get install -y gcc
3.8
requirements/app.txt requirements/test.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
bandit==1.7.10
black==24.8.0
certifi==2025.1.31
cffi==1.17.1
charset-normalizer==3.4.1
click==8.1.8
codecov==2.1.13
colored==1.4.4
coverage==7.6.1
cryptography==44.0.2
daiquiri==3.2.5.1
Deprecated==1.2.18
exceptiongroup==1.2.2
flake8==7.1.2
idna==3.10
iniconfig==2.1.0
markdown-it-py==3.0.0
mccabe==0.7.0
mdurl==0.1.2
mypy-extensions==1.0.0
packaging==24.2
pathspec==0.12.1
pbr==6.1.1
platformdirs==4.3.6
pluggy==0.13.1
pycodestyle==2.12.1
pycparser==2.22
pyflakes==3.2.0
PyGithub==2.6.1
Pygments==2.19.1
PyJWT==2.9.0
PyNaCl==1.5.0
pytest==7.4.4
pytest-cov==5.0.0
pytest-mock==3.14.0
python-gitlab==2.4.0
python-json-logger==3.3.0
PyYAML==6.0.2
-e git+https://github.com/repobee/repobee.git@2d8eecab831a65edf4f838e8ee47a8b687b24406#egg=repobee
requests==2.32.3
rich==14.0.0
stevedore==5.3.0
tomli==2.2.1
typing_extensions==4.13.0
urllib3==2.2.3
wrapt==1.17.2

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
  - 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=3.0.16=h5eee18b_0
  - pip=24.2=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - bandit==1.7.10
      - black==24.8.0
      - certifi==2025.1.31
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - click==8.1.8
      - codecov==2.1.13
      - colored==1.4.4
      - coverage==7.6.1
      - cryptography==44.0.2
      - daiquiri==3.2.5.1
      - deprecated==1.2.18
      - exceptiongroup==1.2.2
      - flake8==7.1.2
      - idna==3.10
      - iniconfig==2.1.0
      - markdown-it-py==3.0.0
      - mccabe==0.7.0
      - mdurl==0.1.2
      - mypy-extensions==1.0.0
      - packaging==24.2
      - pathspec==0.12.1
      - pbr==6.1.1
      - platformdirs==4.3.6
      - pluggy==0.13.1
      - pycodestyle==2.12.1
      - pycparser==2.22
      - pyflakes==3.2.0
      - pygithub==2.6.1
      - pygments==2.19.1
      - pyjwt==2.9.0
      - pynacl==1.5.0
      - pytest==7.4.4
      - pytest-cov==5.0.0
      - pytest-mock==3.14.0
      - python-gitlab==2.4.0
      - python-json-logger==3.3.0
      - pyyaml==6.0.2
      - repobee==3.0.0a3
      - requests==2.32.3
      - rich==14.0.0
      - stevedore==5.3.0
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - urllib3==2.2.3
      - wrapt==1.17.2
prefix: /opt/conda/envs/repobee

tests/unit_tests/repobee/plugin_tests/test_github.py::TestInit::test_raises_on_empty_user_arg tests/unit_tests/repobee/plugin_tests/test_github.py::TestInit::test_raises_when_url_is_bad[https://github.com] tests/unit_tests/repobee/plugin_tests/test_github.py::TestInit::test_raises_when_url_is_bad[https://some_enterprise_host] tests/unit_tests/repobee/plugin_tests/test_github.py::TestInit::test_accepts_valid_urls[https://api.github.com] tests/unit_tests/repobee/plugin_tests/test_github.py::TestInit::test_accepts_valid_urls[https://some_enterprise_host/api/v3] tests/unit_tests/repobee/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_no_previous_teams tests/unit_tests/repobee/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_all_teams_exist_but_without_members tests/unit_tests/repobee/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_raises_on_non_422_exception[unexpected_exc0] tests/unit_tests/repobee/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_raises_on_non_422_exception[unexpected_exc1] tests/unit_tests/repobee/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_raises_on_non_422_exception[unexpected_exc2] tests/unit_tests/repobee/plugin_tests/test_github.py::TestEnsureTeamsAndMembers::test_running_twice_has_no_ill_effects tests/unit_tests/repobee/plugin_tests/test_github.py::TestCreateRepos::test_creates_correct_repos tests/unit_tests/repobee/plugin_tests/test_github.py::TestCreateRepos::test_skips_existing_repos tests/unit_tests/repobee/plugin_tests/test_github.py::TestCreateRepos::test_raises_on_unexpected_error[unexpected_exception0] tests/unit_tests/repobee/plugin_tests/test_github.py::TestCreateRepos::test_raises_on_unexpected_error[unexpected_exception1] tests/unit_tests/repobee/plugin_tests/test_github.py::TestCreateRepos::test_raises_on_unexpected_error[unexpected_exception2] tests/unit_tests/repobee/plugin_tests/test_github.py::TestCreateRepos::test_returns_all_urls tests/unit_tests/repobee/plugin_tests/test_github.py::TestCreateRepos::test_create_repos_without_team_id tests/unit_tests/repobee/plugin_tests/test_github.py::TestGetRepoUrls::test_with_token_and_user tests/unit_tests/repobee/plugin_tests/test_github.py::TestGetRepoUrls::test_with_students tests/unit_tests/repobee/plugin_tests/test_github.py::TestOpenIssue::test_on_existing_repos tests/unit_tests/repobee/plugin_tests/test_github.py::TestOpenIssue::test_on_some_non_existing_repos tests/unit_tests/repobee/plugin_tests/test_github.py::TestOpenIssue::test_no_crash_when_no_repos_are_found tests/unit_tests/repobee/plugin_tests/test_github.py::TestCloseIssue::test_closes_correct_issues tests/unit_tests/repobee/plugin_tests/test_github.py::TestCloseIssue::test_no_crash_if_no_repos_found tests/unit_tests/repobee/plugin_tests/test_github.py::TestCloseIssue::test_no_crash_if_no_issues_found tests/unit_tests/repobee/plugin_tests/test_github.py::TestGetIssues::test_get_all_open_issues tests/unit_tests/repobee/plugin_tests/test_github.py::TestGetIssues::test_get_all_closed_issues tests/unit_tests/repobee/plugin_tests/test_github.py::TestGetIssues::test_get_issues_when_one_repo_doesnt_exist tests/unit_tests/repobee/plugin_tests/test_github.py::TestGetIssues::test_get_open_issues_by_regex tests/unit_tests/repobee/plugin_tests/test_github.py::TestAddReposToReviewTeams::test_with_default_issue tests/unit_tests/repobee/plugin_tests/test_github.py::TestAddReposToTeams::test_happy_path tests/unit_tests/repobee/plugin_tests/test_github.py::TestDeleteTeams::test_delete_non_existing_teams_does_not_crash tests/unit_tests/repobee/plugin_tests/test_github.py::TestDeleteTeams::test_delete_existing_teams tests/unit_tests/repobee/plugin_tests/test_github.py::TestVerifySettings::test_happy_path tests/unit_tests/repobee/plugin_tests/test_github.py::TestVerifySettings::test_empty_token_raises_bad_credentials tests/unit_tests/repobee/plugin_tests/test_github.py::TestVerifySettings::test_incorrect_info_raises_not_found_error[get_user] tests/unit_tests/repobee/plugin_tests/test_github.py::TestVerifySettings::test_incorrect_info_raises_not_found_error[get_organization] tests/unit_tests/repobee/plugin_tests/test_github.py::TestVerifySettings::test_bad_token_scope_raises tests/unit_tests/repobee/plugin_tests/test_github.py::TestVerifySettings::test_not_owner_raises tests/unit_tests/repobee/plugin_tests/test_github.py::TestVerifySettings::test_raises_unexpected_exception_on_unexpected_status tests/unit_tests/repobee/plugin_tests/test_github.py::TestVerifySettings::test_none_user_raises tests/unit_tests/repobee/plugin_tests/test_github.py::TestVerifySettings::test_mismatching_user_login_raises tests/unit_tests/repobee/plugin_tests/test_github.py::TestGetPeerReviewProgress::test_nothing_returns tests/unit_tests/repobee/plugin_tests/test_github.py::TestGetPeerReviewProgress::test_with_review_teams_but_no_repos tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestInit::test_raises_api_error_when_target_group_cant_be_found tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_converts_gitlab_errors_to_repobee_errors[raised_error0-BadCredentials] tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_converts_gitlab_errors_to_repobee_errors[raised_error1-NotFoundError] tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_converts_gitlab_errors_to_repobee_errors[raised_error2-APIError] tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_with_no_pre_existing_groups tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_with_multi_student_groups tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_run_twice tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_respects_permission tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestGetRepoUrls::test_get_master_repo_urls tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestGetRepoUrls::test_get_master_repo_urls_in_master_group tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestGetRepoUrls::test_get_student_repo_urls tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestCreateRepos::test_run_once_for_valid_repos tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestCreateRepos::test_run_twice_for_valid_repos tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_if_token_is_empty tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_on_failed_connection tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_on_bad_token tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_if_group_cant_be_found tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_if_master_group_cant_be_found tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestVerifySettings::test_happy_path tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestDeleteTeams::test_delete_teams_that_dont_exist tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestDeleteTeams::test_delete_all_existing_teams tests/unit_tests/repobee/plugin_tests/test_gitlab.py::TestDeleteTeams::test_delete_some_existing_teams tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_writes_hook_results_correctly[action0-_repobee.command.clone_repos] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_writes_hook_results_correctly[action1-_repobee.command.list_issues] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_does_not_write_hook_results_if_there_are_none tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers0] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers1] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers2] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers3] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers4] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers5] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers6] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers7] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers8] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers9] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers10] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers11] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers12] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_setup_student_repos_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_update_student_repos_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_create_teams_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_open_issue_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_close_issue_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_migrate_repos_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_clone_repos_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_verify_settings_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_verify_settings_called_with_master_org_name tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action0] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action1] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action2] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action3] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action4] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action5] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action6] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action7] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action8] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action9] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action10] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action11] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action12] tests/unit_tests/repobee/test_cli.py::test_create_parser_for_docs tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_show_all_opts_true_shows_configured_args tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_show_all_opts_false_hides_configured_args tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_invalid_org tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_bad_credentials tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_invalid_base_url tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_master_org_overrides_target_org_for_master_repos[action0] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_master_org_overrides_target_org_for_master_repos[action1] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_master_org_name_defaults_to_org_name[action0] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_master_org_name_defaults_to_org_name[action1] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_token_env_variable_picked_up[action0] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_token_env_variable_picked_up[action1] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_token_cli_arg_picked_up[action0] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_token_cli_arg_picked_up[action1] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[http://some_enterprise_host/api/v3] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[ftp://some_enterprise_host/api/v3] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[some_enterprise_host/api/v3] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_correctly_parses_create_teams tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parser_listing_students[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parser_listing_students[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parser_student_file[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parser_student_file[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[issues tests/unit_tests/repobee/test_cli.py::TestConfig::test_full_config[action0-extra_args0] tests/unit_tests/repobee/test_cli.py::TestConfig::test_full_config[action1-extra_args1] tests/unit_tests/repobee/test_cli.py::TestConfig::test_full_config[action2-extra_args2] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[--bu] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[-u] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[--sf] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[-o] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[--mo] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[--bu] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[-u] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[--sf] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[-o] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[--mo] tests/unit_tests/repobee/test_cli.py::TestSetupAndUpdateParsers::test_happy_path[action0] tests/unit_tests/repobee/test_cli.py::TestSetupAndUpdateParsers::test_happy_path[action1] tests/unit_tests/repobee/test_cli.py::TestSetupAndUpdateParsers::test_finds_local_repo[action0] tests/unit_tests/repobee/test_cli.py::TestSetupAndUpdateParsers::test_finds_local_repo[action1] tests/unit_tests/repobee/test_cli.py::TestMigrateParser::test_happy_path tests/unit_tests/repobee/test_cli.py::TestVerifyParser::test_happy_path tests/unit_tests/repobee/test_cli.py::TestCloneParser::test_happy_path tests/unit_tests/repobee/test_cli.py::TestShowConfigParser::test_happy_path
src/repobee_plug/_exceptions.py src/repobee_plug/_apimeta.py src/_repobee/exception.py src/_repobee/cli/parsing.py src/_repobee/ext/defaults/github.py src/_repobee/ext/gitlab.py src/repobee_plug/__init__.py
src/repobee_plug/_exceptions.py src/repobee_plug/_apimeta.py src/_repobee/exception.py src/_repobee/cli/parsing.py src/_repobee/ext/defaults/github.py src/_repobee/ext/gitlab.py src/repobee_plug/__init__.py