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
229 lines
7.8 KiB
Python
229 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
# allow: no-sut-import — guardian; scans the codebase for raw SQL outside allowed locations
|
|
"""Test to verify no raw SQL is used in the codebase (except in allowed locations)."""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(
|
|
0,
|
|
str(Path(__file__).parent.parent.parent.resolve()),
|
|
)
|
|
|
|
import re
|
|
|
|
|
|
def check_file_for_raw_sql(filepath):
|
|
"""Check a single file for raw SQL usage."""
|
|
with open(filepath, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
# Skip if it's a test file, migration file, or database-specific files
|
|
filepath_str = str(filepath).lower()
|
|
if any(
|
|
skip in filepath_str
|
|
for skip in [
|
|
"test",
|
|
"migration",
|
|
"encrypted_db.py",
|
|
"sqlcipher_utils.py",
|
|
"thread_local_session.py",
|
|
"queue/processor.py",
|
|
"database/initialize.py", # Schema migrations using DDL
|
|
"alembic_runner.py", # Migration runner: drops orphan _alembic_tmp_* tables (#3817), toggles foreign_keys (#3990)
|
|
"auth_db.py", # SQLAlchemy DDL (CreateTable/CreateIndex), not raw SQL
|
|
"backup_service.py", # SQLCipher ATTACH/DETACH/export operations
|
|
"journal_quality/db.py", # Read-only SQLite DB build + PRAGMA user_version
|
|
]
|
|
):
|
|
return []
|
|
|
|
violations = []
|
|
lines = content.split("\n")
|
|
|
|
# Patterns that indicate raw SQL
|
|
sql_patterns = [
|
|
r"cursor\.execute\s*\(",
|
|
r"conn\.execute\s*\(",
|
|
r'session\.execute\s*\(\s*["\']',
|
|
r'["\']SELECT\s+.*FROM\s+',
|
|
r'["\']INSERT\s+INTO\s+',
|
|
r'["\']UPDATE\s+.*SET\s+',
|
|
r'["\']DELETE\s+FROM\s+',
|
|
r'["\']CREATE\s+TABLE\s+',
|
|
r'["\']DROP\s+TABLE\s+',
|
|
r'["\']ALTER\s+TABLE\s+',
|
|
]
|
|
|
|
# Allowed patterns (SQLCipher PRAGMAs and simple connection tests)
|
|
allowed_patterns = [
|
|
r"PRAGMA\s+(cipher_|quick_check|rekey)", # SQLCipher pragmas
|
|
r"SELECT\s+1(?:\s|$|;)", # Simple connection test
|
|
r'text\s*\(\s*["\'](?:SELECT\s+1|PRAGMA)', # SQLAlchemy text() with allowed queries
|
|
]
|
|
|
|
for line_num, line in enumerate(lines, 1):
|
|
for pattern in sql_patterns:
|
|
if re.search(pattern, line, re.IGNORECASE):
|
|
# Check if it's in a comment or docstring
|
|
stripped = line.strip()
|
|
if (
|
|
stripped.startswith("#")
|
|
or stripped.startswith('"""')
|
|
or stripped.startswith("'''")
|
|
):
|
|
continue
|
|
|
|
# Check if it's an allowed pattern
|
|
is_allowed = False
|
|
for allowed in allowed_patterns:
|
|
if re.search(allowed, line, re.IGNORECASE):
|
|
is_allowed = True
|
|
break
|
|
|
|
if not is_allowed:
|
|
violations.append((line_num, line.strip()))
|
|
|
|
return violations
|
|
|
|
|
|
def test_no_raw_sql_in_src():
|
|
"""Test that no raw SQL is used in src directory (except allowed patterns).
|
|
|
|
Allowed exceptions:
|
|
- encrypted_db.py file (SQLCipher-specific operations)
|
|
- PRAGMA commands for SQLCipher (cipher_*, quick_check, rekey)
|
|
- Simple connection tests (SELECT 1)
|
|
"""
|
|
src_path = Path(__file__).parent.parent.parent / "src"
|
|
violations = {}
|
|
|
|
# Walk through all Python files
|
|
for filepath in src_path.rglob("*.py"):
|
|
file_violations = check_file_for_raw_sql(filepath)
|
|
if file_violations:
|
|
violations[str(filepath)] = file_violations
|
|
|
|
# Report violations
|
|
if violations:
|
|
print("\n❌ Found raw SQL in the following files:")
|
|
for filepath, file_violations in violations.items():
|
|
print(f"\n{filepath}:")
|
|
for line_num, line in file_violations:
|
|
print(f" Line {line_num}: {line[:80]}...")
|
|
|
|
# This should fail the test
|
|
assert False, f"Found raw SQL in {len(violations)} files"
|
|
else:
|
|
print(
|
|
"✓ No raw SQL found in src directory (excluding allowed patterns)"
|
|
)
|
|
|
|
|
|
def test_orm_imports_used():
|
|
"""Files that touch the DB should reach for the ORM, not raw SQL.
|
|
|
|
Walk the src tree, collect every file that performs a DB operation
|
|
(``get_db_session``, references to ``ResearchHistory`` or
|
|
``ResearchResource``), and require that the vast majority of them
|
|
also import SQLAlchemy or use ORM-style query helpers.
|
|
|
|
The ratio guard is intentionally loose (≥80%) because a handful of
|
|
helper modules legitimately reference these names without
|
|
constructing queries themselves (e.g. type-only imports, fixtures,
|
|
docstring examples).
|
|
"""
|
|
src_path = Path(__file__).parent.parent.parent / "src"
|
|
files_with_db_operations = []
|
|
files_with_orm_imports = []
|
|
|
|
for filepath in src_path.rglob("*.py"):
|
|
with open(filepath, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
# Check if file has database operations
|
|
if any(
|
|
pattern in content
|
|
for pattern in [
|
|
"get_db_session",
|
|
"ResearchHistory",
|
|
"ResearchResource",
|
|
]
|
|
):
|
|
files_with_db_operations.append(filepath)
|
|
|
|
# Check if it has ORM imports
|
|
if any(
|
|
pattern in content
|
|
for pattern in [
|
|
"from sqlalchemy",
|
|
"import.*session",
|
|
".query(",
|
|
".filter",
|
|
]
|
|
):
|
|
files_with_orm_imports.append(filepath)
|
|
|
|
# Sanity: at least some files matched the DB-operation heuristic, or
|
|
# the test has silently stopped finding anything (e.g., a refactor
|
|
# renamed the symbols above and the test would otherwise pass with 0
|
|
# files inspected).
|
|
assert files_with_db_operations, (
|
|
"No files with DB operations detected — patterns may be stale"
|
|
)
|
|
|
|
orm_ratio = len(files_with_orm_imports) / len(files_with_db_operations)
|
|
assert orm_ratio >= 0.8, (
|
|
f"Only {len(files_with_orm_imports)}/{len(files_with_db_operations)} "
|
|
f"files with DB operations use ORM imports (ratio {orm_ratio:.2f} < 0.80)"
|
|
)
|
|
|
|
|
|
def test_models_imported_from_correct_location():
|
|
"""Test that models are imported from the consolidated location."""
|
|
src_path = Path(__file__).parent.parent.parent / "src"
|
|
incorrect_imports = {}
|
|
|
|
# Old import patterns that should not be used
|
|
old_patterns = [
|
|
r"from.*web\.models\.database import.*(?:Research|ResearchHistory)",
|
|
r"from.*web\.database\.models import",
|
|
r"from.*benchmarks\.models\.benchmark_models import",
|
|
]
|
|
|
|
for filepath in src_path.rglob("*.py"):
|
|
with open(filepath, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
violations = []
|
|
lines = content.split("\n")
|
|
|
|
for line_num, line in enumerate(lines, 1):
|
|
for pattern in old_patterns:
|
|
if re.search(pattern, line):
|
|
violations.append((line_num, line.strip()))
|
|
|
|
if violations:
|
|
incorrect_imports[str(filepath)] = violations
|
|
|
|
if incorrect_imports:
|
|
print("\n❌ Found imports from old model locations:")
|
|
for filepath, violations in incorrect_imports.items():
|
|
print(f"\n{filepath}:")
|
|
for line_num, line in violations:
|
|
print(f" Line {line_num}: {line}")
|
|
|
|
# This should fail the test
|
|
assert False, (
|
|
f"Found {len(incorrect_imports)} files with incorrect model imports"
|
|
)
|
|
else:
|
|
print("✓ All models imported from correct location (database.models)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_no_raw_sql_in_src()
|
|
test_orm_imports_used()
|
|
test_models_imported_from_correct_location()
|
|
print("\n✅ All SQL/ORM compliance tests passed!")
|