"""Tests de l'exécution SQL read-only de l'assistant IA.""" import sqlite3 import pytest from plesna_gerance.services.sql_executor import ( _SQLITE_DENY, _SQLITE_OK, _SQLITE_PRAGMA, _SQLITE_READ, _SQLITE_SELECT, MAX_ROWS, _authorizer, _ensure_limit, _validate_sql, execute_readonly_sql, ) # --- Garde lexicale ------------------------------------------------------- def test_validate_rejects_non_select(): with pytest.raises(ValueError): _validate_sql("DELETE FROM tags") def test_validate_rejects_stacked_statements(): with pytest.raises(ValueError): _validate_sql("SELECT 1; DROP TABLE tags") def test_validate_rejects_unknown_pragma(): with pytest.raises(ValueError): _validate_sql("PRAGMA writable_schema = ON") def test_validate_allows_select_and_with(): _validate_sql("SELECT * FROM tags") _validate_sql("WITH t AS (SELECT 1) SELECT * FROM t") _validate_sql("PRAGMA table_info(tags)") def test_ensure_limit_adds_limit(): assert _ensure_limit("SELECT * FROM tags").endswith(f"LIMIT {MAX_ROWS}") def test_ensure_limit_preserves_existing(): q = "SELECT * FROM tags LIMIT 5" assert _ensure_limit(q) == q # --- Autorisation SQLite (unitaire) --------------------------------------- def test_authorizer_allows_reads(): assert _authorizer(_SQLITE_SELECT, None, None, None, None) == _SQLITE_OK assert _authorizer(_SQLITE_READ, "tags", "nom", "main", None) == _SQLITE_OK def test_authorizer_allows_whitelisted_pragma(): assert _authorizer(_SQLITE_PRAGMA, "table_info", "tags", None, None) == _SQLITE_OK def test_authorizer_denies_unknown_pragma(): assert _authorizer(_SQLITE_PRAGMA, "writable_schema", "ON", None, None) == _SQLITE_DENY def test_authorizer_denies_unknown_action(): # 9 = SQLITE_DELETE, doit être refusé assert _authorizer(9, "tags", None, "main", None) == _SQLITE_DENY # --- Exécution réelle (nécessite une base) -------------------------------- def test_execute_select_returns_rows(db_session): # init_db seed des tags prédéfinis result = execute_readonly_sql("SELECT nom FROM tags ORDER BY nom") assert "nom" in result["columns"] assert result["row_count"] >= 1 def test_execute_write_blocked(db_session): # Bloqué par la garde lexicale (ValueError) ou, à défaut, par mode=ro / # l'autorisation au niveau SQLite (DatabaseError). Dans tous les cas : refusé. with pytest.raises((ValueError, sqlite3.DatabaseError)): execute_readonly_sql("DELETE FROM tags") def test_execute_attach_blocked(db_session): with pytest.raises((ValueError, sqlite3.DatabaseError)): execute_readonly_sql("ATTACH DATABASE 'x.db' AS x")