[Tracker-discuss] [issue589] Add GitHub PR to b.p.o issues using GitHub webhooks (depends on issue586)

Maciej Szulik metatracker at psf.upfronthosting.co.za
Wed Dec 21 16:14:29 EST 2016


Maciej Szulik added the comment:

Ups, my previous patch didn't have pullrequestmalformed* data files included. Updated.

_______________________________________________________
PSF Meta Tracker <metatracker at psf.upfronthosting.co.za>
<http://psf.upfronthosting.co.za/roundup/meta/issue589>
_______________________________________________________
-------------- next part --------------
diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
@@ -22,6 +22,7 @@
 from roundup.mailer import Mailer, MessageSendError, encode_quopri
 from roundup.cgi import accept_language
 from roundup import xmlrpc
+from roundup.pull_request import GitHubHandler
 
 from roundup.anypy.cookie_ import CookieError, BaseCookie, SimpleCookie, \
     get_cookie_date
@@ -378,6 +379,8 @@
         try:
             if self.path == 'xmlrpc':
                 self.handle_xmlrpc()
+            elif self.path == 'pull_request':
+                self.handle_pull_request()
             else:
                 self.inner_main()
         finally:
@@ -385,6 +388,34 @@
                 self.db.close()
 
 
+    def handle_pull_request(self):
+        # Set the charset and language, since other parts of
+        # Roundup may depend upon that.
+        self.determine_charset()
+        self.determine_language()
+        # Open the database as the correct user.
+        self.determine_user()
+        self.check_anonymous_access()
+
+        try:
+            handler = GitHubHandler(self)
+            handler.dispatch()
+        except Unauthorised, err:
+            self.response_code = 403
+            self.write(err)
+        except UnsupportedMediaType, err:
+            self.response_code = 415
+            self.write(err)
+        except MethodNotAllowed, err:
+            self.response_code = 405
+            self.write(err)
+        except Reject, err:
+            self.response_code = 400
+            self.write(err)
+        else:
+            self.write("")
+
+
     def handle_xmlrpc(self):
         if self.env.get('CONTENT_TYPE') != 'text/xml':
             self.write("This is the endpoint of Roundup <a href='" +
@@ -1165,7 +1196,7 @@
         if name is None:
             name = 'home'
 
-        tplname = name     
+        tplname = name
         if view:
             tplname = '%s.%s' % (name, view)
 
diff --git a/roundup/exceptions.py b/roundup/exceptions.py
--- a/roundup/exceptions.py
+++ b/roundup/exceptions.py
@@ -21,6 +21,12 @@
     """
     pass
 
+class UnsupportedMediaType(Exception):
+    pass
+
+class MethodNotAllowed(Exception):
+    pass
+
 class UsageError(ValueError):
     pass
 
diff --git a/roundup/pull_request.py b/roundup/pull_request.py
new file mode 100644
--- /dev/null
+++ b/roundup/pull_request.py
@@ -0,0 +1,222 @@
+from roundup.exceptions import *
+
+import hashlib
+import hmac
+import json
+import re
+import os
+import logging
+
+
+if hasattr(hmac, 'compare_digest'):
+    compare_digest = hmac.compare_digest
+else:
+    def compare_digest(a, b):
+        return a == b
+
+url_re = re.compile(r'https://github.com/python/cpython/pull/(?P<number>\d+)')
+issue_re = re.compile(r'fixes\s+(?:bpo(\d+))(?:,\s*bpo(\d+))*', re.I)
+issue_id_re = re.compile(r'bpo(\d+)', re.I)
+
+class GitHubHandler:
+    """
+    GitHubHandler is responsible for parsing and serving all events coming
+    from GitHub. Details about GitHub webhooks can be found at:
+    https://developer.github.com/webhooks/
+    """
+
+    def __init__(self, client):
+        self.db = client.db
+        self.request = client.request
+        self.form = client.form
+        self.env = client.env
+
+    def dispatch(self):
+        try:
+            self.verify_request()
+            self.validate_webhook_secret()
+            self.extract()
+        except (Unauthorised, MethodNotAllowed, UnsupportedMediaType, Reject), err:
+            raise
+        except Exception, err:
+            logging.error(err, exc_info=True)
+            raise Reject()
+
+    def extract(self):
+        """
+        This method is responsible for extracting information from GitHub event
+        and decide what to do with it more. Currently it knows how to handle
+        pull requests and comments.
+        """
+        event = self.get_event()
+        # we're only handling PR-related events, all others just return OK, but
+        # no action is being performed on the bpo side
+        if event in ('pull_request', 'pull_request_review_comment'):
+            data = json.loads(self.form.value)
+            handler = PullRequest(self.db, data)
+            handler.dispatch()
+
+    def validate_webhook_secret(self):
+        """
+        Validates request signature against SECRET_KEY environment variable.
+        This verification is based on HMAC hex digest calculated from the sent
+        payload. The value of SECRET_KEY should be exactly the same as the one
+        configured in GitHub webhook secret field.
+        """
+        key = os.environ.get('SECRET_KEY')
+        if key is None:
+            logging.error('Missing SECRET_KEY environment variable set!')
+            raise Reject()
+        data = str(self.form.value)
+        signature = 'sha1=' + hmac.new(key, data,
+                                       hashlib.sha1).hexdigest()
+        header_signature = self.request.headers.get('X-Hub-Signature', '')
+        if not compare_digest(signature, header_signature):
+            raise Unauthorised()
+
+    def verify_request(self):
+        """
+        Verifies if request contains expected method, content type and event.
+        """
+        method = self.env.get('REQUEST_METHOD', None)
+        if method != 'POST':
+            raise MethodNotAllowed()
+        content_type = self.env.get('CONTENT_TYPE', None)
+        if content_type != 'application/json':
+            raise UnsupportedMediaType()
+        if self.get_event() is None:
+            raise Reject()
+
+    def get_event(self):
+        """
+        Extracts github event from header field.
+        """
+        return self.request.headers.get('X-GitHub-Event', None)
+
+
+class Event:
+    """
+    Event is base class for all GitHub events.
+    """
+
+    def handle_create(self, prid, title, issue_ids):
+        """
+        Helper method for linking GitHub pull request with an issue.
+        """
+        # search for an existing issue first
+        issue_exists = len(self.db.issue.filter(None, {'id': issue_ids})) == len(issue_ids)
+        if not issue_exists:
+            return
+        for issue_id in issue_ids:
+            # verify if this PR is already linked
+            prs = self.db.issue.get(issue_id, 'pull_requests')
+            if set(prs).intersection(self.db.pull_request.filter(None, {'number': prid})):
+                continue
+            # create a new link
+            if title:
+                newpr = self.db.pull_request.create(number=prid, title=title)
+            else:
+                newpr = self.db.pull_request.create(number=prid)
+            prs.append(newpr)
+            self.db.issue.set(issue_id, pull_requests=prs)
+            self.db.commit()
+
+    def handle_update(self, prid, title, issue_ids):
+        """
+        Helper method for updating GitHub pull request.
+        """
+        # update handles only title changes, for now
+        if not title:
+            return
+        # search for an existing issue first
+        issue_exists = len(self.db.issue.filter(None, {'id': issue_ids})) == len(issue_ids)
+        if not issue_exists:
+            return
+        for issue_id in issue_ids:
+            # verify if this PR is already linked
+            prs = self.db.issue.get(issue_id, 'pull_requests')
+            if set(prs).intersection(self.db.pull_request.filter(None, {'number': prid})):
+                for pr in prs:
+                    probj = self.db.pull_request.getnode(pr)
+                    # check if the number match and title did change, and only then update
+                    if probj.number == prid and probj.title != title:
+                        self.db.pull_request.set(probj.id, title=title)
+                        self.db.commit()
+            else:
+                self.handle_create(prid, title, [issue_id])
+
+    def get_issue_ids(self):
+        raise NotImplementedError
+
+    def get_pr_details(self):
+        raise NotImplementedError
+
+
+class PullRequest(Event):
+    """
+    Class responsible for handling pull request events.
+    """
+
+    def __init__(self, db, data):
+        self.db = db
+        self.data = data
+
+    def dispatch(self):
+        github_username = self.get_github_username()
+        user_id = self.db.user.filter(None, {'github': github_username})
+        # TODO set bpobot as userid when none is found
+        if len(user_id) == 1:
+            # TODO what if multiple bpo users have the same github username?
+            username = self.db.user.get(user_id[0], 'username')
+            self.db.setCurrentUser(username)
+        action = self.data.get('action', '').encode('utf-8')
+        issue_ids = self.get_issue_ids()
+        if issue_ids is None:
+            return
+        prid, title = self.get_pr_details()
+        if action in ('opened', 'created'):
+            self.handle_create(prid, title, issue_ids)
+        elif action == 'edited':
+            self.handle_update(prid, title, issue_ids)
+
+    def get_issue_ids(self):
+        """
+        Extract issue IDs from pull request comments.
+        """
+        pull_request = self.data.get('pull_request')
+        if pull_request is None:
+            raise Reject()
+        title = pull_request.get('title', '').encode('utf-8')
+        body = pull_request.get('body', '').encode('utf-8')
+        match = issue_re.search(body) or issue_re.search(title)
+        if match:
+            return issue_id_re.findall(match.group())
+        # no issue id found
+        create_issue = os.environ.get('CREATE_ISSUE', False)
+        if create_issue:
+            # TODO we should fill in the issue with more details
+            return [self.db.issue.create(title=title)]
+        return None
+
+    def get_pr_details(self):
+        """
+        Extract pull request number and title.
+        """
+        pull_request = self.data.get('pull_request')
+        if pull_request is None:
+            raise Reject()
+        number = pull_request.get('number', None)
+        if number is None:
+            raise Reject()
+        title = pull_request.get('title', '').encode('utf-8')
+        return str(number), title
+
+
+    def get_github_username(self):
+        """
+        Extract github username from a pull request.
+        """
+        pull_request = self.data.get('pull_request')
+        if pull_request is None:
+            raise Reject()
+        return pull_request.get('user', {}).get('login', '').encode('utf-8')
diff --git a/share/roundup/templates/classic/detectors/pull_request.py b/share/roundup/templates/classic/detectors/pull_request.py
new file mode 100644
--- /dev/null
+++ b/share/roundup/templates/classic/detectors/pull_request.py
@@ -0,0 +1,69 @@
+# Auditor for GitHub URLs
+# Check if it is a valid GitHub Pull Request URL and extract PR number
+
+import re
+
+
+repo_number_re = re.compile(r'^#?(?P<number>\d+)$')
+url_re = re.compile(r'(https?:\\)?github\.com/python/cpython/pull/(?P<number>\d+)')
+
+def validate_pr_uniqueness(db, cl, nodeid, newvalues):
+    """
+    Verifies if newly added PR isn't already attached to an issue.
+    This process is a 2-level action, first a pull_request object is created, which
+    goes through validate_pr_number to extract the PR number in case an URL is passed,
+    only then we validate PR uniqueness within a single issue.
+    """
+    newprs = set(newvalues.get('pull_requests',()))
+    if not newprs:
+        return
+    oldprs = set()
+    if nodeid:
+        # if this is an existing issue, get the list of existing prs
+        oldprs = set(db.issue.get(nodeid, 'pull_requests'))
+        newprs -= oldprs
+    try:
+        # get the newly created PR number
+        number = db.pull_request.get(newprs.pop(), 'number')
+    except KeyError:
+        return
+    # and compare with those already attached to an issue
+    for oldpr in oldprs:
+        oldnumber = db.pull_request.get(oldpr, 'number')
+        if number == oldnumber:
+            raise ValueError("GitHub PR already added to issue")
+
+def validate_pr_number(db, cl, nodeid, newvalues):
+    try:
+        number = extract_number(newvalues['number'])
+        if number:
+            newvalues['number'] = number
+    except KeyError:
+        pass
+
+def extract_number(input):
+    """
+    Extracts PR number from the following forms:
+    - #number
+    - number
+    - full url
+    and returns its number.
+    """
+    # try matching just the number
+    repo_number_match = repo_number_re.search(input)
+    if repo_number_match:
+        return repo_number_match.group('number')
+    # fallback to parsing the entire url
+    url_match = url_re.search(input)
+    if url_match:
+        return url_match.group('number')
+    # if nothing else raise error
+    raise ValueError("Unknown PR format, acceptable formats are: "
+                     "full github URL, #pr_number, pr_number")
+
+
+def init(db):
+    db.issue.audit('create', validate_pr_uniqueness)
+    db.issue.audit('set', validate_pr_uniqueness)
+    db.pull_request.audit('create', validate_pr_number)
+    db.pull_request.audit('set', validate_pr_number)
diff --git a/share/roundup/templates/classic/schema.py b/share/roundup/templates/classic/schema.py
--- a/share/roundup/templates/classic/schema.py
+++ b/share/roundup/templates/classic/schema.py
@@ -45,7 +45,8 @@
                 alternate_addresses=String(),
                 queries=Multilink('query'),
                 roles=String(),     # comma-separated string of Role names
-                timezone=String())
+                timezone=String(),
+                github=String())
 user.setkey("username")
 db.security.addPermission(name='Register', klass='user',
                           description='User is allowed to register new user')
@@ -65,6 +66,12 @@
 file = FileClass(db, "file",
                 name=String())
 
+pull_request = Class(db, "pull_request",
+                     number=String(),
+                     title=String(),
+                     )
+pull_request.setlabelprop('id')
+
 # IssueClass automatically gets these properties in addition to the Class ones:
 #   title = String()
 #   messages = Multilink("msg")
@@ -75,7 +82,8 @@
                 assignedto=Link("user"),
                 keyword=Multilink("keyword"),
                 priority=Link("priority"),
-                status=Link("status"))
+                status=Link("status"),
+                pull_requests=Multilink('pull_request'))
 
 #
 # TRACKER SECURITY SETTINGS
@@ -92,7 +100,7 @@
 
 # Assign the access and edit Permissions for issue, file and message
 # to regular users now
-for cl in 'issue', 'file', 'msg', 'keyword':
+for cl in 'issue', 'file', 'msg', 'keyword', 'pull_request':
     db.security.addPermissionToRole('User', 'View', cl)
     db.security.addPermissionToRole('User', 'Edit', cl)
     db.security.addPermissionToRole('User', 'Create', cl)
@@ -101,7 +109,7 @@
 
 # May users view other user information? Comment these lines out
 # if you don't want them to
-p = db.security.addPermission(name='View', klass='user', 
+p = db.security.addPermission(name='View', klass='user',
     properties=('id', 'organisation', 'phone', 'realname', 'timezone',
     'username'))
 db.security.addPermissionToRole('User', p)
@@ -167,7 +175,7 @@
 
 # Allow anonymous users access to view issues (and the related, linked
 # information)
-for cl in 'issue', 'file', 'msg', 'keyword', 'priority', 'status':
+for cl in 'issue', 'file', 'msg', 'keyword', 'priority', 'status', 'pull_request':
     db.security.addPermissionToRole('Anonymous', 'View', cl)
 
 # [OPTIONAL]
diff --git a/test/data/no-github-event-header.txt b/test/data/no-github-event-header.txt
new file mode 100644
--- /dev/null
+++ b/test/data/no-github-event-header.txt
@@ -0,0 +1,7 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 2f3784dc.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/9667e0c
+X-GitHub-Delivery: 94a54100-20e0-11e6-8756-bf8bf3b91449
+content-type: application/json
+X-Hub-Signature: sha1=ffda9754ba91e91ec414c2308bd25cd151c69960
diff --git a/test/data/non-json-body.txt b/test/data/non-json-body.txt
new file mode 100644
--- /dev/null
+++ b/test/data/non-json-body.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 2f3784dc.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/9667e0c
+X-GitHub-Event: pull_request
+X-GitHub-Delivery: 94a54100-20e0-11e6-8756-bf8bf3b91449
+content-type: application/x-www-form-urlencoded
+X-Hub-Signature: sha1=06b5c520cacd3cc8828dbf71622659527e0bfc50
+Content-Length: 11
+
+hello:world
diff --git a/test/data/pingevent.txt b/test/data/pingevent.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pingevent.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 2f3784dc.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/9667e0c
+X-GitHub-Event: ping
+X-GitHub-Delivery: 94a54100-20e0-11e6-8756-bf8bf3b91449
+content-type: application/json
+X-Hub-Signature: sha1=95df171f2551526018324c407deed0b4b58a4126
+Content-Length: 6007
+
+{"zen":"Mind your words, they are important.","hook_id":8488302,"hook":{"type":"Repository","id":8488302,"name":"web","active":true,"events":["issue_comment","pull_request"],"config":{"content_type":"json","insecure_ssl":"0","secret":"********","url":"http://90dedf29.ngrok.io/python-dev/github_pullrequest_url"},"updated_at":"2016-05-23T12:19:22Z","created_at":"2016-05-23T12:19:22Z","url":"https://api.github.com/repos/python/cpython/hooks/8488302","test_url":"https://api.github.com/repos/python/cpython/hooks/8488302/test","ping_url":"https://api.github.com/repos/python/cpython/hooks/8488302/pings","last_response":{"code":null,"status":"unused","message":null}},"repository":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/u
 sers/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.g
 ithub.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/pyth
 on/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https:/
 /api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-05-23T06:00:19Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":335381,"stargazers_count":0,"watchers_count":0,"language":"P
 ython","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"},"sender":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.gi
 thub.com/users/python/received_events","type":"User","site_admin":false}}
diff --git a/test/data/pullrequestcommentevent.txt b/test/data/pullrequestcommentevent.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pullrequestcommentevent.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 2f3784dc.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/9667e0c
+X-GitHub-Event: pull_request_review_comment
+X-GitHub-Delivery: c6d12700-270e-11e6-86c5-8e9c9c898175
+content-type: application/json
+X-Hub-Signature: sha1=123a3b255c0411447e4493ed98851801b88a3882
+Content-Length: 20671
+
+{"action":"created","comment":{"url":"https://api.github.com/repos/python/cpython/pulls/comments/92908721","pull_request_review_id":13434573,"id":92908721,"diff_hunk":"@@-97,6+97,7@@\n'THREAD':('SELECTED',),\n'UID':('SELECTED',),\n'UNSUBSCRIBE':('AUTH','SELECTED'),","path":"Lib/imaplib.py","position":3,"original_position":3,"commit_id":"eda90b2ac4c72d4b95ee452f56af0d18ddcd449e","original_commit_id":"eda90b2ac4c72d4b95ee452f56af0d18ddcd449e","user":{"login":"soltysh","id":576341,"avatar_url":"https://avatars.githubusercontent.com/u/576341?v=3","gravatar_id":"","url":"https://api.github.com/users/soltysh","html_url":"https://github.com/soltysh","followers_url":"https://api.github.com/users/soltysh/followers","following_url":"https://api.github.com/users/soltysh/following{/other_user}","gists_url":"https://api.github.com/users/soltysh/gists{/gist_id}","starred_url":"https://api.github.com/users/soltysh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/so
 ltysh/subscriptions","organizations_url":"https://api.github.com/users/soltysh/orgs","repos_url":"https://api.github.com/users/soltysh/repos","events_url":"https://api.github.com/users/soltysh/events{/privacy}","received_events_url":"https://api.github.com/users/soltysh/received_events","type":"User","site_admin":false},"body":"Reviewcomment","created_at":"2016-12-17T00:39:39Z","updated_at":"2016-12-17T00:39:39Z","html_url":"https://github.com/python/cpython/pull/4#discussion_r92908721","pull_request_url":"https://api.github.com/repos/python/cpython/pulls/4","_links":{"self":{"href":"https://api.github.com/repos/python/cpython/pulls/comments/92908721"},"html":{"href":"https://github.com/python/cpython/pull/4#discussion_r92908721"},"pull_request":{"href":"https://api.github.com/repos/python/cpython/pulls/4"}}},"pull_request":{"url":"https://api.github.com/repos/python/cpython/pulls/4","id":98423480,"html_url":"https://github.com/python/cpython/pull/4","diff_url":"https://gith
 ub.com/python/cpython/pull/4.diff","patch_url":"https://github.com/python/cpython/pull/4.patch","issue_url":"https://api.github.com/repos/python/cpython/issues/4","number":4,"state":"open","locked":false,"title":"New pull request","user":{"login":"soltysh","id":576341,"avatar_url":"https://avatars.githubusercontent.com/u/576341?v=3","gravatar_id":"","url":"https://api.github.com/users/soltysh","html_url":"https://github.com/soltysh","followers_url":"https://api.github.com/users/soltysh/followers","following_url":"https://api.github.com/users/soltysh/following{/other_user}","gists_url":"https://api.github.com/users/soltysh/gists{/gist_id}","starred_url":"https://api.github.com/users/soltysh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soltysh/subscriptions","organizations_url":"https://api.github.com/users/soltysh/orgs","repos_url":"https://api.github.com/users/soltysh/repos","events_url":"https://api.github.com/users/soltysh/events{/privacy}","re
 ceived_events_url":"https://api.github.com/users/soltysh/received_events","type":"User","site_admin":false},"body":"Fixes bpo1","created_at":"2016-12-17T00:14:24Z","updated_at":"2016-12-17T00:39:39Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5df75e237e52517314f9e704724c6e528ab537a5","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/python/cpython/pulls/4/commits","review_comments_url":"https://api.github.com/repos/python/cpython/pulls/4/comments","review_comment_url":"https://api.github.com/repos/python/cpython/pulls/comments{/number}","comments_url":"https://api.github.com/repos/python/cpython/issues/4/comments","statuses_url":"https://api.github.com/repos/python/cpython/statuses/eda90b2ac4c72d4b95ee452f56af0d18ddcd449e","head":{"label":"soltysh:issue3","ref":"issue3","sha":"eda90b2ac4c72d4b95ee452f56af0d18ddcd449e","user":{"login":"soltysh","id":576341,"avatar_url":"https://avatars.githubusercontent.com/u/576341?v=3","g
 ravatar_id":"","url":"https://api.github.com/users/soltysh","html_url":"https://github.com/soltysh","followers_url":"https://api.github.com/users/soltysh/followers","following_url":"https://api.github.com/users/soltysh/following{/other_user}","gists_url":"https://api.github.com/users/soltysh/gists{/gist_id}","starred_url":"https://api.github.com/users/soltysh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soltysh/subscriptions","organizations_url":"https://api.github.com/users/soltysh/orgs","repos_url":"https://api.github.com/users/soltysh/repos","events_url":"https://api.github.com/users/soltysh/events{/privacy}","received_events_url":"https://api.github.com/users/soltysh/received_events","type":"User","site_admin":false},"repo":{"id":76687672,"name":"cpython","full_name":"python/cpython","owner":{"login":"soltysh","id":576341,"avatar_url":"https://avatars.githubusercontent.com/u/576341?v=3","gravatar_id":"","url":"https://api.github.com/users/sol
 tysh","html_url":"https://github.com/soltysh","followers_url":"https://api.github.com/users/soltysh/followers","following_url":"https://api.github.com/users/soltysh/following{/other_user}","gists_url":"https://api.github.com/users/soltysh/gists{/gist_id}","starred_url":"https://api.github.com/users/soltysh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soltysh/subscriptions","organizations_url":"https://api.github.com/users/soltysh/orgs","repos_url":"https://api.github.com/users/soltysh/repos","events_url":"https://api.github.com/users/soltysh/events{/privacy}","received_events_url":"https://api.github.com/users/soltysh/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-officialread-onlymirroroftheCPythonMercurialrepository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"http
 s://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.co
 m/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"htt
 ps://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-12-16T21:45:11Z","updated_at":"2016-12-16T21:45:35Z","pushed_at":"2016-12-17T00:39:21Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/c
 python","homepage":"","size":270358,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"soltysh:master","ref":"master","sha":"807a125c1ce83feb4cedd8aac6e774d80b98bb42","user":{"login":"soltysh","id":576341,"avatar_url":"https://avatars.githubusercontent.com/u/576341?v=3","gravatar_id":"","url":"https://api.github.com/users/soltysh","html_url":"https://github.com/soltysh","followers_url":"https://api.github.com/users/soltysh/followers","following_url":"https://api.github.com/users/soltysh/following{/other_user}","gists_url":"https://api.github.com/users/soltysh/gists{/gist_id}","starred_url":"https://api.github.com/users/soltysh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soltysh/subscriptions","organizations_url":"https://api.
 github.com/users/soltysh/orgs","repos_url":"https://api.github.com/users/soltysh/repos","events_url":"https://api.github.com/users/soltysh/events{/privacy}","received_events_url":"https://api.github.com/users/soltysh/received_events","type":"User","site_admin":false},"repo":{"id":76687672,"name":"cpython","full_name":"python/cpython","owner":{"login":"soltysh","id":576341,"avatar_url":"https://avatars.githubusercontent.com/u/576341?v=3","gravatar_id":"","url":"https://api.github.com/users/soltysh","html_url":"https://github.com/soltysh","followers_url":"https://api.github.com/users/soltysh/followers","following_url":"https://api.github.com/users/soltysh/following{/other_user}","gists_url":"https://api.github.com/users/soltysh/gists{/gist_id}","starred_url":"https://api.github.com/users/soltysh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soltysh/subscriptions","organizations_url":"https://api.github.com/users/soltysh/orgs","repos_url":"https://ap
 i.github.com/users/soltysh/repos","events_url":"https://api.github.com/users/soltysh/events{/privacy}","received_events_url":"https://api.github.com/users/soltysh/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-officialread-onlymirroroftheCPythonMercurialrepository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython
 /assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","
 git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"http
 s://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-12-16T21:45:11Z","updated_at":"2016-12-16T21:45:35Z","pushed_at":"2016-12-17T00:39:21Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":270358,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/python/cpython/pulls/4"},"html":{"href":"https://github.com/python/cpython/pull/4"},"issue":{"href":"https://api.github.com/repos/python/cpytho
 n/issues/4"},"comments":{"href":"https://api.github.com/repos/python/cpython/issues/4/comments"},"review_comments":{"href":"https://api.github.com/repos/python/cpython/pulls/4/comments"},"review_comment":{"href":"https://api.github.com/repos/python/cpython/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/python/cpython/pulls/4/commits"},"statuses":{"href":"https://api.github.com/repos/python/cpython/statuses/eda90b2ac4c72d4b95ee452f56af0d18ddcd449e"}}},"repository":{"id":76687672,"name":"cpython","full_name":"python/cpython","owner":{"login":"soltysh","id":576341,"avatar_url":"https://avatars.githubusercontent.com/u/576341?v=3","gravatar_id":"","url":"https://api.github.com/users/soltysh","html_url":"https://github.com/soltysh","followers_url":"https://api.github.com/users/soltysh/followers","following_url":"https://api.github.com/users/soltysh/following{/other_user}","gists_url":"https://api.github.com/users/soltysh/gists{/gist_id}","starred_url":"h
 ttps://api.github.com/users/soltysh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soltysh/subscriptions","organizations_url":"https://api.github.com/users/soltysh/orgs","repos_url":"https://api.github.com/users/soltysh/repos","events_url":"https://api.github.com/users/soltysh/events{/privacy}","received_events_url":"https://api.github.com/users/soltysh/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-officialread-onlymirroroftheCPythonMercurialrepository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/h
 ooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscrib
 ers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pul
 ls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-12-16T21:45:11Z","updated_at":"2016-12-16T21:45:35Z","pushed_at":"2016-12-17T00:39:21Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":270358,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"defau
 lt_branch":"master"},"sender":{"login":"soltysh","id":576341,"avatar_url":"https://avatars.githubusercontent.com/u/576341?v=3","gravatar_id":"","url":"https://api.github.com/users/soltysh","html_url":"https://github.com/soltysh","followers_url":"https://api.github.com/users/soltysh/followers","following_url":"https://api.github.com/users/soltysh/following{/other_user}","gists_url":"https://api.github.com/users/soltysh/gists{/gist_id}","starred_url":"https://api.github.com/users/soltysh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soltysh/subscriptions","organizations_url":"https://api.github.com/users/soltysh/orgs","repos_url":"https://api.github.com/users/soltysh/repos","events_url":"https://api.github.com/users/soltysh/events{/privacy}","received_events_url":"https://api.github.com/users/soltysh/received_events","type":"User","site_admin":false}}
diff --git a/test/data/pullrequestevent.txt b/test/data/pullrequestevent.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pullrequestevent.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 3ab1787e.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/98ea3cc
+X-GitHub-Event: pull_request
+X-GitHub-Delivery: 3d4b5180-5c89-11e6-88fd-1aa99d941991
+content-type: application/json
+X-Hub-Signature: sha1=ea33bfa58c828af071cc1c20d247879e0ba02bfe
+Content-Length: 18982
+
+{"action":"opened","number":11,"pull_request":{"url":"https://api.github.com/repos/python/cpython/pulls/11","id":80363608,"html_url":"https://github.com/python/cpython/pull/11","diff_url":"https://github.com/python/cpython/pull/11.diff","patch_url":"https://github.com/python/cpython/pull/11.patch","issue_url":"https://api.github.com/repos/python/cpython/issues/11","number":11,"state":"open","locked":false,"title":"fixes bpo1","user":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions",
 "organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-08-07T10:25:19Z","updated_at":"2016-08-07T10:25:19Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/python/cpython/pulls/11/commits","review_comments_url":"https://api.github.com/repos/python/cpython/pulls/11/comments","review_comment_url":"https://api.github.com/repos/python/cpython/pulls/comments{/number}","comments_url":"https://api.github.com/repos/python/cpython/issues/11/comments","statuses_url":"https://api.github.com/repos/python/cpython/statuses/170f8a1f0ffbb733de656cd4a332ecea0c526d53","head":{"label":"python:master","ref":"master","sha":"170f8
 a1f0ffbb733de656cd4a332ecea0c526d53","user":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"repo":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","i
 d":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"u
 rl":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpyth
 on/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/
 repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-08-07T10:25:19Z","git_url":"git://github.com/python/cpython.git","ssh_u
 rl":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205407,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"python:revert-1-test","ref":"revert-1-test","sha":"886390c8473a1d52e07d20fd97176a56c153b9a1","user":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users
 /python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"repo":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api
 .github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/numbe
 r}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"htt
 ps://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/num
 ber}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-08-07T10:25:19Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205407,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/python/cpytho
 n/pulls/11"},"html":{"href":"https://github.com/python/cpython/pull/11"},"issue":{"href":"https://api.github.com/repos/python/cpython/issues/11"},"comments":{"href":"https://api.github.com/repos/python/cpython/issues/11/comments"},"review_comments":{"href":"https://api.github.com/repos/python/cpython/pulls/11/comments"},"review_comment":{"href":"https://api.github.com/repos/python/cpython/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/python/cpython/pulls/11/commits"},"statuses":{"href":"https://api.github.com/repos/python/cpython/statuses/170f8a1f0ffbb733de656cd4a332ecea0c526d53"}},"merged":false,"mergeable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"commits":1,"additions":2,"deletions":0,"changed_files":1},"repository":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":
 "https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cp
 ython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuse
 s_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpyth
 on/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-08-07T10:25:19Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url"
 :"https://github.com/python/cpython","homepage":"","size":205407,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"},"sender":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/p
 ython/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false}}
diff --git a/test/data/pullrequestevent1.txt b/test/data/pullrequestevent1.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pullrequestevent1.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 2f3784dc.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/9667e0c
+X-GitHub-Event: pull_request
+X-GitHub-Delivery: 251f6480-3640-11e6-97d9-1d57a5a69e84
+content-type: application/json
+X-Hub-Signature: sha1=ce64c1178a662c30659d9ef7ea99b1ea77a1bbf0
+Content-Length: 19029
+
+{"action":"opened","number":3,"pull_request":{"url":"https://api.github.com/repos/python/cpython/pulls/3","id":74368420,"html_url":"https://github.com/python/cpython/pull/3","diff_url":"https://github.com/python/cpython/pull/3.diff","patch_url":"https://github.com/python/cpython/pull/3.patch","issue_url":"https://api.github.com/repos/python/cpython/issues/3","number":3,"state":"open","locked":false,"title":"Created using GitHub API","user":{"login":"AnishShah","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/AnishShah","html_url":"https://github.com/AnishShah","followers_url":"https://api.github.com/users/AnishShah/followers","following_url":"https://api.github.com/users/AnishShah/following{/other_user}","gists_url":"https://api.github.com/users/AnishShah/gists{/gist_id}","starred_url":"https://api.github.com/users/AnishShah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/
 users/AnishShah/subscriptions","organizations_url":"https://api.github.com/users/AnishShah/orgs","repos_url":"https://api.github.com/users/AnishShah/repos","events_url":"https://api.github.com/users/AnishShah/events{/privacy}","received_events_url":"https://api.github.com/users/AnishShah/received_events","type":"User","site_admin":false},"body":"Fixes bpo1","created_at":"2016-06-19T17:06:21Z","updated_at":"2016-06-19T17:06:21Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/python/cpython/pulls/3/commits","review_comments_url":"https://api.github.com/repos/python/cpython/pulls/3/comments","review_comment_url":"https://api.github.com/repos/python/cpython/pulls/comments{/number}","comments_url":"https://api.github.com/repos/python/cpython/issues/3/comments","statuses_url":"https://api.github.com/repos/python/cpython/statuses/17f854a76947193fadde6837d7d92123f47d7e16","head":{"label":"python:
 test","ref":"test","sha":"17f854a76947193fadde6837d7d92123f47d7e16","user":{"login":"AnishShah","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/AnishShah","html_url":"https://github.com/AnishShah","followers_url":"https://api.github.com/users/AnishShah/followers","following_url":"https://api.github.com/users/AnishShah/following{/other_user}","gists_url":"https://api.github.com/users/AnishShah/gists{/gist_id}","starred_url":"https://api.github.com/users/AnishShah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AnishShah/subscriptions","organizations_url":"https://api.github.com/users/AnishShah/orgs","repos_url":"https://api.github.com/users/AnishShah/repos","events_url":"https://api.github.com/users/AnishShah/events{/privacy}","received_events_url":"https://api.github.com/users/AnishShah/received_events","type":"User","site_admin":false},"repo":{"id":59477756,"name":
 "cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official r
 ead-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/ta
 gs{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/pyth
 on/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-06-1
 5T19:43:02Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205466,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"python:master","ref":"master","sha":"170f8a1f0ffbb733de656cd4a332ecea0c526d53","user":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/
 gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"repo":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/sta
 rred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api
 .github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repo
 s/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https:
 //api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-06-15T19:43:02Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205466,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self
 ":{"href":"https://api.github.com/repos/python/cpython/pulls/3"},"html":{"href":"https://github.com/python/cpython/pull/3"},"issue":{"href":"https://api.github.com/repos/python/cpython/issues/3"},"comments":{"href":"https://api.github.com/repos/python/cpython/issues/3/comments"},"review_comments":{"href":"https://api.github.com/repos/python/cpython/pulls/3/comments"},"review_comment":{"href":"https://api.github.com/repos/python/cpython/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/python/cpython/pulls/3/commits"},"statuses":{"href":"https://api.github.com/repos/python/cpython/statuses/17f854a76947193fadde6837d7d92123f47d7e16"}},"merged":false,"mergeable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"commits":1,"additions":2,"deletions":0,"changed_files":1},"repository":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercon
 tent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","for
 ks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com
 /repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_
 url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-06-15T19:43:02Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"h
 ttps://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205466,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"},"sender":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/or
 gs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false}}
diff --git a/test/data/pullrequestevent2.txt b/test/data/pullrequestevent2.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pullrequestevent2.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 8900ffb8.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/0269732
+X-GitHub-Event: pull_request
+X-GitHub-Delivery: 60418b80-2199-11e6-98bc-9ed2b8e584c9
+content-type: application/json
+X-Hub-Signature: sha1=af2367e9d82d817e41266ab4aa16af009e4a52f9
+Content-Length: 18873
+
+{"action":"opened","number":1,"pull_request":{"url":"https://api.github.com/repos/python/cpython/pulls/1","id":71194519,"html_url":"https://github.com/python/cpython/pull/1","diff_url":"https://github.com/python/cpython/pull/1.diff","patch_url":"https://github.com/python/cpython/pull/1.patch","issue_url":"https://api.github.com/repos/python/cpython/issues/1","number":1,"state":"open","locked":false,"title":"Test","user":{"login":"AnishShah","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/AnishShah","html_url":"https://github.com/AnishShah","followers_url":"https://api.github.com/AnishShah/followers","following_url":"https://api.github.com/AnishShah/following{/other_user}","gists_url":"https://api.github.com/AnishShah/gists{/gist_id}","starred_url":"https://api.github.com/AnishShah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/AnishShah/subscriptions","organizations_url":"http
 s://api.github.com/AnishShah/orgs","repos_url":"https://api.github.com/AnishShah/repos","events_url":"https://api.github.com/AnishShah/events{/privacy}","received_events_url":"https://api.github.com/AnishShah/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-05-24T10:22:11Z","updated_at":"2016-05-24T10:22:11Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/python/cpython/pulls/1/commits","review_comments_url":"https://api.github.com/repos/python/cpython/pulls/1/comments","review_comment_url":"https://api.github.com/repos/python/cpython/pulls/comments{/number}","comments_url":"https://api.github.com/repos/python/cpython/issues/1/comments","statuses_url":"https://api.github.com/repos/python/cpython/statuses/17f854a76947193fadde6837d7d92123f47d7e16","head":{"label":"python:test","ref":"test","sha":"17f854a76947193fadde6837d7d92123f47d7e16","user":{"login":"pytho
 n","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/AnishShah","html_url":"https://github.com/python","followers_url":"https://api.github.com/AnishShah/followers","following_url":"https://api.github.com/AnishShah/following{/other_user}","gists_url":"https://api.github.com/AnishShah/gists{/gist_id}","starred_url":"https://api.github.com/AnishShah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/AnishShah/subscriptions","organizations_url":"https://api.github.com/AnishShah/orgs","repos_url":"https://api.github.com/AnishShah/repos","events_url":"https://api.github.com/AnishShah/events{/privacy}","received_events_url":"https://api.github.com/AnishShah/received_events","type":"User","site_admin":false},"repo":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_i
 d":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/rep
 os/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sh
 a}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/
 python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-05-24T10:19:54Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.gi
 t","svn_url":"https://github.com/python/cpython","homepage":"","size":276517,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"python:master","ref":"master","sha":"b998f589f341718f24013cc79e8358bec03dfde3","user":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions"
 ,"organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"repo":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs",
 "repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.gith
 ub.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/pyth
 on/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,partici
 pating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-05-24T10:19:54Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":276517,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/python/cpython/pulls/1"},"html":{"href":"https://github.com/python/cpython/pull/1"},"issue":{"href":"https://api.git
 hub.com/repos/python/cpython/issues/1"},"comments":{"href":"https://api.github.com/repos/python/cpython/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/python/cpython/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/python/cpython/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/python/cpython/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/python/cpython/statuses/17f854a76947193fadde6837d7d92123f47d7e16"}},"merged":false,"mergeable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"commits":1,"additions":2,"deletions":0,"changed_files":1},"repository":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.git
 hub.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"http
 s://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/r
 epos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url"
 :"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-05-24T10:19:54Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":276517,"stargazers_count":0,"watchers_count":0,"lan
 guage":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"},"sender":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https
 ://api.github.com/users/python/received_events","type":"User","site_admin":false}}
diff --git a/test/data/pullrequestevent3.txt b/test/data/pullrequestevent3.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pullrequestevent3.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 3ab1787e.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/98ea3cc
+X-GitHub-Event: pull_request
+X-GitHub-Delivery: afc32e00-5c97-11e6-8ffa-5bf6b88808be
+content-type: application/json
+X-Hub-Signature: sha1=e99e42e4fb3d8b5148b498b9ae2dd0398bcb8dd5
+Content-Length: 19063
+
+{"action":"opened","number":13,"pull_request":{"url":"https://api.github.com/repos/python/cpython/pulls/13","id":80365455,"html_url":"https://github.com/python/cpython/pull/13","diff_url":"https://github.com/python/cpython/pull/13.diff","patch_url":"https://github.com/python/cpython/pull/13.patch","issue_url":"https://api.github.com/repos/python/cpython/issues/13","number":13,"state":"open","locked":false,"title":"fixes bpo1, bpo2, bpo3","user":{"login":"AnishShah","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/AnishShah","html_url":"https://github.com/AnishShah","followers_url":"https://api.github.com/users/AnishShah/followers","following_url":"https://api.github.com/users/AnishShah/following{/other_user}","gists_url":"https://api.github.com/users/AnishShah/gists{/gist_id}","starred_url":"https://api.github.com/users/AnishShah/starred{/owner}{/repo}","subscriptions_url":"https://api.github
 .com/users/AnishShah/subscriptions","organizations_url":"https://api.github.com/users/AnishShah/orgs","repos_url":"https://api.github.com/users/AnishShah/repos","events_url":"https://api.github.com/users/AnishShah/events{/privacy}","received_events_url":"https://api.github.com/users/AnishShah/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-08-07T12:08:44Z","updated_at":"2016-08-07T12:08:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/python/cpython/pulls/13/commits","review_comments_url":"https://api.github.com/repos/python/cpython/pulls/13/comments","review_comment_url":"https://api.github.com/repos/python/cpython/pulls/comments{/number}","comments_url":"https://api.github.com/repos/python/cpython/issues/13/comments","statuses_url":"https://api.github.com/repos/python/cpython/statuses/170f8a1f0ffbb733de656cd4a332ecea0c526d53","head":{"la
 bel":"python:master","ref":"master","sha":"170f8a1f0ffbb733de656cd4a332ecea0c526d53","user":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/AnishShah","html_url":"https://github.com/AnishShah","followers_url":"https://api.github.com/users/AnishShah/followers","following_url":"https://api.github.com/users/AnishShah/following{/other_user}","gists_url":"https://api.github.com/users/AnishShah/gists{/gist_id}","starred_url":"https://api.github.com/users/AnishShah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AnishShah/subscriptions","organizations_url":"https://api.github.com/users/AnishShah/orgs","repos_url":"https://api.github.com/users/AnishShah/repos","events_url":"https://api.github.com/users/AnishShah/events{/privacy}","received_events_url":"https://api.github.com/users/AnishShah/received_events","type":"User","site_admin":false},"repo":{"id":59
 477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"S
 emi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/
 cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.
 com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_
 at":"2016-08-07T12:07:47Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205407,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"python:revert-1-test","ref":"revert-1-test","sha":"886390c8473a1d52e07d20fd97176a56c153b9a1","user":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.git
 hub.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"repo":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api
 .github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","is
 sue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":
 "https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/numbe
 r}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-08-07T12:07:47Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205407,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch
 ":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/python/cpython/pulls/13"},"html":{"href":"https://github.com/python/cpython/pull/13"},"issue":{"href":"https://api.github.com/repos/python/cpython/issues/13"},"comments":{"href":"https://api.github.com/repos/python/cpython/issues/13/comments"},"review_comments":{"href":"https://api.github.com/repos/python/cpython/pulls/13/comments"},"review_comment":{"href":"https://api.github.com/repos/python/cpython/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/python/cpython/pulls/13/commits"},"statuses":{"href":"https://api.github.com/repos/python/cpython/statuses/170f8a1f0ffbb733de656cd4a332ecea0c526d53"}},"merged":false,"mergeable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"commits":1,"additions":2,"deletions":0,"changed_files":1},"repository":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_u
 rl":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.git
 hub.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","
 trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython
 /compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-08-07T12:07:47Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:
 python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205407,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"},"sender":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"http
 s://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false}}
diff --git a/test/data/pullrequestevent4.txt b/test/data/pullrequestevent4.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pullrequestevent4.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 7a1eec53.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/c80b888
+X-GitHub-Event: pull_request
+X-GitHub-Delivery: 95867d00-5e5c-11e6-97c7-ddc89938af3a
+content-type: application/json
+X-Hub-Signature: sha1=4f865da24d53daf4bb040cd8fe1a31466d9d94ac
+Content-Length: 19080
+
+{"action":"opened","number":14,"pull_request":{"url":"https://api.github.com/repos/python/cpython/pulls/14","id":80667637,"html_url":"https://github.com/python/cpython/pull/14","diff_url":"https://github.com/python/cpython/pull/14.diff","patch_url":"https://github.com/python/cpython/pull/14.patch","issue_url":"https://api.github.com/repos/python/cpython/issues/14","number":14,"state":"open","locked":false,"title":"update .gitignore","user":{"login":"AnishShah","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/AnishShah","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/AnishShah/followers","following_url":"https://api.github.com/users/AnishShah/following{/other_user}","gists_url":"https://api.github.com/users/AnishShah/gists{/gist_id}","starred_url":"https://api.github.com/users/AnishShah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/use
 rs/AnishShah/subscriptions","organizations_url":"https://api.github.com/users/AnishShah/orgs","repos_url":"https://api.github.com/users/AnishShah/repos","events_url":"https://api.github.com/users/AnishShah/events{/privacy}","received_events_url":"https://api.github.com/users/AnishShah/received_events","type":"User","site_admin":false},"body":"fixes bpo1, bpo2, bpo3","created_at":"2016-08-09T18:10:42Z","updated_at":"2016-08-09T18:10:42Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/python/cpython/pulls/14/commits","review_comments_url":"https://api.github.com/repos/python/cpython/pulls/14/comments","review_comment_url":"https://api.github.com/repos/python/cpython/pulls/comments{/number}","comments_url":"https://api.github.com/repos/python/cpython/issues/14/comments","statuses_url":"https://api.github.com/repos/python/cpython/statuses/170f8a1f0ffbb733de656cd4a332ecea0c526d5
 3","head":{"label":"python:master","ref":"master","sha":"170f8a1f0ffbb733de656cd4a332ecea0c526d53","user":{"login":"AnishShah","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/AnishShah","html_url":"https://github.com/AnishShah","followers_url":"https://api.github.com/users/AnishShah/followers","following_url":"https://api.github.com/users/AnishShah/following{/other_user}","gists_url":"https://api.github.com/users/AnishShah/gists{/gist_id}","starred_url":"https://api.github.com/users/AnishShah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AnishShah/subscriptions","organizations_url":"https://api.github.com/users/AnishShah/orgs","repos_url":"https://api.github.com/users/AnishShah/repos","events_url":"https://api.github.com/users/AnishShah/events{/privacy}","received_events_url":"https://api.github.com/users/AnishShah/received_events","type":"User","site_admin":false
 },"repo":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython"
 ,"description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.
 com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"ht
 tps://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11
 :39:38Z","pushed_at":"2016-08-09T18:10:42Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205407,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"python:revert-1-test","ref":"revert-1-test","sha":"886390c8473a1d52e07d20fd97176a56c153b9a1","user":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url"
 :"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"repo":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_
 url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url":"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/c
 python/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","
 subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpy
 thon/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-08-09T18:10:42Z","git_url":"git://github.com/python/cpython.git","ssh_url":"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205407,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":
 0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/python/cpython/pulls/14"},"html":{"href":"https://github.com/python/cpython/pull/14"},"issue":{"href":"https://api.github.com/repos/python/cpython/issues/14"},"comments":{"href":"https://api.github.com/repos/python/cpython/issues/14/comments"},"review_comments":{"href":"https://api.github.com/repos/python/cpython/pulls/14/comments"},"review_comment":{"href":"https://api.github.com/repos/python/cpython/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/python/cpython/pulls/14/commits"},"statuses":{"href":"https://api.github.com/repos/python/cpython/statuses/170f8a1f0ffbb733de656cd4a332ecea0c526d53"}},"merged":false,"mergeable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"commits":1,"additions":2,"deletions":0,"changed_files":1},"repository":{"id":59477756,"name":"cpython","full_name":"python/cpython","owner":{"login":"python","id":
 3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organizations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-official read-only mirror of the CPython Mercurial repository","fork":true,"url"
 :"https://api.github.com/repos/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/python/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/
 git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subscribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/rep
 os/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repos/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":"2016-05-23T11:39:11Z","updated_at":"2016-05-23T11:39:38Z","pushed_at":"2016-08-09T18:10:42Z","git_url":"git://github.com/python/cpython.git","ssh_url"
 :"git at github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":205407,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"},"sender":{"login":"python","id":3175743,"avatar_url":"https://avatars.githubusercontent.com/u/3175743?v=3","gravatar_id":"","url":"https://api.github.com/users/python","html_url":"https://github.com/python","followers_url":"https://api.github.com/users/python/followers","following_url":"https://api.github.com/users/python/following{/other_user}","gists_url":"https://api.github.com/users/python/gists{/gist_id}","starred_url":"https://api.github.com/users/python/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/python/subscriptions","organiz
 ations_url":"https://api.github.com/users/python/orgs","repos_url":"https://api.github.com/users/python/repos","events_url":"https://api.github.com/users/python/events{/privacy}","received_events_url":"https://api.github.com/users/python/received_events","type":"User","site_admin":false}}
diff --git a/test/data/pullrequestmalformed1.txt b/test/data/pullrequestmalformed1.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pullrequestmalformed1.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 3ab1787e.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/98ea3cc
+X-GitHub-Event: pull_request
+X-GitHub-Delivery: 3d4b5180-5c89-11e6-88fd-1aa99d941991
+content-type: application/json
+X-Hub-Signature: sha1=57318827d67d398477a85b161926296eb9ab5be2
+Content-Length: 3
+
+{}
diff --git a/test/data/pullrequestmalformed2.txt b/test/data/pullrequestmalformed2.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pullrequestmalformed2.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 3ab1787e.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/98ea3cc
+X-GitHub-Event: pull_request
+X-GitHub-Delivery: 3d4b5180-5c89-11e6-88fd-1aa99d941991
+content-type: application/json
+X-Hub-Signature: sha1=a25c62779068166ae2f3ddd557e596ba64134a36
+Content-Length: 33
+
+{"action":"opened","number":11}
diff --git a/test/data/pullrequestmalformed3.txt b/test/data/pullrequestmalformed3.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pullrequestmalformed3.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 3ab1787e.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/98ea3cc
+X-GitHub-Event: pull_request
+X-GitHub-Delivery: 3d4b5180-5c89-11e6-88fd-1aa99d941991
+content-type: application/json
+X-Hub-Signature: sha1=a641510115c8e775781cae9d4d288ceafe4c08df
+Content-Length: 51
+
+{"action":"opened","number":11,"pull_request":{}}
diff --git a/test/test_pull_request.py b/test/test_pull_request.py
new file mode 100644
--- /dev/null
+++ b/test/test_pull_request.py
@@ -0,0 +1,231 @@
+import unittest
+import os
+import db_test_base
+import cgi
+from BaseHTTPServer import BaseHTTPRequestHandler
+from StringIO import StringIO
+from roundup.cgi import client
+from roundup.backends import list_backends
+from roundup.pull_request import GitHubHandler
+from roundup.exceptions import *
+
+NEEDS_INSTANCE = 1
+
+
+class HTTPRequest(BaseHTTPRequestHandler):
+    def __init__(self, filename):
+        path = os.path.dirname(os.path.abspath(__file__)) + "/data/" + filename
+        request_file = open(path, 'r')
+        request_text = request_file.read()
+        request_file.close()
+        self.rfile = StringIO(request_text)
+        self.raw_requestline = self.rfile.readline()
+        self.error_code = self.error_message = None
+        self.parse_request()
+
+
+class TestCase(unittest.TestCase):
+
+    backend = None
+
+    def setUp(self):
+        # instance
+        self.dirname = '_test_pull_request'
+        self.instance = db_test_base.setupTracker(self.dirname, self.backend)
+        self.env = {
+            'PATH_INFO': 'http://localhost/pull_request',
+            'HTTP_HOST': 'localhost',
+            'TRACKER_NAME': 'test',
+            'REQUEST_METHOD': 'POST',
+            'CONTENT_TYPE': 'application/json'
+        }
+        os.environ['SECRET_KEY'] = "abcd"
+        os.environ['CREATE_ISSUE'] = 'yes'
+
+    def _make_client(self, filename):
+        request = HTTPRequest(filename)
+        form = cgi.FieldStorage(fp=request.rfile, environ=self.env,
+                                headers=request.headers)
+        dummy_client = client.Client(self.instance, request, self.env, form)
+        dummy_client.opendb("anonymous")
+        self.db = dummy_client.db
+        self.db.issue.create(title="Hello")
+        return dummy_client
+
+    def testMissingSecretKey(self):
+        os.environ.pop('SECRET_KEY')
+        dummy_client = self._make_client("pingevent.txt")
+        with self.assertRaises(Reject) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+
+    def testMissingGitHubEventHeader(self):
+        dummy_client = self._make_client("no-github-event-header.txt")
+        with self.assertRaises(Reject) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+
+    def testNonJSONRequestBody(self):
+        dummy_client = self._make_client("non-json-body.txt")
+        with self.assertRaises(Reject) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+
+    def testSecretKey(self):
+        os.environ['SECRET_KEY'] = "1234"
+        dummy_client = self._make_client("pingevent.txt")
+        with self.assertRaises(Unauthorised) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+
+    def testUnsupportedMediaType(self):
+        dummy_client = self._make_client("pingevent.txt")
+        dummy_client.env['CONTENT_TYPE'] = 'application/xml'
+        with self.assertRaises(UnsupportedMediaType) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+
+    def testMethodNotAllowed(self):
+        dummy_client = self._make_client("pingevent.txt")
+        dummy_client.env['REQUEST_METHOD'] = 'GET'
+        with self.assertRaises(MethodNotAllowed) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+
+    def testPingEvent(self):
+        dummy_client = self._make_client("pingevent.txt")
+        handler = GitHubHandler(dummy_client)
+        handler.dispatch()
+        prs = self.db.issue.get('1', 'pull_requests')
+        self.assertTrue(len(prs) == 0)
+
+    def testPullRequestCommentEvent(self):
+        dummy_client = self._make_client("pullrequestcommentevent.txt")
+        handler = GitHubHandler(dummy_client)
+        handler.dispatch()
+        prs = self.db.issue.get('1', 'pull_requests')
+        self.assertTrue(len(prs) == 1)
+        number = self.db.pull_request.get(prs[0], 'number')
+        self.assertEqual(number, '4')
+        user_id = self.db.pull_request.get(prs[0], 'creator')
+        self.assertEqual(self.db.user.get(user_id, 'username'), 'anonymous')
+
+    def testPullRequestEventMalformed1(self):
+        # empty body
+        dummy_client = self._make_client("pullrequestmalformed1.txt")
+        with self.assertRaises(Reject) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+
+    def testPullRequestEventMalformed2(self):
+        # missing pull_request element
+        dummy_client = self._make_client("pullrequestmalformed2.txt")
+        with self.assertRaises(Reject) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+
+    def testPullRequestEventMalformed3(self):
+        # missing pull_request element
+        dummy_client = self._make_client("pullrequestmalformed3.txt")
+        with self.assertRaises(Reject) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+
+    def testPullRequestEventForTitle(self):
+        # When the title of a PR has string "fixes bpo123"
+        dummy_client = self._make_client("pullrequestevent.txt")
+        handler = GitHubHandler(dummy_client)
+        handler.dispatch()
+        prs = self.db.issue.get('1', 'pull_requests')
+        self.assertTrue(len(prs) == 1)
+        number = self.db.pull_request.get(prs[0], 'number')
+        self.assertEqual(number, '11')
+        user_id = self.db.pull_request.get(prs[0], 'creator')
+        self.assertEqual(self.db.user.get(user_id, 'username'), 'anonymous')
+
+    def testPullRequestEventForBody(self):
+        # When the body of a PR has string "fixes bpo123"
+        dummy_client = self._make_client("pullrequestevent1.txt")
+        self.db.user.create(username="anish.shah", github="AnishShah")
+        handler = GitHubHandler(dummy_client)
+        handler.dispatch()
+        prs = self.db.issue.get('1', 'pull_requests')
+        self.assertTrue(len(prs) == 1)
+        number = self.db.pull_request.get(prs[0], 'number')
+        self.assertEqual(number, '3')
+        title = self.db.pull_request.get(prs[0], 'title')
+        self.assertEqual(title, 'Created using GitHub API')
+        user_id = self.db.pull_request.get(prs[0], 'creator')
+        self.assertEqual(self.db.user.get(user_id, 'github'), 'AnishShah')
+
+    def testPullRequestEventForMultipleIssueReferenceInTitle(self):
+        dummy_client = self._make_client("pullrequestevent3.txt")
+        self.db.issue.create(title="Python")
+        self.db.issue.create(title="CPython")
+        handler = GitHubHandler(dummy_client)
+        handler.dispatch()
+        for i in range(1, 4):
+            prs = self.db.issue.get(str(i), 'pull_requests')
+            self.assertTrue(len(prs) == 1)
+            number = self.db.pull_request.get(prs[0], 'number')
+            self.assertEqual(number, '13')
+            title = self.db.pull_request.get(prs[0], 'title')
+            self.assertEqual(title, 'fixes bpo1, bpo2, bpo3')
+
+    def testPullRequestEventForMultipleIssueReferenceInBody(self):
+        dummy_client = self._make_client("pullrequestevent4.txt")
+        self.db.issue.create(title="Python")
+        self.db.issue.create(title="CPython")
+        handler = GitHubHandler(dummy_client)
+        handler.dispatch()
+        for i in range(1, 4):
+            prs = self.db.issue.get(str(i), 'pull_requests')
+            self.assertTrue(len(prs) == 1)
+            number = self.db.pull_request.get(prs[0], 'number')
+            self.assertEqual(number, '14')
+            title = self.db.pull_request.get(prs[0], 'title')
+            self.assertEqual(title, 'update .gitignore')
+
+    def testPullRequestEventWithoutIssueReference(self):
+        # When no issue is referenced in PR and environment variable is set
+        dummy_client = self._make_client("pullrequestevent2.txt")
+        self.assertEqual(self.db.issue.count(), 1)
+        handler = GitHubHandler(dummy_client)
+        handler.dispatch()
+        self.assertEqual(self.db.issue.count(), 2)
+        user_id = self.db.issue.get('2', 'creator')
+        self.assertEqual(self.db.user.get(user_id, 'username'), 'anonymous')
+
+    def testPullRequestFromGitHubUserWithoutIssueReference(self):
+        # When no issue is referenced in PR and environment variable is set
+        # and Github field of b.p.o user is set
+        dummy_client = self._make_client("pullrequestevent2.txt")
+        self.db.user.create(username="anish.shah", github="AnishShah")
+        self.assertEqual(self.db.issue.count(), 1)
+        handler = GitHubHandler(dummy_client)
+        handler.dispatch()
+        self.assertEqual(self.db.issue.count(), 2)
+        user_id = self.db.issue.get('2', 'creator')
+        self.assertEqual(self.db.user.get(user_id, 'username'), 'anish.shah')
+
+    def testPullRequestEventWithoutIssueReferenceAndEnvVariable(self):
+        # When no issue is referenced in PR and no environment variable is set
+        os.environ.pop('CREATE_ISSUE')
+        dummy_client = self._make_client("pullrequestevent2.txt")
+        self.assertEqual(self.db.issue.count(), 1)
+        handler = GitHubHandler(dummy_client)
+        handler.dispatch()
+        self.assertEqual(self.db.issue.count(), 1)
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    for l in list_backends():
+        dct = dict(backend=l)
+        subcls = type(TestCase)('TestCase_%s' % l, (TestCase,), dct)
+        suite.addTest(unittest.makeSuite(subcls))
+    return suite
+
+if __name__ == '__main__':
+    runner = unittest.TextTestRunner()
+    unittest.main(testRunner=runner)


More information about the Tracker-discuss mailing list