Files
Aislo/resources/tester/test_761_2_clone_replace.py
T
eomsangdonandClaude Opus 5.5 4db52b5504 feat(761-2): 복제 도구 자리 바꿔 넣기 --replace — 대상 id · 이름 · 주인 · 참여자 · 종류 유지
- common_util_project_copy_replace: 대상 옛 행(복제 표 목록) 지우고 원본 행으로 채움 · projects 는 유지 칸 밖만 원본 값 · storage 를 원본과 똑같이(robocopy /MIR) · 넣기 전 대상 행 덤프(--backup-dir)
- common_util_project_copy: 행 넣기를 _insert_rows 로 떼어 full 복제 · 바꿔 넣기가 함께 씀 · robocopy 출력 글자 깨짐 무시
- 지킴 시험 test_761_2_clone_replace(유지 칸 · 지우는 표 = 복제 표 · id 다시 잇기)
- test_532_1 낡은 단정 고침: 지표면 지워진 옛 노선의 NULL(ON DELETE SET NULL)을 새 id 밖 값으로 잘못 셈
- 실제 한 번: 가이드 → 임시 복제 → 그 자리에 --replace → 행 수 · 파일 같음 · 추천 4 꺼내짐 → 앱 영구 삭제 길로 지움

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:57:40 +09:00

119 lines
4.7 KiB
Python

"""761-2 복제 도구 자리 바꿔 넣기(--replace) — 순수 부분(DB · 파일 안 씀).
- 유지 칸: 대상 id · 이름 · 주인 · 회사 · 종류 · 저장 자리 · 담당자는 대상 값 · 나머지는 원본
- 지우는 표 목록 = 복제 표 목록(projects · project_members 뺌)과 같음 — 표가 늘면 여기서 빨강
- id 다시 잇기: 새 행이 새 노선 · 새 지표면 · 새 입력 파일 id 를 가리킴 · 단계 설정 id 도
"""
import asyncio
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from common_util import common_util_project_copy as cp # noqa: E402
from common_util import common_util_project_copy_replace as rp # noqa: E402
class _Cursor:
"""execute 를 적어 두고 · INSERT 마다 새 자동증가 id · SELECT 는 projects 만 한 줄."""
def __init__(self):
self.inserts: list[tuple[str, dict]] = []
self.selects: list[str] = []
self.lastrowid = 1000
self._last = ""
async def execute(self, sql, args=()):
self._last = sql
if sql.startswith("INSERT INTO"):
table = sql.split("`")[1]
names = [part.strip("` ") for part in sql.split("(")[1].split(")")[0].split(",")]
self.inserts.append((table, dict(zip(names, args, strict=True))))
self.lastrowid += 1
else:
self.selects.append(sql)
return 0
async def fetchall(self):
return [{"id": "p", "name": "x"}] if "FROM projects" in self._last else []
def test_merged_project_row_keeps_target_identity():
source = {
"id": "src",
"name": "가이드",
"user_id": 3,
"company_id": 1,
"project_kind": "ROAD",
"storage_path": "storage/1/3/src",
"region": "용화",
"status": "WF3_ANALYZING",
"crs_epsg": 5178,
"pm_user_id": 3,
"updated_at": "t",
}
target = {
**{key: f"T-{key}" for key in source},
"id": "dst",
"storage_path": "storage/1/9/dst",
}
merged = rp.merged_project_row(source, target)
assert "id" not in merged and "updated_at" not in merged
for key in ("name", "user_id", "company_id", "project_kind", "storage_path", "pm_user_id"):
assert merged[key] == target[key]
for key in ("region", "status", "crs_epsg"):
assert merged[key] == source[key]
def test_delete_tables_match_clone_tables():
cursor = _Cursor()
rows = asyncio.run(cp._source_rows(cursor, "p"))
cloned = set(rows) - {"projects", "project_members"}
assert set(rp.ROUTE_CHILD_TABLES) | set(rp.PROJECT_TABLES) == cloned
def test_insert_rows_relinks_ids():
rows = {
"input_files": [{"id": 1, "project_id": "src", "name": "a.las"}],
"processed_point_cloud": [{"id": 2, "project_id": "src", "input_file_id": 1}],
"surface_models": [
{"id": 3, "project_id": "src", "source_file_id": 1, "processed_cloud_id": 2}
],
"terrain_layers": [{"id": 4, "surface_model_id": 3}],
"routes": [{"id": 5, "project_id": "src", "surface_model_id": 3}],
"route_points": [{"id": 6, "route_id": 5}],
"route_statistics": [],
"longitudinal_sections": [{"id": 7, "route_id": 5, "project_id": "src"}],
"cross_sections": [],
"project_workflow_stages": [
{
"id": 8,
"project_id": "src",
"stage_no": 2,
"params": json.dumps({"input_file_id": "1", "surface_model_id": 3}),
}
],
**{table: [] for table in cp._PLAN_ALL},
}
cursor = _Cursor()
model_map = asyncio.run(cp._insert_rows(cursor, rows, "dst"))
new = {table: row for table, row in cursor.inserts}
assert [table for table, _ in cursor.inserts][0] == "input_files"
assert "projects" not in new and "project_members" not in new
assert all(row.get("project_id", "dst") == "dst" for _, row in cursor.inserts)
assert all("id" not in row for _, row in cursor.inserts)
file_id, cloud_id, model_id = 1001, 1002, 1003
assert new["processed_point_cloud"]["input_file_id"] == file_id
assert new["surface_models"]["source_file_id"] == file_id
assert new["surface_models"]["processed_cloud_id"] == cloud_id
assert model_map == {3: model_id}
assert new["terrain_layers"]["surface_model_id"] == model_id
assert new["routes"]["surface_model_id"] == model_id
route_id = 1005
assert new["route_points"]["route_id"] == route_id
assert new["longitudinal_sections"]["route_id"] == route_id
params = json.loads(new["project_workflow_stages"]["params"])
assert params == {"input_file_id": str(file_id), "surface_model_id": model_id}