"""init.sql ↔ ORM 모델 정합성.""" import re from pathlib import Path from common.database.model.models import MAIN_BASE # parents[3] = 레포 루트 (tests → backend → solution → 루트). _INIT_SQL = Path(__file__).resolve().parents[3] / "postgres-init" / "init-data" / "init.sql" _CREATE_TABLE_RE = re.compile( r"CREATE TABLE IF NOT EXISTS\s+(?P\w+)\.(?P\w+)\s*\((?P.*?)\n\);", re.S | re.I, ) def _parse_init_sql() -> dict: """init.sql → {"schema.table": {컬럼명, ...}}""" sql = _INIT_SQL.read_text(encoding="utf-8") out = {} for m in _CREATE_TABLE_RE.finditer(sql): columns = set() for line in m.group("body").splitlines(): line = line.strip() if not line or line.startswith("--"): continue name = line.split()[0] if name.upper() in ("PRIMARY", "UNIQUE", "CONSTRAINT", "FOREIGN", "CHECK"): continue columns.add(name) out[f"{m.group('schema')}.{m.group('table')}"] = columns return out # init.sql 에는 있고 ORM 모델은 없는 표. _NOT_ORM = {"public.schema_migrations"} def _model_tables() -> set: """ORM 모델 → {"public.table", ...}""" return {f"{t.schema or 'public'}.{t.name}" for t in MAIN_BASE.metadata.sorted_tables} def test_init_sql_is_readable(): """검증: init.sql 을 찾고 파싱할 수 있는지.""" assert _INIT_SQL.exists(), f"init.sql 이 없다: {_INIT_SQL}" assert _parse_init_sql(), "init.sql 에서 CREATE TABLE 을 하나도 파싱하지 못했다" def test_every_model_table_exists_in_init_sql(): """검증: ORM 모델의 모든 테이블이 init.sql 에도 있는지.""" sql_tables = set(_parse_init_sql()) model_tables = _model_tables() missing = sorted(model_tables - sql_tables) assert not missing, f"init.sql 에 없는 모델 테이블: {missing}" def test_every_init_sql_table_has_a_model(): """검증: init.sql 의 모든 테이블에 ORM 모델이 있는지.""" sql_tables = set(_parse_init_sql()) - _NOT_ORM model_tables = _model_tables() missing = sorted(sql_tables - model_tables) assert not missing, f"ORM 모델이 없는 init.sql 테이블: {missing}" def test_columns_match_between_model_and_init_sql(): """검증: 테이블마다 컬럼 집합이 양쪽에서 같은지.""" sql_tables = _parse_init_sql() problems = [] compared = 0 for table in MAIN_BASE.metadata.sorted_tables: name = f"{table.schema or 'public'}.{table.name}" if name not in sql_tables: continue compared += 1 model_cols = {c.name for c in table.columns} sql_cols = sql_tables[name] if model_cols != sql_cols: problems.append( f"{name}: 모델에만 {sorted(model_cols - sql_cols)} / init.sql 에만 {sorted(sql_cols - model_cols)}" ) assert not problems, "컬럼 불일치:\n" + "\n".join(problems) # 한 표도 못 찾으면 이 테스트는 아무것도 검사하지 않고 통과한다. assert compared == len(MAIN_BASE.metadata.sorted_tables), ( f"init.sql 에서 {compared}/{len(MAIN_BASE.metadata.sorted_tables)} 개만 찾았다 — 이름 규칙이 어긋났다" ) def test_every_table_has_soft_delete_columns(): """검증: 모든 테이블이 공통 컬럼(created_at·updated_at·deleted)을 갖는지.""" for table in MAIN_BASE.metadata.sorted_tables: cols = {c.name for c in table.columns} assert {"created_at", "updated_at", "deleted"} <= cols, f"{table.schema}.{table.name}: 공통 컬럼 누락"