datasette.client internal requests mechanism

Closes #943

* Datasette now requires httpx>=0.15
* Support OPTIONS without 500, closes #1001
* Added internals tests for datasette.client methods
* Datasette's own test mechanism now uses httpx to simulate requests
* Tests simulate HTTP 1.1 now
* Added base_url in a bunch more places
* Mark some tests as xfail - will remove that when new httpx release ships: #1005
This commit is contained in:
Simon Willison 2020-10-09 09:11:24 -07:00 committed by GitHub
commit 8f97b9b58e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 163 additions and 100 deletions

View file

@ -144,9 +144,7 @@ def make_app_client(
template_dir=template_dir,
)
ds.sqlite_functions.append(("sleep", 1, lambda n: time.sleep(float(n))))
client = TestClient(ds.app())
client.ds = ds
yield client
yield TestClient(ds)
@pytest.fixture(scope="session")
@ -158,9 +156,7 @@ def app_client():
@pytest.fixture(scope="session")
def app_client_no_files():
ds = Datasette([])
client = TestClient(ds.app())
client.ds = ds
yield client
yield TestClient(ds)
@pytest.fixture(scope="session")

View file

@ -739,6 +739,7 @@ def test_table_shape_object_compound_primary_Key(app_client):
assert {"a,b": {"pk1": "a", "pk2": "b", "content": "c"}} == response.json
@pytest.mark.xfail
def test_table_with_slashes_in_name(app_client):
response = app_client.get(
"/fixtures/table%2Fwith%2Fslashes.csv?_shape=objects&_format=json"
@ -1186,6 +1187,7 @@ def test_row_format_in_querystring(app_client):
assert [{"id": "1", "content": "hello"}] == response.json["rows"]
@pytest.mark.xfail
def test_row_strange_table_name(app_client):
response = app_client.get(
"/fixtures/table%2Fwith%2Fslashes.csv/3.json?_shape=objects"

View file

@ -87,7 +87,8 @@ def test_logout(app_client):
cookies={"ds_actor": app_client.actor_cookie({"id": "test"})},
allow_redirects=False,
)
assert "" == response4.cookies["ds_actor"]
# The ds_actor cookie should have been unset
assert response4.cookie_was_deleted("ds_actor")
# Should also have set a message
messages = app_client.ds.unsign(response4.cookies["ds_messages"], "messages")
assert [["You are now logged out", 2]] == messages

View file

@ -108,8 +108,7 @@ def test_metadata_yaml():
open_browser=False,
return_instance=True,
)
client = _TestClient(ds.app())
client.ds = ds
client = _TestClient(ds)
response = client.get("/-/metadata.json")
assert {"title": "Hello from YAML"} == response.json

View file

@ -76,9 +76,7 @@ def config_dir_client(tmp_path_factory):
)
ds = Datasette([], config_dir=config_dir)
client = _TestClient(ds.app())
client.ds = ds
yield client
yield _TestClient(ds)
def test_metadata(config_dir_client):
@ -137,8 +135,7 @@ def test_metadata_yaml(tmp_path_factory, filename):
config_dir = tmp_path_factory.mktemp("yaml-config-dir")
(config_dir / filename).write_text("title: Title from metadata", "utf-8")
ds = Datasette([], config_dir=config_dir)
client = _TestClient(ds.app())
client.ds = ds
client = _TestClient(ds)
response = client.get("/-/metadata.json")
assert 200 == response.status
assert {"title": "Title from metadata"} == response.json

View file

@ -142,6 +142,7 @@ def test_row_redirects_with_url_hash(app_client_with_hash):
assert response.status == 200
@pytest.mark.xfail
def test_row_strange_table_name_with_url_hash(app_client_with_hash):
response = app_client_with_hash.get(
"/fixtures/table%2Fwith%2Fslashes.csv/3", allow_redirects=False
@ -535,6 +536,7 @@ def test_facets_persist_through_filter_form(app_client):
]
@pytest.mark.xfail
@pytest.mark.parametrize(
"path,expected_classes",
[
@ -566,6 +568,7 @@ def test_css_classes_on_body(app_client, path, expected_classes):
assert classes == expected_classes
@pytest.mark.xfail
@pytest.mark.parametrize(
"path,expected_considered",
[

View file

@ -0,0 +1,44 @@
from .fixtures import app_client
import httpx
import pytest
@pytest.fixture
def datasette(app_client):
return app_client.ds
@pytest.mark.asyncio
@pytest.mark.parametrize(
"method,path,expected_status",
[
("get", "/", 200),
("options", "/", 405),
("head", "/", 200),
("put", "/", 405),
("patch", "/", 405),
("delete", "/", 405),
],
)
async def test_client_methods(datasette, method, path, expected_status):
client_method = getattr(datasette.client, method)
response = await client_method(path)
assert isinstance(response, httpx.Response)
assert response.status_code == expected_status
# Try that again using datasette.client.request
response2 = await datasette.client.request(method, path)
assert response2.status_code == expected_status
@pytest.mark.asyncio
async def test_client_post(datasette):
response = await datasette.client.post(
"/-/messages",
data={
"message": "A message",
},
allow_redirects=False,
)
assert isinstance(response, httpx.Response)
assert response.status_code == 302
assert "ds_messages" in response.cookies

View file

@ -25,4 +25,4 @@ def test_messages_are_displayed_and_cleared(app_client):
# Messages should be in that HTML
assert "xmessagex" in response.text
# Cookie should have been set that clears messages
assert "" == response.cookies["ds_messages"]
assert response.cookie_was_deleted("ds_messages")

View file

@ -380,9 +380,7 @@ def view_names_client(tmp_path_factory):
conn = sqlite3.connect(db_path)
conn.executescript(TABLES)
return _TestClient(
Datasette(
[db_path], template_dir=str(templates), plugins_dir=str(plugins)
).app()
Datasette([db_path], template_dir=str(templates), plugins_dir=str(plugins))
)
@ -748,7 +746,7 @@ def test_hook_register_magic_parameters(restore_working_directory):
response = client.post("/data/runme", {}, csrftoken_from=True)
assert 200 == response.status
actual = client.get("/data/logs.json?_sort_desc=rowid&_shape=array").json
assert [{"rowid": 1, "line": "1.0"}] == actual
assert [{"rowid": 1, "line": "1.1"}] == actual
# Now try the GET request against get_uuid
response_get = client.get("/data/get_uuid.json?_shape=array")
assert 200 == response_get.status