"""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}