datasette-configure-fts/tests/test_configure_fts.py
import datasettefrom datasette.app import Datasetteimport sqlite_utilsimport pytestimport jsonimport httpximport rewhitespace = re.compile(r"\s+")@pytest.fixturedef db_path(tmpdir):path = str(tmpdir / "data.db")db = sqlite_utils.Database(path)db["creatures"].insert_all([{"name": "Cleo", "description": "A medium sized dog"},{"name": "Siroco", "description": "A troublesome Kakapo"},])return path@pytest.fixturedef db_path2(tmpdir):path = str(tmpdir / "data2.db")db = sqlite_utils.Database(path)db["creatures"].insert({"name": "Pancakes"})db["dogs"].insert({"name": "Pancakes"})db["mammals"].insert({"name": "Pancakes"})return path@pytest.mark.asyncioasync def test_initial_db_is_not_searchable(db_path):ds = Datasette([db_path])response = await ds.client.get("/data.json")assert response.status_code == 200tables = json.loads(response.content)["tables"]assert len(tables) == 1@pytest.mark.parametrize("path", ["/-/configure-fts", "/-/configure-fts/data"])@pytest.mark.asyncioasync def test_permissions(db_path, path):ds = Datasette([db_path])response = await ds.client.get(path)response.status_code == 403# Now try with a root actorresponse2 = await ds.client.get(path,cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")},)assert response2.status_code != 403@pytest.mark.asyncioasync def test_redirects_to_database_if_only_one(db_path):ds = Datasette([db_path])response = await ds.client.get("/-/configure-fts",cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")},)assert response.status_code == 302assert response.headers["location"] == "/-/configure-fts/data"@pytest.mark.asyncioasync def test_database_page_sets_cookie(db_path):ds = Datasette([db_path])response = await ds.client.get("/-/configure-fts/data",cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")},)assert "ds_csrftoken" in response.cookies@pytest.mark.asyncioasync def test_lists_databases_if_more_than_one(db_path, db_path2):ds = Datasette([db_path, db_path2])response = await ds.client.get("/-/configure-fts",cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")},)assert response.status_code == 200assert '<a href="/-/configure-fts/data">data</a>' in response.textassert '<a href="/-/configure-fts/data2">data2</a>' in response.text@pytest.mark.asyncioasync def test_lists_tables_in_database(db_path2):ds = Datasette([db_path2])response = await ds.client.get("/-/configure-fts/data2",cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")},)assert response.status_code == 200assert "<h2>creatures</h2>" in response.textassert "<h2>dogs</h2>" in response.textassert "<h2>mammals</h2>" in response.text# If we select just two tables, only those tworesponse2 = await ds.client.get("/-/configure-fts/data2?table=dogs&table=mammals",cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")},)assert "<h2>creatures</h2>" not in response2.textassert "<h2>dogs</h2>" in response2.textassert "<h2>mammals</h2>" in response2.text@pytest.mark.asyncioasync def test_text_columns_only(db_path):ds = Datasette([db_path])sqlite_utils.Database(db_path)["mixed_types"].insert({"name": "text","age": 5,"height": 1.4,"description": "description",})response = await ds.client.get("/-/configure-fts/data?table=mixed_types",cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")},)assert response.status_code == 200content = response.textassert 'name="column.name"' in contentassert 'name="column.age"' not in contentassert 'name="column.height"' not in contentassert 'name="column.description"' in content@pytest.mark.asyncioasync def test_make_table_searchable(db_path):ds = Datasette([db_path])response1 = await ds.client.get("/-/configure-fts/data",cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")},)csrftoken = response1.cookies["ds_csrftoken"]response2 = await ds.client.post("/-/configure-fts/data",data={"csrftoken": csrftoken,"table": "creatures","column.name": "on","column.description": "on",},cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor"),"ds_csrftoken": csrftoken,},)assert response2.status_code == 302assert response2.headers["location"] == "/data/creatures"db = sqlite_utils.Database(db_path)assert ["creatures","creatures_fts","creatures_fts_data","creatures_fts_idx","creatures_fts_docsize","creatures_fts_config",] == db.table_names()assert ("CREATE VIRTUAL TABLE [creatures_fts] USING FTS5 ( [name], [description], content=[creatures] )"== whitespace.sub(" ", db["creatures_fts"].schema))# It should have set up triggersrows = db.conn.execute('select name from sqlite_master where type = "trigger" order by name').fetchall()assert rows == [("creatures_ad",), ("creatures_ai",), ("creatures_au",)]@pytest.mark.asyncioasync def test_uncheck_all_columns(db_path):ds = Datasette([db_path])db = sqlite_utils.Database(db_path)db["creatures"].enable_fts(["name"])response1 = await ds.client.get("/-/configure-fts/data",cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")},)csrftoken = response1.cookies["ds_csrftoken"]response2 = await ds.client.post("/-/configure-fts/data",data={"csrftoken": csrftoken,"table": "creatures",},cookies={"ds_actor": ds.sign({"a": {"id": "root"}}, "actor"),"ds_csrftoken": csrftoken,},)assert response2.status_code == 302assert response2.headers["location"] == "/data/creatures"db = sqlite_utils.Database(db_path)assert db.table_names() == ["creatures"]@pytest.mark.parametrize("authenticate", [True, False])@pytest.mark.asyncioasync def test_table_actions(db_path, authenticate):ds = Datasette([db_path])cookies = Noneif authenticate:cookies = {"ds_actor": ds.sign({"a": {"id": "root"}}, "actor")}response = await ds.client.get("/data/creatures", cookies=cookies)assert response.status_code == 200fragment = '<li><a href="/-/configure-fts/data?table=creatures">Configure full-text search</a></li>'if authenticate:# Should have column actionsassert fragment in response.textelse:assert fragment not in response.text