Repository: repobee/repobee

Issue:
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.

Discussion:
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).


Patch:
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()]


Test patch:
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",