{"action":"created","comment":{"url":"https://api.github.com/repos/jdm/highfive/pulls/comments/41200968","id":41200968,"diff_hunk":"@@ -1,124 +1,527 @@\n-from newpr import APIProvider, handle_payload\n+from errorlogparser import ErrorLogParser, ServoErrorLogParser\n+from githubapiprovider import APIProvider, GithubApiProvider\n+from mock import call, Mock, patch\n+from payloadhandler import PayloadHandler, GithubPayloadHandler, TravisPayloadHandler\n+from travisciapiprovider import TravisCiApiProvider\n import json\n import os\n+import payloadreceiver\n import sys\n import traceback\n+import unittest\n+import urlparse\n \n-class TestAPIProvider(APIProvider):\n-    def __init__(self, payload, user, new_contributor, labels, assignee, diff=\"\"):\n-        APIProvider.__init__(self, payload, user)\n-        self.new_contributor = new_contributor\n-        self.comments_posted = []\n-        self.labels = labels\n-        self.assignee = assignee\n-        self.diff = diff\n-\n-    def is_new_contributor(self, username):\n-        return self.new_contributor\n-\n-    def post_comment(self, body):\n-        self.comments_posted += [body]\n-\n-    def add_label(self, label):\n-        self.labels += [label]\n-\n-    def remove_label(self, label):\n-        self.labels.remove(label)\n-\n-    def get_labels(self):\n-        return self.labels\n-\n-    def get_diff(self):\n-        return self.diff\n-\n-    def set_assignee(self, assignee):\n-        self.assignee = assignee\n-\n-def get_payload(filename):\n-    with open(filename) as f:\n-        return json.load(f)\n-\n-tests = []\n-def add_test(filename, initial, expected):\n-    global tests\n-    initial_values = {'new_contributor': initial.get('new_contributor', False),\n-                      'labels': initial.get('labels', []),\n-                      'diff': initial.get('diff', ''),\n-                      'assignee': initial.get('assignee', None)}\n-    expected_values = {'labels': expected.get('labels', []),\n-                       'assignee': expected.get('assignee', None),\n-                       'comments': expected.get('comments', 0)}\n-    tests += [{'filename': filename,\n-               'initial': initial_values,\n-               'expected': expected_values}]\n-\n-def run_tests(tests):\n-    failed = 0\n-    for test in tests:\n-        try:\n-            payload = get_payload(test['filename'])\n-            initial = test['initial']\n-            api = TestAPIProvider(payload, 'highfive', initial['new_contributor'], initial['labels'],\n-                                  initial['assignee'], initial['diff'])\n-            handle_payload(api, payload)\n-            expected = test['expected']\n-            assert len(api.comments_posted) == expected['comments']\n-            assert api.labels == expected['labels']\n-            assert api.assignee == expected['assignee']\n-        except AssertionError:\n-            _, _, tb = sys.exc_info()\n-            traceback.print_tb(tb) # Fixed format\n-            tb_info = traceback.extract_tb(tb)\n-            filename, line, func, text = tb_info[-1]\n-            print('{}: An error occurred on line {} in statement {}'.format(test['filename'], line, text))\n-            failed += 1\n-\n-    possible_tests = [f for f in os.listdir('.') if f.endswith('.json')]\n-    test_files = set([test['filename'] for test in tests])\n-    if len(possible_tests) != len(test_files):\n-        print 'Found unused JSON test data: %s' % ', '.join(filter(lambda x: x not in test_files, possible_tests))\n-        sys.exit(1)\n-    print 'Ran %d tests, %d failed' % (len(tests), failed)\n-\n-    if failed:\n-        sys.exit(1)\n-\n-add_test('test_new_pr.json', {'new_contributor': True},\n-         {'labels': ['S-awaiting-review'], 'comments': 1})\n-\n-add_test('test_new_pr.json', {'diff': \"+ unsafe fn foo()\"},\n-         {'labels': ['S-awaiting-review'], 'comments': 1})\n-\n-add_test('test_new_pr.json', {'diff': \"diff --git components/layout/\"},\n-         {'labels': ['S-awaiting-review'], 'comments': 1})\n-\n-add_test('test_new_pr.json', {'diff': \"diff --git components/layout/\\ndiff --git tests/wpt\"},\n-         {'labels': ['S-awaiting-review'], 'comments': 0})\n-\n-add_test('test_new_pr.json', {'new_contributor': True},\n-         {'labels': ['S-awaiting-review'], 'comments': 1})\n-\n-add_test('test_ignored_action.json', {}, {})\n-\n-add_test('test_synchronize.json', {'labels': ['S-needs-code-changes', 'S-tests-failed', 'S-awaiting-merge']},\n-         {'labels': ['S-awaiting-review']})\n-\n-add_test('test_comment.json', {}, {'assignee': 'jdm'})\n-\n-add_test('test_merge_approved.json', {'labels': ['S-needs-code-changes', 'S-needs-rebase',\n-                                                 'S-tests-failed', 'S-needs-squash',\n-                                                 'S-awaiting-review']}, {'labels': ['S-awaiting-merge']})\n-\n-add_test('test_merge_conflict.json', {'labels': ['S-awaiting-merge']},\n-         {'labels': ['S-needs-rebase']})\n-\n-add_test('test_tests_failed.json', {'labels': ['S-awaiting-merge']},\n-         {'labels': ['S-tests-failed']})\n-\n-add_test('test_post_retry.json', {'labels': ['S-tests-failed']},\n-         {'labels': ['S-awaiting-merge']})\n-\n-add_test('test_post_retry.json', {'labels': ['S-awaiting-merge']},\n-         {'labels': ['S-awaiting-merge']})\n-\n-run_tests(tests)\n+error_sample = [ \n+    {\n+        \"body\": \"use statement is not in alphabetical order\\nexpected: dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull\\nfound: dom::bindings::conversions::get_dom_class\", \n+        \"position\": 7, \n+        \"path\": \"./components/script/dom/eventtarget.rs\"\n+    }, \n+    {\n+        \"body\": \"use statement is not in alphabetical order\\nexpected: dom::bindings::codegen::Bindings::EventListenerBinding::EventListener\\nfound: dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull\", \n+        \"position\": 8, \n+        \"path\": \"./components/script/dom/eventtarget.rs\"\n+    }, \n+    {\n+        \"body\": \"use statement is not in alphabetical order\\nexpected: dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods\\nfound: dom::bindings::codegen::Bindings::EventListenerBinding::EventListener\", \n+        \"position\": 9, \n+        \"path\": \"./components/script/dom/eventtarget.rs\"\n+    }, \n+    {\n+        \"body\": \"use statement is not in alphabetical order\\nexpected: dom::bindings::conversions::get_dom_class\\nfound: dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods\", \n+        \"position\": 10, \n+        \"path\": \"./components/script/dom/eventtarget.rs\"\n+    }, \n+    {\n+        \"body\": \"use statement is not in alphabetical order\\nexpected: dom::browsercontext\\nfound: dom::eventtarget::EventTargetTypeId\", \n+        \"position\": 17, \n+        \"path\": \"./components/script/dom/bindings/utils.rs\"\n+    }, \n+    {\n+        \"body\": \"use statement is not in alphabetical order\\nexpected: dom::eventtarget::EventTargetTypeId\\nfound: dom::browsercontext\", \n+        \"position\": 18, \n+        \"path\": \"./components/script/dom/bindings/utils.rs\"\n+    }\n+]\n+\n+\n+class TestAPIProvider(unittest.TestCase):\n+    def setUp(self):\n+        self.api_provider = APIProvider('jdm')\n+\n+\n+    def test_is_new_contributor(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.api_provider.is_new_contributor(\"jdm\")\n+\n+\n+    def test_post_comment(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.api_provider.post_comment(\"Nice job!\", 3947)\n+\n+\n+    def test_post_review_comment(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.api_provider.post_review_comment(1234, \"a453b3923e893f0383cd2893f...\", \"foo/bar/spam/eggs\", 3, \"Remove extra space\")\n+\n+\n+    def test_add_label(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.api_provider.add_label(\"S-awaiting-review\", 1234)\n+\n+\n+    def test_remove_label(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.api_provider.remove_label(\"S-awaiting-review\", 1234)\n+\n+\n+    def test_get_labels(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.api_provider.get_labels(1234)\n+\n+\n+    def test_get_diff(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.api_provider.get_diff(\"https://github.com/servo/servo/pull/1234.diff\")\n+\n+\n+    def test_set_assignee(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.api_provider.set_assignee(\"jdm\", 1234)\n+\n+\n+# TODO - add tests for exception handling\n+class TestGithubApiProvider(unittest.TestCase):\n+    class FakeHeader():\n+            def get(self, something):\n+                pass\n+\n+    def setUp(self):\n+        self.owner = \"servo\"\n+        self.repo = \"servo\"\n+        self.gh_provider = GithubApiProvider(\"jdm\", \"a453b3923e893f0383cd2893f...\", self.owner, self.repo)\n+        self.gh_provider.api_req = Mock()\n+\n+    @patch('githubapiprovider.gzip')\n+    @patch('githubapiprovider.urllib2')\n+    @patch('githubapiprovider.base64')\n+    def test_api_req(self, base64_mock, urllib_mock, gzip_mock):\n+        class FakeRequest():\n+            def __init__(self):\n+                self.headers = {}\n+                self.get_method = lambda: \"Get\"\n+\n+\n+            def add_header(self, key, value):\n+                self.headers[key] = value\n+\n+\n+        class FakeHeader():\n+            def get(self, type):\n+                return 'gzip'\n+\n+\n+        class FakeResponse():\n+            def __init__(self, fakeHeader):\n+                self.header = fakeHeader\n+\n+\n+            def info(self):\n+                return self.header\n+\n+\n+            def read(self):\n+                return \"content\"\n+\n+        faux_request = FakeRequest()\n+        base64_mock.standard_b64encode = Mock(return_value=\"User:Token\")\n+        urllib_mock.Request = Mock(return_value=faux_request)\n+        urllib_mock.urlopen = Mock(return_value=FakeResponse(FakeHeader()))\n+        gzip_mock.GzipFile = Mock(return_value=FakeResponse(FakeHeader()))\n+        gh_provider = GithubApiProvider(\"jdm\", \"a453b3923e893f0383cd2893f...\", self.owner, self.repo)\n+        gh_provider.api_req(\"GET\", \"https://api.github.com/repos/servo/servo/contributors\", data={\"test\":\"data\"}, media_type=\"Test Media\")\n+        urllib_mock.Request.assert_called_with(\"https://api.github.com/repos/servo/servo/contributors\", json.dumps({\"test\":\"data\"}), {'Content-Type':'application/json'})\n+        urllib_mock.urlopen.assert_called_with(faux_request)\n+\n+\n+    def test_parse_header_links(self):\n+        header_links =  '<https://api.github.com/repos/servo/servo/contributors?page=2>; rel=\"next\", <https://api.github.com/repos/servo/servo/contributors?page=11>; rel=\"last\"'\n+        expected = {'last': 'https://api.github.com/repos/servo/servo/contributors?page=11', 'next': 'https://api.github.com/repos/servo/servo/contributors?page=2'}\n+        gh_provider = GithubApiProvider(\"jdm\", \"a453b3923e893f0383cd2893f...\", self.owner, self.repo)\n+        self.assertEquals(expected, gh_provider.parse_header_links(header_links))\n+        self.assertEquals(None, gh_provider.parse_header_links(None))\n+\n+\n+    def test_is_new_contributor(self):\n+        next_page_url = \"https://api.github.com/repos/servo/servo/contributors?page=2\"\n+        calls = [call(\"GET\", GithubApiProvider.contributors_url % (self.owner, self.repo)), call(\"GET\", next_page_url)]\n+\n+        gh_provider = GithubApiProvider(\"jdm\", \"a453b3923e893f0383cd2893f...\", self.owner, self.repo)\n+        gh_provider.api_req = Mock(side_effect = [{ \"header\": self.FakeHeader(), \"body\": json.dumps([{\"login\":\"Ms2ger\"}])}, { \"header\": self.FakeHeader(), \"body\": json.dumps([{\"login\":\"jdm\"}, {\"login\":\"Ms2ger\"}])}] )\n+        gh_provider.parse_header_links = Mock(side_effect = [{\"next\": next_page_url}, \"\"])\n+        \n+        self.assertFalse(gh_provider.is_new_contributor(\"jdm\"))\n+\n+        gh_provider.api_req = Mock(side_effect = [{ \"header\": self.FakeHeader(), \"body\": json.dumps([{\"login\":\"Ms2ger\"}])}, { \"header\": self.FakeHeader(), \"body\": json.dumps([{\"login\":\"jdm\"}, {\"login\":\"Ms2ger\"}])}] )\n+        gh_provider.parse_header_links = Mock(side_effect = [{\"next\": next_page_url}, \"\"])\n+        \n+        self.assertTrue(gh_provider.is_new_contributor(\"JoshTheGoldfish\"))\n+        \n+        \n+        gh_provider.api_req.assert_has_calls(calls)\n+\n+\n+    def test_post_comment(self):\n+        body = \"Great job!\"\n+        issue_num = 1\n+        self.gh_provider.post_comment(body, issue_num)\n+        \n+        self.gh_provider.api_req.assert_called_with(\"POST\", GithubApiProvider.post_comment_url % (self.owner, self.repo, issue_num), {\"body\":body})\n+\n+\n+    def test_post_review_comment(self):\n+        pr_num = 1\n+        commit_id = \"a453b3923e893f0383cd2893f...\"\n+        path = \"./file/path\",\n+        pos = 1\n+        body = \"Revmoe extra newline\"\n+        self.gh_provider.post_review_comment(pr_num, commit_id, path, pos, body)\n+        \n+        self.gh_provider.api_req.assert_called_with(\"POST\", GithubApiProvider.review_comment_url % (self.owner, self.repo, pr_num), {\"body\": body, \"commit_id\":commit_id, \"path\":path, \"position\":pos})\n+\n+\n+    def test_add_label(self):\n+        label = \"S-awaiting-review\"\n+        issue_num = 1\n+        self.gh_provider.add_label(label, issue_num)\n+        \n+        self.gh_provider.api_req.assert_called_with(\"POST\", GithubApiProvider.add_label_url % (self.owner, self.repo, issue_num), [label])\n+\n+\n+    def test_remove_label(self):\n+        label = \"S-awaiting-review\"\n+        issue_num = 1\n+        self.gh_provider.remove_label(label, issue_num)\n+        \n+        self.gh_provider.api_req.assert_called_with(\"DELETE\", GithubApiProvider.remove_label_url % (self.owner, self.repo, issue_num, label), {})\n+\n+\n+    def test_get_labels(self):\n+        gh_provider = GithubApiProvider(\"jdm\", \"a453b3923e893f0383cd2893f...\", self.owner, self.repo)\n+        issue_num = 1\n+        label1 = 'S-awaiting-review'\n+        label2 = 'C-assigned'\n+        gh_provider.api_req = Mock(return_value = {\"header\":\"\", \"body\":json.dumps([{'name':label1}, {'name':label2}])})\n+        \n+        self.assertEquals([label1, label2], gh_provider.get_labels(issue_num))\n+        gh_provider.api_req.assert_called_with(\"GET\", GithubApiProvider.get_label_url % (self.owner, self.repo, issue_num))\n+\n+\n+    def test_get_diff(self):\n+        gh_provider = GithubApiProvider(\"jdm\", \"a453b3923e893f0383cd2893f...\", self.owner, self.repo)\n+        diff = \"fake diff\"\n+        diff_url = \"https://github.com/servo/servo/pull/1.diff\"\n+        gh_provider.api_req = Mock(return_value = {\"header\":\"\", \"body\":diff})\n+        \n+        self.assertEquals(diff, gh_provider.get_diff(\"https://github.com/servo/servo/pull/1.diff\"))\n+        gh_provider.api_req.assert_called_with(\"GET\", diff_url)\n+\n+\n+    def test_set_assignee(self):\n+        gh_provider = GithubApiProvider(\"jdm\", \"a453b3923e893f0383cd2893f...\", self.owner, self.repo)\n+        issue_num = 1\n+        assignee = \"jdm\"\n+        gh_provider.api_req = Mock(return_value = {\"header\":\"\", \"body\":assignee})\n+\n+        self.assertEquals(assignee, gh_provider.set_assignee(assignee, issue_num))\n+        gh_provider.api_req.assert_called_with(\"PATCH\", GithubApiProvider.issue_url % (self.owner, self.repo, issue_num), {\"assignee\":assignee})\n+\n+@patch('travisciapiprovider.urllib.urlopen')\n+class TestTravisCiApiProvider(unittest.TestCase):\n+    class FakeFile():\n+        def __init__(self, contents):\n+            self.contents = contents\n+\n+\n+        def read(self):\n+            return self.contents\n+\n+\n+    def setUp(self):\n+        self.travis = TravisCiApiProvider()\n+\n+\n+    def test_get_build(self, urlopen_mock):\n+        urlopen_mock.return_value = self.FakeFile(json.dumps({\"Something\":\"here\"}))\n+        self.travis.get_build(1)\n+        urlopen_mock.assert_called_with(TravisCiApiProvider.build_url.format(build_id=1))\n+\n+\n+    def test_get_log(self, urlopen_mock):\n+        job_id = 1\n+        urlopen_mock.return_value = self.FakeFile(open('resources/single-line-comment.log').read())\n+        self.travis.get_log({\"matrix\":[{'id':job_id}]})\n+        urlopen_mock.assert_called_with(TravisCiApiProvider.log_url.format(job_id=job_id))\n+\n+\n+    def test_get_pull_request_number(self, urlopen_mock):\n+        pr_num = 1234\n+        self.assertEquals(1234, self.travis.get_pull_request_number({\"compare_url\":\"https://github.com/servo/servo/{}\".format(pr_num)}))\n+\n+\n+class TestErrorLogParser(unittest.TestCase):\n+    def setUp(self):\n+        self.log_parser = ErrorLogParser()\n+\n+\n+    def test_parse_log(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.log_parser.parse_log(\"log\", \"regex\")\n+\n+\n+class TestServoErrorLogParser(unittest.TestCase):\n+    def setUp(self):\n+        self.error_parser = ServoErrorLogParser()\n+        self.multi_log = open('resources/multi-line-comment.log').read()\n+        self.expected_multi_errors = error_sample\n+        self.single_log = open('resources/single-line-comment.log').read()\n+        self.expected_single_errors = [\n+            {\n+                'body': 'missing space before {', \n+                'position': 49, \n+                'path': './components/plugins/lints/sorter.rs'\n+            }\n+        ]\n+\n+\n+    def test_parse_errors(self):\n+        self.assertEqual(self.expected_multi_errors, list(self.error_parser.parse_log(self.multi_log)))\n+        self.assertEqual(self.expected_single_errors, list(self.error_parser.parse_log(self.single_log)))\n+\n+\n+class TestPayloadHandler(unittest.TestCase):\n+    def setUp(self):\n+        self.handler = PayloadHandler()\n+\n+\n+    def test_handle_payload(self):\n+        with self.assertRaises(NotImplementedError):\n+            self.handler.handle_payload(\"payload\")\n+\n+\n+class TestTravisPayloadHandler(unittest.TestCase):    \n+    def setUp(self):\n+        class TravisDouble():\n+            def get_build(self, build_id):\n+                return 1\n+\n+\n+            def get_log(self, build_data):\n+                return open('resources/multi-line-comment.log').read()\n+\n+\n+            def get_pull_request_number(self, build_data):\n+                return 1\n+\n+\n+        class ErrorParserDouble():\n+            path_key = ServoErrorLogParser.path_key\n+            body_key = ServoErrorLogParser.body_key\n+            position_key = ServoErrorLogParser.position_key\n+\n+            def parse_log(self, log):\n+                return error_sample\n+\n+\n+        travis_dbl = TravisDouble()\n+        error_parser_dbl = ErrorParserDouble()\n+        self.github = GithubApiProvider(\"jdm\", \"a453b3923e893f0383cd2893f...\", \"servo\", \"servo\")\n+        self.github.post_review_comment = Mock()\n+        self.github.get_review_comments = Mock(return_value=json.loads(open('resources/review_comments.json').read()))\n+\n+        self.payload_handler = TravisPayloadHandler(self.github, travis_dbl, error_parser_dbl)\n+        \n+\n+    def test_handle_payload(self):\n+        payload = json.loads(open('resources/test_travis_payload.json').read())\n+        self.payload_handler.handle_payload(payload)\n+        err_msg = TravisPayloadHandler.msg_template\n+\n+        self.github.post_review_comment.assert_called_with(1, \"9b6313fd5ab92de5a3fd9f13f8421a929b2a8ef6\", err_msg.format(error_sample[3]['path'], error_sample[3]['position'], error_sample[3]['body']), error_sample[3]['path'], error_sample[3]['position'])\n+\n+class TestGithubPayloadHandler(unittest.TestCase):\n+    def setUp(self):\n+        self.github_user = \"jdm\"\n+        self.github = GithubApiProvider(self.github_user, \"a453b3923e893f0383cd2893f...\", \"servo\", \"servo\")\n+        self.github.remove_label = Mock()\n+        self.github.add_label = Mock()\n+\n+\n+    def test_handle_payload(self):\n+        pl_handler = GithubPayloadHandler(None)\n+        pl_handler.new_pr = Mock()\n+        pl_handler.update_pr = Mock()\n+        pl_handler.new_comment = Mock()\n+\n+        payload = {\"action\":\"created\", \"issue\":{\"number\":1}}\n+        pl_handler.handle_payload(payload)\n+        pl_handler.new_comment.assert_called_with(\"1\", payload)\n+\n+        payload = {\"action\":\"opened\", \"number\":\"1\"}\n+        pl_handler.handle_payload(payload)\n+        pl_handler.new_pr.assert_called_with(\"1\", payload)\n+\n+        payload = {\"action\":\"synchronize\", \"number\":\"1\"}\n+        pl_handler.handle_payload(payload)\n+        pl_handler.new_pr.update_pr(\"1\", payload)\n+\n+        # should not break\n+        payload = {\"action\":\"other\", \"number\":\"1\"}\n+        pl_handler.handle_payload(payload)\n+\n+\n+    def test_manage_pr_state(self):\n+        self.github.get_labels = Mock(return_value=[\"S-awaiting-merge\", \"S-tests-failed\", \"S-needs-code-changes\", \"S-needs-rebase\"])\n+        pl_handler = GithubPayloadHandler(self.github)\n+        full_cov_payload = {\"action\":\"synchronize\", \"pull_request\":{\"mergeable\":True}}\n+        issue_num = 1\n+        \n+        pl_handler.manage_pr_state(issue_num, full_cov_payload)\n+        self.github.get_labels.assert_called_with(issue_num)\n+        calls = [ \n+            call(\"S-awaiting-merge\", issue_num),\n+            call(\"S-tests-failed\", issue_num),\n+            call(\"S-needs-code-changes\", issue_num),\n+            call(\"S-needs-rebase\", issue_num)\n+        ]\n+        self.github.remove_label.assert_has_calls(calls)\n+        self.github.add_label.assert_called_with(\"S-awaiting-review\", issue_num)\n+\n+    # Not sure it's worth the effort to cover all branches of manage_pr_state\n+\n+\n+    def test_new_comment_no_action(self):\n+        pl_handler = GithubPayloadHandler(self.github)\n+        pl_handler._find_reviewer = Mock()\n+        payload = {\"issue\":{\"state\":\"closed\"}}\n+        issue_num = 1\n+\n+        pl_handler.new_comment(issue_num, payload)\n+        pl_handler._find_reviewer.assert_not_called()\n+\n+        payload = {\"issue\":{\"state\":\"open\",\"pull_request\":1},\"comment\":{\"user\":{\"login\":self.github_user}}}\n+        pl_handler.new_comment(issue_num, payload)\n+        pl_handler._find_reviewer.assert_not_called()\n+\n+\n+    def test_new_comment_approved(self):\n+        self.github.set_assignee = Mock()\n+        self.github.get_labels = Mock(return_value=[\"S-awaiting-review\"])\n+        pl_handler = GithubPayloadHandler(self.github)\n+        payload = {\"issue\":{\"state\":\"open\",\"pull_request\":1},\"comment\":{\"user\":{\"login\":\"bors-servo\"},\"body\":\"\\br?:@jdm Testing commit\"}}\n+        issue_num = 1\n+\n+        pl_handler.new_comment(issue_num, payload)\n+        self.github.set_assignee.assert_called_with(\"jdm\", issue_num)\n+        self.github.get_labels.assert_called_with(issue_num)\n+        self.github.remove_label.assert_called_with(\"S-awaiting-review\", issue_num)\n+        self.github.add_label.assert_called_with(\"S-awaiting-merge\")\n+\n+\n+    def test_new_comment_failed_test(self):\n+        self.github.set_assignee = Mock()\n+        self.github.get_labels = Mock()\n+        pl_handler = GithubPayloadHandler(self.github)\n+        payload = {\"issue\":{\"state\":\"open\",\"pull_request\":1},\"comment\":{\"user\":{\"login\":\"bors-servo\"},\"body\":\"\\br?:@jdm Test failed\"}}\n+        issue_num = 1\n+\n+        pl_handler.new_comment(issue_num, payload)\n+        self.github.remove_label.assert_called_with(\"S-awaiting-merge\", issue_num)\n+        self.github.add_label.assert_called_with(\"S-tests-failed\", issue_num)\n+\n+\n+    def test_new_comment_merge_conflicts(self):\n+        self.github.set_assignee = Mock()\n+        self.github.get_labels = Mock()\n+        pl_handler = GithubPayloadHandler(self.github)\n+        payload = {\"issue\":{\"state\":\"open\",\"pull_request\":1},\"comment\":{\"user\":{\"login\":\"bors-servo\"},\"body\":\"\\br?:@jdm Please resolve the merge conflicts\"}}\n+        issue_num = 1\n+\n+        pl_handler.new_comment(issue_num, payload)\n+        self.github.remove_label.assert_called_with(\"S-awaiting-merge\", issue_num)\n+        self.github.add_label.assert_called_with(\"S-needs-rebase\", issue_num)\n+\n+\n+    @patch('payloadhandler.random')\n+    def test_new_pr_no_msg(self, random_mock):\n+        self.github.is_new_contributor = Mock(return_value=True)\n+        self.github.post_comment = Mock()\n+        self.github.get_diff = Mock(return_value=\"\")\n+        pl_handler = GithubPayloadHandler(self.github)\n+        pl_handler.manage_pr_state = Mock()\n+        pl_handler.post_comment = Mock()\n+        diff_url = \"https://github.com/servo/servo/pull/1234.diff\"\n+        payload = {\"pull_request\":{\"user\":{\"login\":\"jdm\"},\"diff_url\":diff_url}}\n+        issue_num = 1\n+        rand_val = 'jdm'\n+        random_mock.choice.return_value = rand_val\n+\n+        pl_handler.new_pr(issue_num, payload)\n+        pl_handler.manage_pr_state.assert_called_with(issue_num, payload)\n+        self.github.post_comment.assert_called_with(GithubPayloadHandler.welcome_msg % rand_val, issue_num)\n+        self.github.get_diff.assert_called_with(diff_url)","path":"test.py","position":591,"original_position":591,"commit_id":"8b7c65dc5ee6acfa8d276b956d116c55b0f9c60e","original_commit_id":"8b7c65dc5ee6acfa8d276b956d116c55b0f9c60e","user":{"login":"jdm","id":27658,"avatar_url":"https://avatars.githubusercontent.com/u/27658?v=3","gravatar_id":"","url":"https://api.github.com/users/jdm","html_url":"https://github.com/jdm","followers_url":"https://api.github.com/users/jdm/followers","following_url":"https://api.github.com/users/jdm/following{/other_user}","gists_url":"https://api.github.com/users/jdm/gists{/gist_id}","starred_url":"https://api.github.com/users/jdm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdm/subscriptions","organizations_url":"https://api.github.com/users/jdm/orgs","repos_url":"https://api.github.com/users/jdm/repos","events_url":"https://api.github.com/users/jdm/events{/privacy}","received_events_url":"https://api.github.com/users/jdm/received_events","type":"User","site_admin":false},"body":"Real talk, my eyes glazed over while trying to figure out what this test was actually trying to verify :/ This feels like we're testing at the wrong level to me, when the setup is this involved for what feels like very little payoff.","created_at":"2015-10-05T21:35:45Z","updated_at":"2015-10-05T21:35:45Z","html_url":"https://github.com/jdm/highfive/pull/26#discussion_r41200968","pull_request_url":"https://api.github.com/repos/jdm/highfive/pulls/26","_links":{"self":{"href":"https://api.github.com/repos/jdm/highfive/pulls/comments/41200968"},"html":{"href":"https://github.com/jdm/highfive/pull/26#discussion_r41200968"},"pull_request":{"href":"https://api.github.com/repos/jdm/highfive/pulls/26"}}},"pull_request":{"url":"https://api.github.com/repos/jdm/highfive/pulls/26","id":46092622,"html_url":"https://github.com/jdm/highfive/pull/26","diff_url":"https://github.com/jdm/highfive/pull/26.diff","patch_url":"https://github.com/jdm/highfive/pull/26.patch","issue_url":"https://api.github.com/repos/jdm/highfive/issues/26","number":26,"state":"open","locked":false,"title":"Tidy Errors Integration","user":{"login":"JoshTheGoldfish","id":11929593,"avatar_url":"https://avatars.githubusercontent.com/u/11929593?v=3","gravatar_id":"","url":"https://api.github.com/users/JoshTheGoldfish","html_url":"https://github.com/JoshTheGoldfish","followers_url":"https://api.github.com/users/JoshTheGoldfish/followers","following_url":"https://api.github.com/users/JoshTheGoldfish/following{/other_user}","gists_url":"https://api.github.com/users/JoshTheGoldfish/gists{/gist_id}","starred_url":"https://api.github.com/users/JoshTheGoldfish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JoshTheGoldfish/subscriptions","organizations_url":"https://api.github.com/users/JoshTheGoldfish/orgs","repos_url":"https://api.github.com/users/JoshTheGoldfish/repos","events_url":"https://api.github.com/users/JoshTheGoldfish/events{/privacy}","received_events_url":"https://api.github.com/users/JoshTheGoldfish/received_events","type":"User","site_admin":false},"body":"A lot of refactoring took place, mostly moving code into classes and removing existing tests in favor of using python's unittest module. \r\n\r\nI still need to determine a way to test this to make sure the CGI Script reacts properly to the json inputs (possibly just integration tests). \r\n\r\nPR for: https://github.com/servo/servo/issues/7480","created_at":"2015-09-27T00:39:50Z","updated_at":"2015-10-05T21:35:45Z","closed_at":null,"merged_at":null,"merge_commit_sha":"917100d8a1ee007b3ef585234c9c9d034cac1878","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/jdm/highfive/pulls/26/commits","review_comments_url":"https://api.github.com/repos/jdm/highfive/pulls/26/comments","review_comment_url":"https://api.github.com/repos/jdm/highfive/pulls/comments{/number}","comments_url":"https://api.github.com/repos/jdm/highfive/issues/26/comments","statuses_url":"https://api.github.com/repos/jdm/highfive/statuses/8b7c65dc5ee6acfa8d276b956d116c55b0f9c60e","head":{"label":"JoshTheGoldfish:TidyIntegr","ref":"TidyIntegr","sha":"8b7c65dc5ee6acfa8d276b956d116c55b0f9c60e","user":{"login":"JoshTheGoldfish","id":11929593,"avatar_url":"https://avatars.githubusercontent.com/u/11929593?v=3","gravatar_id":"","url":"https://api.github.com/users/JoshTheGoldfish","html_url":"https://github.com/JoshTheGoldfish","followers_url":"https://api.github.com/users/JoshTheGoldfish/followers","following_url":"https://api.github.com/users/JoshTheGoldfish/following{/other_user}","gists_url":"https://api.github.com/users/JoshTheGoldfish/gists{/gist_id}","starred_url":"https://api.github.com/users/JoshTheGoldfish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JoshTheGoldfish/subscriptions","organizations_url":"https://api.github.com/users/JoshTheGoldfish/orgs","repos_url":"https://api.github.com/users/JoshTheGoldfish/repos","events_url":"https://api.github.com/users/JoshTheGoldfish/events{/privacy}","received_events_url":"https://api.github.com/users/JoshTheGoldfish/received_events","type":"User","site_admin":false},"repo":{"id":41765815,"name":"highfive","full_name":"JoshTheGoldfish/highfive","owner":{"login":"JoshTheGoldfish","id":11929593,"avatar_url":"https://avatars.githubusercontent.com/u/11929593?v=3","gravatar_id":"","url":"https://api.github.com/users/JoshTheGoldfish","html_url":"https://github.com/JoshTheGoldfish","followers_url":"https://api.github.com/users/JoshTheGoldfish/followers","following_url":"https://api.github.com/users/JoshTheGoldfish/following{/other_user}","gists_url":"https://api.github.com/users/JoshTheGoldfish/gists{/gist_id}","starred_url":"https://api.github.com/users/JoshTheGoldfish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JoshTheGoldfish/subscriptions","organizations_url":"https://api.github.com/users/JoshTheGoldfish/orgs","repos_url":"https://api.github.com/users/JoshTheGoldfish/repos","events_url":"https://api.github.com/users/JoshTheGoldfish/events{/privacy}","received_events_url":"https://api.github.com/users/JoshTheGoldfish/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/JoshTheGoldfish/highfive","description":"Github hooks to provide an encouraging atmosphere for new contributors","fork":true,"url":"https://api.github.com/repos/JoshTheGoldfish/highfive","forks_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/forks","keys_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/keys{/key_id}","collaborators_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/teams","hooks_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/hooks","issue_events_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/issues/events{/number}","events_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/events","assignees_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/assignees{/user}","branches_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/branches{/branch}","tags_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/tags","blobs_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/git/refs{/sha}","trees_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/git/trees{/sha}","statuses_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/statuses/{sha}","languages_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/languages","stargazers_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/stargazers","contributors_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/contributors","subscribers_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/subscribers","subscription_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/subscription","commits_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/commits{/sha}","git_commits_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/git/commits{/sha}","comments_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/comments{/number}","issue_comment_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/issues/comments{/number}","contents_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/contents/{+path}","compare_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/compare/{base}...{head}","merges_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/merges","archive_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/downloads","issues_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/issues{/number}","pulls_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/pulls{/number}","milestones_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/milestones{/number}","notifications_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/labels{/name}","releases_url":"https://api.github.com/repos/JoshTheGoldfish/highfive/releases{/id}","created_at":"2015-09-01T22:05:22Z","updated_at":"2015-09-01T22:05:23Z","pushed_at":"2015-09-27T13:54:33Z","git_url":"git://github.com/JoshTheGoldfish/highfive.git","ssh_url":"git@github.com:JoshTheGoldfish/highfive.git","clone_url":"https://github.com/JoshTheGoldfish/highfive.git","svn_url":"https://github.com/JoshTheGoldfish/highfive","homepage":null,"size":172,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"jdm:master","ref":"master","sha":"f07dfa01182999c423b62581485175ee2c77dc2c","user":{"login":"jdm","id":27658,"avatar_url":"https://avatars.githubusercontent.com/u/27658?v=3","gravatar_id":"","url":"https://api.github.com/users/jdm","html_url":"https://github.com/jdm","followers_url":"https://api.github.com/users/jdm/followers","following_url":"https://api.github.com/users/jdm/following{/other_user}","gists_url":"https://api.github.com/users/jdm/gists{/gist_id}","starred_url":"https://api.github.com/users/jdm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdm/subscriptions","organizations_url":"https://api.github.com/users/jdm/orgs","repos_url":"https://api.github.com/users/jdm/repos","events_url":"https://api.github.com/users/jdm/events{/privacy}","received_events_url":"https://api.github.com/users/jdm/received_events","type":"User","site_admin":false},"repo":{"id":13803214,"name":"highfive","full_name":"jdm/highfive","owner":{"login":"jdm","id":27658,"avatar_url":"https://avatars.githubusercontent.com/u/27658?v=3","gravatar_id":"","url":"https://api.github.com/users/jdm","html_url":"https://github.com/jdm","followers_url":"https://api.github.com/users/jdm/followers","following_url":"https://api.github.com/users/jdm/following{/other_user}","gists_url":"https://api.github.com/users/jdm/gists{/gist_id}","starred_url":"https://api.github.com/users/jdm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdm/subscriptions","organizations_url":"https://api.github.com/users/jdm/orgs","repos_url":"https://api.github.com/users/jdm/repos","events_url":"https://api.github.com/users/jdm/events{/privacy}","received_events_url":"https://api.github.com/users/jdm/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jdm/highfive","description":"Github hooks to provide an encouraging atmosphere for new contributors","fork":false,"url":"https://api.github.com/repos/jdm/highfive","forks_url":"https://api.github.com/repos/jdm/highfive/forks","keys_url":"https://api.github.com/repos/jdm/highfive/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jdm/highfive/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jdm/highfive/teams","hooks_url":"https://api.github.com/repos/jdm/highfive/hooks","issue_events_url":"https://api.github.com/repos/jdm/highfive/issues/events{/number}","events_url":"https://api.github.com/repos/jdm/highfive/events","assignees_url":"https://api.github.com/repos/jdm/highfive/assignees{/user}","branches_url":"https://api.github.com/repos/jdm/highfive/branches{/branch}","tags_url":"https://api.github.com/repos/jdm/highfive/tags","blobs_url":"https://api.github.com/repos/jdm/highfive/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jdm/highfive/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jdm/highfive/git/refs{/sha}","trees_url":"https://api.github.com/repos/jdm/highfive/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jdm/highfive/statuses/{sha}","languages_url":"https://api.github.com/repos/jdm/highfive/languages","stargazers_url":"https://api.github.com/repos/jdm/highfive/stargazers","contributors_url":"https://api.github.com/repos/jdm/highfive/contributors","subscribers_url":"https://api.github.com/repos/jdm/highfive/subscribers","subscription_url":"https://api.github.com/repos/jdm/highfive/subscription","commits_url":"https://api.github.com/repos/jdm/highfive/commits{/sha}","git_commits_url":"https://api.github.com/repos/jdm/highfive/git/commits{/sha}","comments_url":"https://api.github.com/repos/jdm/highfive/comments{/number}","issue_comment_url":"https://api.github.com/repos/jdm/highfive/issues/comments{/number}","contents_url":"https://api.github.com/repos/jdm/highfive/contents/{+path}","compare_url":"https://api.github.com/repos/jdm/highfive/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jdm/highfive/merges","archive_url":"https://api.github.com/repos/jdm/highfive/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jdm/highfive/downloads","issues_url":"https://api.github.com/repos/jdm/highfive/issues{/number}","pulls_url":"https://api.github.com/repos/jdm/highfive/pulls{/number}","milestones_url":"https://api.github.com/repos/jdm/highfive/milestones{/number}","notifications_url":"https://api.github.com/repos/jdm/highfive/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jdm/highfive/labels{/name}","releases_url":"https://api.github.com/repos/jdm/highfive/releases{/id}","created_at":"2013-10-23T13:17:05Z","updated_at":"2015-04-27T15:43:15Z","pushed_at":"2015-09-27T13:54:34Z","git_url":"git://github.com/jdm/highfive.git","ssh_url":"git@github.com:jdm/highfive.git","clone_url":"https://github.com/jdm/highfive.git","svn_url":"https://github.com/jdm/highfive","homepage":null,"size":406,"stargazers_count":5,"watchers_count":5,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":21,"mirror_url":null,"open_issues_count":15,"forks":21,"open_issues":15,"watchers":5,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jdm/highfive/pulls/26"},"html":{"href":"https://github.com/jdm/highfive/pull/26"},"issue":{"href":"https://api.github.com/repos/jdm/highfive/issues/26"},"comments":{"href":"https://api.github.com/repos/jdm/highfive/issues/26/comments"},"review_comments":{"href":"https://api.github.com/repos/jdm/highfive/pulls/26/comments"},"review_comment":{"href":"https://api.github.com/repos/jdm/highfive/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/jdm/highfive/pulls/26/commits"},"statuses":{"href":"https://api.github.com/repos/jdm/highfive/statuses/8b7c65dc5ee6acfa8d276b956d116c55b0f9c60e"}}}}