kopia lustrzana https://github.com/simonw/datasette
Ported test_api.py app_client test to ds_client, refs #1959
rodzic
5ee954e34b
commit
b077e63dc6
|
@ -281,7 +281,7 @@ class Datasette:
|
||||||
raise
|
raise
|
||||||
self.crossdb = crossdb
|
self.crossdb = crossdb
|
||||||
self.nolock = nolock
|
self.nolock = nolock
|
||||||
if memory or crossdb or not self.files:
|
if memory or crossdb or (not self.files and memory is not False):
|
||||||
self.add_database(
|
self.add_database(
|
||||||
Database(self, is_mutable=False, is_memory=True), name="_memory"
|
Database(self, is_mutable=False, is_memory=True), name="_memory"
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,4 +8,5 @@ filterwarnings=
|
||||||
ignore:.*current_task.*:PendingDeprecationWarning
|
ignore:.*current_task.*:PendingDeprecationWarning
|
||||||
markers =
|
markers =
|
||||||
serial: tests to avoid using with pytest-xdist
|
serial: tests to avoid using with pytest-xdist
|
||||||
|
ds_client: tests using the ds_client fixture
|
||||||
asyncio_mode = strict
|
asyncio_mode = strict
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import asyncio
|
||||||
import httpx
|
import httpx
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import pytest
|
import pytest
|
||||||
|
import pytest_asyncio
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -23,6 +25,43 @@ UNDOCUMENTED_PERMISSIONS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def event_loop():
|
||||||
|
return asyncio.get_event_loop()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_asyncio.fixture(scope="session")
|
||||||
|
async def ds_client():
|
||||||
|
from datasette.app import Datasette
|
||||||
|
from .fixtures import METADATA, PLUGINS_DIR
|
||||||
|
|
||||||
|
ds = Datasette(
|
||||||
|
memory=False,
|
||||||
|
metadata=METADATA,
|
||||||
|
plugins_dir=PLUGINS_DIR,
|
||||||
|
settings={
|
||||||
|
"default_page_size": 50,
|
||||||
|
"max_returned_rows": 100,
|
||||||
|
"sql_time_limit_ms": 200,
|
||||||
|
# Default is 3 but this results in "too many open files"
|
||||||
|
# errors when running the full test suite:
|
||||||
|
"num_sql_threads": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
from .fixtures import TABLES, TABLE_PARAMETERIZED_SQL
|
||||||
|
|
||||||
|
db = ds.add_memory_database("fixtures")
|
||||||
|
|
||||||
|
def prepare(conn):
|
||||||
|
conn.executescript(TABLES)
|
||||||
|
for sql, params in TABLE_PARAMETERIZED_SQL:
|
||||||
|
with conn:
|
||||||
|
conn.execute(sql, params)
|
||||||
|
|
||||||
|
await db.execute_write_fn(prepare)
|
||||||
|
return ds.client
|
||||||
|
|
||||||
|
|
||||||
def pytest_report_header(config):
|
def pytest_report_header(config):
|
||||||
return "SQLite: {}".format(
|
return "SQLite: {}".format(
|
||||||
sqlite3.connect(":memory:").execute("select sqlite_version()").fetchone()[0]
|
sqlite3.connect(":memory:").execute("select sqlite_version()").fetchone()[0]
|
||||||
|
|
|
@ -23,12 +23,15 @@ import sys
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
|
|
||||||
def test_homepage(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/.json")
|
@pytest.mark.asyncio
|
||||||
assert response.status == 200
|
async def test_homepage(ds_client):
|
||||||
|
response = await ds_client.get("/.json")
|
||||||
|
assert response.status_code == 200
|
||||||
assert "application/json; charset=utf-8" == response.headers["content-type"]
|
assert "application/json; charset=utf-8" == response.headers["content-type"]
|
||||||
assert response.json.keys() == {"fixtures": 0}.keys()
|
data = response.json()
|
||||||
d = response.json["fixtures"]
|
assert data.keys() == {"fixtures": 0}.keys()
|
||||||
|
d = data["fixtures"]
|
||||||
assert d["name"] == "fixtures"
|
assert d["name"] == "fixtures"
|
||||||
assert d["tables_count"] == 24
|
assert d["tables_count"] == 24
|
||||||
assert len(d["tables_and_views_truncated"]) == 5
|
assert len(d["tables_and_views_truncated"]) == 5
|
||||||
|
@ -36,15 +39,17 @@ def test_homepage(app_client):
|
||||||
# 4 hidden FTS tables + no_primary_key (hidden in metadata)
|
# 4 hidden FTS tables + no_primary_key (hidden in metadata)
|
||||||
assert d["hidden_tables_count"] == 6
|
assert d["hidden_tables_count"] == 6
|
||||||
# 201 in no_primary_key, plus 6 in other hidden tables:
|
# 201 in no_primary_key, plus 6 in other hidden tables:
|
||||||
assert d["hidden_table_rows_sum"] == 207, response.json
|
assert d["hidden_table_rows_sum"] == 207, data
|
||||||
assert d["views_count"] == 4
|
assert d["views_count"] == 4
|
||||||
|
|
||||||
|
|
||||||
def test_homepage_sort_by_relationships(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/.json?_sort=relationships")
|
@pytest.mark.asyncio
|
||||||
assert response.status == 200
|
async def test_homepage_sort_by_relationships(ds_client):
|
||||||
|
response = await ds_client.get("/.json?_sort=relationships")
|
||||||
|
assert response.status_code == 200
|
||||||
tables = [
|
tables = [
|
||||||
t["name"] for t in response.json["fixtures"]["tables_and_views_truncated"]
|
t["name"] for t in response.json()["fixtures"]["tables_and_views_truncated"]
|
||||||
]
|
]
|
||||||
assert tables == [
|
assert tables == [
|
||||||
"simple_primary_key",
|
"simple_primary_key",
|
||||||
|
@ -55,10 +60,12 @@ def test_homepage_sort_by_relationships(app_client):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_database_page(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/fixtures.json")
|
@pytest.mark.asyncio
|
||||||
assert response.status == 200
|
async def test_database_page(ds_client):
|
||||||
data = response.json
|
response = await ds_client.get("/fixtures.json")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
assert data["database"] == "fixtures"
|
assert data["database"] == "fixtures"
|
||||||
assert data["tables"] == [
|
assert data["tables"] == [
|
||||||
{
|
{
|
||||||
|
@ -633,11 +640,13 @@ def test_database_page_for_database_with_dot_in_name(app_client_with_dot):
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
def test_custom_sql(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get(
|
@pytest.mark.asyncio
|
||||||
|
async def test_custom_sql(ds_client):
|
||||||
|
response = await ds_client.get(
|
||||||
"/fixtures.json?sql=select+content+from+simple_primary_key&_shape=objects"
|
"/fixtures.json?sql=select+content+from+simple_primary_key&_shape=objects"
|
||||||
)
|
)
|
||||||
data = response.json
|
data = response.json()
|
||||||
assert {"sql": "select content from simple_primary_key", "params": {}} == data[
|
assert {"sql": "select content from simple_primary_key", "params": {}} == data[
|
||||||
"query"
|
"query"
|
||||||
]
|
]
|
||||||
|
@ -673,41 +682,51 @@ def test_sql_time_limit(app_client_shorter_time_limit):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_custom_sql_time_limit(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/fixtures.json?sql=select+sleep(0.01)")
|
@pytest.mark.asyncio
|
||||||
assert 200 == response.status
|
async def test_custom_sql_time_limit(ds_client):
|
||||||
response = app_client.get("/fixtures.json?sql=select+sleep(0.01)&_timelimit=5")
|
response = await ds_client.get("/fixtures.json?sql=select+sleep(0.01)")
|
||||||
assert 400 == response.status
|
assert response.status_code == 200
|
||||||
assert "SQL Interrupted" == response.json["title"]
|
response = await ds_client.get("/fixtures.json?sql=select+sleep(0.01)&_timelimit=5")
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response.json()["title"] == "SQL Interrupted"
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_custom_sql(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/fixtures.json?sql=.schema")
|
@pytest.mark.asyncio
|
||||||
assert response.status == 400
|
async def test_invalid_custom_sql(ds_client):
|
||||||
assert response.json["ok"] is False
|
response = await ds_client.get("/fixtures.json?sql=.schema")
|
||||||
assert "Statement must be a SELECT" == response.json["error"]
|
assert response.status_code == 400
|
||||||
|
assert response.json()["ok"] is False
|
||||||
|
assert "Statement must be a SELECT" == response.json()["error"]
|
||||||
|
|
||||||
|
|
||||||
def test_row(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/fixtures/simple_primary_key/1.json?_shape=objects")
|
@pytest.mark.asyncio
|
||||||
assert response.status == 200
|
async def test_row(ds_client):
|
||||||
assert [{"id": "1", "content": "hello"}] == response.json["rows"]
|
response = await ds_client.get("/fixtures/simple_primary_key/1.json?_shape=objects")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json()["rows"] == [{"id": "1", "content": "hello"}]
|
||||||
|
|
||||||
|
|
||||||
def test_row_strange_table_name(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get(
|
@pytest.mark.asyncio
|
||||||
|
async def test_row_strange_table_name(ds_client):
|
||||||
|
response = await ds_client.get(
|
||||||
"/fixtures/table~2Fwith~2Fslashes~2Ecsv/3.json?_shape=objects"
|
"/fixtures/table~2Fwith~2Fslashes~2Ecsv/3.json?_shape=objects"
|
||||||
)
|
)
|
||||||
assert response.status == 200
|
assert response.status_code == 200
|
||||||
assert [{"pk": "3", "content": "hey"}] == response.json["rows"]
|
assert response.json()["rows"] == [{"pk": "3", "content": "hey"}]
|
||||||
|
|
||||||
|
|
||||||
def test_row_foreign_key_tables(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get(
|
@pytest.mark.asyncio
|
||||||
|
async def test_row_foreign_key_tables(ds_client):
|
||||||
|
response = await ds_client.get(
|
||||||
"/fixtures/simple_primary_key/1.json?_extras=foreign_key_tables"
|
"/fixtures/simple_primary_key/1.json?_extras=foreign_key_tables"
|
||||||
)
|
)
|
||||||
assert response.status == 200
|
assert response.status_code == 200
|
||||||
assert response.json["foreign_key_tables"] == [
|
assert response.json()["foreign_key_tables"] == [
|
||||||
{
|
{
|
||||||
"other_table": "foreign_key_references",
|
"other_table": "foreign_key_references",
|
||||||
"column": "id",
|
"column": "id",
|
||||||
|
@ -762,47 +781,58 @@ def test_databases_json(app_client_two_attached_databases_one_immutable):
|
||||||
assert False == fixtures_database["is_memory"]
|
assert False == fixtures_database["is_memory"]
|
||||||
|
|
||||||
|
|
||||||
def test_metadata_json(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/-/metadata.json")
|
@pytest.mark.asyncio
|
||||||
assert METADATA == response.json
|
async def test_metadata_json(ds_client):
|
||||||
|
response = await ds_client.get("/-/metadata.json")
|
||||||
|
assert response.json() == METADATA
|
||||||
|
|
||||||
|
|
||||||
def test_threads_json(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/-/threads.json")
|
@pytest.mark.asyncio
|
||||||
|
async def test_threads_json(ds_client):
|
||||||
|
response = await ds_client.get("/-/threads.json")
|
||||||
expected_keys = {"threads", "num_threads"}
|
expected_keys = {"threads", "num_threads"}
|
||||||
if sys.version_info >= (3, 7, 0):
|
if sys.version_info >= (3, 7, 0):
|
||||||
expected_keys.update({"tasks", "num_tasks"})
|
expected_keys.update({"tasks", "num_tasks"})
|
||||||
assert expected_keys == set(response.json.keys())
|
assert set(response.json().keys()) == expected_keys
|
||||||
|
|
||||||
|
|
||||||
def test_plugins_json(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/-/plugins.json")
|
@pytest.mark.asyncio
|
||||||
assert EXPECTED_PLUGINS == sorted(response.json, key=lambda p: p["name"])
|
async def test_plugins_json(ds_client):
|
||||||
|
response = await ds_client.get("/-/plugins.json")
|
||||||
|
assert EXPECTED_PLUGINS == sorted(response.json(), key=lambda p: p["name"])
|
||||||
# Try with ?all=1
|
# Try with ?all=1
|
||||||
response = app_client.get("/-/plugins.json?all=1")
|
response = await ds_client.get("/-/plugins.json?all=1")
|
||||||
names = {p["name"] for p in response.json}
|
names = {p["name"] for p in response.json()}
|
||||||
assert names.issuperset(p["name"] for p in EXPECTED_PLUGINS)
|
assert names.issuperset(p["name"] for p in EXPECTED_PLUGINS)
|
||||||
assert names.issuperset(DEFAULT_PLUGINS)
|
assert names.issuperset(DEFAULT_PLUGINS)
|
||||||
|
|
||||||
|
|
||||||
def test_versions_json(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/-/versions.json")
|
@pytest.mark.asyncio
|
||||||
assert "python" in response.json
|
async def test_versions_json(ds_client):
|
||||||
assert "3.0" == response.json.get("asgi")
|
response = await ds_client.get("/-/versions.json")
|
||||||
assert "version" in response.json["python"]
|
data = response.json()
|
||||||
assert "full" in response.json["python"]
|
assert "python" in data
|
||||||
assert "datasette" in response.json
|
assert "3.0" == data.get("asgi")
|
||||||
assert "version" in response.json["datasette"]
|
assert "version" in data["python"]
|
||||||
assert response.json["datasette"]["version"] == __version__
|
assert "full" in data["python"]
|
||||||
assert "sqlite" in response.json
|
assert "datasette" in data
|
||||||
assert "version" in response.json["sqlite"]
|
assert "version" in data["datasette"]
|
||||||
assert "fts_versions" in response.json["sqlite"]
|
assert data["datasette"]["version"] == __version__
|
||||||
assert "compile_options" in response.json["sqlite"]
|
assert "sqlite" in data
|
||||||
|
assert "version" in data["sqlite"]
|
||||||
|
assert "fts_versions" in data["sqlite"]
|
||||||
|
assert "compile_options" in data["sqlite"]
|
||||||
|
|
||||||
|
|
||||||
def test_settings_json(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.get("/-/settings.json")
|
@pytest.mark.asyncio
|
||||||
assert {
|
async def test_settings_json(ds_client):
|
||||||
|
response = await ds_client.get("/-/settings.json")
|
||||||
|
assert response.json() == {
|
||||||
"default_page_size": 50,
|
"default_page_size": 50,
|
||||||
"default_facet_size": 30,
|
"default_facet_size": 30,
|
||||||
"facet_suggest_time_limit_ms": 50,
|
"facet_suggest_time_limit_ms": 50,
|
||||||
|
@ -825,9 +855,11 @@ def test_settings_json(app_client):
|
||||||
"template_debug": False,
|
"template_debug": False,
|
||||||
"trace_debug": False,
|
"trace_debug": False,
|
||||||
"base_url": "/",
|
"base_url": "/",
|
||||||
} == response.json
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.ds_client
|
||||||
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path,expected_redirect",
|
"path,expected_redirect",
|
||||||
(
|
(
|
||||||
|
@ -835,9 +867,9 @@ def test_settings_json(app_client):
|
||||||
("/-/config", "/-/settings"),
|
("/-/config", "/-/settings"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_config_redirects_to_settings(app_client, path, expected_redirect):
|
async def test_config_redirects_to_settings(ds_client, path, expected_redirect):
|
||||||
response = app_client.get(path)
|
response = await ds_client.get(path)
|
||||||
assert response.status == 301
|
assert response.status_code == 301
|
||||||
assert response.headers["Location"] == expected_redirect
|
assert response.headers["Location"] == expected_redirect
|
||||||
|
|
||||||
|
|
||||||
|
@ -846,6 +878,8 @@ test_json_columns_default_expected = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.ds_client
|
||||||
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"extra_args,expected",
|
"extra_args,expected",
|
||||||
[
|
[
|
||||||
|
@ -859,15 +893,15 @@ test_json_columns_default_expected = [
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_json_columns(app_client, extra_args, expected):
|
async def test_json_columns(ds_client, extra_args, expected):
|
||||||
sql = """
|
sql = """
|
||||||
select 1 as intval, "s" as strval, 0.5 as floatval,
|
select 1 as intval, "s" as strval, 0.5 as floatval,
|
||||||
'{"foo": "bar"}' as jsonval
|
'{"foo": "bar"}' as jsonval
|
||||||
"""
|
"""
|
||||||
path = "/fixtures.json?" + urllib.parse.urlencode({"sql": sql, "_shape": "array"})
|
path = "/fixtures.json?" + urllib.parse.urlencode({"sql": sql, "_shape": "array"})
|
||||||
path += extra_args
|
path += extra_args
|
||||||
response = app_client.get(path)
|
response = await ds_client.get(path)
|
||||||
assert expected == response.json
|
assert response.json() == expected
|
||||||
|
|
||||||
|
|
||||||
def test_config_cache_size(app_client_larger_cache_size):
|
def test_config_cache_size(app_client_larger_cache_size):
|
||||||
|
@ -966,9 +1000,11 @@ def test_inspect_file_used_for_count(app_client_immutable_and_inspect_file):
|
||||||
assert response.json["filtered_table_rows_count"] == 100
|
assert response.json["filtered_table_rows_count"] == 100
|
||||||
|
|
||||||
|
|
||||||
def test_http_options_request(app_client):
|
@pytest.mark.ds_client
|
||||||
response = app_client.request("/fixtures", method="OPTIONS")
|
@pytest.mark.asyncio
|
||||||
assert response.status == 200
|
async def test_http_options_request(ds_client):
|
||||||
|
response = await ds_client.options("/fixtures")
|
||||||
|
assert response.status_code == 200
|
||||||
assert response.text == "ok"
|
assert response.text == "ok"
|
||||||
|
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue