simonw / datasette-graphql

Datasette plugin providing an automatic GraphQL API for your SQLite databases
https://datasette-graphql-demo.datasette.io/
Apache License 2.0
100 stars 6 forks source link

Tests are broken against Datasette 0.54 #71

Closed simonw closed 3 years ago

simonw commented 3 years ago

The test suite is failing at the moment.

simonw commented 3 years ago
=================================================================================================== FAILURES ====================================================================================================
_______________________________________________________________________________________________ test_table_action _______________________________________________________________________________________________

db_path = PosixPath('/private/var/folders/wr/hn3206rs1yzgq3r49bz8nvnh0000gn/T/pytest-of-simon/pytest-345/dbs0/test.db')

    @pytest.mark.asyncio
    async def test_table_action(db_path):
        ds = Datasette([db_path])
        response = await ds.client.get("/test/repos")
        html = response.text
        prefix = '<li><a href="/graphql?query='
>       assert prefix in html
E       assert '<li><a href="/graphql?query=' in '<!DOCTYPE html>\n<html>\n<head>\n    <title>Error 500</title>\n    <link rel="stylesheet" href="/-/static/app.css?4e3...ils != detailsClickedWithin\n    ).forEach(details => details.open = false);\n});\n</script>\n\n\n\n\n</body>\n</html>'

