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

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

```diff
@@            Coverage Diff             @@
##           master     #428      +/-   ##
==========================================
- Coverage   91.41%   86.64%   -4.78%     
==========================================
  Files          28       28              
  Lines        1818     1812       -6     
  Branches      337      338       +1     
==========================================
- Hits         1662     1570      -92     
- Misses        124      208      +84     
- Partials       32       34       +2     
```


| [Impacted Files](https://codecov.io/gh/repobee/repobee/pull/428?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/\_repobee/exception.py](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4Y2VwdGlvbi5weQ==) | `100.00% <ø> (ø)` | |
| [src/\_repobee/ext/gitlab.py](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9naXRsYWIucHk=) | `63.86% <83.33%> (-28.42%)` | :arrow_down: |
| [src/\_repobee/git.py](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2dpdC5weQ==) | `96.62% <0.00%> (-3.38%)` | :arrow_down: |
| [src/\_repobee/command/repos.py](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NvbW1hbmQvcmVwb3MucHk=) | `92.68% <0.00%> (-2.44%)` | :arrow_down: |
| [src/\_repobee/cli/parsing.py](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NsaS9wYXJzaW5nLnB5) | `91.27% <0.00%> (-2.33%)` | :arrow_down: |
| [src/\_repobee/ext/query.py](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9xdWVyeS5weQ==) | `48.48% <0.00%> (-1.52%)` | :arrow_down: |
| [src/\_repobee/ext/github.py](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9naXRodWIucHk=) | `90.29% <0.00%> (-0.11%)` | :arrow_down: |
| [src/\_repobee/plugin.py](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL3BsdWdpbi5weQ==) | `91.50% <0.00%> (-0.08%)` | :arrow_down: |
| [src/\_repobee/ext/configwizard.py](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9jb25maWd3aXphcmQucHk=) | `100.00% <0.00%> (ø)` | |
| ... and [1 more](https://codecov.io/gh/repobee/repobee/pull/428/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/repobee/repobee/pull/428?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/428?src=pr&el=footer). Last update [bfb4792...fa69d54](https://codecov.io/gh/repobee/repobee/pull/428?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/src/_repobee/exception.py b/src/_repobee/exception.py
index a6d24ab..f1ed370 100644
--- a/src/_repobee/exception.py
+++ b/src/_repobee/exception.py
@@ -45,7 +45,7 @@ class APIError(RepoBeeException):
 
 
 class NotFoundError(APIError):
-    """An exception raised when the API responds with a 404."""
+    """An exception raised when a platform API fails to find a resource."""
 
 
 class ServiceNotFoundError(APIError):
diff --git a/src/_repobee/ext/gitlab.py b/src/_repobee/ext/gitlab.py
index 2b8b406..8bad8fd 100644
--- a/src/_repobee/ext/gitlab.py
+++ b/src/_repobee/ext/gitlab.py
@@ -91,6 +91,8 @@ def _try_api_request(ignore_statuses: Optional[Iterable[int]] = None):
             ) from e
         else:
             raise exception.APIError(str(e), status=e.response_code) from e
+    except (exception.RepoBeeException, plug.PlugError):
+        raise
     except Exception as e:
         raise exception.UnexpectedException(
             "a {} occured unexpectedly: {}".format(type(e).__name__, str(e))
@@ -198,11 +200,16 @@ class GitLabAPI(plug.API):
         self._add_to_team(missing_members, team, permission)
 
     def _get_organization(self, org_name):
-        return [
+        matches = [
             g
             for g in self._gitlab.groups.list(search=org_name)
             if g.path == org_name
-        ][0]
+        ]
+
+        if not matches:
+            raise exception.NotFoundError(org_name, status=404)
+
+        return matches[0]
 
     def _get_members(self, group):
         return [self._User(m.id, m.username) for m in group.members.list()]

GitLab plugin crashes unexpectedly when specifying non-existent master/target group
If the target or master orgs don't actually exist, the GitLab plugin crashes with an IndexError. Trace:

```
[ERROR] a IndexError occured unexpectedly: list index out of range
[INFO] exception was raised before pre-initialization was complete. This is usually due to incorrect settings. Try running the `verify-settings` command and see if the problem can be resolved. If all fails, please open an issue at https://github.com/repobee/repobee/issues/new and supply the stack trace below.
[ERROR] Critical exception
Traceback (most recent call last):
  File "/home/slarse/.local/lib/python3.8/site-packages/_repobee/ext/gitlab.py", line 80, in _try_api_request
    yield
  File "/home/slarse/.local/lib/python3.8/site-packages/_repobee/ext/gitlab.py", line 116, in __init__
    self._group = self._get_organization(self._group_name)
  File "/home/slarse/.local/lib/python3.8/site-packages/_repobee/ext/gitlab.py", line 201, in _get_organization
    return [
IndexError: list index out of range

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/slarse/.local/lib/python3.8/site-packages/_repobee/main.py", line 63, in main
    parsed_args, api = _repobee.cli.parsing.handle_args(
  File "/home/slarse/.local/lib/python3.8/site-packages/_repobee/cli/parsing.py", line 67, in handle_args
    processed_args, api = _process_args(args, ext_commands)
  File "/home/slarse/.local/lib/python3.8/site-packages/_repobee/cli/parsing.py", line 149, in _process_args
    api = _connect_to_api(args.base_url, args.token, args.org_name, args.user)
  File "/home/slarse/.local/lib/python3.8/site-packages/_repobee/cli/parsing.py", line 290, in _connect_to_api
    return api_class(**kwargs)
  File "/home/slarse/.local/lib/python3.8/site-packages/_repobee/ext/gitlab.py", line 116, in __init__
    self._group = self._get_organization(self._group_name)
  File "/usr/lib/python3.8/contextlib.py", line 131, in __exit__
    self.gen.throw(type, value, traceback)
  File "/home/slarse/.local/lib/python3.8/site-packages/_repobee/ext/gitlab.py", line 95, in _try_api_request
    raise exception.UnexpectedException(
_repobee.exception.UnexpectedException: a IndexError occured unexpectedly: list index out of range
```

`verify-settings` correctly identifies the problem, but the crash shouldn't occur like this.
diff --git a/tests/unit_tests/plugin_tests/test_gitlab.py b/tests/unit_tests/plugin_tests/test_gitlab.py
index e815597..e17876c 100644
--- a/tests/unit_tests/plugin_tests/test_gitlab.py
+++ b/tests/unit_tests/plugin_tests/test_gitlab.py
@@ -298,6 +298,14 @@ def raise_(error):
     return inner
 
 
+class TestInit:
+    """Tests for the GitLabAPI constructor."""
+
+    def test_raises_api_error_when_target_group_cant_be_found(self):
+        with pytest.raises(exception.NotFoundError):
+            _repobee.ext.gitlab.GitLabAPI(BASE_URL, TOKEN, "fake-name")
+
+
 class TestEnsureTeamsAndMembers:
     @pytest.mark.parametrize(
         "raised_error,expected_error",

pip install -e .[TEST]
parse_log_pytest
requirements.txt
pytest>=4.0.0 pytest-cov>=2.6.1 pytest-mock codecov pytest
apt-get update apt-get install -y gcc
3.7
requirements/app.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.15.0
python-json-logger==3.0.1
-e git+https://github.com/repobee/repobee.git@e09f89e67e43e9ee6cd24ace38bbb9313207166d#egg=repobee
repobee-plug==0.12.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.15.0
      - python-json-logger==3.0.1
      - repobee==2.4.0
      - repobee-plug==0.12.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/plugin_tests/test_gitlab.py::TestInit::test_raises_api_error_when_target_group_cant_be_found
tests/unit_tests/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_converts_gitlab_errors_to_repobee_errors[raised_error0-BadCredentials] tests/unit_tests/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_converts_gitlab_errors_to_repobee_errors[raised_error1-NotFoundError] tests/unit_tests/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_converts_gitlab_errors_to_repobee_errors[raised_error2-APIError] tests/unit_tests/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_with_no_pre_existing_groups tests/unit_tests/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_with_multi_student_groups tests/unit_tests/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_run_twice tests/unit_tests/plugin_tests/test_gitlab.py::TestEnsureTeamsAndMembers::test_respects_permission tests/unit_tests/plugin_tests/test_gitlab.py::TestGetRepoUrls::test_get_master_repo_urls tests/unit_tests/plugin_tests/test_gitlab.py::TestGetRepoUrls::test_get_master_repo_urls_in_master_group tests/unit_tests/plugin_tests/test_gitlab.py::TestGetRepoUrls::test_get_student_repo_urls tests/unit_tests/plugin_tests/test_gitlab.py::TestCreateRepos::test_run_once_for_valid_repos tests/unit_tests/plugin_tests/test_gitlab.py::TestCreateRepos::test_run_twice_for_valid_repos tests/unit_tests/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_if_token_is_empty tests/unit_tests/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_on_failed_connection tests/unit_tests/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_on_bad_token tests/unit_tests/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_if_group_cant_be_found tests/unit_tests/plugin_tests/test_gitlab.py::TestVerifySettings::test_raises_if_master_group_cant_be_found tests/unit_tests/plugin_tests/test_gitlab.py::TestVerifySettings::test_happy_path tests/unit_tests/plugin_tests/test_gitlab.py::TestDeleteTeams::test_delete_teams_that_dont_exist tests/unit_tests/plugin_tests/test_gitlab.py::TestDeleteTeams::test_delete_all_existing_teams tests/unit_tests/plugin_tests/test_gitlab.py::TestDeleteTeams::test_delete_some_existing_teams
src/_repobee/ext/gitlab.py src/_repobee/exception.py
src/_repobee/ext/gitlab.py src/_repobee/exception.py