Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1

This commit is contained in:
2026-10-30 14:43:13 +09:00
4 changed files with 189 additions and 1 deletions
@@ -3,6 +3,7 @@
견본 = 같은 연도 조각 둘(본선 p1 · 갈래 p3, 2026) + 본선 p2(2027) · 업체 둘(p1 = 회사 5 · p3 = 회사 6).
- D04 요약 = 조각 줄 · 이름 「본선 0+0~12+10」 · 지정 회사 = 자기 조각 · 자기 행만
- 594-2 붙인 선 이름 = 조각 이름표 · zip 폴더 · 좌표표 xlsx 「선」 칸
- 내보내기 = 조각마다 폴더 · 전체 = 선마다 레코드 · 지정 회사 = 확정된 자기 조각만
- 확정 메일 = 업체마다 자기 조각 zip(남의 조각 안 샘)
- 업체 저장 = 조각 id 검사 · 연도는 조각에서 · 같은 업체 두 조각 = 두 행
@@ -21,6 +22,7 @@ from pathlib import Path
from uuid import uuid4
import geopandas as gpd
import openpyxl
import pytest
from fastapi import HTTPException
@@ -280,3 +282,27 @@ def test_assigned_list_piece_label_length(plan, monkeypatch):
assert [(r["piece_id"], r["year"], r["piece_label"], r["length_m"]) for r in out] == [
("p3", 2026, "갈래 1 0+0~6+10", 130.0),
]
def test_line_name_flows_to_labels_zip_xlsx(plan):
"""594-2 붙인 선 이름 = 조각 이름표 · zip 폴더 · 좌표표 xlsx 「선」 칸(빈칸 뺌)."""
named = [{**LINES[0], "name": "용화 본선"}, LINES[1]]
plan["file"]["plan"] = plan_of(named, MARKS, PIECES)
rows = asyncio.run(d04.get_plan_summary(PLAN_ID, FOREST))["pieces"]
assert [r["label"] for r in rows] == [
"용화 본선 0+0~12+10",
"갈래 1 0+0~6+10",
"용화 본선 12+10~25+19.3",
]
body = asyncio.run(d04.export_plan(PLAN_ID, FOREST)).body
with zipfile.ZipFile(io.BytesIO(body)) as z:
assert {n.split("/")[0] for n in z.namelist()} == {
"2026_용화본선_p1",
"2026_갈래1_p3",
"2027_용화본선_p2",
"전체",
}
book = next(n for n in z.namelist() if n.startswith("전체/") and n.endswith(".xlsx"))
sheet = openpyxl.load_workbook(io.BytesIO(z.read(book))).active
lines = [row[14] for row in sheet.iter_rows(min_row=3, values_only=True)]
assert sorted(lines) == ["갈래1", "용화본선", "용화본선"]
+82
View File
@@ -0,0 +1,82 @@
"""594-4 공용 주소 · 장소 찾기 길(`GET /api/map/search`) — VWorld 가짜 응답.
- 주소(도로명 → 지번) 먼저 · 장소 뒤 · 같은 이름 · 주소 줄 하나 · 키는 서버(응답에 없음).
- 빈 값 = 빈 목록 · 100 자 넘음 = 422 · VWorld 실패 = 빈 목록.
./venv/Scripts/python.exe -m pytest resources/tester/test_594_4_map_search.py -q
"""
from __future__ import annotations
import json
from urllib.parse import parse_qs, urlparse
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from B01_Dashboard import B01_Dashboard_Map as vmap
from common_util import common_util_map_router as map_router
ADDRESS = {
"address": {"zipcode": "36509", "road": "", "parcel": "경상북도 영양군 일월면 용화리 1"},
"point": {"x": "129.165", "y": "36.815"},
}
PLACE = {
"title": "용화보건진료소",
"address": {"road": "경상북도 영양군 일월면 영양로 3773", "parcel": "… 용화리 225-1"},
"point": {"x": "129.150", "y": "36.789"},
}
@pytest.fixture
def api(monkeypatch):
asked = []
def fetch(url, timeout=5):
query = {k: v[0] for k, v in parse_qs(urlparse(url).query).items()}
asked.append(query)
if query.get("query") == "고장":
raise OSError("down")
if query["type"] == "address":
items = [ADDRESS] if query["category"] == "parcel" else []
else:
items = [PLACE, PLACE] # VWorld 가 같은 장소를 두 번 줌
return json.dumps({"response": {"status": "OK", "result": {"items": items}}}).encode()
monkeypatch.setattr(vmap, "_fetch", fetch)
app = FastAPI()
app.include_router(map_router.router)
client = TestClient(app)
client.asked = asked
return client
def test_address_then_place(api) -> None:
got = api.get("/api/map/search", params={"q": " 용화리 "})
assert got.status_code == 200
items = got.json()["items"]
assert items == [
{
"kind": "address",
"title": "경상북도 영양군 일월면 용화리 1",
"address": "",
"lon": 129.165,
"lat": 36.815,
},
{
"kind": "place",
"title": "용화보건진료소",
"address": "경상북도 영양군 일월면 영양로 3773",
"lon": 129.15,
"lat": 36.789,
},
]
assert {q["query"] for q in api.asked} == {"용화리"} # 앞뒤 빈칸 뺌
assert "key" not in got.text # 키는 서버만
def test_empty_long_and_failure(api) -> None:
assert api.get("/api/map/search", params={"q": " "}).json()["items"] == []
assert api.get("/api/map/search", params={"q": "가" * 101}).status_code == 422
assert api.get("/api/map/search", params={"q": "고장"}).json()["items"] == []