Smail/test/deployment/test_formal_review_tools.py
smail b71fbc49fe
Some checks are pending
Security Checks / security (push) Waiting to run
完善单数据库识别并对齐统一配置迁移验收
2026-09-22 10:25:53 +08:00

151 lines
6.9 KiB
Python

"""Formal cloud acceptance tool single-database boundary tests."""
from contextlib import closing
from pathlib import Path
import sqlite3
import sys
import tempfile
import unittest
TOOLS = Path(__file__).resolve().parents[1] / 'tools'
sys.path.insert(0, str(TOOLS))
import deploy_review
import internal_smtp_check
import restore_review
import verify_review
import runtime_review
from database_inventory import sqlite_inventory
from unittest.mock import patch
class FormalReviewDatabaseTest(unittest.TestCase):
modules = (deploy_review, internal_smtp_check, restore_review, verify_review)
def make_database(self, root):
data = root / 'data'
data.mkdir()
with closing(sqlite3.connect(data / 'smail.db')) as database:
database.execute('CREATE TABLE marker (id INTEGER)')
def test_all_formal_tools_accept_one_database(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
self.make_database(root)
for module in self.modules:
with self.subTest(module=module.__name__):
self.assertEqual(module.database_path(root), root / 'data/smail.db')
def test_all_formal_tools_reject_legacy_database(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
self.make_database(root)
(root / 'data/smail-state.db').touch()
for module in self.modules:
with self.subTest(module=module.__name__), self.assertRaises(AssertionError):
module.database_path(root)
def test_all_formal_tools_reject_nested_database(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
self.make_database(root)
nested = root / 'data' / 'archive'
nested.mkdir()
(nested / 'old.db').touch()
for module in self.modules:
with self.subTest(module=module.__name__), self.assertRaises(AssertionError):
module.database_path(root)
def test_all_formal_tools_reject_missing_database_without_creating_one(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / 'data').mkdir()
for module in self.modules:
with self.subTest(module=module.__name__), self.assertRaises(AssertionError):
module.database_path(root)
self.assertEqual(list((root / 'data').iterdir()), [])
def test_extra_sqlite_files_cannot_hide_by_name_or_location(self):
for name in ('extra.db', 'STATE.DB', '.hidden.sqlite3', 'nested/extra.sqlite',
'../config/other.db', '../renamed.bin', 'old.db-wal', 'old.sqlite-shm'):
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
self.make_database(root)
path = root / 'data' / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b'SQLite format 3\x00')
for module in self.modules:
with self.subTest(module=module.__name__), self.assertRaises(AssertionError):
module.database_path(root)
with self.assertRaises(AssertionError):
runtime_review.database_integrity(root)
def test_expected_sqlite_sidecars_are_not_second_databases(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
self.make_database(root)
for suffix in ('-wal', '-shm', '-journal'):
(root / 'data' / ('smail.db' + suffix)).touch()
for module in self.modules:
self.assertEqual(module.database_path(root), root / 'data/smail.db')
def test_sql_mode_rejects_renamed_database_and_orphan_sidecars(self):
for name in ('renamed.bin', 'data/.hidden.sqlite', 'data/old.db-wal'):
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
(root / 'data').mkdir()
self.assertEqual(sqlite_inventory(root, []), [])
(root / name).write_bytes(b'SQLite format 3\x00')
with self.assertRaises(AssertionError):
sqlite_inventory(root, [])
def test_directory_links_are_rejected_before_reading_their_contents(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
self.make_database(root)
linked = root / 'data' / 'linked'
linked.mkdir()
original = Path.is_symlink
with patch.object(Path, 'is_symlink', lambda path: path == linked or original(path)):
with self.assertRaises(AssertionError):
sqlite_inventory(root, [root / 'data/smail.db'])
def test_snapshot_and_restore_counts_close_their_connections(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
self.make_database(root)
source = root / 'data/smail.db'
target = root / 'snapshot.db'
deploy_review.snapshot(source, target)
moved = root / 'snapshot-moved.db'
target.rename(moved)
with closing(sqlite3.connect(moved)) as database:
self.assertEqual(database.execute('PRAGMA integrity_check').fetchone(), ('ok',))
isolated = root / 'isolated'
isolated.mkdir()
self.make_database(isolated)
self.assertEqual(restore_review.database_counts(isolated), {'smail.db': {'marker': 0}})
(isolated / 'data/smail.db').rename(isolated / 'data/moved.db')
def test_deployed_schema_requires_unified_configuration_history(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory).resolve()
self.make_database(root)
with closing(sqlite3.connect(root / 'data/smail.db')) as database:
database.executescript('CREATE TABLE schema_migration (version INTEGER); '
'INSERT INTO schema_migration VALUES (23); '
'CREATE TABLE outbound_delivery (state TEXT); '
"INSERT INTO outbound_delivery VALUES ('pending');")
with self.assertRaises(AssertionError):
deploy_review.database_state(root)
database.executescript('INSERT INTO schema_migration VALUES (24);')
with self.assertRaises(sqlite3.OperationalError):
deploy_review.database_state(root)
database.executescript('CREATE TABLE config_records (id TEXT);')
self.assertEqual(deploy_review.database_state(root), (24, {'pending': 1}))
if __name__ == '__main__':
unittest.main()