Maciej Szulik added the comment:

I'm uploading the current version of the patch, it's still not fully reviewed, 
I've left a few TODO's here and there for what should be double checked.

_______________________________________________________
PSF Meta Tracker <metatrac...@psf.upfronthosting.co.za>
<http://psf.upfronthosting.co.za/roundup/meta/issue589>
_______________________________________________________
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, message:
+            self.response_code = 403
+            self.write(message)
+        except UnsupportedMediaType, message:
+            self.response_code = 415
+            self.write(message)
+        except MethodNotAllowed, message:
+            self.response_code = 405
+            self.write(message)
+        except Reject, message:
+            self.response_code = 400
+            self.write(message)
+        else:
+            self.write("Done!")
+
+
     def handle_xmlrpc(self):
         if self.env.get('CONTENT_TYPE') != 'text/xml':
             self.write("This is the endpoint of Roundup <a href='" +
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,241 @@
+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, message:
+            logging.error(message, exc_info=True)
+            raise
+        except MethodNotAllowed, message:
+            logging.error(message, exc_info=True)
+            raise
+        except UnsupportedMediaType, message:
+            logging.error(message, exc_info=True)
+            raise
+        except Exception, message:
+            logging.error(message, exc_info=True)
+            raise Reject('Invalid Request')
+
+    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()
+        if event not in ('pull_request', 'issue_comment'):
+            raise Reject('Unkown X-GitHub-Event {}'.format(event))
+        data = json.loads(self.form.value)
+        if event == 'pull_request':
+            handler = PullRequest(self.db, data)
+            handler.dispatch()
+        # TODO: verify if this is about gh issues or pr comments, not sure?
+        elif event == 'issue_comment':
+            handler = IssueComment(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['SECRET_KEY']
+        data = 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('The provided secret does not match')
+
+    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('unsupported HTTP method %s' % method)
+        content_type = self.env.get('CONTENT_TYPE', None)
+        if content_type != 'application/json':
+            raise UnsupportedMediaType('unsupported Content-Type %s' %
+                                       content_type)
+        if self.get_event() is None:
+            raise Reject('missing X-GitHub-Event header')
+
+    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 issue_exists:
+            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
+                newpr = None
+                if title:
+                    newpr = self.db.pull_request.create(number=prid, 
title=title)
+                else:
+                    # TODO verify if we can get pr title here
+                    newpr = self.db.pull_request.create(number=prid)
+                prs.append(newpr)
+                self.db.issue.set(issue_id, pull_requests=prs)
+                self.db.commit()
+
+    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:
+            username = self.db.user.get(user_id[0], 'username')
+            self.db.setCurrentUser(username)
+        action = self.data['action'].encode('utf-8')
+        issue_id = self.get_issue_ids()
+        prid, title = self.get_pr_details()
+        if action == 'opened' and issue_id is not None and prid is not None:
+            self.handle_create(prid, title, issue_id)
+
+    def get_issue_ids(self):
+        """
+        Extract issue IDs from pull request comments.
+        """
+        title = self.data['pull_request']['title'].encode('utf-8')
+        body = self.data['pull_request']['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.
+        """
+        number = self.data['pull_request']['number']
+        title = self.data['pull_request']['title'].encode('utf-8')
+        return str(number), title
+
+
+    def get_github_username(self):
+        """
+        Extract github username from a pull request.
+        """
+        return self.data['pull_request']['user']['login'].encode('utf-8')
+
+
+class IssueComment(Event):
+    """
+    Class responsible for handling comment 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:
+            username = self.db.user.get(user_id[0], 'username')
+            self.db.setCurrentUser(username)
+        action = self.data['action'].encode('utf-8')
+        issue_id = self.get_issue_ids()
+        prid, title = self.get_pr_details()
+        if action == 'created' and issue_id is not None and prid is not None:
+            self.handle_create(prid, title, issue_id)
+
+    def get_issue_ids(self):
+        """
+        Extract issue IDs from comments.
+        """
+        body = self.data['comment']['body'].encode('utf-8')
+        match = issue_re.search(body)
+        if match:
+            return issue_id_re.findall(match.group())
+        return None
+
+    def get_pr_details(self):
+        """
+        Extract pull request number and title.
+        """
+        url = self.data['issue']['pull_request']['html_url'].encode('utf-8')
+        prid_match = url_re.search(url)
+        if not prid_match:
+            return (None, None)
+        return prid_match.group('number'), None
+
+    def get_github_username(self):
+        """
+        Extract github username from a comment.
+        """
+        return self.data['issue']['user']['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/issuecommentevent.txt b/test/data/issuecommentevent.txt
new file mode 100644
--- /dev/null
+++ b/test/data/issuecommentevent.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: issue_comment
+X-GitHub-Delivery: c6d12700-270e-11e6-86c5-8e9c9c898175
+content-type: application/json
+X-Hub-Signature: sha1=8b34ff28b2b312be5c6439c267afc236ff49475d
+Content-Length: 8404
+
+{"action":"created","issue":{"url":"https://api.github.com/repos/python/cpython/issues/1","repository_url":"https://api.github.com/repos/python/cpython","labels_url":"https://api.github.com/repos/python/cpython/issues/1/labels{/name}","comments_url":"https://api.github.com/repos/python/cpython/issues/1/comments","events_url":"https://api.github.com/repos/python/cpython/issues/1/events","html_url":"https://github.com/python/cpython/pull/1","id":156471938,"number":1,"title":"Test","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},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2016-05-24T10:22:11Z","updated_at":"2016-05-31T09:05:10Z","closed_at":"2016-05-24T10:23:56Z","pull_request":{"url":"https://api.github.com/repos/python/cpython/pulls/1","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"},"body":""},"comment":{"url":"https://api.github.com/repos/python/cpython/issues/comments/222632341","html_url":"https://github.com/python/cpython/pull/1#i
 
ssuecomment-222632341","issue_url":"https://api.github.com/repos/python/cpython/issues/1","id":222632341,"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":fa
 
lse},"created_at":"2016-05-31T09:05:10Z","updated_at":"2016-05-31T09:05:10Z","body":"fixes
 
bpo1"},"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_ev
 
ents","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/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-24T13:18:37Z","git_url":"git://github.com/python/cpython.git","ssh_url":"g...@github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":276521,"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":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.github.com/users/python/received_events","type":"User","site_admin":false}}
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: ping
+X-GitHub-Delivery: 94a54100-20e0-11e6-8756-bf8bf3b91449
+content-type: application/json
+X-Hub-Signature: sha1=01f7eac733e0923dfc6af20b168765578fab9e20
+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":"g...@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/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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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":"g...@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";
 
:"g...@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/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,223 @@
+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()
+        self.assertEqual(str(context.exception),
+                         "Invalid Request")
+
+    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()
+        self.assertEqual(str(context.exception),
+                         "Invalid Request")
+
+    def testNonJSONRequestBody(self):
+        dummy_client = self._make_client("non-json-body.txt")
+        with self.assertRaises(Reject) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+        self.assertEqual(str(context.exception),
+                         "Invalid Request")
+
+    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()
+        self.assertEqual(str(context.exception),
+                         "The provided secret does not match")
+
+    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()
+        self.assertEqual(str(context.exception),
+                         "unsupported Content-Type application/xml")
+
+    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()
+        self.assertEqual(str(context.exception),
+                         "unsupported HTTP method GET")
+
+    def testPingEvent(self):
+        dummy_client = self._make_client("pingevent.txt")
+        with self.assertRaises(Reject) as context:
+            handler = GitHubHandler(dummy_client)
+            handler.dispatch()
+        self.assertEqual(str(context.exception), "Invalid Request")
+
+    def testIssueCommentEvent(self):
+        dummy_client = self._make_client("issuecommentevent.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, '1')
+        user_id = self.db.pull_request.get(prs[0], 'creator')
+        self.assertEqual(self.db.user.get(user_id, 'github'), 'AnishShah')
+
+    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)
_______________________________________________
Tracker-discuss mailing list
Tracker-discuss@python.org
https://mail.python.org/mailman/listinfo/tracker-discuss
Code of Conduct: https://www.python.org/psf/codeofconduct/

Reply via email to