Files
turnstone/tests/test_fts5.py
T
Patrick Buckley 2b58c127b1 Add pluggable storage backend (SQLite + PostgreSQL) and deployment packaging (#20)
* Add pluggable storage backend (SQLite + PostgreSQL) and deployment packaging

Database abstraction: StorageBackend protocol with 21 methods, SQLAlchemy Core
schema, SQLite backend (FTS5), PostgreSQL backend (tsvector/ILIKE), Alembic
migrations, singleton registry. memory.py reduced to thin facade. Session.py
open_db() calls replaced with generic KV methods. [database] config section
with env var support.

Deployment: Docker Compose production profile with PostgreSQL, Dockerfile with
postgres extras and migration entrypoint, Helm chart with bitnami subcharts,
Terraform AWS ECS/Fargate module with RDS + ElastiCache + ALB.

39 new storage tests (934 total). mypy strict clean. Docs and diagrams updated.

* Address PR #20 review feedback (16 items)

- Backends only call create_all() when Alembic migrations are disabled
- Helm configmap uses correct TURNSTONE_DB_BACKEND env var; DB URL
  constructed via env expansion with secret reference instead of ConfigMap
- Migration errors fail fast for PostgreSQL (only non-fatal for SQLite)
- save_memory/delete_memory wrapped in exception handling like other facade fns
- pool_size passed through from config/env to init_storage() in cli + server
- Terraform: DB URL moved to Secrets Manager, auth enabled flag set,
  optional TLS listeners with certificate_arn, Redis transit encryption on
- Docker entrypoint no longer suppresses migration output
- Diagram fixes: removed StaticPool claim, removed non-existent migration ref
- compose.yaml/README: clarified production profile requires DB env vars
2026-03-03 22:57:34 -08:00

49 lines
1.4 KiB
Python

"""Tests for SQLite FTS5 query building and LIKE escaping."""
from turnstone.core.storage._sqlite import _escape_like, _fts5_query
class TestFts5Query:
def test_single_word(self):
result = _fts5_query("hello")
assert result == '"hello"'
def test_multiple_words_joined_with_and(self):
result = _fts5_query("hello world")
assert result == '"hello" "world"'
def test_special_chars_safely_quoted(self):
result = _fts5_query("test*")
assert result == '"test*"'
def test_dash_safely_quoted(self):
result = _fts5_query("-negative")
assert result == '"-negative"'
def test_embedded_double_quotes(self):
result = _fts5_query('say"hello')
assert result == '"say""hello"'
def test_empty_query(self):
assert _fts5_query("") == ""
def test_whitespace_only(self):
assert _fts5_query(" ") == ""
class TestEscapeLike:
def test_percent_escaped(self):
assert _escape_like("100%") == "100\\%"
def test_underscore_escaped(self):
assert _escape_like("a_b") == "a\\_b"
def test_backslash_escaped(self):
assert _escape_like("a\\b") == "a\\\\b"
def test_no_metacharacters(self):
assert _escape_like("hello") == "hello"
def test_combined(self):
assert _escape_like("50%_off\\sale") == "50\\%\\_off\\\\sale"