| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- """Verify a DMS data export by importing it into an isolated scratch database."""
- from __future__ import annotations
- import argparse
- import os
- from pathlib import Path
- from alembic import command
- from alembic.config import Config
- from dotenv import dotenv_values
- from pymysql.constants import CLIENT
- from sqlalchemy import create_engine, text
- from sqlalchemy.engine import make_url
- TABLES = {
- "sys_organization": 3,
- "sys_user": 3,
- "doc_category": 21,
- "doc_document": 31,
- "doc_attachment_binding": 19,
- "doc_permission": 7,
- "sys_audit_log": 291,
- }
- def execute_script(database_url: str, path: Path) -> None:
- engine = create_engine(
- database_url,
- connect_args={"client_flag": CLIENT.MULTI_STATEMENTS},
- )
- connection = engine.raw_connection()
- try:
- driver_connection = connection.driver_connection
- driver_connection.autocommit(True)
- with driver_connection.cursor() as cursor:
- cursor.execute(path.read_text(encoding="utf-8"))
- while cursor.nextset():
- pass
- finally:
- connection.close()
- engine.dispose()
- def main() -> None:
- parser = argparse.ArgumentParser()
- parser.add_argument("--env-file", type=Path, required=True)
- parser.add_argument("--schema", type=Path, required=True)
- parser.add_argument("--data", type=Path, required=True)
- parser.add_argument("--backend", type=Path, required=True)
- parser.add_argument("--scratch-database", required=True)
- args = parser.parse_args()
- if not args.scratch_database.startswith("dms_full_demo_verify_"):
- raise RuntimeError("Scratch database name must start with dms_full_demo_verify_")
- source_url = make_url(dotenv_values(args.env_file).get("DMS_DATABASE_URL") or "")
- server_url = source_url.set(database=None)
- scratch_url = source_url.set(database=args.scratch_database)
- server_engine = create_engine(server_url)
- scratch_created = False
- try:
- with server_engine.connect().execution_options(isolation_level="AUTOCOMMIT") as connection:
- exists = connection.execute(
- text("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME=:name"),
- {"name": args.scratch_database},
- ).first()
- if exists:
- raise RuntimeError("Scratch database already exists; refusing to overwrite it")
- connection.execute(text(f"CREATE DATABASE `{args.scratch_database}` CHARACTER SET utf8mb4"))
- scratch_created = True
- scratch_url_text = scratch_url.render_as_string(hide_password=False)
- execute_script(scratch_url_text, args.schema.resolve())
- previous_directory = Path.cwd()
- try:
- os.chdir(args.backend.resolve())
- os.environ["DMS_DATABASE_URL"] = scratch_url_text
- os.environ["DMS_JWT_SECRET"] = "verification-only-secret-not-for-deployment"
- command.upgrade(Config("migrations/alembic.ini"), "head")
- finally:
- os.chdir(previous_directory)
- execute_script(scratch_url_text, args.data.resolve())
- scratch_engine = create_engine(scratch_url)
- try:
- with scratch_engine.connect() as connection:
- for table_name, expected in TABLES.items():
- actual = connection.execute(text(f"SELECT COUNT(*) FROM `{table_name}`")).scalar_one()
- if actual != expected:
- raise RuntimeError(f"{table_name}: expected {expected}, got {actual}")
- print(f"{table_name}={actual}")
- finally:
- scratch_engine.dispose()
- print("scratch import verification passed")
- finally:
- if scratch_created:
- with server_engine.connect().execution_options(isolation_level="AUTOCOMMIT") as connection:
- connection.execute(text(f"DROP DATABASE `{args.scratch_database}`"))
- print("scratch database removed")
- server_engine.dispose()
- if __name__ == "__main__":
- main()
|