Repository: slarse/labelbot

Issue:
Function for labeling an issue with allowed labels
It should do everything (delegating to other functions for subtasks).

* Fetch the `.allowed-labels` file and parse the allowed labels #12
    - Abort if there is no `.allowed-labels` file
    - Maybe set an `allowed-labels-missing` label on the issue or something?
* Fetch the issue body and parse the requested labels #15 
* Based on the allowed labels, determine a set `new` of labels that should be set
    - Maybe set a `bad-label-request` label on an issue if one or more requested labels are not allowed?
* Fetch the set of labels `current` that are already on the issue.
* Set the labels `current + new` on the issue.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/slarse/labelbot/pull/26?src=pr&el=h1) Report
> Merging [#26](https://codecov.io/gh/slarse/labelbot/pull/26?src=pr&el=desc) into [master](https://codecov.io/gh/slarse/labelbot/commit/f41ded54f0647bc4dacfd0c92bb829f958c21bba?src=pr&el=desc) will **decrease** coverage by `0.58%`.
> The diff coverage is `20.68%`.

[![Impacted file tree graph](https://codecov.io/gh/slarse/labelbot/pull/26/graphs/tree.svg?width=650&token=vwkCZPYZ7N&height=150&src=pr)](https://codecov.io/gh/slarse/labelbot/pull/26?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #26      +/-   ##
==========================================
- Coverage   65.93%   65.34%   -0.59%     
==========================================
  Files           4        4              
  Lines          91      101      +10     
  Branches        2        3       +1     
==========================================
+ Hits           60       66       +6     
- Misses         31       35       +4
```


| [Impacted Files](https://codecov.io/gh/slarse/labelbot/pull/26?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [labelbot/bot.py](https://codecov.io/gh/slarse/labelbot/pull/26/diff?src=pr&el=tree#diff-bGFiZWxib3QvYm90LnB5) | `0% <0%> (ø)` | :arrow_up: |
| [labelbot/github\_api.py](https://codecov.io/gh/slarse/labelbot/pull/26/diff?src=pr&el=tree#diff-bGFiZWxib3QvZ2l0aHViX2FwaS5weQ==) | `100% <100%> (ø)` | :arrow_up: |

------

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


Patch:
diff --git a/labelbot/bot.py b/labelbot/bot.py
index b9cc5e3..34c6916 100644
--- a/labelbot/bot.py
+++ b/labelbot/bot.py
@@ -4,39 +4,39 @@ import python_jwt
 import os
 import boto3
 import botocore
-import labelbot.auth as auth
-import labelbot.github_api as github_api
-
-
-
-
-def get_pem(bucket_name, key):
-    """Reads key from s3"""
-    s3 = boto3.resource('s3')
-    s3.Bucket(bucket_name).download_file(key, '/tmp/key.pem')
-    with open('/tmp/key.pem', "rb") as f:
-        pem = f.read()
-    return pem
+from labelbot import auth
+from labelbot import github_api
+from labelbot import parse
 
 
 def lambda_handler(event, context):
-    # TODO implement
-    
     body = json.loads(event["body"])
-    installation_id = body['installation']['id']
-    owner = body['repository']['owner']['login']
-    repo = body['repository']['name']
-    number = body['issue']['number']
-    issue_url = body['issue']['url']
-    issue_body = body['issue']['body']
+    installation_id = body["installation"]["id"]
+    owner = body["repository"]["owner"]["login"]
+    repo = body["repository"]["name"]
+    issue_nr = body["issue"]["number"]
+    issue_body = body["issue"]["body"]
+    current_labels = [label["name"] for label in body["issue"]["labels"]]
 
-    app_id = int(os.environ['APP_ID'])
-    bucket_name = os.environ['BUCKET_NAME']
-    bucket_key = os.environ['BUCKET_KEY']
-    pem = files.get_pem(bucket_name, bucket_key)
+    app_id = int(os.environ["APP_ID"])
+    bucket_name = os.environ["BUCKET_NAME"]
+    bucket_key = os.environ["BUCKET_KEY"]
+    pem = get_pem(bucket_name, bucket_key)
 
+    jwt_token = auth.generate_jwt_token(pem, app_id)
+    access_token = auth.generate_installation_access_token(jwt_token, installation_id)
 
+    success = github_api.set_allowed_labels(
+        owner, repo, issue_nr, issue_body, current_labels, access_token
+    )
 
-    return {"statusCode": 200, "body": json.dumps("temp")}
+    return {"statusCode": 200 if success else 403, "body": json.dumps("temp")}
 
 
+def get_pem(bucket_name, key):
+    """Reads key from s3"""
+    s3 = boto3.resource("s3")
+    s3.Bucket(bucket_name).download_file(key, "/tmp/key.pem")
+    with open("/tmp/key.pem", "rb") as f:
+        pem = f.read()
+    return pem
diff --git a/labelbot/github_api.py b/labelbot/github_api.py
index dff6bc4..d1a052b 100644
--- a/labelbot/github_api.py
+++ b/labelbot/github_api.py
@@ -9,6 +9,8 @@ import base64
 from typing import Sequence, List
 import requests
 
+from labelbot import parse
+
 BASE_URL = "https://api.github.com"
 ALLOWED_LABELS_FILE = ".allowed-labels"
 
@@ -31,6 +33,32 @@ def _allowed_labels_url(owner, repo):
     return f"{BASE_URL}/repos/{owner}/{repo}/contents/{ALLOWED_LABELS_FILE}"
 
 
+def set_allowed_labels(
+    owner: str,
+    repo: str,
+    issue_nr: int,
+    issue_body: str,
+    current_labels: List[str],
+    access_token: str,
+) -> bool:
+    """Set the current labels plus any requested labels in the issue body that
+    are also allowed by the .allowed-labels file.
+
+    Args:
+        owner: User/Organization that owns the repo.
+        repo: Name of the repo.
+        issue_nr: Number of the issue.
+        access_token: An installation access token for the repo.
+
+    """
+    allowed_labels = parse.parse_allowed_labels(
+        get_file_contents(owner, repo, ALLOWED_LABELS_FILE, access_token)
+    )
+    wanted_labels = parse.parse_wanted_labels(issue_body)
+    labels_to_set = set(current_labels) | (set(allowed_labels) & set(wanted_labels))
+    return set_labels(labels_to_set, owner, repo, issue_nr, access_token)
+
+
 def set_labels(
     labels: Sequence[str], owner: str, repo: str, issue_nr: int, access_token: str
 ) -> bool:


Test patch:
diff --git a/tests/test_github_api.py b/tests/test_github_api.py
index 4b09282..8ae56c5 100644
--- a/tests/test_github_api.py
+++ b/tests/test_github_api.py
@@ -1,10 +1,12 @@
 import json
+import contextlib
+from unittest.mock import patch
+
+import responses
 import pytest
 
 from labelbot import github_api
 
-import responses
-
 OWNER = "someone"
 REPO = "best-repo"
 ISSUE_NR = 231
@@ -114,6 +116,49 @@ class TestGetFileContents:
         assert f"could not fetch {github_api.ALLOWED_LABELS_FILE}" in str(exc_info)
 
 
+class TestSetAllowedLabels:
+    """Tests for set_allowed_labels."""
+
+    @contextlib.contextmanager
+    def _mocked_apis(self, allowed_labels, wanted_labels, set_labels_result):
+        """All apis mocked out returning the specified values. Yields the
+        set_labels mock.
+        """
+        with patch(
+            "labelbot.github_api.parse.parse_allowed_labels",
+            autospec=True,
+            return_value=allowed_labels,
+        ), patch(
+            "labelbot.github_api.parse.parse_wanted_labels",
+            autospec=True,
+            return_value=wanted_labels,
+        ), patch(
+            "labelbot.github_api.get_file_contents", autospec=True, return_value=""
+        ):
+            with patch(
+                "labelbot.github_api.set_labels", autospec=True, return_value=True
+            ) as set_labels:
+                yield set_labels
+
+    def test_happy_path(self):
+        current_labels = ["enhancement"]
+        allowed_labels = ["bug", "feature request"]
+        wanted_labels = ["feature request", "help", "feature"]
+        expected_labels = {"enhancement", "feature request"}
+
+        with self._mocked_apis(
+            allowed_labels, wanted_labels, set_labels_result=True
+        ) as set_labels:
+            res = github_api.set_allowed_labels(
+                OWNER, REPO, ISSUE_NR, "", current_labels, ACCESS_TOKEN
+            )
+
+        assert res
+        set_labels.assert_called_once_with(
+            expected_labels, OWNER, REPO, ISSUE_NR, ACCESS_TOKEN
+        )
+
+
 ALLOWED_LABELS_CONTENT = "# labels that the labelbot are allowed to set\n# at the behest of users without read-access\nhelp\nbug\nfeature request\n"
 
 JSON_ALLOWED_LABELS_PAYLOAD = json.dumps(