7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
279 lines
9.9 KiB
Python
279 lines
9.9 KiB
Python
"""
|
|
Tests for SQLCipher performance pragmas.
|
|
|
|
These tests verify that performance-related PRAGMA settings are
|
|
correctly applied for optimal database performance.
|
|
"""
|
|
|
|
import shutil
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from src.local_deep_research.database.sqlcipher_utils import (
|
|
apply_cipher_defaults_before_key,
|
|
apply_performance_pragmas,
|
|
apply_sqlcipher_pragmas,
|
|
set_sqlcipher_key,
|
|
)
|
|
from src.local_deep_research.database.sqlcipher_compat import (
|
|
get_sqlcipher_module,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_db_path():
|
|
"""Create a temporary database path."""
|
|
temp_dir = tempfile.mkdtemp()
|
|
db_path = Path(temp_dir) / "test_performance.db"
|
|
yield db_path
|
|
shutil.rmtree(temp_dir)
|
|
|
|
|
|
@pytest.fixture
|
|
def sqlcipher_module():
|
|
"""Get the SQLCipher module."""
|
|
return get_sqlcipher_module()
|
|
|
|
|
|
@pytest.fixture
|
|
def configured_connection(sqlcipher_module, temp_db_path):
|
|
"""Create a fully configured database connection."""
|
|
conn = sqlcipher_module.connect(str(temp_db_path))
|
|
cursor = conn.cursor()
|
|
# New database: cipher_default_* before key
|
|
apply_cipher_defaults_before_key(cursor)
|
|
set_sqlcipher_key(cursor, "testpassword")
|
|
apply_sqlcipher_pragmas(cursor, creation_mode=True)
|
|
apply_performance_pragmas(cursor)
|
|
cursor.close()
|
|
return conn
|
|
|
|
|
|
class TestWALMode:
|
|
"""Tests for WAL (Write-Ahead Logging) mode."""
|
|
|
|
def test_wal_mode_enabled(self, configured_connection):
|
|
"""Verify WAL journal mode is enabled by default."""
|
|
result = configured_connection.execute("PRAGMA journal_mode").fetchone()
|
|
assert result is not None
|
|
assert result[0].lower() == "wal", f"Expected WAL mode, got {result[0]}"
|
|
configured_connection.close()
|
|
|
|
def test_wal_creates_additional_files(self, sqlcipher_module, temp_db_path):
|
|
"""Verify WAL mode creates -wal and -shm files."""
|
|
conn = sqlcipher_module.connect(str(temp_db_path))
|
|
cursor = conn.cursor()
|
|
# New database: cipher_default_* before key
|
|
apply_cipher_defaults_before_key(cursor)
|
|
set_sqlcipher_key(cursor, "testpassword")
|
|
apply_performance_pragmas(cursor)
|
|
cursor.close()
|
|
|
|
# Create a table and write data to trigger WAL file creation
|
|
conn.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)")
|
|
conn.execute("INSERT INTO test VALUES (1, 'test_data')")
|
|
conn.commit()
|
|
|
|
# Note: WAL files (-wal, -shm) may not exist immediately or may be cleaned up
|
|
# The main verification is that WAL mode is enabled
|
|
assert (
|
|
conn.execute("PRAGMA journal_mode").fetchone()[0].lower() == "wal"
|
|
)
|
|
conn.close()
|
|
|
|
def test_concurrent_reads_with_wal(self, sqlcipher_module, temp_db_path):
|
|
"""Verify WAL mode allows concurrent read connections."""
|
|
# Create and populate database
|
|
conn1 = sqlcipher_module.connect(str(temp_db_path))
|
|
cursor1 = conn1.cursor()
|
|
# New database: cipher_default_* before key
|
|
apply_cipher_defaults_before_key(cursor1)
|
|
set_sqlcipher_key(cursor1, "testpassword")
|
|
apply_performance_pragmas(cursor1)
|
|
cursor1.close()
|
|
|
|
conn1.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)")
|
|
conn1.execute("INSERT INTO test VALUES (1, 'data1')")
|
|
conn1.commit()
|
|
|
|
# Open second read connection while first is open
|
|
conn2 = sqlcipher_module.connect(str(temp_db_path))
|
|
cursor2 = conn2.cursor()
|
|
# Existing database: key first, then cipher_* pragmas
|
|
set_sqlcipher_key(cursor2, "testpassword")
|
|
apply_sqlcipher_pragmas(cursor2, creation_mode=False)
|
|
apply_performance_pragmas(cursor2)
|
|
cursor2.close()
|
|
|
|
# Both connections should be able to read
|
|
result1 = conn1.execute("SELECT * FROM test").fetchall()
|
|
result2 = conn2.execute("SELECT * FROM test").fetchall()
|
|
|
|
assert result1 == result2 == [(1, "data1")]
|
|
|
|
conn2.close()
|
|
conn1.close()
|
|
|
|
|
|
class TestCacheSettings:
|
|
"""Tests for cache size settings."""
|
|
|
|
def test_cache_size_applied(self, configured_connection):
|
|
"""Verify cache_size pragma is applied."""
|
|
result = configured_connection.execute("PRAGMA cache_size").fetchone()
|
|
assert result is not None
|
|
# Default is 64MB = 65536KB, stored as negative value
|
|
assert result[0] < 0, "Cache size should be negative (KB format)"
|
|
configured_connection.close()
|
|
|
|
def test_temp_store_memory(self, configured_connection):
|
|
"""Verify temp_store is set to MEMORY."""
|
|
result = configured_connection.execute("PRAGMA temp_store").fetchone()
|
|
assert result is not None
|
|
# 2 = MEMORY
|
|
assert result[0] == 2, (
|
|
f"Expected temp_store=2 (MEMORY), got {result[0]}"
|
|
)
|
|
configured_connection.close()
|
|
|
|
|
|
class TestBusyTimeout:
|
|
"""Tests for busy timeout setting."""
|
|
|
|
def test_busy_timeout_applied(self, configured_connection):
|
|
"""Verify busy_timeout is set to prevent immediate lock failures."""
|
|
result = configured_connection.execute("PRAGMA busy_timeout").fetchone()
|
|
assert result is not None
|
|
assert result[0] == 10000, (
|
|
f"Expected busy_timeout=10000, got {result[0]}"
|
|
)
|
|
configured_connection.close()
|
|
|
|
def test_busy_timeout_prevents_immediate_lock_error(
|
|
self, sqlcipher_module, temp_db_path
|
|
):
|
|
"""Verify busy_timeout allows waiting for locks."""
|
|
|
|
# Create database
|
|
conn1 = sqlcipher_module.connect(str(temp_db_path))
|
|
cursor1 = conn1.cursor()
|
|
# New database: cipher_default_* before key
|
|
apply_cipher_defaults_before_key(cursor1)
|
|
set_sqlcipher_key(cursor1, "testpassword")
|
|
apply_performance_pragmas(cursor1)
|
|
cursor1.close()
|
|
|
|
conn1.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)")
|
|
conn1.execute("INSERT INTO test VALUES (1, 'data1')")
|
|
conn1.commit()
|
|
|
|
# The busy_timeout should allow retries
|
|
# This is a basic test that the setting is applied
|
|
result = conn1.execute("PRAGMA busy_timeout").fetchone()
|
|
assert result[0] == 10000
|
|
|
|
conn1.close()
|
|
|
|
|
|
class TestSynchronousMode:
|
|
"""Tests for synchronous mode setting."""
|
|
|
|
def test_synchronous_mode_normal(self, configured_connection):
|
|
"""Verify synchronous mode is set to NORMAL (good balance)."""
|
|
result = configured_connection.execute("PRAGMA synchronous").fetchone()
|
|
assert result is not None
|
|
# Default is NORMAL (1)
|
|
assert result[0] == 1, (
|
|
f"Expected synchronous=1 (NORMAL), got {result[0]}"
|
|
)
|
|
configured_connection.close()
|
|
|
|
|
|
class TestPerformanceIntegration:
|
|
"""Integration tests for performance settings."""
|
|
|
|
def test_all_performance_pragmas_applied(self, configured_connection):
|
|
"""Verify all performance pragmas are applied correctly."""
|
|
pragmas = {
|
|
"journal_mode": lambda x: x.lower() == "wal",
|
|
"temp_store": lambda x: x == 2,
|
|
"busy_timeout": lambda x: x == 10000,
|
|
"synchronous": lambda x: x == 1,
|
|
}
|
|
|
|
for pragma, validator in pragmas.items():
|
|
result = configured_connection.execute(
|
|
f"PRAGMA {pragma}"
|
|
).fetchone()
|
|
assert result is not None, f"PRAGMA {pragma} returned None"
|
|
assert validator(result[0]), (
|
|
f"PRAGMA {pragma} has unexpected value: {result[0]}"
|
|
)
|
|
|
|
configured_connection.close()
|
|
|
|
def test_performance_after_reopen(self, sqlcipher_module, temp_db_path):
|
|
"""Verify performance settings persist across connections."""
|
|
# Create database with performance pragmas
|
|
conn1 = sqlcipher_module.connect(str(temp_db_path))
|
|
cursor1 = conn1.cursor()
|
|
# New database: cipher_default_* before key
|
|
apply_cipher_defaults_before_key(cursor1)
|
|
set_sqlcipher_key(cursor1, "testpassword")
|
|
apply_performance_pragmas(cursor1)
|
|
cursor1.close()
|
|
|
|
conn1.execute("CREATE TABLE test (id INTEGER)")
|
|
conn1.commit()
|
|
conn1.close()
|
|
|
|
# Reopen and verify
|
|
conn2 = sqlcipher_module.connect(str(temp_db_path))
|
|
cursor2 = conn2.cursor()
|
|
# Existing database: key first, then cipher_* pragmas
|
|
set_sqlcipher_key(cursor2, "testpassword")
|
|
apply_sqlcipher_pragmas(cursor2, creation_mode=False)
|
|
apply_performance_pragmas(cursor2)
|
|
cursor2.close()
|
|
|
|
# WAL mode persists (stored in database file)
|
|
result = conn2.execute("PRAGMA journal_mode").fetchone()
|
|
assert result[0].lower() == "wal"
|
|
|
|
# Other settings are per-connection but should still be applied
|
|
result = conn2.execute("PRAGMA busy_timeout").fetchone()
|
|
assert result[0] == 10000
|
|
|
|
conn2.close()
|
|
|
|
def test_write_performance_with_settings(self, configured_connection):
|
|
"""Verify database performs well with applied settings."""
|
|
# audit: PUNCHLIST reviewed 2026-05 — issue resolved by prior PR (recommendation: remove timing assertion or mark @pytest.mark.slow; keep the count==100 assertion).
|
|
import time
|
|
|
|
configured_connection.execute(
|
|
"CREATE TABLE perf_test (id INTEGER PRIMARY KEY, data TEXT)"
|
|
)
|
|
|
|
# Insert 100 rows and measure time
|
|
start = time.time()
|
|
for i in range(100):
|
|
configured_connection.execute(
|
|
f"INSERT INTO perf_test VALUES ({i}, 'test_data_{i}')"
|
|
)
|
|
configured_connection.commit()
|
|
elapsed = time.time() - start
|
|
|
|
# Should complete in reasonable time (< 5 seconds with WAL)
|
|
assert elapsed < 5, f"100 inserts took {elapsed:.2f}s - too slow"
|
|
|
|
# Verify all data was written
|
|
count = configured_connection.execute(
|
|
"SELECT COUNT(*) FROM perf_test"
|
|
).fetchone()[0]
|
|
assert count == 100
|
|
|
|
configured_connection.close()
|