"""init.sql ↔ ORM 모델 정합성. 스키마 정의가 두 곳(마이그레이션 SQL / SQLAlchemy 모델)에 있으므로 둘이 어긋나면 "테스트는 통과하는데 실서버에서 컬럼이 없는" 상황이 난다. DB 없이 파일만 비교해 그걸 잡는다. """ import re from pathlib import Path from common.database.model.models import MAIN_BASE # parents[3] = 레포 루트 (tests → backend → solution → 루트). # ★ 백엔드가 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 def test_init_sql_is_readable(): """검증: init.sql 을 찾고 파싱할 수 있는지. 기대결과: 파일이 존재하고 CREATE TABLE 이 1개 이상 파싱된다.""" 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 = {f"{t.schema}.{t.name}" for t in MAIN_BASE.metadata.sorted_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()) model_tables = {f"{t.schema}.{t.name}" for t in MAIN_BASE.metadata.sorted_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 = [] for table in MAIN_BASE.metadata.sorted_tables: name = f"{table.schema}.{table.name}" if name not in sql_tables: continue 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) def test_every_table_has_soft_delete_columns(): """검증: 모든 테이블이 공통 컬럼(created_at·updated_at·deleted)을 갖는지. 기대결과: MainTableMixin 을 빠뜨린 모델이 없다 — 소프트 삭제 전제가 깨지면 유니크 부분 인덱스도 깨진다.""" 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}: 공통 컬럼 누락"