Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

319 lines
10 KiB
Python

#!/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"])