tinyweb/tests/test_db_schema.py
blankie 0669aaf3a0 src layout: move core code into src/tinyweb/ package
- Moved app.py, db.py, gateway.py, templates.py, embeddings.py,
  rns_client.py, and handlers/ into src/tinyweb/
- Created root app.py shim (adds src/ to sys.path, imports main)
- Created pyproject.toml with setuptools config (where = ["src"])
- Added src/tinyweb/__init__.py
- Updated all internal imports to use tinyweb. prefix (73 occurrences)
- Removed sys.path.insert hack from conftest.py
- Updated Dockerfile: pip install -e /app before running
- Updated gateway.py usage message: python -m tinyweb.gateway
- Updated README.md gateway usage instructions
2026-06-17 05:03:23 +00:00

90 lines
2.6 KiB
Python

"""Tests for `init_db` and the settings key-value store.
`init_db` is called unconditionally on startup, so it must be idempotent
and create every table/trigger the rest of the app expects.
"""
from tinyweb.db import get_db, return_db, init_db, get_setting, set_setting, get_site_name
EXPECTED_TABLES = {
"pages", "links", "settings", "subscriptions",
"remote_pages", "tags", "page_tags", "chunks",
# FTS5 virtual tables:
"pages_fts", "remote_pages_fts",
}
def test_all_expected_tables_exist(temp_db):
db = get_db()
try:
rows = db.execute(
"SELECT name FROM sqlite_master WHERE type IN ('table') AND name NOT LIKE 'sqlite_%'"
).fetchall()
names = {r["name"] for r in rows}
finally:
return_db(db)
missing = EXPECTED_TABLES - names
assert not missing, f"tables missing after init_db: {missing}"
def test_fts_triggers_exist(temp_db):
db = get_db()
try:
rows = db.execute(
"SELECT name FROM sqlite_master WHERE type = 'trigger'"
).fetchall()
names = {r["name"] for r in rows}
finally:
return_db(db)
# These triggers keep pages_fts in sync with pages on insert/update/delete.
for trigger in ("pages_ai", "pages_ad", "pages_au"):
assert trigger in names, f"missing trigger {trigger}"
def test_init_db_is_idempotent(temp_db):
"""Running init_db twice on the same DB must not error or duplicate anything."""
init_db()
init_db() # second call should be a no-op
db = get_db()
try:
count = db.execute(
"SELECT count(*) FROM sqlite_master WHERE name = 'pages'"
).fetchone()[0]
finally:
return_db(db)
assert count == 1
def test_get_setting_returns_default_when_missing(temp_db):
assert get_setting("nonexistent", "fallback") == "fallback"
assert get_setting("nonexistent") == ""
def test_set_setting_then_get(temp_db):
set_setting("site_name", "my-personal-index")
assert get_setting("site_name") == "my-personal-index"
def test_set_setting_updates_existing(temp_db):
set_setting("key", "first")
set_setting("key", "second")
assert get_setting("key") == "second"
def test_get_site_name_has_default(temp_db):
assert get_site_name() == "tinyweb"
def test_get_site_name_reflects_override(temp_db):
set_setting("site_name", "custom-site")
assert get_site_name() == "custom-site"
def test_foreign_keys_pragma_enabled(temp_db):
"""Pool connections should have foreign_keys=ON so CASCADE deletes work."""
db = get_db()
try:
row = db.execute("PRAGMA foreign_keys").fetchone()
finally:
return_db(db)
assert row[0] == 1