92 lines
3.6 KiB
Python
92 lines
3.6 KiB
Python
from pathlib import Path
|
|
import re
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SRC = ROOT / "src"
|
|
DIALECTS = ("sqlite", "mysql", "pgsql")
|
|
|
|
SQL_LITERAL = re.compile(
|
|
r'(?:r#{0,8})?"\s*(?:SELECT\s+(?:\*|[A-Za-z_(])|INSERT\s+INTO|INSERT\s+OR\s+(?:IGNORE|REPLACE)\s+INTO|UPDATE\s+[A-Za-z_]|DELETE\s+FROM|PRAGMA\s+[A-Za-z_]|WITH\s+[A-Za-z_]|CREATE\s+(?:TABLE|INDEX)|ALTER\s+TABLE|DROP\s+(?:TABLE|INDEX))',
|
|
re.IGNORECASE | re.MULTILINE,
|
|
)
|
|
CONST_NAME = re.compile(r"pub const\s+([A-Z][A-Z0-9_]*)\s*:")
|
|
MODULE = re.compile(r"^pub mod\s+([a-zA-Z0-9_]+)\s*\{", re.MULTILINE)
|
|
|
|
|
|
def read(path: str) -> str:
|
|
return (ROOT / path).read_text()
|
|
|
|
|
|
def test_central_query_tree_exists():
|
|
for dialect in DIALECTS:
|
|
assert (ROOT / f"src/queries/{dialect}/queries.rs").is_file()
|
|
assert (ROOT / f"src/queries/{dialect}/mod.rs").is_file()
|
|
assert not (ROOT / "src/session_store/queries").exists()
|
|
|
|
|
|
def test_session_store_uses_central_query_tree():
|
|
db = read("src/session_store/database.rs")
|
|
for dialect in DIALECTS:
|
|
assert f"queries::{dialect}::queries::sessions::" in db
|
|
|
|
|
|
def test_no_executable_sql_literals_outside_query_tree():
|
|
offenders = []
|
|
for path in SRC.rglob("*.rs"):
|
|
if "queries" in path.parts:
|
|
continue
|
|
text = path.read_text()
|
|
if SQL_LITERAL.search(text):
|
|
offenders.append(str(path.relative_to(ROOT)))
|
|
assert offenders == []
|
|
|
|
|
|
def test_dialect_catalogs_have_same_modules_and_query_constants():
|
|
catalogs = {d: read(f"src/queries/{d}/queries.rs") for d in DIALECTS}
|
|
base_modules = set(MODULE.findall(catalogs["sqlite"]))
|
|
assert "sessions" in base_modules
|
|
for dialect in DIALECTS[1:]:
|
|
assert set(MODULE.findall(catalogs[dialect])) == base_modules
|
|
|
|
# Every dialect must expose the same named query constants/templates.
|
|
base_consts = set(CONST_NAME.findall(catalogs["sqlite"]))
|
|
assert len(base_consts) >= 10
|
|
for dialect in DIALECTS[1:]:
|
|
assert set(CONST_NAME.findall(catalogs[dialect])) == base_consts
|
|
|
|
|
|
def test_backend_specific_session_placeholders_are_preserved():
|
|
sqlite = read("src/queries/sqlite/queries.rs")
|
|
mysql = read("src/queries/mysql/queries.rs")
|
|
pgsql = read("src/queries/pgsql/queries.rs")
|
|
assert 'BY_TOKEN: &str = "SELECT * FROM sessions WHERE token = ?"' in sqlite
|
|
assert 'BY_TOKEN: &str = "SELECT * FROM sessions WHERE token = ?"' in mysql
|
|
assert 'BY_TOKEN: &str = "SELECT * FROM sessions WHERE token = $1"' in pgsql
|
|
|
|
|
|
def test_dynamic_query_macros_do_not_capture_caller_locals():
|
|
sqlite = read("src/queries/sqlite/queries.rs")
|
|
# macro_rules identifiers are hygienic: caller-local values must arrive as metavariables.
|
|
forbidden = [
|
|
'WHERE id = ?{idx}',
|
|
'ORDER BY {order_by}',
|
|
'{where_clause}',
|
|
'SET {set_clause}',
|
|
'user_version = {current}',
|
|
'user_version = {version}',
|
|
]
|
|
macro_section = sqlite[sqlite.index("#[macro_export]"):]
|
|
for fragment in forbidden:
|
|
assert fragment not in macro_section
|
|
|
|
calendar = read("src/db/calendar.rs")
|
|
messages = read("src/db/messages.rs")
|
|
notifications = read("src/db/notification_preferences.rs")
|
|
pool = read("src/db/pool.rs")
|
|
assert 'rustbox_query_db_calendar_004!(sets.join(", "), idx)' in calendar
|
|
assert 'rustbox_query_db_messages_002!(&order_by)' in messages
|
|
assert 'rustbox_query_db_messages_005!(&where_clause, param_values.len() + 1)' in messages
|
|
assert 'rustbox_query_db_notification_preferences_001!(&set_clause, idx)' in notifications
|
|
assert re.search(r'db_pool::set_user_version\(\s*version,?\s*\)', pool)
|
|
assert 'refinery_schema_history' not in pool
|