2020-08-12 00:24:40 +00:00
|
|
|
from asgiref.sync import async_to_sync
|
2020-10-09 16:11:24 +00:00
|
|
|
from urllib.parse import urlencode
|
2020-08-12 00:24:40 +00:00
|
|
|
import json
|
|
|
|
|
2020-10-09 16:11:24 +00:00
|
|
|
# These wrapper classes pre-date the introduction of
|
|
|
|
# datasette.client and httpx to Datasette. They could
|
|
|
|
# be removed if the Datasette tests are modified to
|
|
|
|
# call datasette.client directly.
|
|
|
|
|
2020-08-12 00:24:40 +00:00
|
|
|
|
|
|
|
class TestResponse:
|
2020-10-09 16:11:24 +00:00
|
|
|
def __init__(self, httpx_response):
|
|
|
|
self.httpx_response = httpx_response
|
|
|
|
|
|
|
|
@property
|
|
|
|
def status(self):
|
|
|
|
return self.httpx_response.status_code
|
|
|
|
|
2022-12-15 22:44:30 +00:00
|
|
|
# Supports both for test-writing convenience
|
|
|
|
@property
|
|
|
|
def status_code(self):
|
|
|
|
return self.status
|
|
|
|
|
2020-10-09 16:11:24 +00:00
|
|
|
@property
|
|
|
|
def headers(self):
|
|
|
|
return self.httpx_response.headers
|
|
|
|
|
|
|
|
@property
|
|
|
|
def body(self):
|
|
|
|
return self.httpx_response.content
|
2020-08-12 00:24:40 +00:00
|
|
|
|
2022-12-15 22:44:30 +00:00
|
|
|
@property
|
|
|
|
def content(self):
|
|
|
|
return self.body
|
|
|
|
|
2020-08-12 00:24:40 +00:00
|
|
|
@property
|
|
|
|
def cookies(self):
|
2020-10-09 16:11:24 +00:00
|
|
|
return dict(self.httpx_response.cookies)
|
|
|
|
|
2020-08-12 00:24:40 +00:00
|
|
|
@property
|
|
|
|
def json(self):
|
|
|
|
return json.loads(self.text)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def text(self):
|
|
|
|
return self.body.decode("utf8")
|
|
|
|
|
|
|
|
|
|
|
|
class TestClient:
|
|
|
|
max_redirects = 5
|
|
|
|
|
2020-10-09 16:11:24 +00:00
|
|
|
def __init__(self, ds):
|
|
|
|
self.ds = ds
|
2020-08-12 00:24:40 +00:00
|
|
|
|
|
|
|
def actor_cookie(self, actor):
|
|
|
|
return self.ds.sign({"a": actor}, "actor")
|
|
|
|
|
|
|
|
@async_to_sync
|
|
|
|
async def get(
|
2022-05-03 14:59:18 +00:00
|
|
|
self,
|
|
|
|
path,
|
|
|
|
follow_redirects=False,
|
|
|
|
redirect_count=0,
|
|
|
|
method="GET",
|
|
|
|
cookies=None,
|
|
|
|
if_none_match=None,
|
2022-10-26 02:18:41 +00:00
|
|
|
headers=None,
|
2020-08-12 00:24:40 +00:00
|
|
|
):
|
|
|
|
return await self._request(
|
2022-05-03 14:59:18 +00:00
|
|
|
path=path,
|
|
|
|
follow_redirects=follow_redirects,
|
|
|
|
redirect_count=redirect_count,
|
|
|
|
method=method,
|
|
|
|
cookies=cookies,
|
|
|
|
if_none_match=if_none_match,
|
2022-10-26 02:18:41 +00:00
|
|
|
headers=headers,
|
2020-08-12 00:24:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
@async_to_sync
|
|
|
|
async def post(
|
|
|
|
self,
|
|
|
|
path,
|
|
|
|
post_data=None,
|
2020-09-14 20:18:15 +00:00
|
|
|
body=None,
|
2021-10-14 18:03:44 +00:00
|
|
|
follow_redirects=False,
|
2020-08-12 00:24:40 +00:00
|
|
|
redirect_count=0,
|
|
|
|
content_type="application/x-www-form-urlencoded",
|
|
|
|
cookies=None,
|
2020-09-14 21:23:18 +00:00
|
|
|
headers=None,
|
2020-08-12 00:24:40 +00:00
|
|
|
csrftoken_from=None,
|
|
|
|
):
|
|
|
|
cookies = cookies or {}
|
|
|
|
post_data = post_data or {}
|
2020-09-14 20:18:15 +00:00
|
|
|
assert not (post_data and body), "Provide one or other of body= or post_data="
|
2020-08-12 00:24:40 +00:00
|
|
|
# Maybe fetch a csrftoken first
|
|
|
|
if csrftoken_from is not None:
|
2020-09-14 20:18:15 +00:00
|
|
|
assert body is None, "body= is not compatible with csrftoken_from="
|
2020-08-12 00:24:40 +00:00
|
|
|
if csrftoken_from is True:
|
|
|
|
csrftoken_from = path
|
|
|
|
token_response = await self._request(csrftoken_from, cookies=cookies)
|
|
|
|
csrftoken = token_response.cookies["ds_csrftoken"]
|
|
|
|
cookies["ds_csrftoken"] = csrftoken
|
|
|
|
post_data["csrftoken"] = csrftoken
|
2020-09-14 20:18:15 +00:00
|
|
|
if post_data:
|
|
|
|
body = urlencode(post_data, doseq=True)
|
2020-08-12 00:24:40 +00:00
|
|
|
return await self._request(
|
2020-09-14 21:23:18 +00:00
|
|
|
path=path,
|
2021-10-14 18:03:44 +00:00
|
|
|
follow_redirects=follow_redirects,
|
2020-09-14 21:23:18 +00:00
|
|
|
redirect_count=redirect_count,
|
|
|
|
method="POST",
|
|
|
|
cookies=cookies,
|
|
|
|
headers=headers,
|
|
|
|
post_body=body,
|
|
|
|
content_type=content_type,
|
2020-08-12 00:24:40 +00:00
|
|
|
)
|
|
|
|
|
2020-12-03 00:44:03 +00:00
|
|
|
@async_to_sync
|
|
|
|
async def request(
|
|
|
|
self,
|
|
|
|
path,
|
2021-10-14 18:03:44 +00:00
|
|
|
follow_redirects=True,
|
2020-12-03 00:44:03 +00:00
|
|
|
redirect_count=0,
|
|
|
|
method="GET",
|
|
|
|
cookies=None,
|
|
|
|
headers=None,
|
|
|
|
post_body=None,
|
|
|
|
content_type=None,
|
2022-05-03 14:59:18 +00:00
|
|
|
if_none_match=None,
|
2020-12-03 00:44:03 +00:00
|
|
|
):
|
|
|
|
return await self._request(
|
|
|
|
path,
|
2021-10-14 18:03:44 +00:00
|
|
|
follow_redirects=follow_redirects,
|
2020-12-03 00:44:03 +00:00
|
|
|
redirect_count=redirect_count,
|
|
|
|
method=method,
|
|
|
|
cookies=cookies,
|
|
|
|
headers=headers,
|
|
|
|
post_body=post_body,
|
|
|
|
content_type=content_type,
|
2022-05-03 14:59:18 +00:00
|
|
|
if_none_match=if_none_match,
|
2020-12-03 00:44:03 +00:00
|
|
|
)
|
|
|
|
|
2020-08-12 00:24:40 +00:00
|
|
|
async def _request(
|
|
|
|
self,
|
|
|
|
path,
|
2021-10-14 18:03:44 +00:00
|
|
|
follow_redirects=True,
|
2020-08-12 00:24:40 +00:00
|
|
|
redirect_count=0,
|
|
|
|
method="GET",
|
|
|
|
cookies=None,
|
2020-09-14 21:23:18 +00:00
|
|
|
headers=None,
|
2020-09-14 20:18:15 +00:00
|
|
|
post_body=None,
|
2020-08-12 00:24:40 +00:00
|
|
|
content_type=None,
|
2022-05-03 14:59:18 +00:00
|
|
|
if_none_match=None,
|
2020-08-12 00:24:40 +00:00
|
|
|
):
|
2022-09-17 03:38:15 +00:00
|
|
|
await self.ds.invoke_startup()
|
2020-10-09 16:11:24 +00:00
|
|
|
headers = headers or {}
|
2020-08-12 00:24:40 +00:00
|
|
|
if content_type:
|
2020-10-09 16:11:24 +00:00
|
|
|
headers["content-type"] = content_type
|
2022-05-03 14:59:18 +00:00
|
|
|
if if_none_match:
|
|
|
|
headers["if-none-match"] = if_none_match
|
2020-10-09 16:11:24 +00:00
|
|
|
httpx_response = await self.ds.client.request(
|
|
|
|
method,
|
|
|
|
path,
|
2021-10-14 18:03:44 +00:00
|
|
|
follow_redirects=follow_redirects,
|
2021-06-05 18:59:54 +00:00
|
|
|
avoid_path_rewrites=True,
|
2020-10-09 16:11:24 +00:00
|
|
|
cookies=cookies,
|
|
|
|
headers=headers,
|
|
|
|
content=post_body,
|
2020-08-12 00:24:40 +00:00
|
|
|
)
|
2020-10-09 16:11:24 +00:00
|
|
|
response = TestResponse(httpx_response)
|
2021-10-14 18:03:44 +00:00
|
|
|
if follow_redirects and response.status in (301, 302):
|
2020-08-12 00:24:40 +00:00
|
|
|
assert (
|
|
|
|
redirect_count < self.max_redirects
|
2020-11-15 23:24:22 +00:00
|
|
|
), f"Redirected {redirect_count} times, max_redirects={self.max_redirects}"
|
2020-08-12 00:24:40 +00:00
|
|
|
location = response.headers["Location"]
|
|
|
|
return await self._request(
|
2021-10-14 18:03:44 +00:00
|
|
|
location, follow_redirects=True, redirect_count=redirect_count + 1
|
2020-08-12 00:24:40 +00:00
|
|
|
)
|
|
|
|
return response
|