여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다. - ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제 - 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다 - eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다 파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이). site·frontend·admin tsc, site vitest 105 passed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
"""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<schema>\w+)\.(?P<table>\w+)\s*\((?P<body>.*?)\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}: 공통 컬럼 누락"
|