tests/test_graphql.py:731: AssertionError
--------------------------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------------------------
Error binding parameter 1 - probably unsupported type.
Traceback (most recent call last):
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/app.py", line 1099, in route_path
    response = await view(request, send)
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/views/base.py", line 146, in view
    return await self.dispatch_request(
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/views/base.py", line 119, in dispatch_request
    await self.ds.refresh_schemas()
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/app.py", line 343, in refresh_schemas
    await internal_db.execute_write(
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 99, in execute_write
    return await self.execute_write_fn(_inner, block=block)
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 115, in execute_write_fn
    raise result
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 136, in _execute_writes
    result = task.fn(conn)
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 97, in _inner
    return conn.execute(sql, params or [])
sqlite3.InterfaceError: Error binding parameter 1 - probably unsupported type.
_ test_schema_caching[\n{% set users = graphql("""\n{\n    users {\n        nodes {\n            name\n            points\n            score\n        }\n    }\n}\n""")["users"] %}\n{% for user in users.nodes %}\n    <p>{{ user.name }} - points: {{ user.points }}, score = {{ user.score }}</p>\n{% endfor %}\n-<p>cleopaws - points: 5, score = 51.5</p>\n\n    <p>simonw - points: 3, score = 35.2</p>] _

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x105072c10>, _basetemp=PosixPath('/private/var/folders/wr/hn3206rs1yzgq3r49bz8nvnh0000gn/T/pytest-of-simon/pytest-345'))
db_path = PosixPath('/private/var/folders/wr/hn3206rs1yzgq3r49bz8nvnh0000gn/T/pytest-of-simon/pytest-345/dbs4/test.db')
template = '\n{% set users = graphql("""\n{\n    users {\n        nodes {\n            name\n            points\n            scor...er in users.nodes %}\n    <p>{{ user.name }} - points: {{ user.points }}, score = {{ user.score }}</p>\n{% endfor %}\n'
expected = '<p>cleopaws - points: 5, score = 51.5</p>\n\n    <p>simonw - points: 3, score = 35.2</p>'

    @pytest.mark.asyncio
    @pytest.mark.parametrize(
        "template,expected",
        [
            (
                TEMPLATE,
                "<p>cleopaws - points: 5, score = 51.5</p>\n\n"
                "    <p>simonw - points: 3, score = 35.2</p>",
            ),
            (TEMPLATE_WITH_VARS, "<p>2: simonw</p>"),
        ],
    )
    async def test_schema_caching(tmp_path_factory, db_path, template, expected):
        template_dir = tmp_path_factory.mktemp("templates")
        pages_dir = template_dir / "pages"
        pages_dir.mkdir()
        (pages_dir / "about.html").write_text(template)

        ds = Datasette([db_path], template_dir=template_dir)
        async with httpx.AsyncClient(app=ds.app()) as client:
            response = await client.get("http://localhost/about")
>           assert response.status_code == 200
E           assert 500 == 200
E             +500
E             -200

tests/test_template_tag.py:62: AssertionError
--------------------------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------------------------
Error binding parameter 1 - probably unsupported type.
Traceback (most recent call last):
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/app.py", line 1099, in route_path
    response = await view(request, send)
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/views/base.py", line 146, in view
    return await self.dispatch_request(
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/views/base.py", line 119, in dispatch_request
    await self.ds.refresh_schemas()
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/app.py", line 343, in refresh_schemas
    await internal_db.execute_write(
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 99, in execute_write
    return await self.execute_write_fn(_inner, block=block)
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 115, in execute_write_fn
    raise result
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 136, in _execute_writes
    result = task.fn(conn)
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 97, in _inner
    return conn.execute(sql, params or [])
sqlite3.InterfaceError: Error binding parameter 1 - probably unsupported type.
_ test_schema_caching[\n{% set user = graphql("""\nquery ($id: Int) {\n    users_row(id: $id) {\n        id\n        name\n    }\n}\n""", variables={"id": 2})["users_row"] %}\n<p>{{ user.id }}: {{ user.name }}</p>\n-<p>2: simonw</p>] _

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x105072c10>, _basetemp=PosixPath('/private/var/folders/wr/hn3206rs1yzgq3r49bz8nvnh0000gn/T/pytest-of-simon/pytest-345'))
db_path = PosixPath('/private/var/folders/wr/hn3206rs1yzgq3r49bz8nvnh0000gn/T/pytest-of-simon/pytest-345/dbs4/test.db')
template = '\n{% set user = graphql("""\nquery ($id: Int) {\n    users_row(id: $id) {\n        id\n        name\n    }\n}\n""", variables={"id": 2})["users_row"] %}\n<p>{{ user.id }}: {{ user.name }}</p>\n'
expected = '<p>2: simonw</p>'

    @pytest.mark.asyncio
    @pytest.mark.parametrize(
        "template,expected",
        [
            (
                TEMPLATE,
                "<p>cleopaws - points: 5, score = 51.5</p>\n\n"
                "    <p>simonw - points: 3, score = 35.2</p>",
            ),
            (TEMPLATE_WITH_VARS, "<p>2: simonw</p>"),
        ],
    )
    async def test_schema_caching(tmp_path_factory, db_path, template, expected):
        template_dir = tmp_path_factory.mktemp("templates")
        pages_dir = template_dir / "pages"
        pages_dir.mkdir()
        (pages_dir / "about.html").write_text(template)

        ds = Datasette([db_path], template_dir=template_dir)
        async with httpx.AsyncClient(app=ds.app()) as client:
            response = await client.get("http://localhost/about")
>           assert response.status_code == 200
E           assert 500 == 200
E             +500
E             -200

tests/test_template_tag.py:62: AssertionError
--------------------------------------------------------------------------------------------- Captured stderr call ----------------------------------------------------------------------------------------------
Error binding parameter 1 - probably unsupported type.
Traceback (most recent call last):
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/app.py", line 1099, in route_path
    response = await view(request, send)
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/views/base.py", line 146, in view
    return await self.dispatch_request(
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/views/base.py", line 119, in dispatch_request
    await self.ds.refresh_schemas()
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/app.py", line 343, in refresh_schemas
    await internal_db.execute_write(
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 99, in execute_write
    return await self.execute_write_fn(_inner, block=block)
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 115, in execute_write_fn
    raise result
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 136, in _execute_writes
    result = task.fn(conn)
  File "/Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/datasette/database.py", line 97, in _inner
    return conn.execute(sql, params or [])
sqlite3.InterfaceError: Error binding parameter 1 - probably unsupported type.
=============================================================================================== warnings summary ================================================================================================
../../../.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/graphene/types/field.py:2
  /Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/graphene/types/field.py:2: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.9 it will stop working
    from collections import Mapping, OrderedDict

../../../.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/graphene/relay/connection.py:2
  /Users/simon/.local/share/virtualenvs/datasette-graphql-n1OSJCS8/lib/python3.8/site-packages/graphene/relay/connection.py:2: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.9 it will stop working
    from collections import Iterable, OrderedDict

-- Docs: https://docs.pytest.org/en/stable/warnings.html
============================================================================================ short test summary info ============================================================================================
FAILED tests/test_graphql.py::test_table_action - assert '<li><a href="/graphql?query=' in '<!DOCTYPE html>\n<html>\n<head>\n    <title>Error 500</title>\n    <link rel="stylesheet" href="/-/static/app.css?...
FAILED tests/test_template_tag.py::test_schema_caching[\n{% set users = graphql("""\n{\n    users {\n        nodes {\n            name\n            points\n            score\n        }\n    }\n}\n""")["users"] %}\n{% for user in users.nodes %}\n    <p>{{ user.name }} - points: {{ user.points }}, score = {{ user.score }}</p>\n{% endfor %}\n-<p>cleopaws - points: 5, score = 51.5</p>\n\n    <p>simonw - points: 3, score = 35.2</p>]
FAILED tests/test_template_tag.py::test_schema_caching[\n{% set user = graphql("""\nquery ($id: Int) {\n    users_row(id: $id) {\n        id\n        name\n    }\n}\n""", variables={"id": 2})["users_row"] %}\n<p>{{ user.id }}: {{ user.name }}</p>\n-<p>2: simonw</p>]
=================================================================================== 3 failed, 64 passed, 2 warnings in 3.71s ====================================================================================
(datasette-graphql) datasette-graphql % 
simonw commented 3 years ago

There are two fundamental issues here.

sqlite3.InterfaceError: Error binding parameter 1 - probably unsupported type

This is caused by code like this:

@pytest.mark.asyncio
async def test_table_action(db_path):
    ds = Datasette([db_path])

The db_path variable here is a pytest path. This throws an error when the new .refresh_schemas() method in Datasette tries to use that variable as part of a SQL query.

One solution is to wrap this in str() in the test itself. Another would be for Datasette to grow the ability to have a path passed to the Datasette([...])` constructor.

simonw commented 3 years ago

The second issue is caused by this code: https://github.com/simonw/datasette-graphql/blob/c5702c3d05954ceb06a3f0d945128b752ee63e60/datasette_graphql/__init__.py#L207-L213

Datasette 0.54 introduces the _internal database, which means that len(datasette.databases) is 1 at the start instead of 0 at the start.

simonw commented 3 years ago

I'm going to alter the table_actions() menu to ALWAYS link to the /graphql/dbname endpoint for the query, even for instances with only a single attached database.