feat(구조물표): 323-7 새 종류 · 정리 — M02 종류 더하기(본보기 복사 · 일곱 갈래 본보기 그대로) · 손코딩 동작 · 명세 = 본보기 키 · 특수 동작 = 동작 이름 · 정본 목록 = 표 · drawing_views 걷음 · 옛 이름 되짚기 걷음(자리로 가름) · 허용 거리 파이썬 한 곳 · 벽 툴팁 = 종류 이름
- M02 종류별 탭 [종류 더하기] = 본보기(구조물 정본 원본) 복사 — 키 자동 · 이름 「본보기 2」 · 옵션 · 표시 · 그룹 · 배치 · 규칙 · 종류 덧칸 · 반영 그대로 · 종류.{새}.본보기 · 관 지점 종류 · 복사본은 본보기 밖
- 구조물 표 = 본보기 처음값 이어받음(resolveCatalog · resolve_catalog 짝 · 줄 칸 base) · baseType/base_type · specFor(명세) · 덧단 · 길이 전후 풀기 · 옹벽 추가 단 콘크리트
- 검사(화면 = 서버 짝) — 본보기 = 원본 · 같은 군 · 자리꼴 · 정본 · 만든 뒤 고정 · 복사본은 본보기가 숨긴 칸 이어받음 · 서버 덧칸 본보기 받음
- 특수 동작 = 동작 이름 — 바로 안 놓기 · 포장 나누기 = 포장_물넘이_겹침 · 월류 높이 기다림 = 월류단면
- 정본 목록 = 표 — 파이썬 관 지점 종류 = catalog owner · B05 시설 서브폼 = 표 형상 · 안 쓰는 손 목록 걷음
- drawing_views — 스키마에서 빼고 읽을 때 버림 · M02 [저장] 때 빠짐(코드는 마스터 안 고침)
- 이관 = 지난 판 이름 대신 측점 계획 자리(planted)로 가름 · all_type_names 걷음
- 허용 거리 = common_util_structure_station.py 한 곳(SAME_STATION_M · EDGE_TOLERANCE_M · PIPE_MATCH_M) · TS 표는 station.ts 머리
- 시험: test_323_7_new_type(템플릿 · 일곱 갈래 · 그림 · 검사 짝 · 서버 읽기) · 322-0 유령 시험 = 자리 · 옛 도움 파일 동작 · 표 길 · 기대 글 고침
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
@@ -49,7 +49,17 @@ const { addsAtOnce, finishAdd } = loadTs(
|
||||
const { loadForm } = loadTs(path.join(B05, "B05_Profile_UI_Structures_Panel_Commit.ts"));
|
||||
|
||||
const entry = (required, value) => ({ required, isEmpty: () => value === "" });
|
||||
const type = (type_id, managed_by = null) => ({ type_id, group: "C", managed_by });
|
||||
// 동작 = 정의 종류 덧칸(앱의 `withDefinition` 과 같음 · 323-7 종류 키 대신 동작 이름)
|
||||
const DEFINED = JSON.parse(
|
||||
fs.readFileSync(path.join(ROOT, "resources", "master_template", "구조물정의.json"), "utf8"),
|
||||
);
|
||||
const behaviors = (typeId) => DEFINED.종류?.[typeId]?.동작 ?? [];
|
||||
const type = (type_id, managed_by = null) => ({
|
||||
type_id,
|
||||
group: "C",
|
||||
managed_by,
|
||||
behaviors: behaviors(type_id),
|
||||
});
|
||||
|
||||
/** 목록 알림 → 페이지가 임시 id 를 붙여 패널 배열 안을 갈아 채우는 것(`withLocalIds` · `setStructures`). */
|
||||
function fakeCommit(structures, adds) {
|
||||
|
||||
@@ -43,7 +43,16 @@ const { fordHeightM } = loadTs(
|
||||
path.join(ROOT, "A00_Common", "design", "design_structure_ford_calc.ts"),
|
||||
);
|
||||
|
||||
const type = (type_id) => ({ type_id, group: "A", managed_by: "pipe_points" });
|
||||
// 동작 = 정의 종류 덧칸(앱의 `withDefinition` 과 같음 · 323-7 종류 키 대신 동작 이름)
|
||||
const DEFINED = JSON.parse(
|
||||
fs.readFileSync(path.join(ROOT, "resources", "master_template", "구조물정의.json"), "utf8"),
|
||||
);
|
||||
const type = (type_id) => ({
|
||||
type_id,
|
||||
group: "A",
|
||||
managed_by: "pipe_points",
|
||||
behaviors: DEFINED.종류?.[type_id]?.동작 ?? [],
|
||||
});
|
||||
|
||||
/** 우클릭 → 바로 추가 — 관 지점 정본에 한 번 넘기고 그 측점 관을 고름 · 알림 한 줄. */
|
||||
async function rightClick(typeId) {
|
||||
@@ -116,7 +125,14 @@ function respond(points, basins, pending) {
|
||||
once: {
|
||||
fordBridge: addsAtOnce(type("ford_bridge"), []),
|
||||
fordPavement: addsAtOnce(type("ford_pavement"), []),
|
||||
pavement: addsAtOnce({ type_id: "pavement_concrete", managed_by: null }, []),
|
||||
pavement: addsAtOnce(
|
||||
{
|
||||
type_id: "pavement_concrete",
|
||||
managed_by: null,
|
||||
behaviors: DEFINED.종류?.pavement_concrete?.동작 ?? [],
|
||||
},
|
||||
[],
|
||||
),
|
||||
},
|
||||
bridge: await rightClick("ford_bridge"),
|
||||
paveRc: await rightClick("ford_pavement"),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1)
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
@@ -18,6 +19,7 @@ function loadTs(file) {
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (CATALOG.isCatalogRequest(request)) return CATALOG; // 구조물 표(본보기 · 323-7)
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
return require(request);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/* PLAN 323-7 종류 더하기 헬퍼 — 진짜 `M02_SO_Def_Add.ts`(본보기 복사) · 구조물 표 · 측점 계획 · 형상 얹기 · 벽 단 ·
|
||||
* 명세 고르기 · M02 검사를 그 자리에서 트랜스파일해 Node 로 돌림(마스터는 읽기만 · 사본에서 더함) · 결과 = JSON.
|
||||
* 짝 = `test_323_7_new_type.py`(파이썬 검사 · 표 · 벽 제원이 같은 사본을 읽음).
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const ts = require(path.join(ROOT, "config", "node_modules", "typescript"));
|
||||
|
||||
const cache = new Map();
|
||||
function load(file) {
|
||||
const full = path.resolve(file);
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
if (full.endsWith(".json")) return JSON.parse(fs.readFileSync(full, "utf8"));
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
target: ts.ScriptTarget.ES2020,
|
||||
esModuleInterop: true,
|
||||
},
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const need = (spec) => {
|
||||
if (spec.startsWith("@ui/")) return new Proxy({}, { get: () => () => {} });
|
||||
const base = path.join(path.dirname(full), spec);
|
||||
const hit = [`${base}.ts`, base.replace(/\.js$/, ".ts"), base].find(
|
||||
(p) => fs.existsSync(p) && fs.statSync(p).isFile(),
|
||||
);
|
||||
if (!hit) throw new Error(`모름: ${spec} (${full})`);
|
||||
return load(hit);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, need);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const C = (name) => path.join(ROOT, "common_util", `${name}.ts`);
|
||||
const M = (name) => path.join(ROOT, "M02_MasterTemplete", "StructOptions", `${name}.ts`);
|
||||
const master = JSON.parse(
|
||||
fs.readFileSync(path.join(ROOT, "resources", "master_template", "구조물정의.json"), "utf8"),
|
||||
);
|
||||
const def = JSON.parse(JSON.stringify(master));
|
||||
const add = load(M("M02_SO_Def_Add"));
|
||||
const catalog = load(C("common_util_structure_catalog"));
|
||||
const station = load(C("common_util_structure_station"));
|
||||
const attach = load(C("common_util_structure_attach"));
|
||||
const walls = load(C("common_util_structure_walls"));
|
||||
const pipeSpecs = load(path.join(ROOT, "A00_Common", "design", "design_structure_pipe.ts"));
|
||||
const check = load(M("M02_SO_Def_Check"));
|
||||
|
||||
const out = {};
|
||||
out.templates = add.templates(def).map(([key]) => key);
|
||||
out.pipeRefused = add.addFromTemplate(def, "pipe");
|
||||
const wall = add.addFromTemplate(def, "retaining_wall");
|
||||
const pave = add.addFromTemplate(def, "pavement_concrete");
|
||||
out.keys = [wall, pave];
|
||||
const types = def.types.map((t) => ({ managed_by: null, enabled: true, ...t }));
|
||||
catalog.useCatalogDefinition({ ...def, types });
|
||||
const BRANCHES = [
|
||||
"list",
|
||||
"station",
|
||||
"title",
|
||||
"shape",
|
||||
"shapeOpts",
|
||||
"model3d",
|
||||
"placement",
|
||||
"owner",
|
||||
];
|
||||
const pick = (row) => Object.fromEntries(BRANCHES.map((k) => [k, row[k]]));
|
||||
out.rows = {
|
||||
wall: [pick(catalog.catalogRow("retaining_wall")), pick(catalog.catalogRow(wall))],
|
||||
pave: [pick(catalog.catalogRow("pavement_concrete")), pick(catalog.catalogRow(pave))],
|
||||
};
|
||||
out.base = [catalog.baseType(wall), catalog.baseType("retaining_wall")];
|
||||
out.names = [
|
||||
types.find((t) => t.type_id === wall).name,
|
||||
types.find((t) => t.type_id === pave).name,
|
||||
];
|
||||
const structure = {
|
||||
structure_id: "n1",
|
||||
type_id: wall,
|
||||
placement: "interval",
|
||||
start_m: 40,
|
||||
chainage_m: 50,
|
||||
end_m: 60,
|
||||
options: { height_m: 2, tier_1_height: 1 },
|
||||
};
|
||||
out.plan = station.planStations([], [structure], types).map((s) => [s.chainage_m, s.title]);
|
||||
const sections = [40, 50, 60].map((at) => ({ chainage_m: at }));
|
||||
attach.attachShapes(sections, [], [structure], types);
|
||||
out.attached = sections.map((s) => [s.revetment?.type_id ?? null, s.revetment?.name ?? null]);
|
||||
out.tier = walls.tierExtras(wall, { tier_1_height: 1 });
|
||||
out.spec =
|
||||
!!pipeSpecs.specFor(pave) && pipeSpecs.specFor(pave) === pipeSpecs.specFor("pavement_concrete");
|
||||
out.problems = check.catalogProblems(def, master);
|
||||
// 본보기 바꿈 · 복사본의 복사 = 거절(만든 뒤 고정 · 원본만)
|
||||
const bad = JSON.parse(JSON.stringify(def));
|
||||
bad.종류[wall].본보기 = "soil_guard";
|
||||
out.badBase = check.catalogProblems(bad, def).length > 0;
|
||||
const chain = JSON.parse(JSON.stringify(def));
|
||||
chain.종류[pave].본보기 = wall;
|
||||
out.chain = check.catalogProblems(chain, null).length > 0;
|
||||
out.def = def;
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1)
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
@@ -19,6 +20,7 @@ function loadTs(file) {
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (CATALOG.isCatalogRequest(request)) return CATALOG; // 구조물 표(본보기 · 323-7)
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
return require(request);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* 정의는 진짜 `구조물정의.json` 을 읽어 메모리 사본만 고침(파일 쓰기 없음). */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1)
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
@@ -16,9 +17,11 @@ const load = (...parts) => {
|
||||
}).outputText;
|
||||
const box = (cache[file] = { exports: {} });
|
||||
const near = (id) =>
|
||||
id.startsWith(".")
|
||||
? load(path.relative(ROOT, path.resolve(path.dirname(file), `${id}.ts`)))
|
||||
: require(id);
|
||||
CATALOG.isCatalogRequest(id) // 구조물 표(본보기 · 323-7)
|
||||
? CATALOG
|
||||
: id.startsWith(".")
|
||||
? load(path.relative(ROOT, path.resolve(path.dirname(file), `${id}.ts`)))
|
||||
: require(id);
|
||||
new Function("exports", "module", "require", js)(box.exports, box, near);
|
||||
return box.exports;
|
||||
};
|
||||
|
||||
@@ -128,7 +128,8 @@ def test_횡단_형상_단_수_단마다_형태_높이(out):
|
||||
|
||||
def test_배선():
|
||||
spec = _read("B05_Profile", "B05_Profile_UI_Structures_Spec.ts")
|
||||
assert "STRUCTURE_SPECS[type.type_id] ?? ADDON_SPECS[type.type_id]" in spec
|
||||
# 명세 · 덧붙이 명세 = 본보기 키로(323-7 · 복사한 종류도 같은 상자)
|
||||
assert "specFor(type.type_id) ?? ADDON_SPECS[baseType(type.type_id)]" in spec
|
||||
walls = _read("common_util", "common_util_structure_walls.ts")
|
||||
assert "파이썬 `tier_extras` 짝" in walls
|
||||
engine = _read("B06_Section", "B06_Section_Engine_Structures_Wall.py")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""PLAN 322-0 ② — 「기타」 유령 길 막기: 종류 이름을 바꾼 뒤에도 옛 이름 측점을 구조물로 되옮기지 않음.
|
||||
"""PLAN 322-0 ② → 323-7 — 「기타」 유령 길 막기: 종류 이름을 바꾼 뒤에도 옛 이름 측점을 안 되옮김.
|
||||
|
||||
정의 사본(임시 폴더 · 마스터 안 고침)에서 종류 이름을 바꾸고 앞 판을 `구조물정의_이력/판_N.json` 으로 둠 →
|
||||
옛 이름 · 새 이름 측점 모두 이관 0건. 옛 꼴 자유 글(기성막이 · 대피로 폭 · 임의 이름)은 종전대로 옮김.
|
||||
정의 사본(임시 폴더 · 마스터 안 고침)에서 종류 이름을 바꿈 → 지금 관 · 구조물이 심는 자리(측점
|
||||
계획)의 측점은 옛 이름 · 새 이름 모두 이관 0건(323-7 · 자리로 가름 · 지난 판 이름 되짚기 걷음).
|
||||
옛 꼴 자유 글(기성막이 · 대피로 폭 · 임의 이름)은 심는 자리가 아니면 종전대로 옮김.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -37,23 +38,25 @@ def renamed(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict:
|
||||
return old
|
||||
|
||||
|
||||
def _moved(label: str) -> int:
|
||||
plan = migrate_irregular_stations([{"chainage_m": 120.0, "structure": label}])
|
||||
def _moved(label: str, planted: tuple[float, ...] = ()) -> int:
|
||||
plan = migrate_irregular_stations([{"chainage_m": 120.0, "structure": label}], planted)
|
||||
return len(plan.structures) + len(plan.pipe_facilities)
|
||||
|
||||
|
||||
def test_old_and_new_names_are_not_migrated(renamed: dict) -> None:
|
||||
def test_old_and_new_names_at_planted_spots_are_not_migrated(renamed: dict) -> None:
|
||||
# 323-7 — 자리로 가름(지금 관 · 구조물이 심는 자리) · 이름을 바꿔도 자리는 그대로
|
||||
assert schema.structure_type_map()["box_culvert"].name == "박스암거" # 사본을 읽음
|
||||
for type_id, name in RENAMED.items():
|
||||
old_name = next(t["name"] for t in renamed["types"] if t["type_id"] == type_id)
|
||||
assert _moved(old_name) == 0, old_name
|
||||
assert _moved(name) == 0, name
|
||||
assert _moved(old_name, (120.0,)) == 0, old_name
|
||||
assert _moved(name, (120.0,)) == 0, name
|
||||
assert _moved(name) == 0, name # 지금 판 이름은 자리 없이도
|
||||
|
||||
|
||||
def test_every_type_name_of_any_version_is_blocked(renamed: dict) -> None:
|
||||
names = {t["name"] for t in renamed["types"]} | set(RENAMED.values())
|
||||
assert names <= schema.all_type_names()
|
||||
assert [name for name in sorted(names) if _moved(name)] == []
|
||||
def test_history_names_are_not_read(renamed: dict) -> None:
|
||||
# 옛 이름 되짚기 걷음(323-7) — 자리를 못 받은 옛 이름 = 옛 자유 글처럼 「기타」
|
||||
assert not hasattr(schema, "all_type_names")
|
||||
assert _moved("노출형 횡단수로") == 1
|
||||
|
||||
|
||||
def test_legacy_free_text_still_migrates(renamed: dict) -> None:
|
||||
@@ -62,12 +65,4 @@ def test_legacy_free_text_still_migrates(renamed: dict) -> None:
|
||||
assert [s.type_id for s in plan.structures] == ["refuge"]
|
||||
plan = migrate_irregular_stations([{"chainage_m": 15.0, "structure": "돌망태 보강"}])
|
||||
assert [(s.type_id, s.options["name"]) for s in plan.structures] == [("etc", "돌망태 보강")]
|
||||
|
||||
|
||||
def test_master_history_names_are_read() -> None:
|
||||
"""실제 마스터 이력(판_1 ~)의 이름도 모음 — 이력 파일은 읽기만."""
|
||||
history = MASTER.parent / schema.HISTORY_DIRNAME
|
||||
names = set()
|
||||
for path in history.glob("판_*.json"):
|
||||
names |= {t["name"] for t in json.loads(path.read_text(encoding="utf-8"))["types"]}
|
||||
assert names <= schema.all_type_names()
|
||||
assert _moved("돌망태 보강", (120.004,)) == 0 # 심는 자리면 글이 무엇이든 안 옮김
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
"""PLAN 323-7 종류 더하기 — 본보기 복사 한 종류가 일곱 갈래(목록 · 횡단도 · 카드이름 · 형상 · 형상칸 · 3D · 자리꼴)
|
||||
를 본보기 그대로 그림 · 손코딩 동작 · 명세는 본보기 키로(`baseType`) · 검사는 화면 = 서버.
|
||||
|
||||
마스터는 읽기만 — 사본(헬퍼가 더한 정의)을 임시 파일로 두고 서버 쪽 읽기를 그리로 돌림.
|
||||
TS 쪽 = `helper_323_7_new_type.cjs`(Node).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from B05_Profile import B05_Profile_Structures_Schema as schema
|
||||
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import wall_specs_from
|
||||
from common_util import common_util_structure_catalog as catalog
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Definition as definition
|
||||
|
||||
HERE = Path(__file__).parent
|
||||
ROOT = HERE.parent.parent
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not (ROOT / "config" / "node_modules").is_dir(), reason="프론트엔드 의존성 미설치"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ts() -> dict:
|
||||
run = subprocess.run(
|
||||
["node", str(HERE / "helper_323_7_new_type.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=120,
|
||||
check=True,
|
||||
)
|
||||
return json.loads(run.stdout)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def copied(ts: dict, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict:
|
||||
path = tmp_path / "구조물정의.json"
|
||||
path.write_text(json.dumps(ts["def"], ensure_ascii=False), encoding="utf-8")
|
||||
monkeypatch.setattr(schema, "DEFINITION_PATH", str(path))
|
||||
monkeypatch.setattr(catalog, "DEFINITION_PATH", str(path), raising=False)
|
||||
return ts["def"]
|
||||
|
||||
|
||||
def test_only_structure_originals_are_templates(ts: dict) -> None:
|
||||
assert "pipe" not in ts["templates"] and "retaining_wall" in ts["templates"]
|
||||
assert ts["pipeRefused"] is None
|
||||
|
||||
|
||||
def test_seven_branches_follow_template(ts: dict) -> None:
|
||||
for name, (source, copy) in ts["rows"].items():
|
||||
assert copy == source, name
|
||||
assert ts["base"] == ["retaining_wall", "retaining_wall"]
|
||||
assert ts["names"] == ["옹벽 2", "콘크리트 포장 2"]
|
||||
|
||||
|
||||
def test_new_type_draws_like_template(ts: dict) -> None:
|
||||
wall = ts["keys"][0]
|
||||
assert ts["plan"] == [[40, "옹벽 2"], [50, "옹벽 2"], [60, "옹벽 2"]]
|
||||
assert ts["attached"] == [[wall, "옹벽 2"]] * 3
|
||||
assert ts["tier"] == [
|
||||
{"form": "콘크리트", "height_m": 1}
|
||||
] # 옹벽 추가 단 = 콘크리트(본보기 동작)
|
||||
assert ts["spec"] is True # 포장 명세 = 본보기 명세
|
||||
|
||||
|
||||
def test_check_screen_equals_server(ts: dict, copied: dict) -> None:
|
||||
master = json.loads(Path(definition.schema.DEFINITION_PATH).read_text(encoding="utf-8"))
|
||||
assert ts["problems"] == []
|
||||
real = json.loads(
|
||||
(ROOT / "resources" / "master_template" / "구조물정의.json").read_text("utf-8")
|
||||
)
|
||||
assert definition.check(copied, real) == []
|
||||
assert ts["badBase"] is True and ts["chain"] is True
|
||||
bad = json.loads(json.dumps(copied))
|
||||
bad["종류"][ts["keys"][0]]["본보기"] = "soil_guard"
|
||||
assert any("본보기" in e for e in definition.check(bad, copied))
|
||||
assert master["types"] # 사본을 읽음
|
||||
|
||||
|
||||
def test_server_reads_copy_like_template(ts: dict, copied: dict) -> None:
|
||||
wall = ts["keys"][0]
|
||||
rows = catalog.resolve_catalog(copied)
|
||||
keep = ("list", "station", "title", "shape", "shapeOpts", "model3d", "placement", "owner")
|
||||
assert {k: rows[wall][k] for k in keep} == {k: rows["retaining_wall"][k] for k in keep}
|
||||
assert rows[wall]["base"] == "retaining_wall"
|
||||
item = StructureInstance.model_validate(
|
||||
{
|
||||
"structure_id": "n1",
|
||||
"type_id": wall,
|
||||
"placement": "interval",
|
||||
"start_m": 40,
|
||||
"chainage_m": 50,
|
||||
"end_m": 60,
|
||||
"options": {"height_m": 2, "tier_1_height": 1},
|
||||
}
|
||||
)
|
||||
(spec,) = wall_specs_from([item])
|
||||
assert spec["name"] == "옹벽 2" and spec["extras"][0]["form"] == "콘크리트"
|
||||
@@ -110,9 +110,18 @@ def test_폼이_적는_칸이_모두_목록에_있다(merged) -> None:
|
||||
"""폼에 칸을 늘리고 목록에 안 넣으면 여기서 잡힘."""
|
||||
source = FORM_TS.read_text(encoding="utf-8")
|
||||
body = source[source.index("readOptions() {") :]
|
||||
branches = re.split(r'current === "(\w+)"', body)[1:]
|
||||
assert branches[::2] == ["pipe", "box_culvert", "ford_pavement", "ford_bridge", "revetment"]
|
||||
for facility, block in zip(branches[::2], branches[1::2]):
|
||||
# 폼 갈래 = 구조물 표 형상(323-7 · 종류 키 대신) → 관 지점 종류
|
||||
branches = re.split(r'shapeOf\(current\) === "(\w+)"', body)[1:]
|
||||
kinds = {
|
||||
"pipe_set": "pipe",
|
||||
"box": "box_culvert",
|
||||
"ford_pavement": "ford_pavement",
|
||||
"ford": "ford_bridge",
|
||||
"hidden_revet": "revetment",
|
||||
}
|
||||
assert branches[::2] == list(kinds)
|
||||
for shape, block in zip(branches[::2], branches[1::2]):
|
||||
facility = kinds[shape]
|
||||
written = set(re.findall(r'putNumber\(options, "(\w+)"', block))
|
||||
written |= set(re.findall(r"options\.(\w+) =", block))
|
||||
missing = written - set(merged["keys"][facility])
|
||||
|
||||
@@ -92,8 +92,11 @@ def test_화면_고침을_그대로_받아_판_더하기_1_지난_판_길(copy)
|
||||
assert res.status_code == 200, res.text
|
||||
got = res.json()
|
||||
assert got["판"] == doc["판"] + 1
|
||||
for key in ("구조물군", "종류", "배치", "표시", "그룹", "규칙", "types"):
|
||||
for key in ("구조물군", "종류", "배치", "표시", "그룹", "규칙"):
|
||||
assert got[key] == sent[key], key
|
||||
# 옛 칸(drawing_views)은 [저장] 때 빠짐(323-7 · 아무도 안 읽음) — 나머지는 그대로
|
||||
retired = schema.without_retired
|
||||
assert got["types"] == [retired(t) for t in sent["types"]]
|
||||
assert list(got["구조물군"]) == list(sent["구조물군"]) # 순서 그대로
|
||||
rows = admin.get(f"{URL}/history").json()
|
||||
assert [r["판"] for r in rows] == [doc["판"]] and len(rows[0]["저장일"]) == 16
|
||||
|
||||
Reference in New Issue
Block a user