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
113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
"""Regression guard for the encrypted-DB CreateIndex defect.
|
|
|
|
Until v1.6.x, ``encrypted_db.create_user_database()`` only emitted
|
|
``CreateTable`` statements and never ``CreateIndex``. As a result every
|
|
model-declared index (``index=True``, ``unique=True``, explicit
|
|
``Index(...)`` in ``__table_args__``) was missing in user databases.
|
|
|
|
This test asserts that for every table in ``Base.metadata``, every named
|
|
index declared by the model is present in a freshly created encrypted
|
|
database — both column-level and table-level.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from sqlalchemy import inspect
|
|
|
|
pytest.importorskip(
|
|
"sqlcipher3",
|
|
reason="SQLCipher is required to test the encrypted-DB schema path.",
|
|
)
|
|
|
|
sys.path.insert(
|
|
0,
|
|
str(Path(__file__).parent.parent.parent.resolve()),
|
|
)
|
|
|
|
from local_deep_research.database.encrypted_db import DatabaseManager
|
|
from local_deep_research.database.models import Base
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_data_dir(monkeypatch):
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
path = Path(tmpdir)
|
|
monkeypatch.setattr(
|
|
"local_deep_research.database.encrypted_db.get_data_directory",
|
|
lambda: path,
|
|
)
|
|
yield path
|
|
|
|
|
|
@pytest.fixture
|
|
def db_manager(temp_data_dir):
|
|
m = DatabaseManager()
|
|
yield m
|
|
for username in list(m.connections.keys()):
|
|
m.close_user_database(username)
|
|
|
|
|
|
def test_every_model_declared_index_exists_in_fresh_db(db_manager):
|
|
"""Iterate Base.metadata; assert every named index is present."""
|
|
assert db_manager.has_encryption, (
|
|
"sqlcipher3 imports but DatabaseManager reported has_encryption=False"
|
|
)
|
|
username = "indexcheck"
|
|
password = "StrongPassword1!"
|
|
db_manager.create_user_database(username, password)
|
|
|
|
engine = db_manager.connections[username]
|
|
inspector = inspect(engine)
|
|
|
|
missing: list[tuple[str, str]] = []
|
|
for table in Base.metadata.sorted_tables:
|
|
if table.name == "users":
|
|
continue
|
|
if not inspector.has_table(table.name):
|
|
continue
|
|
existing = {idx["name"] for idx in inspector.get_indexes(table.name)}
|
|
for index in table.indexes:
|
|
if not index.name:
|
|
continue
|
|
if index.name not in existing:
|
|
missing.append((table.name, index.name))
|
|
|
|
assert not missing, (
|
|
f"Missing model-declared indexes in fresh user DB: {missing}. "
|
|
f"This usually means encrypted_db.create_user_database() is not "
|
|
f"emitting CreateIndex for declared indexes."
|
|
)
|
|
|
|
|
|
def test_download_tracker_url_hash_has_unique_backing(db_manager):
|
|
"""The exact regression that caused #3697 — url_hash must be UNIQUE.
|
|
|
|
Without UNIQUE backing on the FK target, SQLCipher raises
|
|
"foreign key mismatch — download_attempts referencing download_tracker"
|
|
on cascade delete.
|
|
"""
|
|
assert db_manager.has_encryption
|
|
username = "trackercheck"
|
|
password = "StrongPassword1!"
|
|
db_manager.create_user_database(username, password)
|
|
|
|
engine = db_manager.connections[username]
|
|
inspector = inspect(engine)
|
|
|
|
indexes = inspector.get_indexes("download_tracker")
|
|
unique_on_url_hash = [
|
|
idx
|
|
for idx in indexes
|
|
if idx.get("unique") and idx.get("column_names") == ["url_hash"]
|
|
]
|
|
assert unique_on_url_hash, (
|
|
"download_tracker.url_hash must have a UNIQUE backing index for "
|
|
"FK references from download_attempts/download_duplicates to "
|
|
f"resolve. Indexes present: {indexes}"
|
|
)
|