chore: import upstream snapshot with attribution
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test that database schema (table names) never changes unexpectedly.
|
||||
|
||||
Renaming or removing tables will cause data loss for existing users.
|
||||
These tests catch accidental schema changes before they're deployed.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# Expected table names - DO NOT REMOVE ANY
|
||||
# Adding new tables is fine, but removing/renaming breaks existing databases
|
||||
EXPECTED_TABLES = {
|
||||
# Auth (in auth.db, not user dbs)
|
||||
"users",
|
||||
# Settings
|
||||
"api_keys",
|
||||
"settings",
|
||||
"user_settings",
|
||||
# Queue
|
||||
"queue_status",
|
||||
"task_metadata",
|
||||
# Research
|
||||
"research",
|
||||
"research_history",
|
||||
"research_resources",
|
||||
"research_strategies",
|
||||
"research_tasks",
|
||||
"search_queries",
|
||||
"search_results",
|
||||
"queued_researches",
|
||||
"user_active_researches",
|
||||
# Reports
|
||||
"reports",
|
||||
"report_sections",
|
||||
# Library
|
||||
"collections",
|
||||
"collection_folders",
|
||||
"collection_folder_files",
|
||||
"documents",
|
||||
"document_blobs",
|
||||
"document_chunks",
|
||||
"document_collections",
|
||||
"download_queue",
|
||||
"library_statistics",
|
||||
"rag_document_status",
|
||||
"rag_indices",
|
||||
"source_types",
|
||||
"upload_batches",
|
||||
# Download tracking
|
||||
"download_tracker",
|
||||
"download_duplicates",
|
||||
"download_attempts",
|
||||
# Metrics
|
||||
"token_usage",
|
||||
"model_usage",
|
||||
"research_ratings",
|
||||
"search_calls",
|
||||
# News
|
||||
"news_cards",
|
||||
"news_interests",
|
||||
"news_subscriptions",
|
||||
"news_user_preferences",
|
||||
"news_user_ratings",
|
||||
"subscription_folders",
|
||||
"user_news_search_history",
|
||||
# File integrity
|
||||
"file_integrity_records",
|
||||
"file_verification_failures",
|
||||
# Providers
|
||||
"provider_models",
|
||||
# Rate limiting
|
||||
"rate_limit_attempts",
|
||||
"rate_limit_estimates",
|
||||
# Domain classification
|
||||
"domain_classifications",
|
||||
# Logs
|
||||
"app_logs",
|
||||
"journals",
|
||||
# Papers (deduplicated academic papers)
|
||||
"papers",
|
||||
"paper_appearances",
|
||||
# Benchmark
|
||||
"benchmark_configs",
|
||||
"benchmark_progress",
|
||||
"benchmark_results",
|
||||
"benchmark_runs",
|
||||
# Chat
|
||||
"chat_sessions",
|
||||
"chat_messages",
|
||||
"chat_progress_steps",
|
||||
# Zotero
|
||||
"zotero_sync_state",
|
||||
"zotero_item_map",
|
||||
}
|
||||
|
||||
|
||||
class TestSchemaStability:
|
||||
"""
|
||||
Verify that database table names haven't changed.
|
||||
|
||||
Renaming or removing tables will cause existing user databases
|
||||
to lose data or fail to open properly.
|
||||
"""
|
||||
|
||||
def test_no_tables_removed(self):
|
||||
"""
|
||||
Ensure no expected tables have been removed.
|
||||
|
||||
Removing a table definition will cause data loss when users
|
||||
upgrade, as SQLAlchemy won't know how to access that data.
|
||||
"""
|
||||
from local_deep_research.database.models import Base
|
||||
|
||||
# Get all actual table names from the models
|
||||
actual_tables = set(Base.metadata.tables.keys())
|
||||
|
||||
# Check that all expected tables still exist
|
||||
missing_tables = EXPECTED_TABLES - actual_tables
|
||||
|
||||
assert not missing_tables, (
|
||||
f"CRITICAL: Database tables have been removed!\n"
|
||||
f"Missing tables: {missing_tables}\n\n"
|
||||
"Removing tables will cause data loss for existing users.\n"
|
||||
"If you intentionally removed these tables, you need a migration plan.\n"
|
||||
"Otherwise, REVERT THIS CHANGE."
|
||||
)
|
||||
|
||||
def test_no_tables_renamed(self):
|
||||
"""
|
||||
Detect if tables might have been renamed.
|
||||
|
||||
If new tables appear and expected tables are missing,
|
||||
it's likely a rename which will cause data loss.
|
||||
"""
|
||||
from local_deep_research.database.models import Base
|
||||
|
||||
actual_tables = set(Base.metadata.tables.keys())
|
||||
missing_tables = EXPECTED_TABLES - actual_tables
|
||||
|
||||
if missing_tables:
|
||||
# Check if there are new tables that might be renames
|
||||
new_tables = actual_tables - EXPECTED_TABLES
|
||||
|
||||
if new_tables:
|
||||
pytest.fail(
|
||||
f"Possible table rename detected!\n"
|
||||
f"Missing: {missing_tables}\n"
|
||||
f"New: {new_tables}\n\n"
|
||||
"If you renamed tables, existing data will be lost.\n"
|
||||
"You need a migration to copy data from old to new tables."
|
||||
)
|
||||
|
||||
def test_new_tables_are_documented(self):
|
||||
"""
|
||||
Ensure any new tables are added to EXPECTED_TABLES.
|
||||
|
||||
This is a reminder to update this test when adding new tables.
|
||||
New tables should be added to EXPECTED_TABLES to track them.
|
||||
"""
|
||||
from local_deep_research.database.models import Base
|
||||
|
||||
actual_tables = set(Base.metadata.tables.keys())
|
||||
new_tables = actual_tables - EXPECTED_TABLES
|
||||
|
||||
# These are okay - just a reminder to update the test
|
||||
if new_tables:
|
||||
pytest.fail(
|
||||
f"New tables detected that aren't in EXPECTED_TABLES:\n"
|
||||
f"{new_tables}\n\n"
|
||||
"Please add these to EXPECTED_TABLES in this test file.\n"
|
||||
"This ensures they'll be protected from accidental removal."
|
||||
)
|
||||
|
||||
|
||||
class TestCriticalColumns:
|
||||
"""
|
||||
Verify that critical columns in key tables haven't been removed.
|
||||
|
||||
These are columns that store important user data.
|
||||
"""
|
||||
|
||||
def test_user_settings_has_required_columns(self):
|
||||
"""Verify UserSettings table has all required columns."""
|
||||
from local_deep_research.database.models import UserSettings
|
||||
|
||||
required_columns = {"id", "key", "value", "category"}
|
||||
actual_columns = set(UserSettings.__table__.columns.keys())
|
||||
|
||||
missing = required_columns - actual_columns
|
||||
assert not missing, (
|
||||
f"UserSettings is missing required columns: {missing}\n"
|
||||
"This will break user settings storage."
|
||||
)
|
||||
|
||||
def test_research_has_required_columns(self):
|
||||
"""Verify Research table has all required columns."""
|
||||
from local_deep_research.database.models.research import Research
|
||||
|
||||
required_columns = {"id", "query", "status", "mode", "created_at"}
|
||||
actual_columns = set(Research.__table__.columns.keys())
|
||||
|
||||
missing = required_columns - actual_columns
|
||||
assert not missing, (
|
||||
f"Research is missing required columns: {missing}\n"
|
||||
"This will break research history."
|
||||
)
|
||||
|
||||
def test_api_keys_has_required_columns(self):
|
||||
"""Verify APIKey table has all required columns."""
|
||||
from local_deep_research.database.models import APIKey
|
||||
|
||||
required_columns = {"id", "provider", "key", "is_active"}
|
||||
actual_columns = set(APIKey.__table__.columns.keys())
|
||||
|
||||
missing = required_columns - actual_columns
|
||||
assert not missing, (
|
||||
f"APIKey is missing required columns: {missing}\n"
|
||||
"This will break API key storage."
|
||||
)
|
||||
|
||||
def test_journal_has_exact_column_set(self):
|
||||
"""Journal is an LLM-only cache; the column set is deliberately
|
||||
minimal. Lock it down so an accidental add/drop in the model
|
||||
gets caught without a matching migration.
|
||||
|
||||
The journal-quality redesign intentionally excluded the
|
||||
bibliometric columns (issn, h_index, impact_factor, ...) that
|
||||
would have served a Tier 2/3 cache; those values live in the
|
||||
read-only reference DB instead. Re-adding any of them to the
|
||||
model without a new migration would cause the schema drift
|
||||
this test catches.
|
||||
"""
|
||||
from local_deep_research.database.models.logs import Journal
|
||||
|
||||
expected = {
|
||||
"id",
|
||||
"name",
|
||||
"name_lower",
|
||||
"quality",
|
||||
"score_source",
|
||||
"quality_model",
|
||||
"quality_analysis_time",
|
||||
}
|
||||
actual = set(Journal.__table__.columns.keys())
|
||||
missing = expected - actual
|
||||
extra = actual - expected
|
||||
assert not missing, (
|
||||
f"Journal model is missing required columns: {sorted(missing)}"
|
||||
)
|
||||
assert not extra, (
|
||||
f"Journal model has unexpected columns: {sorted(extra)}. "
|
||||
"If you intentionally added a column, also update this test "
|
||||
"and the journal-quality migration (and its downgrade)."
|
||||
)
|
||||
|
||||
def test_chat_tables_have_exact_column_sets(self):
|
||||
"""Lock down the column sets for the three chat tables introduced
|
||||
in migration 0010. Drift detection — accidental rename, removal,
|
||||
or new column without a migration would surface as a clear diff.
|
||||
|
||||
Mirrors the journals pattern above. Currently `phase` on
|
||||
ChatProgressStep has no behavior tests pinning it; this test
|
||||
catches a silent drop.
|
||||
"""
|
||||
from local_deep_research.database.models.chat import (
|
||||
ChatMessage,
|
||||
ChatProgressStep,
|
||||
ChatSession,
|
||||
)
|
||||
|
||||
expected = {
|
||||
ChatSession: {
|
||||
"id",
|
||||
"title",
|
||||
"status",
|
||||
"accumulated_context",
|
||||
"created_at",
|
||||
"message_count",
|
||||
},
|
||||
ChatMessage: {
|
||||
"id",
|
||||
"session_id",
|
||||
"research_id",
|
||||
"role",
|
||||
"message_type",
|
||||
"content",
|
||||
"sequence_number",
|
||||
"created_at",
|
||||
},
|
||||
ChatProgressStep: {
|
||||
"id",
|
||||
"research_id",
|
||||
"session_id",
|
||||
"phase",
|
||||
"content",
|
||||
"sequence_number",
|
||||
"created_at",
|
||||
},
|
||||
}
|
||||
for model, cols in expected.items():
|
||||
actual = set(model.__table__.columns.keys())
|
||||
missing = cols - actual
|
||||
extra = actual - cols
|
||||
assert not missing, (
|
||||
f"{model.__name__} is missing required columns: "
|
||||
f"{sorted(missing)}"
|
||||
)
|
||||
assert not extra, (
|
||||
f"{model.__name__} has unexpected columns: {sorted(extra)}. "
|
||||
"If intentional, update this test and migration 0010 "
|
||||
"(or open a follow-up migration)."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user