Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_4' into sub_laptop_1
This commit is contained in:
@@ -156,10 +156,8 @@ export function routeCrossCard(
|
||||
box,
|
||||
);
|
||||
// 그래프만(393-3) — 카드 머리 · 줌 단추 · 발(암 경계 · 요약) 걷음 · 벽 · 선 · 면적 고름 막음
|
||||
// 경고 칩(빈칸 · 기본값 · 사면 열림) · 구조물 이름(표시만 종류 · 507-1)만 그래프 오른쪽 위에 겹쳐 남김(505-3)
|
||||
const chips = card.querySelectorAll(
|
||||
":scope > header .b06-cross-card__warning, :scope > header .b06-cross-card__structure",
|
||||
);
|
||||
// 경고 칩(빈칸 · 기본값 · 사면 열림)만 그래프 오른쪽 위에 겹쳐 남김(505-3)
|
||||
const chips = card.querySelectorAll(":scope > header .b06-cross-card__warning");
|
||||
if (chips.length) {
|
||||
const box = document.createElement("div");
|
||||
box.className = "b05-cross-chips";
|
||||
|
||||
@@ -150,12 +150,6 @@
|
||||
"읽는 칸": ["pave_spec", "height_m", "widening_off"],
|
||||
"자리꼴": ["interval"],
|
||||
"정본": ["structures"]
|
||||
},
|
||||
"mark": {
|
||||
"이름": "표시만(형상 없음 · 측점 · 이름만)",
|
||||
"읽는 칸": [],
|
||||
"자리꼴": ["point", "interval", "site"],
|
||||
"정본": ["pipe_points", "structures"]
|
||||
}
|
||||
},
|
||||
"3D": {
|
||||
@@ -229,16 +223,16 @@
|
||||
},
|
||||
"cross_drain_exposed": {
|
||||
"목록": "both",
|
||||
"횡단도": "anchor",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "mark",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"open_ditch": {
|
||||
"목록": "both",
|
||||
"횡단도": "anchor",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "mark",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"cross_underdrain": {
|
||||
|
||||
@@ -53,8 +53,8 @@ const enabled = (id) => def.types.find((t) => t.type_id === id).enabled;
|
||||
const own = (id) => clone(reflect.reflectOf(def)[id] ?? null);
|
||||
reflect.setReflect(def, "wildlife_path", "목록", "both");
|
||||
reflect.setReflect(def, "retaining_wall", "목록", "b06");
|
||||
reflect.setReflect(def, "open_ditch", "횡단도", "anchor");
|
||||
reflect.setReflect(def, "cross_drain_exposed", "횡단도", "none");
|
||||
reflect.setReflect(def, "open_ditch", "횡단도", "none");
|
||||
reflect.setReflect(def, "cross_drain_exposed", "횡단도", "anchor");
|
||||
const setBefore = reflect.isSet(def, "cross_drain_exposed", "횡단도");
|
||||
reflect.setShapeOpt(def, "soil_guard", "weep", true);
|
||||
const weepOn = own("soil_guard");
|
||||
@@ -95,8 +95,8 @@ const steps = [];
|
||||
reflect.setReflect(work, "masonry_wet", "목록", "both");
|
||||
steps.push(reflect.differs(work, base));
|
||||
reflect.setReflect(work, "masonry_wet", "목록", "hidden");
|
||||
reflect.setReflect(work, "open_ditch", "횡단도", "none");
|
||||
reflect.setReflect(work, "open_ditch", "횡단도", "anchor");
|
||||
reflect.setReflect(work, "open_ditch", "횡단도", "none");
|
||||
reflect.setShapeOpt(work, "soil_guard", "form", "콘크리트");
|
||||
reflect.setShapeOpt(work, "soil_guard", "form", "통나무·목재틀");
|
||||
reflect.setShown(work, "pipe", ["wing_wall_type"], false);
|
||||
|
||||
@@ -108,6 +108,7 @@ STUBS["B06_Section_UI_Standard_Panel"] = {
|
||||
|
||||
// 구조물 초안 얹기(측점 계획 · 형상) — 이 시험 밖 · 아무 일 없음
|
||||
STUBS["B06_Section_Section_Drafts"] = {
|
||||
supersedeDraftStations: () => undefined,
|
||||
withDraftStations: async () => undefined,
|
||||
withDraftShapes: async () => undefined,
|
||||
forgetDraftStations: () => undefined,
|
||||
|
||||
@@ -7,24 +7,11 @@
|
||||
* 입력 argv[2] JSON · argv[3] 격자 이진. 결과 = 표준출력 JSON. 짝 = `test_349_0l_undo_draw.py`. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
const CATALOG = require("./helper_322_catalog_module.cjs");
|
||||
const { makeLoader, memoryStorage } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const job = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
|
||||
function memoryStorage() {
|
||||
const map = new Map();
|
||||
return {
|
||||
getItem: (key) => (map.has(key) ? map.get(key) : null),
|
||||
setItem: (key, value) => map.set(key, String(value)),
|
||||
removeItem: (key) => map.delete(key),
|
||||
key: (index) => [...map.keys()][index] ?? null,
|
||||
get length() {
|
||||
return map.size;
|
||||
},
|
||||
};
|
||||
}
|
||||
const windowBase = {
|
||||
sessionStorage: memoryStorage(),
|
||||
localStorage: memoryStorage(),
|
||||
@@ -52,31 +39,7 @@ globalThis.fetch = (url, init) =>
|
||||
});
|
||||
|
||||
const STUBS = {};
|
||||
const cache = new Map();
|
||||
function loadTs(full) {
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (CATALOG.isCatalogRequest(request)) return CATALOG;
|
||||
const key = Object.keys(STUBS).find((name) => request.endsWith(name));
|
||||
if (key) return STUBS[key];
|
||||
if (request.startsWith("@config/")) return { API_BASE_URL: "http://fake" };
|
||||
if (request.startsWith("@ui/")) return anyModule();
|
||||
if (request.endsWith(".css")) return {};
|
||||
if (request.endsWith(".json")) return require(path.join(path.dirname(full), request));
|
||||
if (request.startsWith("@util/"))
|
||||
return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`));
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
const load = (...parts) => loadTs(path.join(ROOT, ...parts));
|
||||
const { load, loadTs } = makeLoader({ root: ROOT, stubs: STUBS, ui: () => anyModule() });
|
||||
|
||||
const sampler = load("common_util", "common_util_surface_sampler.ts");
|
||||
const bytes = fs.readFileSync(process.argv[3]);
|
||||
@@ -141,6 +104,7 @@ STUBS["B06_Section_UI_Standard_Panel"] = {
|
||||
};
|
||||
// 구조물 초안 얹기(측점 계획 · 형상) — 이 시험 밖 · 아무 일 없음
|
||||
STUBS["B06_Section_Section_Drafts"] = {
|
||||
supersedeDraftStations: () => undefined,
|
||||
withDraftStations: async () => undefined,
|
||||
withDraftShapes: async () => undefined,
|
||||
forgetDraftStations: () => undefined,
|
||||
|
||||
@@ -7,24 +7,11 @@
|
||||
* 입력 argv[2] JSON · argv[3] 격자 이진. 결과 = 표준출력 JSON. 짝 = `test_349_0o_redo_same.py`. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
const CATALOG = require("./helper_322_catalog_module.cjs");
|
||||
const { makeLoader, memoryStorage } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const job = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
|
||||
function memoryStorage() {
|
||||
const map = new Map();
|
||||
return {
|
||||
getItem: (key) => (map.has(key) ? map.get(key) : null),
|
||||
setItem: (key, value) => map.set(key, String(value)),
|
||||
removeItem: (key) => map.delete(key),
|
||||
key: (index) => [...map.keys()][index] ?? null,
|
||||
get length() {
|
||||
return map.size;
|
||||
},
|
||||
};
|
||||
}
|
||||
const windowBase = {
|
||||
sessionStorage: memoryStorage(),
|
||||
localStorage: memoryStorage(),
|
||||
@@ -52,31 +39,7 @@ globalThis.fetch = (url, init) =>
|
||||
});
|
||||
|
||||
const STUBS = {};
|
||||
const cache = new Map();
|
||||
function loadTs(full) {
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (CATALOG.isCatalogRequest(request)) return CATALOG;
|
||||
const key = Object.keys(STUBS).find((name) => request.endsWith(name));
|
||||
if (key) return STUBS[key];
|
||||
if (request.startsWith("@config/")) return { API_BASE_URL: "http://fake" };
|
||||
if (request.startsWith("@ui/")) return anyModule();
|
||||
if (request.endsWith(".css")) return {};
|
||||
if (request.endsWith(".json")) return require(path.join(path.dirname(full), request));
|
||||
if (request.startsWith("@util/"))
|
||||
return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`));
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
const load = (...parts) => loadTs(path.join(ROOT, ...parts));
|
||||
const { load, loadTs } = makeLoader({ root: ROOT, stubs: STUBS, ui: () => anyModule() });
|
||||
|
||||
const sampler = load("common_util", "common_util_surface_sampler.ts");
|
||||
const bytes = fs.readFileSync(process.argv[3]);
|
||||
@@ -141,6 +104,7 @@ STUBS["B06_Section_UI_Standard_Panel"] = {
|
||||
};
|
||||
// 구조물 초안 얹기(측점 계획 · 형상) — 이 시험 밖 · 아무 일 없음
|
||||
STUBS["B06_Section_Section_Drafts"] = {
|
||||
supersedeDraftStations: () => undefined,
|
||||
withDraftStations: async () => undefined,
|
||||
withDraftShapes: async () => undefined,
|
||||
forgetDraftStations: () => undefined,
|
||||
|
||||
@@ -140,6 +140,7 @@ STUBS["B06_Section_UI_Standard_Panel"] = {
|
||||
};
|
||||
// 구조물 초안 얹기(측점 계획 · 형상) — 이 시험 밖 · 아무 일 없음
|
||||
STUBS["B06_Section_Section_Drafts"] = {
|
||||
supersedeDraftStations: () => undefined,
|
||||
withDraftStations: async () => undefined,
|
||||
withDraftShapes: async () => undefined,
|
||||
forgetDraftStations: () => undefined,
|
||||
|
||||
@@ -4,24 +4,11 @@
|
||||
* 입력 argv[2] JSON · argv[3] 격자 이진. 결과 = 표준출력 JSON. 짝 = `test_349_0s_draw_work.py`. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
const CATALOG = require("./helper_322_catalog_module.cjs");
|
||||
const { makeLoader, memoryStorage } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const job = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
|
||||
function memoryStorage() {
|
||||
const map = new Map();
|
||||
return {
|
||||
getItem: (key) => (map.has(key) ? map.get(key) : null),
|
||||
setItem: (key, value) => map.set(key, String(value)),
|
||||
removeItem: (key) => map.delete(key),
|
||||
key: (index) => [...map.keys()][index] ?? null,
|
||||
get length() {
|
||||
return map.size;
|
||||
},
|
||||
};
|
||||
}
|
||||
const windowBase = {
|
||||
sessionStorage: memoryStorage(),
|
||||
localStorage: memoryStorage(),
|
||||
@@ -49,31 +36,7 @@ globalThis.fetch = (url, init) =>
|
||||
});
|
||||
|
||||
const STUBS = {};
|
||||
const cache = new Map();
|
||||
function loadTs(full) {
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (CATALOG.isCatalogRequest(request)) return CATALOG;
|
||||
const key = Object.keys(STUBS).find((name) => request.endsWith(name));
|
||||
if (key) return STUBS[key];
|
||||
if (request.startsWith("@config/")) return { API_BASE_URL: "http://fake" };
|
||||
if (request.startsWith("@ui/")) return anyModule();
|
||||
if (request.endsWith(".css")) return {};
|
||||
if (request.endsWith(".json")) return require(path.join(path.dirname(full), request));
|
||||
if (request.startsWith("@util/"))
|
||||
return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`));
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
const load = (...parts) => loadTs(path.join(ROOT, ...parts));
|
||||
const { load, loadTs } = makeLoader({ root: ROOT, stubs: STUBS, ui: () => anyModule() });
|
||||
|
||||
const sampler = load("common_util", "common_util_surface_sampler.ts");
|
||||
const bytes = fs.readFileSync(process.argv[3]);
|
||||
@@ -138,6 +101,7 @@ STUBS["B06_Section_UI_Standard_Panel"] = {
|
||||
};
|
||||
// 구조물 초안 얹기(측점 계획 · 형상) — 이 시험 밖 · 아무 일 없음
|
||||
STUBS["B06_Section_Section_Drafts"] = {
|
||||
supersedeDraftStations: () => undefined,
|
||||
withDraftStations: async () => undefined,
|
||||
withDraftShapes: async () => undefined,
|
||||
forgetDraftStations: () => undefined,
|
||||
|
||||
@@ -141,6 +141,7 @@ STUBS["B06_Section_UI_Standard_Panel"] = {
|
||||
};
|
||||
// 구조물 초안 얹기(측점 계획 · 형상) — 이 시험 밖 · 아무 일 없음
|
||||
STUBS["B06_Section_Section_Drafts"] = {
|
||||
supersedeDraftStations: () => undefined,
|
||||
withDraftStations: async () => undefined,
|
||||
withDraftShapes: async () => undefined,
|
||||
forgetDraftStations: () => undefined,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/* 507-4 공용 불러오기 본보기 — 진짜 `design_calc_sites.ts` 를 공용 로더로 불러 판 읽기를 가짜 fetch 로 시험.
|
||||
* node helper_507_4_loader_smoke.cjs (짝 = `test_507_4_ts_stub_loader.py`) */
|
||||
const path = require("path");
|
||||
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const calls = [];
|
||||
globalThis.fetch = async (url) => {
|
||||
calls.push(url);
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
version: 1,
|
||||
sites: { ground_sample: "server", corridor_mesh: "both" },
|
||||
groups: [],
|
||||
}),
|
||||
};
|
||||
};
|
||||
const { load } = makeLoader({ root: ROOT });
|
||||
const sites = load("A00_Common", "design", "design_calc_sites.ts");
|
||||
|
||||
(async () => {
|
||||
const out = {
|
||||
ground: await sites.calcOnServer("ground_sample"),
|
||||
corridor: await sites.calcOnServer("corridor_mesh"),
|
||||
};
|
||||
out.calls = calls;
|
||||
console.log(JSON.stringify(out));
|
||||
})();
|
||||
@@ -0,0 +1,59 @@
|
||||
/* 진짜 TS 를 CommonJS 로 트랜스파일해 부르는 공용 불러오기 + 가짜 모듈 끼워 넣기(507-4 · 새 시험부터 이걸 씀).
|
||||
* 옛 헬퍼 198 벌이 저마다 베낀 `loadTs`(+ `STUBS`) 한 벌 — 진짜 모듈이 새 export · import 를 늘리면 헬퍼마다 낡아
|
||||
* 깨졌음(482). 여기만 고치면 되게 모음.
|
||||
*
|
||||
* const { makeLoader, memoryStorage } = require("./helper_ts_stub_loader.cjs");
|
||||
* const STUBS = {}; // 끝이 이 이름으로 끝나는 import 를 가짜로(헬퍼가 나중에 채움)
|
||||
* const { load, loadTs } = makeLoader({ root: ROOT, stubs: STUBS, ui: () => anyModule() });
|
||||
* const store = load("B06_Section", "B06_Section_Section_Store.ts");
|
||||
*
|
||||
* 풀이 순서: 카탈로그 · STUBS · @config(가짜 주소) · @ui(`ui` 쪽 값 · 기본 showToast) · .css · .json · @util · 상대 경로 · node.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
const CATALOG = require("./helper_322_catalog_module.cjs");
|
||||
|
||||
function memoryStorage() {
|
||||
const map = new Map();
|
||||
return {
|
||||
getItem: (key) => (map.has(key) ? map.get(key) : null),
|
||||
setItem: (key, value) => map.set(key, String(value)),
|
||||
removeItem: (key) => map.delete(key),
|
||||
key: (index) => [...map.keys()][index] ?? null,
|
||||
get length() {
|
||||
return map.size;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeLoader({ root, stubs = {}, ui = () => ({ showToast: () => undefined }) }) {
|
||||
const cache = new Map();
|
||||
function loadTs(full) {
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (CATALOG.isCatalogRequest(request)) return CATALOG;
|
||||
const key = Object.keys(stubs).find((name) => request.endsWith(name));
|
||||
if (key) return stubs[key];
|
||||
if (request.startsWith("@config/")) return { API_BASE_URL: "http://fake" };
|
||||
if (request.startsWith("@ui/")) return ui();
|
||||
if (request.endsWith(".css")) return {};
|
||||
if (request.endsWith(".json")) return require(path.join(path.dirname(full), request));
|
||||
if (request.startsWith("@util/"))
|
||||
return loadTs(path.join(root, "common_util", `${request.slice(6)}.ts`));
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
return { loadTs, load: (...parts) => loadTs(path.join(root, ...parts)) };
|
||||
}
|
||||
|
||||
module.exports = { makeLoader, memoryStorage };
|
||||
@@ -0,0 +1,64 @@
|
||||
"""검증 프로젝트 찾기 — 읽기만 하는 시험이 필요한 파일을 가진 storage 프로젝트 하나를 고름(507-4).
|
||||
|
||||
옛 시험은 지워진 검증_sub7 의 uuid 를 박아 늘 건너뜀 → 지금 storage 에 있는 프로젝트 가운데
|
||||
OWNERS.md 의 검증_subN 가운데 필요한 파일이 다 있고 가장 오래 안 바뀐 것을 씀(쓰는 중인 프로젝트 피함). `AISLO_TEST_PROJECT=<uuid>` 로 고정 가능. 없으면
|
||||
`NONE` 경로(존재하지 않음) 를 돌려줘 시험이 `SKIP_REASON` 글로 건너뜀. 쓰는 시험 금지(읽기 전용).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
NONE = ROOT / "storage" / "_no_verify_project"
|
||||
SKIP_REASON = "검증 프로젝트 없음 — storage/*/*/ 에 필요한 파일(격자 · 종단 · 노선 · 관)을 다 가진 프로젝트가 없음"
|
||||
|
||||
#: B05 노선 · B06 상세 · 관 · 격자를 쓰는 349 계열 시험이 읽는 파일
|
||||
ROUTE_SET = (
|
||||
"B04_PreProcess/models/dtm_classification_smooth.npz",
|
||||
"B06_Section/longitudinal/longitudinal.json",
|
||||
"B05_Profile/route/planned_route_curves.json",
|
||||
"B05_Profile/route/planned_route.csv",
|
||||
"B05_Profile/route/planned_route_nodes.csv",
|
||||
"B04_PreProcess/drainage/edits/pipe_points.json",
|
||||
)
|
||||
|
||||
|
||||
def _verify_ids() -> set[str]:
|
||||
"""OWNERS.md 표의 검증_subN 프로젝트 uuid — 사용자 프로젝트(엄상준_검증 등)는 시험이 안 열음."""
|
||||
text = (ROOT / "OWNERS.md").read_text(encoding="utf-8")
|
||||
return set(re.findall(r"`검증_sub\d+`\s*\|\s*`([0-9a-f-]{36})`", text))
|
||||
|
||||
|
||||
def _touched(project: Path, rels: tuple[str, ...]) -> float:
|
||||
"""가장 늦게 바뀐 때 — 다른 창이 쓰는 중인 프로젝트(최근)는 뒤로."""
|
||||
sections = project / "B06_Section" / "cross_sections"
|
||||
stamps = [(project / rel).stat().st_mtime for rel in rels] + [sections.stat().st_mtime]
|
||||
return max(stamps + [f.stat().st_mtime for f in sections.glob("*.json")])
|
||||
|
||||
|
||||
def find_project(*rels: str) -> Path:
|
||||
pinned = os.environ.get("AISLO_TEST_PROJECT")
|
||||
candidates = (
|
||||
list((ROOT / "storage").glob(f"*/*/{pinned}"))
|
||||
if pinned
|
||||
else [p for p in (ROOT / "storage").glob("*/*/*") if p.name in _verify_ids()]
|
||||
)
|
||||
found = [
|
||||
project
|
||||
for project in candidates
|
||||
if all((project / rel).is_file() for rel in rels)
|
||||
and (project / "B06_Section" / "cross_sections").is_dir()
|
||||
]
|
||||
return min(found, key=lambda project: _touched(project, rels), default=NONE)
|
||||
|
||||
|
||||
#: 검증 프로젝트로 처음 돌려 본 때(507-4) 이미 빨강 — 대부분 프로젝트와 무관(여섯 프로젝트 같음) · 옛 기대가 낡았는지 제품 흠인지 가려질 때까지(0q 메아리 = 돌 때마다 흔들림)
|
||||
DRIFT = pytest.mark.xfail(
|
||||
strict=False,
|
||||
reason="507-4: 검증 프로젝트로 돌리니 빨강 · 흔들림 — 기대 낡음 또는 제품 흠(브레인 가림 전)",
|
||||
)
|
||||
@@ -41,7 +41,7 @@ PIPES = [
|
||||
},
|
||||
]
|
||||
STRUCTURES = [
|
||||
# 노출형 = 구조물 표 횡단도 anchor · 형상 표시만(507-1 · 요구 311-2) — 측점 심음
|
||||
# 노출형 = 구조물 표 횡단도 none(322-1 ★1) — 측점 안 심음
|
||||
{"type_id": "cross_drain_exposed", "placement": "point", "chainage_m": 120.0},
|
||||
{
|
||||
"type_id": "retaining_wall",
|
||||
@@ -67,7 +67,7 @@ STRUCTURES = [
|
||||
},
|
||||
{"type_id": "evac_road", "placement": "point", "chainage_m": 720.0},
|
||||
]
|
||||
EXPECTED = [95, 120, 160, 230, 300, 395, 400, 410, 500, 510, 530, 545, 560]
|
||||
EXPECTED = [95, 160, 230, 300, 395, 400, 410, 500, 510, 530, 545, 560]
|
||||
# 368-2 추가 측점 — 47 만 듦(95 관 · 300 세월교 자리는 그쪽이 이김)
|
||||
PLUS = [47.0, 95.0, 300.0]
|
||||
|
||||
|
||||
@@ -18,21 +18,26 @@ import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
BASE = "http://localhost:5177"
|
||||
API = "http://localhost:8004" # 5177 이 프록시하는 이 폴더 백엔드(OWNERS.md) — 다른 폴더로 가면 그 폴더 마스터를 씀
|
||||
BOARD = ROOT / "resources" / "master_template" / "calc_sites.json"
|
||||
PROJECT = "cb15892a-5788-4a26-9316-20a15421f68b" # 검증_sub2(OWNERS.md)
|
||||
SHOTS = ROOT / "tmp" / "shots_504_2"
|
||||
CARD = "계산 자리"
|
||||
|
||||
|
||||
def _up() -> bool:
|
||||
def _started(base: str) -> str | None:
|
||||
try:
|
||||
urllib.request.urlopen(BASE, timeout=3)
|
||||
return True
|
||||
with urllib.request.urlopen(f"{base}/api/health", timeout=3) as res:
|
||||
return json.loads(res.read()).get("started_at")
|
||||
except Exception:
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
pytestmark = pytest.mark.skipif(not _up(), reason="5177 서버 없음")
|
||||
_UP = _started(BASE)
|
||||
# 5177 이 이 폴더 8004 가 아닌 서버(예 8000 = 메인 폴더)로 가면 PUT 이 남의 판을 고침 → 돌리지 않음
|
||||
pytestmark = pytest.mark.skipif(
|
||||
_UP is None or _UP != _started(API), reason="5177 서버 없음 · 5177 이 8004 로 안 감"
|
||||
)
|
||||
|
||||
|
||||
def _cred(who: str) -> dict:
|
||||
@@ -51,9 +56,11 @@ def browser():
|
||||
).stdout # fmt: skip
|
||||
with sync.sync_playwright() as play:
|
||||
chrome = play.chromium.launch(channel="msedge", headless=True)
|
||||
yield chrome
|
||||
chrome.close()
|
||||
BOARD.write_bytes(committed) # 판 = 커밋한 기본값 그대로
|
||||
try:
|
||||
yield chrome
|
||||
finally: # 실패해도 판 = 커밋한 기본값 그대로
|
||||
chrome.close()
|
||||
BOARD.write_bytes(committed)
|
||||
|
||||
|
||||
def _context(browser, who: str):
|
||||
@@ -131,6 +138,8 @@ def test_시스템_관리자_카드_창_G1_서버_길(browser):
|
||||
assert "지금 서버 1 줄" in dialog.inner_text()
|
||||
page.locator(".b01-dashboard__calc-sites .ui-modal__actions button").click()
|
||||
page.wait_for_selector(".b01-dashboard__calc-sites", state="detached", timeout=30000)
|
||||
got = ctx.request.get(f"{BASE}/api/dashboard/calc-sites").json()["sites"]
|
||||
assert got["ground_sample"] == "server", (got, BOARD.read_text("utf-8"))
|
||||
assert json.loads(BOARD.read_text("utf-8"))["sites"]["ground_sample"] == "server"
|
||||
logs = ctx.request.get(f"{BASE}/api/dashboard/admin/audit-logs").json()["items"]
|
||||
assert any(
|
||||
|
||||
@@ -38,8 +38,8 @@ TABLE = {
|
||||
"box_culvert": ("both", "anchor", "name", "box", "box"),
|
||||
"ford_pavement": ("both", "anchor", "name", "ford_pavement", "chute"),
|
||||
"ford_bridge": ("both", "anchor", "name", "ford", "ford"),
|
||||
"cross_drain_exposed": ("both", "anchor", "name", "mark", "none"), # 표시만(요구 311-2 · 507-1)
|
||||
"open_ditch": ("both", "anchor", "name", "mark", "none"), # 표시만(요구 311-2 · 507-1)
|
||||
"cross_drain_exposed": ("both", "none", "name", "none", "none"),
|
||||
"open_ditch": ("both", "none", "name", "none", "none"),
|
||||
"cross_underdrain": ("both", "anchor", "name", "underdrain", "surface"),
|
||||
**{
|
||||
t: ("hidden", "none", "name", "none", "none")
|
||||
|
||||
@@ -63,7 +63,7 @@ def _cases() -> dict[str, tuple[dict, dict | None]]:
|
||||
bad["부품"] = {"형상.zz": False, "횡단도.span3": "no", "끔": False}
|
||||
cases["shape_bad"] = (bad, real)
|
||||
rule = copy.deepcopy(real)
|
||||
rule["반영"] = {"mirror": {"횡단도": "anchor"}} # 형상 없음(개거 = 표시만 · 507-1)
|
||||
rule["반영"] = {"open_ditch": {"횡단도": "anchor"}}
|
||||
cases["rule218"] = (rule, real)
|
||||
mismatch = copy.deepcopy(real)
|
||||
mismatch["반영"] = {"retaining_wall": {"형상": "pipe_set"}, "berm": {"횡단도": "anchor"}}
|
||||
@@ -126,7 +126,7 @@ def test_check_rules(ts: dict) -> None:
|
||||
"부품 「횡단도.span3」 — 참거짓",
|
||||
"부품 「끔」 — 없는 부품",
|
||||
]
|
||||
assert got["rule218"] == ["「반사경」 형상이 「없음」이면 횡단도도 「안 만듦」"]
|
||||
assert got["rule218"] == ["「개거(겉도랑)」 형상이 「없음」이면 횡단도도 「안 만듦」"]
|
||||
assert got["mismatch"] == ["「옹벽」 형상 「배관 세트」 — 구간 · 구조물에 안 맞음"]
|
||||
assert got["hide_read"] == [
|
||||
"「배관」 형상 「배관 세트」 이 쓰는 칸 「흙막이 기초」 — 숨길 수 없음"
|
||||
@@ -158,8 +158,8 @@ def test_reflect_edits(ts: dict) -> None:
|
||||
r = ts["reflect"]
|
||||
assert r["wildlife"] == [None, True] # b06 → both = enabled 참이 곧 처음 값 짝(반영 안 남음)
|
||||
assert r["wall"] == [None, False] # both → b06 = enabled 거짓
|
||||
assert r["openDitch"] is None # 처음 값(anchor · 표시만 507-1)과 같음 — 반영 안 남음
|
||||
assert r["exposed"] == [{"횡단도": "none"}, True]
|
||||
assert r["openDitch"] is None # 처음 값(none)과 같음 — 반영 안 남음
|
||||
assert r["exposed"] == [{"횡단도": "anchor"}, True]
|
||||
assert r["weepOn"] == {"형상칸": {"weep": True}} and r["weepOff"] is None
|
||||
assert r["shown"] == {"pipe_kind": False, "inlet_type": False} # 정의 옵션만
|
||||
assert r["readPipe"] == ["inlet_basin_height_m", "revet_foundation"] # 기본값 없는 읽는 칸
|
||||
@@ -233,7 +233,7 @@ def test_server_save_reflect_parts_color_enabled(copy_def: Path) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
"change",
|
||||
[
|
||||
lambda d: d.update(반영={"mirror": {"횡단도": "anchor"}}), # 218
|
||||
lambda d: d.update(반영={"open_ditch": {"횡단도": "anchor"}}), # 218
|
||||
lambda d: d["표시"].setdefault("pipe", {}).update(revet_foundation=False), # 못 숨김 칸
|
||||
lambda d: next(o for o in _type(d, "pipe")["options"] if o["key"] == "pipe_kind")[
|
||||
"choices"
|
||||
|
||||
@@ -147,7 +147,6 @@ def test_plan_rules(ts: dict) -> None:
|
||||
mixed = ts["mixed"]
|
||||
assert [round(s["chainage_m"], 3) for s in mixed] == [
|
||||
95.0,
|
||||
120.0, # 노출형 횡단수로 = 표시만 측점(507-1)
|
||||
160.0,
|
||||
230.0,
|
||||
395.0,
|
||||
@@ -165,8 +164,7 @@ def test_plan_rules(ts: dict) -> None:
|
||||
assert first["blank"] == ["pipe_diameter_mm", "culvert_height_m"]
|
||||
joint = next(s for s in mixed if s["chainage_m"] == 530.0) # 옹벽 끝 = 소단 시작
|
||||
assert joint["owners"] == ["s2", "s3"] and joint["title"] == "옹벽" and joint["role"] == "end"
|
||||
exposed = next(s for s in mixed if s["chainage_m"] == 120.0) # 표시만 = 이름 · 빈칸 없음(507-1)
|
||||
assert exposed["title"] == "노출형 횡단수로" and exposed["blank"] == []
|
||||
assert all(s["title"] != "노출형 횡단수로" for s in mixed) # ★1 횡단도 none
|
||||
kinds = ts["pipe_kinds"]
|
||||
assert [s["title"] for s in kinds] == ["흄관 D800", "파형강관 D1000", "세월교"]
|
||||
assert kinds[1]["blank"] == ["pipe_diameter_mm"] and kinds[0]["blank"] == []
|
||||
|
||||
@@ -31,6 +31,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile import B05_Profile_Engine_Sections_Core as core # noqa: E402
|
||||
from B05_Profile.B05_Profile_Engine_Grade import resolve_grade_options # noqa: E402
|
||||
from B05_Profile.B05_Profile_Engine_Grade_Profile import ( # noqa: E402
|
||||
@@ -50,7 +52,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
REAL_GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
REAL_NODES = PROJECT / "B05_Profile/route/planned_route_nodes.csv"
|
||||
MIN_RADIUS_M = 12.0
|
||||
@@ -96,7 +98,7 @@ def _fake(folder: Path) -> tuple[Path, list[list[float]]]:
|
||||
|
||||
def _real(_folder: Path) -> tuple[Path, list[list[float]]]:
|
||||
if not (REAL_GRID.is_file() and REAL_NODES.is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
with REAL_NODES.open(encoding="utf-8") as handle:
|
||||
return REAL_GRID, [[float(r["x"]), float(r["y"])] for r in csv.DictReader(handle)]
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
|
||||
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
|
||||
@@ -35,7 +37,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
ROUTE = PROJECT / "B05_Profile/route"
|
||||
SECTION = PROJECT / "B06_Section"
|
||||
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
@@ -79,7 +81,7 @@ def _structure(sid: str, line: list[list[float]], ratio: float, off: float) -> d
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
curves = _json(ROUTE / "planned_route_curves.json")
|
||||
sections = sorted(
|
||||
(_json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
||||
@@ -132,6 +134,7 @@ def test_열기만_하면_가게_안_씀(r):
|
||||
assert o["followed"] == []
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_꼭짓점_끌기_가게에_새_측점_한_번(r):
|
||||
d = r["drag"]
|
||||
assert d["runs"] == [["route"]], d["runs"]
|
||||
@@ -144,6 +147,7 @@ def test_꼭짓점_끌기_가게에_새_측점_한_번(r):
|
||||
assert d["blocked"] == 0
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_되돌리기_초안_전_값(r):
|
||||
u = r["undo"]
|
||||
assert u["runs"] == [["route"]], u["runs"]
|
||||
|
||||
@@ -23,6 +23,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
|
||||
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
|
||||
@@ -32,7 +34,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
ROUTE = PROJECT / "B05_Profile/route"
|
||||
SECTION = PROJECT / "B06_Section"
|
||||
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
@@ -51,7 +53,7 @@ def _json(path: Path):
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
curves = _json(ROUTE / "planned_route_curves.json")
|
||||
sections = sorted(
|
||||
(_json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
||||
|
||||
@@ -22,6 +22,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
|
||||
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
|
||||
@@ -31,7 +33,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
ROUTE = PROJECT / "B05_Profile/route"
|
||||
SECTION = PROJECT / "B06_Section"
|
||||
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
@@ -50,7 +52,7 @@ def _json(path: Path):
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
curves = _json(ROUTE / "planned_route_curves.json")
|
||||
sections = sorted(
|
||||
(_json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
||||
|
||||
@@ -23,6 +23,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
|
||||
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
|
||||
@@ -32,7 +34,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
ROUTE = PROJECT / "B05_Profile/route"
|
||||
SECTION = PROJECT / "B06_Section"
|
||||
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
@@ -51,7 +53,7 @@ def _json(path: Path):
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
curves = _json(ROUTE / "planned_route_curves.json")
|
||||
sections = sorted(
|
||||
(_json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
||||
@@ -128,6 +130,7 @@ def test_끌기_새_측점(r):
|
||||
assert d["final"] != r["open"]["final"]
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_되돌리기_열기_값_다시_셈_0(r):
|
||||
u, o = r["undo"], r["open"]
|
||||
assert [(x["sources"], x["restored"], x["why"]) for x in u["runs"]] == [
|
||||
|
||||
@@ -25,6 +25,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
|
||||
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
|
||||
@@ -34,7 +36,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
ROUTE = PROJECT / "B05_Profile/route"
|
||||
SECTION = PROJECT / "B06_Section"
|
||||
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
@@ -64,7 +66,7 @@ def _between(sections: list[dict], index: int) -> dict:
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
curves = _json(ROUTE / "planned_route_curves.json")
|
||||
sections = sorted(
|
||||
(_json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
||||
@@ -147,6 +149,7 @@ def test_열기(r):
|
||||
assert o["final"] is not None
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_끌기_새_노선(r):
|
||||
d = r["first"]
|
||||
assert [x["sources"] for x in d["runs"]] == [["route"]], d["runs"]
|
||||
@@ -154,6 +157,7 @@ def test_끌기_새_노선(r):
|
||||
assert d["final"] != r["open"]["final"]
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_되돌리기_열기_값_측점_다름은_기록(r):
|
||||
o = r["open"]
|
||||
for u in (r["back1"], r["back2"]):
|
||||
@@ -185,6 +189,7 @@ def test_다시_하기_첫_끌기_값(r):
|
||||
assert d["stations"] == f["stations"]
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_횡단_보기_다녀와도_열기_값(r):
|
||||
o = r["open"]
|
||||
for step in (r["cross"], r["back"]):
|
||||
|
||||
@@ -24,6 +24,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
|
||||
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
|
||||
@@ -33,7 +35,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
ROUTE = PROJECT / "B05_Profile/route"
|
||||
SECTION = PROJECT / "B06_Section"
|
||||
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
@@ -63,7 +65,7 @@ def _between(sections: list[dict], index: int) -> dict:
|
||||
def build_job() -> dict:
|
||||
"""입력 한 벌 — 356-4 시험도 빌림."""
|
||||
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
curves = _json(ROUTE / "planned_route_curves.json")
|
||||
sections = sorted(
|
||||
(_json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
||||
@@ -159,6 +161,7 @@ def test_되돌리기_열기_객체_그대로(r):
|
||||
assert d["pipeline"] > d["saved"] and isinstance(d["pipelineM"], float)
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_메아리_차례_갈아끼움_없음_열기_값(r):
|
||||
o, e = r["open"], r["echo"]
|
||||
assert [x["outcome"] for x in e["runs"]], e["runs"] # 묶음이 실제로 돎
|
||||
|
||||
@@ -23,6 +23,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
|
||||
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
|
||||
@@ -32,7 +34,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
ROUTE = PROJECT / "B05_Profile/route"
|
||||
SECTION = PROJECT / "B06_Section"
|
||||
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
@@ -62,7 +64,7 @@ def _between(sections: list[dict], index: int) -> dict:
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
curves = _json(ROUTE / "planned_route_curves.json")
|
||||
sections = sorted(
|
||||
(_json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
||||
@@ -147,6 +149,7 @@ def test_그리기_몫_기록(r):
|
||||
assert x["totalMs"] >= x["stagesMs"] * 0.99
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_되돌리기_그대로(r):
|
||||
u = r["undo"]
|
||||
assert [x["why"] for x in u["runs"]] == ["saved-diff"], u["runs"] # 세월교 하나 더(0o 꼴)
|
||||
|
||||
@@ -23,6 +23,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from common_util.common_util_drainage_pipes import ( # noqa: E402
|
||||
PipePoint,
|
||||
@@ -37,7 +39,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
ROUTE = PROJECT / "B05_Profile/route"
|
||||
SECTION = PROJECT / "B06_Section"
|
||||
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
@@ -81,7 +83,7 @@ def _structure(sid: str, line: list[list[float]], ratio: float, off: float) -> d
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
curves = _json(ROUTE / "planned_route_curves.json")
|
||||
sections = sorted(
|
||||
(_json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
||||
@@ -139,6 +141,7 @@ def _moved(before: list, after: list) -> list[float]:
|
||||
return [b[1] - a[1] for a, b in zip(before, after)]
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_앞쪽_꺾임점_끌기_측점_밀림(r):
|
||||
d = r["drag"]
|
||||
assert d["runs"] == [["route"]], d["runs"]
|
||||
@@ -155,6 +158,7 @@ def test_앞쪽_꺾임점_끌기_측점_밀림(r):
|
||||
assert len(d["followed"]) == 1
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_되돌리기_초안_값(r):
|
||||
for u in (r["undo"], r["undo2"]):
|
||||
assert u["runs"] == [["route"]], u["runs"]
|
||||
|
||||
@@ -25,6 +25,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from common_util.common_util_drainage_pipes import ( # noqa: E402
|
||||
parse_pipe_points,
|
||||
project_pipe_points,
|
||||
@@ -36,7 +38,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
PIPES = PROJECT / "B04_PreProcess/drainage/edits/pipe_points.json"
|
||||
LINE = PROJECT / "B05_Profile/route/planned_route.csv"
|
||||
|
||||
@@ -52,10 +54,12 @@ def _vertices(line: list[list[float]]) -> list[RouteVertex]:
|
||||
@pytest.fixture(scope="module")
|
||||
def case() -> dict:
|
||||
if not (PIPES.is_file() and LINE.is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
with LINE.open(encoding="utf-8") as handle:
|
||||
line = [[float(r["x"]), float(r["y"])] for r in csv.DictReader(handle)]
|
||||
raw = json.loads(PIPES.read_text(encoding="utf-8"))["points"]
|
||||
if len(raw) < 6: # 시험이 관 여섯 개째(p5)까지 만짐
|
||||
pytest.skip("검증 프로젝트 관 6 개 미만")
|
||||
before = [{**pipe, "id": f"p{i}"} for i, pipe in enumerate(raw)]
|
||||
# 서버 노선 저장 — 앞쪽 꼭짓점을 서쪽으로 크게 옮긴 새 노선에 좌표 투영 + 새 교차 관 하나
|
||||
moved = copy.deepcopy(line)
|
||||
|
||||
@@ -23,6 +23,8 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from helper_verify_project import DRIFT, ROUTE_SET, SKIP_REASON, find_project # noqa: E402
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from common_util.common_util_surface_sampler import dtm_grid_bytes # noqa: E402
|
||||
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
|
||||
@@ -32,7 +34,7 @@ pytestmark = pytest.mark.skipif(
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
PROJECT = ROOT / "storage/1/3/6d3b2812-6d5a-4168-a932-40e4ae303d5a" # 검증_sub7
|
||||
PROJECT = find_project(*ROUTE_SET) # 지금 있는 검증 프로젝트(읽기만)
|
||||
ROUTE = PROJECT / "B05_Profile/route"
|
||||
SECTION = PROJECT / "B06_Section"
|
||||
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
||||
@@ -51,7 +53,7 @@ def _json(path: Path):
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
||||
pytest.skip("검증_sub7 없음")
|
||||
pytest.skip(SKIP_REASON)
|
||||
curves = _json(ROUTE / "planned_route_curves.json")
|
||||
sections = sorted(
|
||||
(_json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
||||
@@ -101,6 +103,7 @@ def _same(a: list[float], b: list[float]) -> bool:
|
||||
return len(a) == len(b) and all(abs(x - y) < 1e-6 for x, y in zip(a, b))
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_열_때_한_번_저장_측점_그대로(r):
|
||||
o = r["open"]
|
||||
assert [x["outcome"] for x in o["runs"]] == ["done"], o["runs"]
|
||||
@@ -118,6 +121,7 @@ def test_꼭짓점_끌기_한_번(r):
|
||||
assert d["swaps"] == 1 # 새 노선 측점으로 갈아끼움은 한 번(고리 아님)
|
||||
|
||||
|
||||
@DRIFT
|
||||
def test_되돌리기_한_번_저장값으로(r):
|
||||
u = r["undo"]
|
||||
assert [x["sources"] for x in u["runs"]] == [["route"]], u["runs"]
|
||||
|
||||
@@ -11,14 +11,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
BASE = "http://localhost:5179"
|
||||
PROJECT = "c725d747-50ae-46b0-87ce-4d4e56a53cf3" # 검증_sub4(OWNERS.md)
|
||||
# 기본 = sub4 환경 · 다른 창은 AISLO_505_3_BASE · AISLO_505_3_PROJECT 로 바꿔 test_0 만 돌림(test_1 · 2 는 sub4 자료)
|
||||
BASE = os.environ.get("AISLO_505_3_BASE", "http://localhost:5179")
|
||||
PROJECT = os.environ.get("AISLO_505_3_PROJECT", "c725d747-50ae-46b0-87ce-4d4e56a53cf3") # 검증_sub4
|
||||
SHOTS = ROOT / "tmp" / "shots_505_3"
|
||||
PIPE_M = 388 # 검증_sub4 배관 하나(관경 빈칸 · 19+8)
|
||||
PREFS = "/api/dashboard/me/ui-prefs"
|
||||
@@ -34,7 +36,7 @@ def _up() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
pytestmark = pytest.mark.skipif(not _up(), reason="5179 서버 없음")
|
||||
pytestmark = pytest.mark.skipif(not _up(), reason="서버 없음")
|
||||
|
||||
# 측점 m 을 가로 가운데로 · 그 측점선과 원지반선 교점(화면 좌표) · 그 자리 맨 위 요소
|
||||
JS_SCROLL = r"""(m) => {
|
||||
@@ -59,6 +61,26 @@ JS_POINT = r"""(m) => {
|
||||
const s = pt.matrixTransform(poly.getScreenCTM());
|
||||
return { x: s.x, y: s.y, hit: document.elementFromPoint(s.x, s.y)?.getAttribute('class') };
|
||||
}"""
|
||||
# 지도 캔버스에서 교각점 원(--map-route-node 색)을 찾아 화면 좌표로 — 픽셀 고정 자리 대신 요소 · 색으로
|
||||
JS_NODES = r"""() => {
|
||||
const canvas = document.querySelector('.b05-routeedit__canvas');
|
||||
const box = canvas.getBoundingClientRect();
|
||||
const hex = getComputedStyle(canvas).getPropertyValue('--map-route-node').trim() || '#15803d';
|
||||
const want = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
||||
const sx = canvas.width / box.width;
|
||||
const data = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data;
|
||||
const cell = 14, cells = new Map();
|
||||
for (let y = 0; y < canvas.height; y++) for (let x = 0; x < canvas.width; x++) {
|
||||
const i = (y * canvas.width + x) * 4;
|
||||
if (Math.abs(data[i] - want[0]) + Math.abs(data[i + 1] - want[1]) + Math.abs(data[i + 2] - want[2]) > 12) continue;
|
||||
const key = `${Math.floor(x / cell)},${Math.floor(y / cell)}`;
|
||||
const c = cells.get(key) ?? { n: 0, x: 0, y: 0 };
|
||||
c.n++; c.x += x; c.y += y; cells.set(key, c);
|
||||
}
|
||||
return [...cells.values()].filter((c) => c.n > 60)
|
||||
.map((c) => ({ x: box.left + c.x / c.n / sx, y: box.top + c.y / c.n / sx }))
|
||||
.filter((p) => document.elementFromPoint(p.x, p.y) === canvas);
|
||||
}"""
|
||||
JS_MODAL = r"""() => {
|
||||
const r = document.querySelector('.b05-crossmodal');
|
||||
const card = r.querySelector('.b06-cross-card');
|
||||
@@ -145,17 +167,29 @@ def test_0_곡선_판_도구줄_횡단판_비킴(page):
|
||||
page.locator(".b05-routeedit__side-wrap").get_by_text("횡단").first.click()
|
||||
page.wait_for_timeout(2000)
|
||||
shown = "() => [...document.querySelectorAll('.b05-routeedit__label')].some((x) => !x.hidden)"
|
||||
for _ in range(3): # 첫 누름이 앞 고름(구조물) 풂으로 갈 수 있음
|
||||
page.mouse.click(1199, 296) # 검증_sub4 첫 화면의 14번째 꺾임점
|
||||
page.wait_for_timeout(1500)
|
||||
nodes = page.evaluate(JS_NODES)
|
||||
assert nodes, "지도에서 교각점을 못 찾음"
|
||||
for node in nodes[1:-1] or nodes: # 양끝(시점 · 종점) 말고 곡선 있는 가운데 점
|
||||
for _ in range(3): # 첫 누름이 앞 고름(구조물) 풂으로 갈 수 있음
|
||||
page.mouse.click(node["x"], node["y"])
|
||||
page.wait_for_timeout(1500)
|
||||
if page.evaluate(shown):
|
||||
break
|
||||
if page.evaluate(shown):
|
||||
break
|
||||
page.screenshot(path=str(SHOTS / "curve_pick.png"))
|
||||
assert page.evaluate(shown)
|
||||
# 지도를 위 · 오른쪽으로 밀어 꺾임점을 도구줄 · 오른쪽 끝 밑으로
|
||||
page.mouse.move(800, 650)
|
||||
# 지도를 밀어 고른 꺾임점을 도구줄 · 오른쪽 끝 밑으로
|
||||
canvas = page.locator(".b05-routeedit__canvas").bounding_box()
|
||||
tools = page.evaluate(JS_RECTS)["tools"]
|
||||
goal = (canvas["x"] + canvas["width"] - 60, tools[3] + 40)
|
||||
page.mouse.move(*(canvas["x"] + canvas["width"] / 2, canvas["y"] + canvas["height"] * 0.65))
|
||||
page.mouse.down(button="middle")
|
||||
page.mouse.move(1050, 380, steps=16)
|
||||
page.mouse.move(
|
||||
canvas["x"] + canvas["width"] / 2 + goal[0] - node["x"],
|
||||
canvas["y"] + canvas["height"] * 0.65 + goal[1] - node["y"],
|
||||
steps=16,
|
||||
)
|
||||
page.mouse.up(button="middle")
|
||||
page.wait_for_timeout(1200)
|
||||
rects = page.evaluate(JS_RECTS)
|
||||
@@ -175,7 +209,7 @@ def test_0_곡선_판_도구줄_횡단판_비킴(page):
|
||||
def test_1_빈칸_칩_카드(page):
|
||||
modal = _open_modal(page)
|
||||
assert modal["station"] == "19+8" and modal["card"]
|
||||
assert modal["chips"] == ["⚠ 배관 관경 빈칸", "배수관"] # 이름 칩(507-1)
|
||||
assert modal["chips"] == ["⚠ 배관 관경 빈칸"]
|
||||
assert modal["pipes"] == 0 and modal["width"] == 380
|
||||
assert all("ui-btn ui-btn--ghost ui-btn--sm" in name for name in modal["buttons"])
|
||||
page.locator(".b05-crossmodal").get_by_text("닫기").click()
|
||||
@@ -198,7 +232,7 @@ def test_2_횡단_판_칩_관경_넣으면_형상_확대_밝음_어두움(page):
|
||||
).first.select_option("800") # 세션 초안만 — [저장] 안 누름
|
||||
page.wait_for_timeout(5000)
|
||||
modal = _open_modal(page)
|
||||
assert modal["chips"] == ["배수관"] and modal["pipes"] > 0
|
||||
assert modal["chips"] == [] and modal["pipes"] > 0
|
||||
shot = page.locator(".b05-crossmodal")
|
||||
shot.screenshot(path=str(SHOTS / "modal_light.png"))
|
||||
page.evaluate("document.documentElement.setAttribute('data-theme', 'dark')")
|
||||
|
||||
@@ -47,7 +47,7 @@ def test_판_재료_한_벌() -> None:
|
||||
|
||||
def test_빈칸_칩은_머리_걷기_전에_꺼냄() -> None:
|
||||
card = _read("B05_Profile", "B05_Profile_UI_RouteEdit_Cross_Card.ts")
|
||||
chips = card.index('":scope > header .b06-cross-card__warning') # 이름 칩도 같이(507-1)
|
||||
chips = card.index('":scope > header .b06-cross-card__warning"')
|
||||
remove = card.index(':scope > header, :scope > footer, .b06-cross-card__zoom"')
|
||||
assert chips < remove
|
||||
assert 'box.className = "b05-cross-chips";' in card
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
"""PLAN 507-1 노출형 횡단수로 · 개거 = 횡단면 표시만(요구 311-2).
|
||||
|
||||
- 부품 상자 형상 `mark`(표시만) — 읽는 칸 · 필수 칸 없음 → 빈칸 경고 없음 · 그리는 함수 없음.
|
||||
- 처음값 = 횡단도 anchor(측점 세움) · 카드이름 name · 형상 mark · 3D none.
|
||||
- 측점 계획 = 그 자리에 측점 하나 · 이름 = 종류 이름.
|
||||
- 실제 정의(사용자 마스터 · 반영 없음) = M02 검사 오류 없음(218 규칙 = 형상 none 만 막음).
|
||||
- B05 「횡단」 판 · 임시 횡단 창 = 카드 머리를 걷기 전에 구조물 이름 칩을 꺼내 남김.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance, load_definition
|
||||
from common_util.common_util_structure_catalog import (
|
||||
catalog,
|
||||
load_parts,
|
||||
resolve_catalog,
|
||||
shape_needs,
|
||||
)
|
||||
from common_util.common_util_structure_station import plan_stations
|
||||
from M02_MasterTemplete.StructOptions.M02_SO_Check import catalog_errors
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
KINDS = {"cross_drain_exposed": "노출형 횡단수로", "open_ditch": "개거(겉도랑)"}
|
||||
|
||||
|
||||
def test_mark_part_has_no_fields() -> None:
|
||||
mark = load_parts()["parts"]["형상"]["mark"]
|
||||
assert mark["읽는 칸"] == [] and "필수 칸" not in mark
|
||||
assert "point" in mark["자리꼴"] and "structures" in mark["정본"]
|
||||
|
||||
|
||||
def test_start_values_are_mark_only() -> None:
|
||||
rows = resolve_catalog(load_definition())
|
||||
for type_id in KINDS:
|
||||
row = rows[type_id]
|
||||
got = (row["station"], row["title"], row["shape"], row["model3d"])
|
||||
assert got == ("anchor", "name", "mark", "none"), type_id
|
||||
assert shape_needs(type_id) == []
|
||||
|
||||
|
||||
def test_station_planted_with_name() -> None:
|
||||
structures = [
|
||||
StructureInstance.model_validate(
|
||||
{
|
||||
"structure_id": f"s{i}",
|
||||
"type_id": type_id,
|
||||
"placement": "point",
|
||||
"chainage_m": at,
|
||||
"options": {},
|
||||
}
|
||||
)
|
||||
for i, (type_id, at) in enumerate((("cross_drain_exposed", 120.0), ("open_ditch", 240.0)))
|
||||
]
|
||||
plan = plan_stations([], structures)
|
||||
assert [(round(s["chainage_m"], 3), s["title"], s["blank"]) for s in plan] == [
|
||||
(120.0, KINDS["cross_drain_exposed"], []),
|
||||
(240.0, KINDS["open_ditch"], []),
|
||||
]
|
||||
|
||||
|
||||
def test_real_definition_passes_m02_check() -> None:
|
||||
assert load_definition().get("반영") == {} # 사용자 마스터 값은 안 고침
|
||||
assert catalog_errors(load_definition(), None) == []
|
||||
assert all(catalog()[t]["shape"] == "mark" for t in KINDS)
|
||||
|
||||
|
||||
def test_b05_card_keeps_structure_name_chip() -> None:
|
||||
card = (ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit_Cross_Card.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
keep = card.index(":scope > header .b06-cross-card__structure")
|
||||
assert keep < card.index(':scope > header, :scope > footer, .b06-cross-card__zoom"')
|
||||
@@ -1,189 +0,0 @@
|
||||
"""PLAN 507-1 화면 — 자기 포트(5179) Playwright 머리 없는 브라우저 · 서버가 떠 있을 때만 · 검증_sub4.
|
||||
|
||||
- B05 「구조물 배치」 노출형 횡단수로 7+7(147 m) 추가(세션만 · [저장] 안 함) → 측점 목록에 섬
|
||||
- 「횡단」 판 · 임시 횡단 창 = B06 카드 + 이름 칩 「노출형 횡단수로」 · 구조물 형상 0
|
||||
- B06 횡단 카드 머리 = 이름 · 형상 0 → 지움(세션) · 측점 목록에서 빠짐
|
||||
- 계정 화면 취향만 시험 앞 값으로 되돌림 · DB · storage 안 씀
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_507_1_exposed_mark_screen.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
BASE = "http://localhost:5179"
|
||||
PROJECT = "c725d747-50ae-46b0-87ce-4d4e56a53cf3" # 검증_sub4(OWNERS.md)
|
||||
SHOTS = ROOT / "tmp" / "shots_507_1"
|
||||
AT_M = 147 # 7+7 — 격자 · 관(19+8) 측점과 안 겹침
|
||||
NAME = "노출형 횡단수로"
|
||||
PREFS = "/api/dashboard/me/ui-prefs"
|
||||
TOUCHED = ("profile-collapsed", "profile-height", "b05-routeedit-cross-v2")
|
||||
#: 구조물 형상 그림(관 · 박스 · 세월교 · 벽 · 맹암거) — 표시만이면 0
|
||||
SHAPES = (
|
||||
".b06-chart__culvert-pipe, [class*='b06-chart__box'], [class*='b06-chart__ford'],"
|
||||
" [class*='b06-chart__wall'], [class*='underdrain']"
|
||||
)
|
||||
|
||||
|
||||
def _up() -> bool:
|
||||
try:
|
||||
urllib.request.urlopen(BASE, timeout=3)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
pytestmark = pytest.mark.skipif(not _up(), reason="5179 서버 없음")
|
||||
|
||||
JS_ADD = r"""async ([no, dist]) => {
|
||||
const w = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const sec = document.querySelector('.b05-structure-section');
|
||||
const fire = (el, v) => { el.value = v; el.dispatchEvent(new Event('change', { bubbles: true })); };
|
||||
const [group, type] = sec.querySelectorAll('.b05-structure__grid select');
|
||||
fire(group, 'A'); await w(300);
|
||||
fire(type, 'cross_drain_exposed'); await w(500);
|
||||
const [a, b] = sec.querySelectorAll('.b05-structure__position-row > label')[2].querySelectorAll('input');
|
||||
fire(a, String(no)); fire(b, String(dist)); await w(200);
|
||||
sec.querySelector('.is-primary').click(); await w(1500);
|
||||
}"""
|
||||
JS_ITEMS = (
|
||||
"() => [...document.querySelectorAll('.b05-route__irregular-list li')]"
|
||||
".map((l) => l.textContent.trim())"
|
||||
)
|
||||
JS_SCROLL = r"""(m) => {
|
||||
const marker = [...document.querySelectorAll('.b06-chart__station')]
|
||||
.find((e) => Math.abs(+e.getAttribute('data-chainage') - m) < 1e-6);
|
||||
const body = marker.closest('.b05-route-profile__body');
|
||||
body.scrollLeft = Math.max(0, +marker.querySelector('line').getAttribute('x1') - body.clientWidth / 2);
|
||||
}"""
|
||||
JS_POINT = r"""(m) => {
|
||||
const marker = [...document.querySelectorAll('.b06-chart__station')]
|
||||
.find((e) => Math.abs(+e.getAttribute('data-chainage') - m) < 1e-6);
|
||||
const r = marker.querySelector('.b06-chart__station-line').getBoundingClientRect();
|
||||
return { x: r.left + r.width / 2, y: r.top + r.height * 0.3 };
|
||||
}"""
|
||||
JS_CARD = r"""(scope) => {
|
||||
const root = document.querySelector(scope);
|
||||
const card = root?.querySelector('.b06-cross-card');
|
||||
return {
|
||||
card: !!card,
|
||||
chips: [...(root?.querySelectorAll('.b05-cross-chips > *') ?? [])].map((e) => e.textContent),
|
||||
shapes: card ? card.querySelectorAll(SHAPES).length : -1,
|
||||
};
|
||||
}""".replace("SHAPES", json.dumps(SHAPES))
|
||||
JS_B06 = r"""(c) => ({
|
||||
head: c.querySelector(':scope > header .b06-cross-card__structure')?.textContent ?? null,
|
||||
label: c.querySelector('.b06-cross-card__title strong')?.textContent ?? null,
|
||||
shapes: c.querySelectorAll(SHAPES).length,
|
||||
})""".replace("SHAPES", json.dumps(SHAPES))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def page():
|
||||
sync = pytest.importorskip("playwright.sync_api")
|
||||
cred = json.loads((ROOT / "tmp" / "aislo_cred.json").read_text(encoding="utf-8"))
|
||||
with sync.sync_playwright() as play:
|
||||
chrome = play.chromium.launch(channel="msedge", headless=True)
|
||||
ctx = chrome.new_context(viewport={"width": 1600, "height": 1000})
|
||||
login = {k: cred[k] for k in ("email", "password")}
|
||||
assert ctx.request.post(f"{BASE}/api/auth/login/request", data=login, timeout=60000).ok
|
||||
prefs = ctx.request.get(f"{BASE}{PREFS}").json().get("prefs", {})
|
||||
tab = ctx.new_page()
|
||||
tab.add_init_script(f"localStorage.setItem('frd_current_project_id', '{PROJECT}')")
|
||||
tab.goto(f"{BASE}/#/b05-profile", wait_until="domcontentloaded")
|
||||
tab.wait_for_function("() => !!window.__aisloPipeline", timeout=90000)
|
||||
tab.wait_for_timeout(8000)
|
||||
SHOTS.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
yield tab
|
||||
finally:
|
||||
tab.wait_for_timeout(2000)
|
||||
now = ctx.request.get(f"{BASE}{PREFS}").json().get("prefs", {})
|
||||
for key in (f"aislo:pref:{name}" for name in TOUCHED):
|
||||
if key in prefs:
|
||||
now[key] = prefs[key]
|
||||
else:
|
||||
now.pop(key, None)
|
||||
assert ctx.request.put(f"{BASE}{PREFS}", data={"prefs": now}).ok
|
||||
chrome.close()
|
||||
|
||||
|
||||
def _profile(page, open_: bool) -> None:
|
||||
height = page.evaluate(
|
||||
"() => document.querySelector('.b05-route-profile')?.getBoundingClientRect().height ?? 0"
|
||||
)
|
||||
if (height > 100) != open_:
|
||||
page.get_by_text("종단", exact=True).last.click()
|
||||
page.wait_for_timeout(3000)
|
||||
|
||||
|
||||
def test_1_추가_측점_섬(page):
|
||||
before = page.evaluate(JS_ITEMS)
|
||||
assert not any(NAME in t for t in before)
|
||||
page.evaluate(JS_ADD, [AT_M // 20, AT_M % 20])
|
||||
page.wait_for_timeout(4000)
|
||||
items = page.evaluate(JS_ITEMS)
|
||||
assert f"7+7{NAME}" in items, items
|
||||
assert len(items) == len(before) + 1
|
||||
|
||||
|
||||
def test_2_횡단_판_이름_칩_형상_0(page):
|
||||
_profile(page, False)
|
||||
page.locator(".b05-route__irregular-item", has_text=NAME).first.click()
|
||||
page.wait_for_timeout(4000)
|
||||
got = page.evaluate(JS_CARD, ".b05-routeedit__cross")
|
||||
page.locator(".b05-routeedit__cross").first.screenshot(path=str(SHOTS / "panel.png"))
|
||||
assert got["card"] and NAME in got["chips"] and got["shapes"] == 0, got
|
||||
|
||||
|
||||
def test_3_임시_횡단_이름_칩_형상_0(page):
|
||||
_profile(page, True)
|
||||
page.evaluate(JS_SCROLL, AT_M)
|
||||
page.wait_for_timeout(800)
|
||||
at = page.evaluate(JS_POINT, AT_M)
|
||||
page.mouse.move(at["x"], at["y"])
|
||||
page.wait_for_timeout(300)
|
||||
page.mouse.click(at["x"], at["y"])
|
||||
page.wait_for_function(
|
||||
"() => document.querySelector('.b05-crossmodal .b06-cross-card')", timeout=30000
|
||||
)
|
||||
page.wait_for_timeout(1500)
|
||||
station = page.evaluate("() => document.querySelector('.b05-crossmodal__station').textContent")
|
||||
got = page.evaluate(JS_CARD, ".b05-crossmodal")
|
||||
page.locator(".b05-crossmodal").screenshot(path=str(SHOTS / "modal.png"))
|
||||
page.locator(".b05-crossmodal").get_by_text("닫기").click()
|
||||
assert station == "7+7" and NAME in got["chips"] and got["shapes"] == 0, (station, got)
|
||||
|
||||
|
||||
def test_4_B06_카드_머리_이름_형상_0_지움(page):
|
||||
page.evaluate("() => { location.hash = '#/b06-section'; }")
|
||||
page.wait_for_timeout(9000)
|
||||
page.locator(".b05-route__irregular-item:visible", has_text=NAME).first.click()
|
||||
page.wait_for_timeout(3000)
|
||||
card = page.locator(".b06-cross-card:visible", has_text=NAME).first
|
||||
card.scroll_into_view_if_needed()
|
||||
page.wait_for_timeout(1500)
|
||||
got = card.evaluate(JS_B06)
|
||||
card.screenshot(path=str(SHOTS / "b06_card.png"))
|
||||
assert got == {"head": NAME, "label": "7+7", "shapes": 0}, got
|
||||
# 되돌림 — 고른 채로 세션에서 지움([저장] 안 함)
|
||||
section = page.locator(".b05-structure-section:visible").first
|
||||
if not section.locator("button.is-danger", has_text="삭제").first.is_visible():
|
||||
page.get_by_text("구조물 배치", exact=True).first.click() # B06 = 칸이 접혀 있음
|
||||
page.wait_for_timeout(1500)
|
||||
# 「구조물 전체 삭제」 와 헷갈리지 않게 글이 꼭 「삭제」 인 것만
|
||||
remove = section.get_by_role("button", name="삭제", exact=True)
|
||||
if remove.is_disabled(): # 칸을 펴면 고름이 풀림 — 다시 고름
|
||||
page.locator(".b05-route__irregular-item:visible", has_text=NAME).first.click()
|
||||
page.wait_for_timeout(1500)
|
||||
remove.click()
|
||||
page.wait_for_timeout(3000)
|
||||
left = page.evaluate(JS_ITEMS)
|
||||
page.screenshot(path=str(SHOTS / "b06_after_delete.png"))
|
||||
assert not any(NAME in t for t in left), left
|
||||
@@ -0,0 +1,29 @@
|
||||
"""507-4 — 공용 TS 불러오기 `helper_ts_stub_loader.cjs`: 진짜 TS 를 불러 가짜 @config · fetch 로 돎.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_507_4_ts_stub_loader.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
TSC = HERE.parents[1] / "config" / "node_modules" / "typescript"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
shutil.which("node") is None or not TSC.is_dir(), reason="node · typescript 없음"
|
||||
)
|
||||
def test_공용_로더가_진짜_TS_불러_가짜_주소로_판_읽음():
|
||||
run = subprocess.run(
|
||||
["node", str(HERE / "helper_507_4_loader_smoke.cjs")],
|
||||
capture_output=True, text=True, encoding="utf-8", timeout=60, check=True,
|
||||
) # fmt: skip
|
||||
got = json.loads(run.stdout)
|
||||
assert got["ground"] is True and got["corridor"] is False
|
||||
assert got["calls"] == ["http://fake/dashboard/calc-sites"] # 판은 한 번만 받아 둠
|
||||
Reference in New Issue
Block a user