"""Tests for the FTS5 message-search index (app.db.fts). SQLite FTS5 trigram tokenizer gives Chinese substring matching at index speed instead of a 4s LIKE scan over 675k rows (2026-08-21). The module: * ``ensure_fts`` creates the ``message_fts`` virtual table + sync triggers (idempotent; no-op when already present, tolerant of MySQL where FTS5 doesn't exist — callers fall back to LIKE). * ORM inserts/updates keep the index in sync via the triggers. * ``fts_available`` reports whether the index exists on this engine. """ from __future__ import annotations import pytest import sqlalchemy as sa from app.db.base import Base from app.db.fts import ensure_fts, fts_available, fts_match_rows from app.db.models import Message @pytest.fixture async def engine(): from sqlalchemy.ext.asyncio import create_async_engine eng = create_async_engine("sqlite+aiosqlite:///:memory:") async with eng.begin() as conn: await conn.run_sync(Base.metadata.create_all) try: yield eng finally: await eng.dispose() async def test_ensure_fts_creates_index_and_triggers(engine): async with engine.begin() as conn: await ensure_fts(conn) assert await fts_available(engine) is True async def test_ensure_fts_idempotent(engine): async with engine.begin() as conn: await ensure_fts(conn) await ensure_fts(conn) # 二次调用不报错 assert await fts_available(engine) is True async def test_orm_insert_syncs_fts(engine): """ORM 插入消息 → 触发器自动进 FTS 索引 → 能 MATCH 到。""" from sqlalchemy.ext.asyncio import async_sessionmaker async with engine.begin() as conn: await ensure_fts(conn) maker = async_sessionmaker(engine, expire_on_commit=False) async with maker() as session: session.add(Message(msg_id="m1", channel="wechat", owner_account_id="o", talker_id="t", text="24年大众途安 简约白", timestamp=1000)) session.add(Message(msg_id="m2", channel="wechat", owner_account_id="o", talker_id="t", text="丰田汉兰达", timestamp=2000)) await session.commit() rows = await fts_match_rows(engine, "大众途安") assert [r.msg_id for r in rows] == ["m1"] async def test_fts_match_chinese_substring(engine): """trigram 中文子串匹配:搜「途安」(2字,LIKE 回退)与「大众途安」(4字,FTS)。""" from sqlalchemy.ext.asyncio import async_sessionmaker async with engine.begin() as conn: await ensure_fts(conn) maker = async_sessionmaker(engine, expire_on_commit=False) async with maker() as session: session.add(Message(msg_id="m1", channel="wechat", owner_account_id="o", talker_id="t", text="推荐24年大众途安L", timestamp=1000)) await session.commit() rows = await fts_match_rows(engine, "大众途安") assert len(rows) == 1 async def test_fts_absent_on_fresh_db(engine): """未 ensure 过的库 → fts_available False(搜索走 LIKE 回退)。""" assert await fts_available(engine) is False