def test_avatar_upload(client: Client, correct_login: Dict, delete_user_images: Callable, request) -> None: """Проверка загрузки, когда все входные данные корректны""" response = client.post('/api/v1/auth/login/', data=correct_login, content_type='application/json') response_body = json.loads(response.content, encoding='utf-8') token = response_body['tokens']['access'] headers = { 'HTTP_AUTHORIZATION': f'Bearer {token}', **UPLOAD_AVATAR_HEADERS } response = client.generic('POST', '/api/v1/user/update/avatar/', generate_dump(1024), **headers) response_body = json.loads(response.content, encoding='utf8') # Сначала проверяем, что сервер вернул корректные данные assert \ response.status_code == 200 \ and response_body['status'] == 'ok' \ and response_body['url'] # Теперь проверяем, что файл действительно сохранился на диске # Это будет работать, если файл сохраняется на диске на той же машине # Если файл сохраняется на другом сервере или в облаке, надо будет # эту проверку заменить filename = response_body['url'].split('/')[-1] path = '/'.join([settings.MEDIA_ROOT, correct_login['email'], filename]) # после теста удаляем всю папку медиа для этого пользователя request.node.user_media = '/'.join( [settings.MEDIA_ROOT, correct_login['email']]) assert os.path.isfile(path)
def test_avatar_upload_with_incorrect_headers(client: Client, correct_login: Dict, headers: Dict, delete_user_images: Callable, request) -> None: """Проверка загрузки, когда заголовки некорректны""" response = client.post('/api/v1/auth/login/', data={}, content_type='application/json') response_body = json.loads(response.content, encoding='utf-8') token = response_body['tokens']['access'] headers = {'HTTP_AUTHORIZATION': f'Bearer {token}', **headers} response = client.generic('POST', '/api/v1/user/update/avatar/', generate_dump(1024), **headers) response_body = json.loads(response.content, encoding='utf8') # На всякий случай удаляем папку медиа пользователя, вдруг # тест провалился и файл загрузился request.node.user_media = '/'.join( [settings.MEDIA_ROOT, correct_login['email']]) assert \ response.status_code == 400 and \ response_body['status'] == 'error' and \ response_body['message']
class TestCapture(BaseTest): """ Tests all data capture endpoints (e.g. `/capture` `/track`). We use Django's base test class instead of DRF's because we need granular control over the Content-Type sent over. """ CLASS_DATA_LEVEL_SETUP = False def setUp(self): super().setUp() self.client = Client() def _to_json(self, data: Union[Dict, List]) -> str: return json.dumps(data) def _dict_to_b64(self, data: dict) -> str: return base64.b64encode( json.dumps(data).encode("utf-8")).decode("utf-8") def _dict_from_b64(self, data: str) -> dict: return json.loads(base64.b64decode(data)) def _to_arguments(self, patch_process_event_with_plugins: Any) -> dict: args = patch_process_event_with_plugins.call_args[1]["args"] distinct_id, ip, site_url, data, team_id, now, sent_at = args return { "distinct_id": distinct_id, "ip": ip, "site_url": site_url, "data": data, "team_id": team_id, "now": now, "sent_at": sent_at, } @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_capture_event(self, patch_process_event_with_plugins): data = { "event": "$autocapture", "properties": { "distinct_id": 2, "token": self.team.api_token, "$elements": [ { "tag_name": "a", "nth_child": 1, "nth_of_type": 2, "attr__class": "btn btn-sm", }, { "tag_name": "div", "nth_child": 1, "nth_of_type": 2, "$el_text": "💻", }, ], }, } now = timezone.now() with freeze_time(now): with self.assertNumQueries(2): response = self.client.get( "/e/?data=%s" % quote(self._to_json(data)), HTTP_ORIGIN="https://localhost", ) self.assertEqual(response.get("access-control-allow-origin"), "https://localhost") arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate arguments.pop("sent_at") # can't compare fakedate self.assertDictEqual( arguments, { "distinct_id": "2", "ip": "127.0.0.1", "site_url": "http://testserver", "data": data, "team_id": self.team.pk, }, ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_test_api_key(self, patch_process_event_with_plugins): api_token = "test_" + self.team.api_token data: Dict[str, Any] = { "event": "$autocapture", "properties": { "distinct_id": 2, "token": api_token, "$elements": [ { "tag_name": "a", "nth_child": 1, "nth_of_type": 2, "attr__class": "btn btn-sm", }, { "tag_name": "div", "nth_child": 1, "nth_of_type": 2, "$el_text": "💻", }, ], }, } now = timezone.now() with freeze_time(now): with self.assertNumQueries(2): response = self.client.get( "/e/?data=%s" % quote(self._to_json(data)), HTTP_ORIGIN="https://localhost", ) self.assertEqual(response.get("access-control-allow-origin"), "https://localhost") arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate arguments.pop("sent_at") # can't compare fakedate self.assertDictEqual( arguments, { "distinct_id": "2", "ip": "127.0.0.1", "site_url": "http://testserver", "data": { **data, "properties": { **data["properties"], "$environment": ENVIRONMENT_TEST } }, "team_id": self.team.pk, }, ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_personal_api_key(self, patch_process_event_with_plugins): key = PersonalAPIKey(label="X", user=self.user) key.save() data = { "event": "$autocapture", "api_key": key.value, "project_id": self.team.id, "properties": { "distinct_id": 2, "$elements": [ { "tag_name": "a", "nth_child": 1, "nth_of_type": 2, "attr__class": "btn btn-sm", }, { "tag_name": "div", "nth_child": 1, "nth_of_type": 2, "$el_text": "💻", }, ], }, } now = timezone.now() with freeze_time(now): with self.assertNumQueries(5): response = self.client.get( "/e/?data=%s" % quote(self._to_json(data)), HTTP_ORIGIN="https://localhost", ) self.assertEqual(response.get("access-control-allow-origin"), "https://localhost") arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate arguments.pop("sent_at") # can't compare fakedate self.assertDictEqual( arguments, { "distinct_id": "2", "ip": "127.0.0.1", "site_url": "http://testserver", "data": data, "team_id": self.team.pk, }, ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_personal_api_key_from_batch_request( self, patch_process_event_with_plugins): # Originally issue POSTHOG-2P8 key = PersonalAPIKey(label="X", user=self.user) key.save() data = [{ "event": "$pageleave", "api_key": key.value, "project_id": self.team.id, "properties": { "$os": "Linux", "$browser": "Chrome", "$device_type": "Desktop", "distinct_id": "94b03e599131fd5026b", "token": "fake token", # as this is invalid, will do API key authentication }, "timestamp": "2021-04-20T19:11:33.841Z", }] response = self.client.get("/e/?data=%s" % quote(self._to_json(data))) self.assertEqual(response.status_code, status.HTTP_200_OK) arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate arguments.pop("sent_at") # can't compare fakedate self.assertDictEqual( arguments, { "distinct_id": "94b03e599131fd5026b", "ip": "127.0.0.1", "site_url": "http://testserver", "data": { "event": "$pageleave", "api_key": key.value, "project_id": self.team.id, "properties": { "$os": "Linux", "$browser": "Chrome", "$device_type": "Desktop", "distinct_id": "94b03e599131fd5026b", "token": "fake token", }, "timestamp": "2021-04-20T19:11:33.841Z", }, "team_id": self.team.id, }, ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_multiple_events(self, patch_process_event_with_plugins): self.client.post( "/track/", data={ "data": json.dumps([ { "event": "beep", "properties": { "distinct_id": "eeee", "token": self.team.api_token, }, }, { "event": "boop", "properties": { "distinct_id": "aaaa", "token": self.team.api_token, }, }, ]), "api_key": self.team.api_token, }, ) self.assertEqual(patch_process_event_with_plugins.call_count, 2) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_emojis_in_text(self, patch_process_event_with_plugins): self.team.api_token = "xp9qT2VLY76JJg" self.team.save() # Make sure the endpoint works with and without the trailing slash self.client.post( "/track", data={ "data": "eyJldmVudCI6ICIkd2ViX2V2ZW50IiwicHJvcGVydGllcyI6IHsiJG9zIjogIk1hYyBPUyBYIiwiJGJyb3dzZXIiOiAiQ2hyb21lIiwiJHJlZmVycmVyIjogImh0dHBzOi8vYXBwLmhpYmVybHkuY29tL2xvZ2luP25leHQ9LyIsIiRyZWZlcnJpbmdfZG9tYWluIjogImFwcC5oaWJlcmx5LmNvbSIsIiRjdXJyZW50X3VybCI6ICJodHRwczovL2FwcC5oaWJlcmx5LmNvbS8iLCIkYnJvd3Nlcl92ZXJzaW9uIjogNzksIiRzY3JlZW5faGVpZ2h0IjogMjE2MCwiJHNjcmVlbl93aWR0aCI6IDM4NDAsInBoX2xpYiI6ICJ3ZWIiLCIkbGliX3ZlcnNpb24iOiAiMi4zMy4xIiwiJGluc2VydF9pZCI6ICJnNGFoZXFtejVrY3AwZ2QyIiwidGltZSI6IDE1ODA0MTAzNjguMjY1LCJkaXN0aW5jdF9pZCI6IDYzLCIkZGV2aWNlX2lkIjogIjE2ZmQ1MmRkMDQ1NTMyLTA1YmNhOTRkOWI3OWFiLTM5NjM3YzBlLTFhZWFhMC0xNmZkNTJkZDA0NjQxZCIsIiRpbml0aWFsX3JlZmVycmVyIjogIiRkaXJlY3QiLCIkaW5pdGlhbF9yZWZlcnJpbmdfZG9tYWluIjogIiRkaXJlY3QiLCIkdXNlcl9pZCI6IDYzLCIkZXZlbnRfdHlwZSI6ICJjbGljayIsIiRjZV92ZXJzaW9uIjogMSwiJGhvc3QiOiAiYXBwLmhpYmVybHkuY29tIiwiJHBhdGhuYW1lIjogIi8iLCIkZWxlbWVudHMiOiBbCiAgICB7InRhZ19uYW1lIjogImJ1dHRvbiIsIiRlbF90ZXh0IjogIu2gve2yuyBXcml0aW5nIGNvZGUiLCJjbGFzc2VzIjogWwogICAgImJ0biIsCiAgICAiYnRuLXNlY29uZGFyeSIKXSwiYXR0cl9fY2xhc3MiOiAiYnRuIGJ0bi1zZWNvbmRhcnkiLCJhdHRyX19zdHlsZSI6ICJjdXJzb3I6IHBvaW50ZXI7IG1hcmdpbi1yaWdodDogOHB4OyBtYXJnaW4tYm90dG9tOiAxcmVtOyIsIm50aF9jaGlsZCI6IDIsIm50aF9vZl90eXBlIjogMX0sCiAgICB7InRhZ19uYW1lIjogImRpdiIsIm50aF9jaGlsZCI6IDEsIm50aF9vZl90eXBlIjogMX0sCiAgICB7InRhZ19uYW1lIjogImRpdiIsImNsYXNzZXMiOiBbCiAgICAiZmVlZGJhY2stc3RlcCIsCiAgICAiZmVlZGJhY2stc3RlcC1zZWxlY3RlZCIKXSwiYXR0cl9fY2xhc3MiOiAiZmVlZGJhY2stc3RlcCBmZWVkYmFjay1zdGVwLXNlbGVjdGVkIiwibnRoX2NoaWxkIjogMiwibnRoX29mX3R5cGUiOiAxfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwiY2xhc3NlcyI6IFsKICAgICJnaXZlLWZlZWRiYWNrIgpdLCJhdHRyX19jbGFzcyI6ICJnaXZlLWZlZWRiYWNrIiwiYXR0cl9fc3R5bGUiOiAid2lkdGg6IDkwJTsgbWFyZ2luOiAwcHggYXV0bzsgZm9udC1zaXplOiAxNXB4OyBwb3NpdGlvbjogcmVsYXRpdmU7IiwibnRoX2NoaWxkIjogMSwibnRoX29mX3R5cGUiOiAxfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwiYXR0cl9fc3R5bGUiOiAib3ZlcmZsb3c6IGhpZGRlbjsiLCJudGhfY2hpbGQiOiAxLCJudGhfb2ZfdHlwZSI6IDF9LAogICAgeyJ0YWdfbmFtZSI6ICJkaXYiLCJjbGFzc2VzIjogWwogICAgIm1vZGFsLWJvZHkiCl0sImF0dHJfX2NsYXNzIjogIm1vZGFsLWJvZHkiLCJhdHRyX19zdHlsZSI6ICJmb250LXNpemU6IDE1cHg7IiwibnRoX2NoaWxkIjogMiwibnRoX29mX3R5cGUiOiAyfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwiY2xhc3NlcyI6IFsKICAgICJtb2RhbC1jb250ZW50IgpdLCJhdHRyX19jbGFzcyI6ICJtb2RhbC1jb250ZW50IiwibnRoX2NoaWxkIjogMSwibnRoX29mX3R5cGUiOiAxfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwiY2xhc3NlcyI6IFsKICAgICJtb2RhbC1kaWFsb2ciLAogICAgIm1vZGFsLWxnIgpdLCJhdHRyX19jbGFzcyI6ICJtb2RhbC1kaWFsb2cgbW9kYWwtbGciLCJhdHRyX19yb2xlIjogImRvY3VtZW50IiwibnRoX2NoaWxkIjogMSwibnRoX29mX3R5cGUiOiAxfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwiY2xhc3NlcyI6IFsKICAgICJtb2RhbCIsCiAgICAiZmFkZSIsCiAgICAic2hvdyIKXSwiYXR0cl9fY2xhc3MiOiAibW9kYWwgZmFkZSBzaG93IiwiYXR0cl9fc3R5bGUiOiAiZGlzcGxheTogYmxvY2s7IiwibnRoX2NoaWxkIjogMiwibnRoX29mX3R5cGUiOiAyfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwibnRoX2NoaWxkIjogMSwibnRoX29mX3R5cGUiOiAxfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwibnRoX2NoaWxkIjogMSwibnRoX29mX3R5cGUiOiAxfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwiY2xhc3NlcyI6IFsKICAgICJrLXBvcnRsZXRfX2JvZHkiLAogICAgIiIKXSwiYXR0cl9fY2xhc3MiOiAiay1wb3J0bGV0X19ib2R5ICIsImF0dHJfX3N0eWxlIjogInBhZGRpbmc6IDBweDsiLCJudGhfY2hpbGQiOiAyLCJudGhfb2ZfdHlwZSI6IDJ9LAogICAgeyJ0YWdfbmFtZSI6ICJkaXYiLCJjbGFzc2VzIjogWwogICAgImstcG9ydGxldCIsCiAgICAiay1wb3J0bGV0LS1oZWlnaHQtZmx1aWQiCl0sImF0dHJfX2NsYXNzIjogImstcG9ydGxldCBrLXBvcnRsZXQtLWhlaWdodC1mbHVpZCIsIm50aF9jaGlsZCI6IDEsIm50aF9vZl90eXBlIjogMX0sCiAgICB7InRhZ19uYW1lIjogImRpdiIsImNsYXNzZXMiOiBbCiAgICAiY29sLWxnLTYiCl0sImF0dHJfX2NsYXNzIjogImNvbC1sZy02IiwibnRoX2NoaWxkIjogMSwibnRoX29mX3R5cGUiOiAxfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwiY2xhc3NlcyI6IFsKICAgICJyb3ciCl0sImF0dHJfX2NsYXNzIjogInJvdyIsIm50aF9jaGlsZCI6IDEsIm50aF9vZl90eXBlIjogMX0sCiAgICB7InRhZ19uYW1lIjogImRpdiIsImF0dHJfX3N0eWxlIjogInBhZGRpbmc6IDQwcHggMzBweCAwcHg7IGJhY2tncm91bmQtY29sb3I6IHJnYigyMzksIDIzOSwgMjQ1KTsgbWFyZ2luLXRvcDogLTQwcHg7IG1pbi1oZWlnaHQ6IGNhbGMoMTAwdmggLSA0MHB4KTsiLCJudGhfY2hpbGQiOiAyLCJudGhfb2ZfdHlwZSI6IDJ9LAogICAgeyJ0YWdfbmFtZSI6ICJkaXYiLCJhdHRyX19zdHlsZSI6ICJtYXJnaW4tdG9wOiAwcHg7IiwibnRoX2NoaWxkIjogMiwibnRoX29mX3R5cGUiOiAyfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwiY2xhc3NlcyI6IFsKICAgICJBcHAiCl0sImF0dHJfX2NsYXNzIjogIkFwcCIsImF0dHJfX3N0eWxlIjogImNvbG9yOiByZ2IoNTIsIDYxLCA2Mik7IiwibnRoX2NoaWxkIjogMSwibnRoX29mX3R5cGUiOiAxfSwKICAgIHsidGFnX25hbWUiOiAiZGl2IiwiYXR0cl9faWQiOiAicm9vdCIsIm50aF9jaGlsZCI6IDEsIm50aF9vZl90eXBlIjogMX0sCiAgICB7InRhZ19uYW1lIjogImJvZHkiLCJudGhfY2hpbGQiOiAyLCJudGhfb2ZfdHlwZSI6IDF9Cl0sInRva2VuIjogInhwOXFUMlZMWTc2SkpnIn19" }, ) self.assertEqual( patch_process_event_with_plugins.call_args[1]["args"][3] ["properties"]["$elements"][0]["$el_text"], "💻 Writing code", ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_js_gzip(self, patch_process_event_with_plugins): self.team.api_token = "rnEnwNvmHphTu5rFG4gWDDs49t00Vk50tDOeDdedMb4" self.team.save() self.client.post( "/track?compression=gzip-js", data= b"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xadRKn\xdb0\x10\xbdJ@xi\xd9CY\xd6o[\xf7\xb3\xe8gS4\x8b\xa2\x10(r$\x11\xa6I\x81\xa2\xe4\x18A.\xd1\x0b\xf4 \xbdT\x8f\xd0a\x93&mQt\xd5\x15\xc9\xf7\xde\xbc\x19\xf0\xcd-\xc3\x05m`5;]\x92\xfb\xeb\x9a\x8d\xde\x8d\xe8\x83\xc6\x89\xd5\xb7l\xe5\xe8`\xaf\xb5\x9do\x88[\xb5\xde\x9d'\xf4\x04=\x1b\xbc;a\xc4\xe4\xec=\x956\xb37\x84\x0f!\x8c\xf5vk\x9c\x14fpS\xa8K\x00\xbeUNNQ\x1b\x11\x12\xfd\xceFb\x14a\xb0\x82\x0ck\xf6(~h\xd6,\xe8'\xed,\xab\xcb\x82\xd0IzD\xdb\x0c\xa8\xfb\x81\xbc8\x94\xf0\x84\x9e\xb5\n\x03\x81U\x1aA\xa3[\xf2;c\x1b\xdd\xe8\xf1\xe4\xc4\xf8\xa6\xd8\xec\x92\x16\x83\xd8T\x91\xd5\x96:\x85F+\xe2\xaa\xb44Gq\xe1\xb2\x0cp\x03\xbb\x1f\xf3\x05\x1dg\xe39\x14Y\x9a\xf3|\xb7\xe1\xb0[3\xa5\xa7\xa0\xad|\xa8\xe3E\x9e\xa5P\x89\xa2\xecv\xb2H k1\xcf\xabR\x08\x95\xa7\xfb\x84C\n\xbc\x856\xe1\x9d\xc8\x00\x92Gu\x05y\x0e\xb1\x87\xc2EK\xfc?^\xda\xea\xa0\x85i<vH\xf1\xc4\xc4VJ{\x941\xe2?Xm\xfbF\xb9\x93\xd0\xf1c~Q\xfd\xbd\xf6\xdf5B\x06\xbd`\xd3\xa1\x08\xb3\xa7\xd3\x88\x9e\x16\xe8#\x1b)\xec\xc1\xf5\x89\xf7\x14G2\x1aq!\xdf5\xebfc\x92Q\xf4\xf8\x13\xfat\xbf\x80d\xfa\xed\xcb\xe7\xafW\xd7\x9e\x06\xb5\xfd\x95t*\xeeZpG\x8c\r\xbd}n\xcfo\x97\xd3\xabqx?\xef\xfd\x8b\x97Y\x7f}8LY\x15\x00>\x1c\xf7\x10\x0e\xef\xf0\xa0P\xbdi3vw\xf7\x1d\xccN\xdf\x13\xe7\x02\x00\x00", content_type="text/plain", ) self.assertEqual(patch_process_event_with_plugins.call_count, 1) self.assertEqual( patch_process_event_with_plugins.call_args[1]["args"][3]["event"], "my-event") self.assertEqual( patch_process_event_with_plugins.call_args[1]["args"][3] ["properties"]["prop"], "💻 Writing code", ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_js_gzip_with_no_content_type(self, patch_process_event_with_plugins): "IE11 sometimes does not send content_type" self.team.api_token = "rnEnwNvmHphTu5rFG4gWDDs49t00Vk50tDOeDdedMb4" self.team.save() self.client.post( "/track?compression=gzip-js", data= b"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xadRKn\xdb0\x10\xbdJ@xi\xd9CY\xd6o[\xf7\xb3\xe8gS4\x8b\xa2\x10(r$\x11\xa6I\x81\xa2\xe4\x18A.\xd1\x0b\xf4 \xbdT\x8f\xd0a\x93&mQt\xd5\x15\xc9\xf7\xde\xbc\x19\xf0\xcd-\xc3\x05m`5;]\x92\xfb\xeb\x9a\x8d\xde\x8d\xe8\x83\xc6\x89\xd5\xb7l\xe5\xe8`\xaf\xb5\x9do\x88[\xb5\xde\x9d'\xf4\x04=\x1b\xbc;a\xc4\xe4\xec=\x956\xb37\x84\x0f!\x8c\xf5vk\x9c\x14fpS\xa8K\x00\xbeUNNQ\x1b\x11\x12\xfd\xceFb\x14a\xb0\x82\x0ck\xf6(~h\xd6,\xe8'\xed,\xab\xcb\x82\xd0IzD\xdb\x0c\xa8\xfb\x81\xbc8\x94\xf0\x84\x9e\xb5\n\x03\x81U\x1aA\xa3[\xf2;c\x1b\xdd\xe8\xf1\xe4\xc4\xf8\xa6\xd8\xec\x92\x16\x83\xd8T\x91\xd5\x96:\x85F+\xe2\xaa\xb44Gq\xe1\xb2\x0cp\x03\xbb\x1f\xf3\x05\x1dg\xe39\x14Y\x9a\xf3|\xb7\xe1\xb0[3\xa5\xa7\xa0\xad|\xa8\xe3E\x9e\xa5P\x89\xa2\xecv\xb2H k1\xcf\xabR\x08\x95\xa7\xfb\x84C\n\xbc\x856\xe1\x9d\xc8\x00\x92Gu\x05y\x0e\xb1\x87\xc2EK\xfc?^\xda\xea\xa0\x85i<vH\xf1\xc4\xc4VJ{\x941\xe2?Xm\xfbF\xb9\x93\xd0\xf1c~Q\xfd\xbd\xf6\xdf5B\x06\xbd`\xd3\xa1\x08\xb3\xa7\xd3\x88\x9e\x16\xe8#\x1b)\xec\xc1\xf5\x89\xf7\x14G2\x1aq!\xdf5\xebfc\x92Q\xf4\xf8\x13\xfat\xbf\x80d\xfa\xed\xcb\xe7\xafW\xd7\x9e\x06\xb5\xfd\x95t*\xeeZpG\x8c\r\xbd}n\xcfo\x97\xd3\xabqx?\xef\xfd\x8b\x97Y\x7f}8LY\x15\x00>\x1c\xf7\x10\x0e\xef\xf0\xa0P\xbdi3vw\xf7\x1d\xccN\xdf\x13\xe7\x02\x00\x00", content_type="", ) self.assertEqual(patch_process_event_with_plugins.call_count, 1) self.assertEqual( patch_process_event_with_plugins.call_args[1]["args"][3]["event"], "my-event") self.assertEqual( patch_process_event_with_plugins.call_args[1]["args"][3] ["properties"]["prop"], "💻 Writing code", ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_invalid_gzip(self, patch_process_event_with_plugins): self.team.api_token = "rnEnwNvmHphTu5rFG4gWDDs49t00Vk50tDOeDdedMb4" self.team.save() response = self.client.post( "/track?compression=gzip", data=b"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03", content_type="text/plain", ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json(), self.validation_error_response( "Malformed request data: Failed to decompress data. Compressed file ended before the end-of-stream marker was reached", code="invalid_payload", ), ) self.assertEqual(patch_process_event_with_plugins.call_count, 0) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_invalid_lz64(self, patch_process_event_with_plugins): self.team.api_token = "rnEnwNvmHphTu5rFG4gWDDs49t00Vk50tDOeDdedMb4" self.team.save() response = self.client.post( "/track?compression=lz64", data="foo", content_type="text/plain", ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json(), self.validation_error_response( "Malformed request data: Failed to decompress data.", code="invalid_payload", ), ) self.assertEqual(patch_process_event_with_plugins.call_count, 0) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_incorrect_padding(self, patch_process_event_with_plugins): response = self.client.get( "/e/?data=eyJldmVudCI6IndoYXRldmVmciIsInByb3BlcnRpZXMiOnsidG9rZW4iOiJ0b2tlbjEyMyIsImRpc3RpbmN0X2lkIjoiYXNkZiJ9fQ", content_type="application/json", HTTP_REFERER="https://localhost", ) self.assertEqual(response.json()["status"], 1) self.assertEqual( patch_process_event_with_plugins.call_args[1]["args"][3]["event"], "whatevefr") @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_empty_request_returns_an_error(self, patch_process_event_with_plugins): """ Empty requests that fail silently cause confusion as to whether they were successful or not. """ # Empty GET response = self.client.get( "/e/?data=", content_type="application/json", HTTP_ORIGIN="https://localhost", ) self.assertEqual(response.status_code, 400) self.assertEqual(patch_process_event_with_plugins.call_count, 0) # Empty POST response = self.client.post( "/e/", {}, content_type="application/json", HTTP_ORIGIN="https://localhost", ) self.assertEqual(response.status_code, 400) self.assertEqual(patch_process_event_with_plugins.call_count, 0) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_batch(self, patch_process_event_with_plugins): data = { "type": "capture", "event": "user signed up", "distinct_id": "2" } response = self.client.post( "/batch/", data={ "api_key": self.team.api_token, "batch": [data] }, content_type="application/json", ) arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate arguments.pop("sent_at") # can't compare fakedate self.assertDictEqual( arguments, { "distinct_id": "2", "ip": "127.0.0.1", "site_url": "http://testserver", "data": { **data, "properties": {} }, "team_id": self.team.pk, }, ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_batch_gzip_header(self, patch_process_event_with_plugins): data = { "api_key": self.team.api_token, "batch": [{ "type": "capture", "event": "user signed up", "distinct_id": "2", }], } response = self.client.generic( "POST", "/batch/", data=gzip.compress(json.dumps(data).encode()), content_type="application/json", HTTP_CONTENT_ENCODING="gzip", ) arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate arguments.pop("sent_at") # can't compare fakedate self.assertDictEqual( arguments, { "distinct_id": "2", "ip": "127.0.0.1", "site_url": "http://testserver", "data": { **data["batch"][0], "properties": {} }, "team_id": self.team.pk, }, ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_batch_gzip_param(self, patch_process_event_with_plugins): data = { "api_key": self.team.api_token, "batch": [{ "type": "capture", "event": "user signed up", "distinct_id": "2" }], } response = self.client.generic( "POST", "/batch/?compression=gzip", data=gzip.compress(json.dumps(data).encode()), content_type="application/json", ) arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate arguments.pop("sent_at") # can't compare fakedate self.assertDictEqual( arguments, { "distinct_id": "2", "ip": "127.0.0.1", "site_url": "http://testserver", "data": { **data["batch"][0], "properties": {} }, "team_id": self.team.pk, }, ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_batch_lzstring(self, patch_process_event_with_plugins): data = { "api_key": self.team.api_token, "batch": [{ "type": "capture", "event": "user signed up", "distinct_id": "2" }], } response = self.client.generic( "POST", "/batch", data=lzstring.LZString().compressToBase64( json.dumps(data)).encode(), content_type="application/json", HTTP_CONTENT_ENCODING="lz64", ) arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate arguments.pop("sent_at") # can't compare fakedate self.assertDictEqual( arguments, { "distinct_id": "2", "ip": "127.0.0.1", "site_url": "http://testserver", "data": { **data["batch"][0], "properties": {} }, "team_id": self.team.pk, }, ) @patch("posthog.api.capture.celery_app.send_task") def test_lz64_with_emoji(self, patch_process_event_with_plugins): self.team.api_token = "KZZZeIpycLH-tKobLBET2NOg7wgJF2KqDL5yWU_7tZw" self.team.save() response = self.client.post( "/batch", data= "NoKABBYN4EQKYDc4DsAuMBcYaD4NwyLswA0MADgE4D2JcZqAlnAM6bQwAkFzWMAsgIYBjMAHkAymAAaRdgCNKAd0Y0WMAMIALSgFs40tgICuZMilQB9IwBsV61KhIYA9I4CMAJgDsAOgAMvry4YABw+oY4AJnBaFHrqnOjc7t5+foEhoXokfKjqyHw6KhFRMcRschSKNGZIZIx0FMgsQQBspYwCJihm6nB0AOa2LC4+AKw+bR1wXfJ04TlDzSGllnQyKvJwa8ur1TR1DSou/j56dMhKtGaz6wBeAJ4GQagALPJ8buo3I8iLevQFWBczVGIxGAGYPABONxeMGQlzEcJ0Rj0ZACczXbg3OCQgBCyFxAlxAE1iQBBADSAC0ANYAVT4NIAKmDRC4eAA5AwAMUYABkAJIAcQMPCouOeZCCAFotAA1cLNeR6SIIOgCOBXcKHDwjSFBNyQnzA95BZ7SnxuAQjFwuABmYKCAg8bh8MqBYLgzRcIzc0pcfDgfD4Pn9uv1huNPhkwxGegMFy1KmxeIJRNJlNpDOZrPZXN5gpFYpIEqlsoVStOyDo9D4ljMJjtNBMZBsdgcziSxwCwVCPkclgofTOAH5kHAAB6oAC8jirNbodYbcCbxjOfTM4QoWj4Z0Onm7aT70hI8TiG5q+0aiQCzV80nUfEYZkYlkENLMGxkcQoNJYdrrJRSkEegkDMJtsiMTU7TfPouDAUBIGwED6nOaUDAnaVXWGdwYBAABdYhUF/FAVGpKkqTgAUSDuAQ+QACWlVAKQoGQ+VxABRJk3A5YQ+g8eQ+gAKW5NwKQARwAET5EY7gAdTpMwPFQKllQAX2ICg7TtJQEjAMFQmeNSCKAA==", content_type="application/json", HTTP_CONTENT_ENCODING="lz64", ) self.assertEqual(response.status_code, 200) arguments = self._to_arguments(patch_process_event_with_plugins) self.assertEqual(arguments["data"]["event"], "🤓") def test_batch_incorrect_token(self): response = self.client.post( "/batch/", data={ "api_key": "this-token-doesnt-exist", "batch": [ { "type": "capture", "event": "user signed up", "distinct_id": "whatever", }, ], }, content_type="application/json", ) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) self.assertEqual( response.json(), self.unauthenticated_response( "Project API key invalid. You can find your project API key in PostHog project settings.", code="invalid_api_key", ), ) def test_batch_token_not_set(self): response = self.client.post( "/batch/", data={ "batch": [ { "type": "capture", "event": "user signed up", "distinct_id": "whatever", }, ] }, content_type="application/json", ) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) self.assertEqual( response.json(), self.unauthenticated_response( "API key not provided. You can find your project API key in PostHog project settings.", code="missing_api_key", ), ) def test_batch_distinct_id_not_set(self): response = self.client.post( "/batch/", data={ "api_key": self.team.api_token, "batch": [ { "type": "capture", "event": "user signed up", }, ], }, content_type="application/json", ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json(), self.validation_error_response( "You need to set user distinct ID field `distinct_id`.", code="required", attr="distinct_id"), ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_engage(self, patch_process_event_with_plugins): response = self.client.get( "/engage/?data=%s" % quote( self._to_json({ "$set": { "$os": "Mac OS X", }, "$token": "token123", "$distinct_id": 3, "$device_id": "16fd4afae9b2d8-0fce8fe900d42b-39637c0e-7e9000-16fd4afae9c395", "$user_id": 3, })), content_type="application/json", HTTP_ORIGIN="https://localhost", ) arguments = self._to_arguments(patch_process_event_with_plugins) self.assertEqual(arguments["data"]["event"], "$identify") arguments.pop("now") # can't compare fakedate arguments.pop("sent_at") # can't compare fakedate arguments.pop("data") # can't compare fakedate self.assertDictEqual( arguments, { "distinct_id": "3", "ip": "127.0.0.1", "site_url": "http://testserver", "team_id": self.team.pk, }, ) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_python_library(self, patch_process_event_with_plugins): self.client.post( "/track/", data={ "data": self._dict_to_b64({ "event": "$pageview", "properties": { "distinct_id": "eeee", }, }), "api_key": self.team.api_token, # main difference in this test }, ) arguments = self._to_arguments(patch_process_event_with_plugins) self.assertEqual(arguments["team_id"], self.team.pk) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_base64_decode_variations(self, patch_process_event_with_plugins): base64 = "eyJldmVudCI6IiRwYWdldmlldyIsInByb3BlcnRpZXMiOnsiZGlzdGluY3RfaWQiOiJlZWVlZWVlZ8+lZWVlZWUifX0=" dict = self._dict_from_b64(base64) self.assertDictEqual( dict, { "event": "$pageview", "properties": { "distinct_id": "eeeeeeegϥeeeee", }, }, ) # POST with "+" in the base64 self.client.post( "/track/", data={ "data": base64, "api_key": self.team.api_token, }, # main difference in this test ) arguments = self._to_arguments(patch_process_event_with_plugins) self.assertEqual(arguments["team_id"], self.team.pk) self.assertEqual(arguments["distinct_id"], "eeeeeeegϥeeeee") # POST with " " in the base64 instead of the "+" self.client.post( "/track/", data={ "data": base64.replace("+", " "), "api_key": self.team.api_token, }, # main difference in this test ) arguments = self._to_arguments(patch_process_event_with_plugins) self.assertEqual(arguments["team_id"], self.team.pk) self.assertEqual(arguments["distinct_id"], "eeeeeeegϥeeeee") @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_js_library_underscore_sent_at(self, patch_process_event_with_plugins): now = timezone.now() tomorrow = now + timedelta(days=1, hours=2) tomorrow_sent_at = now + timedelta(days=1, hours=2, minutes=10) data = { "event": "movie played", "timestamp": tomorrow.isoformat(), "properties": { "distinct_id": 2, "token": self.team.api_token }, } self.client.get( "/e/?_=%s&data=%s" % (int(tomorrow_sent_at.timestamp()), quote(self._to_json(data))), content_type="application/json", HTTP_ORIGIN="https://localhost", ) arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate # right time sent as sent_at to process_event self.assertEqual(arguments["sent_at"].tzinfo, timezone.utc) timediff = arguments["sent_at"].timestamp( ) - tomorrow_sent_at.timestamp() self.assertLess(abs(timediff), 1) self.assertEqual(arguments["data"]["timestamp"], tomorrow.isoformat()) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_long_distinct_id(self, patch_process_event_with_plugins): now = timezone.now() tomorrow = now + timedelta(days=1, hours=2) tomorrow_sent_at = now + timedelta(days=1, hours=2, minutes=10) data = { "event": "movie played", "timestamp": tomorrow.isoformat(), "properties": { "distinct_id": "a" * 250, "token": self.team.api_token }, } self.client.get( "/e/?_=%s&data=%s" % (int(tomorrow_sent_at.timestamp()), quote(self._to_json(data))), content_type="application/json", HTTP_ORIGIN="https://localhost", ) arguments = self._to_arguments(patch_process_event_with_plugins) self.assertEqual(len(arguments["distinct_id"]), 200) @patch("posthog.models.team.TEAM_CACHE", {}) @patch("posthog.api.capture.celery_app.send_task") def test_sent_at_field(self, patch_process_event_with_plugins): now = timezone.now() tomorrow = now + timedelta(days=1, hours=2) tomorrow_sent_at = now + timedelta(days=1, hours=2, minutes=10) self.client.post( "/track", data={ "sent_at": tomorrow_sent_at.isoformat(), "data": self._dict_to_b64({ "event": "$pageview", "timestamp": tomorrow.isoformat(), "properties": { "distinct_id": "eeee", }, }), "api_key": self.team.api_token, # main difference in this test }, ) arguments = self._to_arguments(patch_process_event_with_plugins) arguments.pop("now") # can't compare fakedate # right time sent as sent_at to process_event timediff = arguments["sent_at"].timestamp( ) - tomorrow_sent_at.timestamp() self.assertLess(abs(timediff), 1) self.assertEqual(arguments["data"]["timestamp"], tomorrow.isoformat()) def test_incorrect_json(self): response = self.client.post( "/capture/", '{"event": "incorrect json with trailing comma",}', content_type="application/json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json(), self.validation_error_response( "Malformed request data: Invalid JSON: Expecting property name enclosed in double quotes: line 1 column 48 (char 47)", code="invalid_payload", ), ) def test_nan(self): response = self.client.post( "/track/", data={ "data": json.dumps([{ "event": "beep", "properties": { "distinct_id": float("nan") } }]), "api_key": self.team.api_token, }, ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json(), self.validation_error_response( "Distinct ID field `distinct_id` must have a non-empty value.", code="required", attr="distinct_id"), ) def test_distinct_id_set_but_null(self): response = self.client.post( "/e/", data={ "api_key": self.team.api_token, "type": "capture", "event": "user signed up", "distinct_id": None }, content_type="application/json", ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json(), self.validation_error_response( "Distinct ID field `distinct_id` must have a non-empty value.", code="required", attr="distinct_id"), ) @patch("posthog.api.capture.celery_app.send_task") def test_add_feature_flags_if_missing( self, patch_process_event_with_plugins) -> None: self.assertListEqual(self.team.event_properties_numerical, []) FeatureFlag.objects.create(team=self.team, created_by=self.user, key="test-ff", rollout_percentage=100) self.client.post( "/track/", data={ "data": json.dumps([{ "event": "purchase", "properties": { "distinct_id": "xxx", "$lib": "web" } }]), "api_key": self.team.api_token, }, ) arguments = self._to_arguments(patch_process_event_with_plugins) self.assertEqual( arguments["data"]["properties"]["$active_feature_flags"], ["test-ff"]) def test_handle_lacking_event_name_field(self): response = self.client.post( "/e/", data={ "distinct_id": "abc", "properties": { "cost": 2 }, "api_key": self.team.api_token }, content_type="application/json", ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json(), self.validation_error_response( 'Invalid payload: All events must have the event name field "event"!', code="invalid_payload", ), ) def test_handle_invalid_snapshot(self): response = self.client.post( "/e/", data={ "event": "$snapshot", "distinct_id": "abc", "api_key": self.team.api_token }, content_type="application/json", ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json(), self.validation_error_response( 'Invalid payload: $snapshot events must contain property "$snapshot_data"!', code="invalid_payload", ), ) def test_batch_request_with_invalid_auth(self): data = [{ "event": "$pageleave", "project_id": self.team.id, "properties": { "$os": "Linux", "$browser": "Chrome", "token": "fake token", # as this is invalid, will do API key authentication }, "timestamp": "2021-04-20T19:11:33.841Z", }] response = self.client.get("/e/?data=%s" % quote(self._to_json(data))) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) self.assertEqual( response.json(), { "type": "authentication_error", "code": "invalid_personal_api_key", "detail": "Invalid Personal API key.", "attr": None, }, )
class SimpleAPITests(unittest.TestCase): def setUp(self): User = get_user_model() self.password = '******' self.username = '******' self.device_uid = 'test-device' self.user = User(username=self.username, email='*****@*****.**') self.user.set_password(self.password) self.user.save() self.user.is_active = True self.client = Client() self.extra = { 'HTTP_AUTHORIZATION': create_auth_string(self.username, self.password) } self.formats = ['txt', 'json', 'jsonp', 'opml'] self.subscriptions_urls = dict( (fmt, self.get_subscriptions_url(fmt)) for fmt in self.formats) self.blank_values = { 'txt': b'\n', 'json': b'[]', 'opml': Exporter('Subscriptions').generate([]), } self.all_subscriptions_url = reverse( 'api-all-subscriptions', kwargs={ 'format': 'txt', 'username': self.user.username }, ) self.toplist_urls = dict( (fmt, self.get_toplist_url(fmt)) for fmt in self.formats) self.search_urls = dict( (fmt, self.get_search_url(fmt)) for fmt in self.formats) def tearDown(self): self.user.delete() def get_toplist_url(self, fmt): return reverse('api-simple-toplist-50', kwargs={'format': fmt}) def get_subscriptions_url(self, fmt): return reverse( 'api-simple-subscriptions', kwargs={ 'format': fmt, 'username': self.user.username, 'device_uid': self.device_uid, }, ) def get_search_url(self, fmt): return reverse('api-simple-search', kwargs={'format': fmt}) def _test_response_for_data(self, url, data, status_code, content): response = self.client.get(url, data) self.assertEqual(response.status_code, status_code) self.assertEqual(response.content, content) def test_get_subscriptions_empty(self): testers = { 'txt': lambda c: self.assertEqual(c, b''), 'json': lambda c: self.assertEqual(c, b'[]'), 'jsonp': lambda c: self.assertEqual(c, b'test([])'), 'opml': lambda c: self.assertListEqual(Importer(c).items, []), } for fmt in self.formats: url = self.subscriptions_urls[fmt] response = self.client.get(url, data={'jsonp': 'test'}, **self.extra) self.assertEqual(response.status_code, 200, response.content) testers[fmt](response.content) def test_get_subscriptions_invalid_jsonp(self): url = self.subscriptions_urls['jsonp'] response = self.client.get(url, data={'jsonp': '!'}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_subscriptions_with_content(self): sample_url = 'http://example.com/directory-podcast.xml' podcast = Podcast.objects.get_or_create_for_url(sample_url, defaults={ 'title': 'My Podcast' }).object with unittest.mock.patch( 'mygpo.users.models.Client.get_subscribed_podcasts' ) as mock_get: mock_get.return_value = [podcast] response = self.client.get(self.subscriptions_urls['txt'], **self.extra) self.assertEqual(response.status_code, 200, response.content) retrieved_urls = response.content.split(b'\n')[:-1] expected_urls = [sample_url.encode()] self.assertEqual(retrieved_urls, expected_urls) def test_post_subscription_valid(self): sample_url = 'http://example.com/directory-podcast.xml' podcast = Podcast.objects.get_or_create_for_url(sample_url, defaults={ 'title': 'My Podcast' }).object payloads = { 'txt': sample_url, 'json': json.dumps([sample_url]), #'opml': Exporter('Subscriptions').generate([sample_url]), 'opml': Exporter('Subscriptions').generate([podcast]), } payloads = dict( (fmt, format_podcast_list([podcast], fmt, 'test title').content) for fmt in self.formats) for fmt in self.formats: url = self.subscriptions_urls[fmt] payload = payloads[fmt] response = self.client.generic('POST', url, payload, **self.extra) self.assertEqual(response.status_code, 200, response.content) def test_post_subscription_invalid(self): url = self.subscriptions_urls['json'] payload = 'invalid_json' response = self.client.generic('POST', url, payload, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_all_subscriptions_invalid_scale(self): response = self.client.get(self.all_subscriptions_url, data={'scale_logo': 0}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_all_subscriptions_non_numeric_scale(self): response = self.client.get(self.all_subscriptions_url, data={'scale_logo': 'a'}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_all_subscriptions_valid_empty(self): response = self.client.get(self.all_subscriptions_url, **self.extra) self.assertEqual(response.status_code, 200, response.content) def test_get_toplist_invalid_scale(self): response = self.client.get(self.toplist_urls['opml'], data={'scale_logo': 0}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_toplist_non_numeric_scale(self): response = self.client.get(self.toplist_urls['txt'], data={'scale_logo': 'a'}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_toplist_valid_empty(self): response = self.client.get(self.toplist_urls['json'], **self.extra) self.assertEqual(response.status_code, 200, response.content) def test_search_non_numeric_scale_logo(self): data = {'scale_logo': 'a'} expected_status = 400 expected_content = b'scale_logo has to be a numeric value' self._test_response_for_data(self.search_urls['json'], data, expected_status, expected_content) def test_search_scale_out_of_range(self): data = {'scale_logo': 3000} expected_status = 400 expected_content = b'scale_logo has to be a number from 1 to 256' self._test_response_for_data(self.search_urls['opml'], data, expected_status, expected_content) def test_search_no_query(self): data = {'scale_logo': 1} expected_status = 400 expected_content = b'/search.opml|txt|json?q={query}' self._test_response_for_data(self.search_urls['opml'], data, expected_status, expected_content) def test_search_valid_query_status(self): data = {'scale_logo': 1, 'q': 'foo'} expected_status = 200 response = self.client.get(self.search_urls['json'], data) self.assertEqual(response.status_code, expected_status)
class SimpleAPITests(unittest.TestCase): def setUp(self): User = get_user_model() self.password = "******" self.username = "******" self.device_uid = "test-device" self.user = User(username=self.username, email="*****@*****.**") self.user.set_password(self.password) self.user.save() self.user.is_active = True self.client = Client() self.extra = { "HTTP_AUTHORIZATION": create_auth_string(self.username, self.password) } self.formats = ["txt", "json", "jsonp", "opml"] self.subscriptions_urls = dict( (fmt, self.get_subscriptions_url(fmt)) for fmt in self.formats) self.blank_values = { "txt": b"\n", "json": b"[]", "opml": Exporter("Subscriptions").generate([]), } self.all_subscriptions_url = reverse( "api-all-subscriptions", kwargs={ "format": "txt", "username": self.user.username }, ) self.toplist_urls = dict( (fmt, self.get_toplist_url(fmt)) for fmt in self.formats) self.search_urls = dict( (fmt, self.get_search_url(fmt)) for fmt in self.formats) def tearDown(self): self.user.delete() def get_toplist_url(self, fmt): return reverse("api-simple-toplist-50", kwargs={"format": fmt}) def get_subscriptions_url(self, fmt): return reverse( "api-simple-subscriptions", kwargs={ "format": fmt, "username": self.user.username, "device_uid": self.device_uid, }, ) def get_search_url(self, fmt): return reverse("api-simple-search", kwargs={"format": fmt}) def _test_response_for_data(self, url, data, status_code, content): response = self.client.get(url, data) self.assertEqual(response.status_code, status_code) self.assertEqual(response.content, content) def test_get_subscriptions_empty(self): testers = { "txt": lambda c: self.assertEqual(c, b""), "json": lambda c: self.assertEqual(c, b"[]"), "jsonp": lambda c: self.assertEqual(c, b"test([])"), "opml": lambda c: self.assertListEqual(Importer(c).items, []), } for fmt in self.formats: url = self.subscriptions_urls[fmt] response = self.client.get(url, data={"jsonp": "test"}, **self.extra) self.assertEqual(response.status_code, 200, response.content) testers[fmt](response.content) def test_get_subscriptions_invalid_jsonp(self): url = self.subscriptions_urls["jsonp"] response = self.client.get(url, data={"jsonp": "!"}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_subscriptions_with_content(self): sample_url = "http://example.com/directory-podcast.xml" podcast = Podcast.objects.get_or_create_for_url(sample_url, defaults={ "title": "My Podcast" }).object with unittest.mock.patch( "mygpo.users.models.Client.get_subscribed_podcasts" ) as mock_get: mock_get.return_value = [podcast] response = self.client.get(self.subscriptions_urls["txt"], **self.extra) self.assertEqual(response.status_code, 200, response.content) retrieved_urls = response.content.split(b"\n")[:-1] expected_urls = [sample_url.encode()] self.assertEqual(retrieved_urls, expected_urls) def test_post_subscription_valid(self): sample_url = "http://example.com/directory-podcast.xml" podcast = Podcast.objects.get_or_create_for_url(sample_url, defaults={ "title": "My Podcast" }).object payloads = { "txt": sample_url, "json": json.dumps([sample_url]), #'opml': Exporter('Subscriptions').generate([sample_url]), "opml": Exporter("Subscriptions").generate([podcast]), } payloads = dict( (fmt, format_podcast_list([podcast], fmt, "test title").content) for fmt in self.formats) for fmt in self.formats: url = self.subscriptions_urls[fmt] payload = payloads[fmt] response = self.client.generic("POST", url, payload, **self.extra) self.assertEqual(response.status_code, 200, response.content) def test_post_subscription_invalid(self): url = self.subscriptions_urls["json"] payload = "invalid_json" response = self.client.generic("POST", url, payload, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_all_subscriptions_invalid_scale(self): response = self.client.get(self.all_subscriptions_url, data={"scale_logo": 0}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_all_subscriptions_non_numeric_scale(self): response = self.client.get(self.all_subscriptions_url, data={"scale_logo": "a"}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_all_subscriptions_valid_empty(self): response = self.client.get(self.all_subscriptions_url, **self.extra) self.assertEqual(response.status_code, 200, response.content) def test_get_toplist_invalid_scale(self): response = self.client.get(self.toplist_urls["opml"], data={"scale_logo": 0}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_toplist_non_numeric_scale(self): response = self.client.get(self.toplist_urls["txt"], data={"scale_logo": "a"}, **self.extra) self.assertEqual(response.status_code, 400, response.content) def test_get_toplist_valid_empty(self): response = self.client.get(self.toplist_urls["json"], **self.extra) self.assertEqual(response.status_code, 200, response.content) def test_search_non_numeric_scale_logo(self): data = {"scale_logo": "a"} expected_status = 400 expected_content = b"scale_logo has to be a numeric value" self._test_response_for_data(self.search_urls["json"], data, expected_status, expected_content) def test_search_scale_out_of_range(self): data = {"scale_logo": 3000} expected_status = 400 expected_content = b"scale_logo has to be a number from 1 to 256" self._test_response_for_data(self.search_urls["opml"], data, expected_status, expected_content) def test_search_no_query(self): data = {"scale_logo": 1} expected_status = 400 expected_content = b"/search.opml|txt|json?q={query}" self._test_response_for_data(self.search_urls["opml"], data, expected_status, expected_content) def test_search_valid_query_status(self): data = {"scale_logo": 1, "q": "foo"} expected_status = 200 response = self.client.get(self.search_urls["json"], data) self.assertEqual(response.status_code, expected_status)