fix(514-4 추천 저장본 · 끌기 시험): 프로젝트 접근 = 표준 횡단면 「없음」과 config 기본값을 같은 뜻으로 두 꼴 다 견줌(지금 꼴 먼저 · 추천 · 초기 종단 · standard_forms) — B05 · B06 [저장]이 손대지 않은 기본값을 종단 정본에 실어 전처리(None) 저장본 키가 어긋나 다섯 갈래 409 · 고친 표준은 그대로 409 · Store 파일 그대로(엔진 지문 안 바뀜) · test_508_2 끌기 = 계정 보던 화면(user_ui_prefs) 시작 전 비움 · 끝나면 되돌림 · 둘째 도엽 올 때까지 기다림 · 새 시험 test_514_4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -157,15 +157,17 @@ async def compute_initial(
|
||||
forget_pool_on_standard(pool_key, inputs["standard"])
|
||||
stored = await asyncio.to_thread(read_stored, project_root, key)
|
||||
if stored is None and not may_compute:
|
||||
# 프로젝트 접근 — 엔진 코드 판이 달라도 같은 입력 저장본을 꺼냄(505-1 덧)
|
||||
found = await asyncio.to_thread(
|
||||
read_stored_any,
|
||||
project_root,
|
||||
pool_key,
|
||||
lambda source: request_fingerprint(
|
||||
route, terrain, streams, **inputs, source=source
|
||||
)[0],
|
||||
)
|
||||
# 프로젝트 접근 — 엔진 코드 판이 달라도 같은 입력 저장본을 꺼냄(505-1 덧 · 514-4)
|
||||
found = None
|
||||
for form in standard_forms(inputs["standard"]):
|
||||
found = found or await asyncio.to_thread(
|
||||
read_stored_any,
|
||||
project_root,
|
||||
pool_key,
|
||||
lambda source, form=form: request_fingerprint(
|
||||
route, terrain, streams, **{**inputs, "standard": form}, source=source
|
||||
)[0],
|
||||
)
|
||||
if found is None:
|
||||
return JSONResponse(
|
||||
status_code=409, content={"status": "error", "message": NOT_PREPARED}
|
||||
@@ -249,6 +251,16 @@ async def project_standard(project_id: UUID) -> dict[str, Any] | None:
|
||||
return stored_standard_cross_section({"data": data if isinstance(data, dict) else None})
|
||||
|
||||
|
||||
def standard_forms(standard: dict[str, Any] | None) -> list[dict[str, Any] | None]:
|
||||
"""같은 뜻 표준 횡단면 꼴(지금 꼴 먼저) — 없음(None) = config 기본값 · 키 · 지문은 둘을 가름 ·
|
||||
[저장]은 손대지 않은 패널 기본값도 실어 보냄 → 프로젝트 접근은 두 꼴 저장본 다 꺼냄(514-4)."""
|
||||
from config.config_system import STANDARD_CROSS_SECTION
|
||||
|
||||
if standard is None:
|
||||
return [None, STANDARD_CROSS_SECTION]
|
||||
return [standard, None] if standard == STANDARD_CROSS_SECTION else [standard]
|
||||
|
||||
|
||||
async def precompute_defaults(project_id: UUID) -> None:
|
||||
"""체인 끝 예약(PLAN 327-2 · 351-1) — 기본 다섯 알약 + 저장된 제약(initial_choice)의 다섯 ×
|
||||
고를 수 있는 지형(509-5 · 설계 지형 먼저 · 수치지형도는 DB 모델 있을 때만).
|
||||
|
||||
@@ -49,6 +49,7 @@ async def initial_profile_of(
|
||||
NOT_PREPARED,
|
||||
balanced_profile_of,
|
||||
project_standard,
|
||||
standard_forms,
|
||||
)
|
||||
from B06_Section.B06_Section_Repository import (
|
||||
get_latest_section_options,
|
||||
@@ -113,10 +114,13 @@ async def initial_profile_of(
|
||||
"road": await replan._road_settings(project_id),
|
||||
}
|
||||
mark = ip.fingerprint(parts)
|
||||
# 프로젝트 접근(compute 거짓) = 코드 지문 안 견줌(505-1 덧)
|
||||
stored = await asyncio.to_thread(
|
||||
ip.read_initial, project_root, key, mark, None if compute else parts
|
||||
)
|
||||
# 프로젝트 접근(compute 거짓) = 코드 지문 안 견줌(505-1 덧) · 표준 두 꼴(514-4)
|
||||
forms = standard_forms(parts["standard"])
|
||||
stored = None
|
||||
for form in [None] if compute else [{**parts, "standard": f} for f in forms]:
|
||||
stored = stored or await asyncio.to_thread(
|
||||
ip.read_initial, project_root, key, mark, form
|
||||
)
|
||||
if stored is not None:
|
||||
return stored
|
||||
if not compute:
|
||||
|
||||
@@ -134,6 +134,45 @@ def _drop_logs() -> None:
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
# 계정 화면 취향(서버 user_ui_prefs) — 새 문맥이어도 남음 · 끌기 시험이 건드리는 것
|
||||
VIEW_PREFS = (f"aislo:pref:routeedit-view:{PROJECT}", "aislo:pref:profile-collapsed")
|
||||
|
||||
|
||||
def _prefs(email: str, values: dict[str, str | None] | None = None) -> dict[str, str | None]:
|
||||
"""`VIEW_PREFS` 값 읽기 · `values` 있으면 그 키만 되씀(None = 지움 · 다른 키 그대로)."""
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
|
||||
from config.config_db import close_db_pool, init_db_pool
|
||||
|
||||
async def run():
|
||||
pool = await init_db_pool()
|
||||
try:
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
await cursor.execute("SELECT id FROM users WHERE email = %s", (email,))
|
||||
user = (await cursor.fetchone())[0]
|
||||
for key, value in (values or {}).items():
|
||||
path = f'$."{key}"'
|
||||
if value is None:
|
||||
sql, args = "JSON_REMOVE(prefs, %s)", (path,)
|
||||
else:
|
||||
sql, args = "JSON_SET(prefs, %s, %s)", (path, value)
|
||||
await cursor.execute(
|
||||
f"UPDATE user_ui_prefs SET prefs = {sql} WHERE user_id = %s", (*args, user)
|
||||
)
|
||||
await connection.commit()
|
||||
await cursor.execute("SELECT prefs FROM user_ui_prefs WHERE user_id = %s", (user,))
|
||||
row = await cursor.fetchone()
|
||||
prefs = json.loads(row[0]) if row else {}
|
||||
return {key: prefs.get(key) for key in VIEW_PREFS}
|
||||
finally:
|
||||
await close_db_pool()
|
||||
|
||||
# 플레이라이트 고리 안 — asyncio.run 은 딴 실에서
|
||||
with concurrent.futures.ThreadPoolExecutor(1) as pool:
|
||||
return pool.submit(asyncio.run, run()).result()
|
||||
|
||||
|
||||
def _board(corridor: str | None) -> None:
|
||||
board = json.loads(BOARD.read_text("utf-8"))
|
||||
board["sites"]["corridor_mesh"] = corridor or "both"
|
||||
@@ -269,6 +308,17 @@ def test_노선_크게_끌어_놓기_등고선_다시_받음(ctx):
|
||||
# 새 문맥 — 앞 시험 페이지가 남긴 보던 화면 · 보관함 없이(축척 · 받기가 늘 같게)
|
||||
fresh_ctx = ctx.browser.new_context(viewport={"width": 1600, "height": 1000}, user_agent=MARK)
|
||||
cred = json.loads((ROOT / "tmp" / "aislo_cred.json").read_text(encoding="utf-8"))
|
||||
# 보던 화면은 계정(서버)에도 남음 — 지난 판 끌기 · 줄임이 쌓여 시작 화면이 매번 달랐음(514-4)
|
||||
kept = _prefs(cred["email"])
|
||||
_prefs(cred["email"], {VIEW_PREFS[0]: None})
|
||||
try:
|
||||
_drag_and_fetch(fresh_ctx, cred, got, on_response)
|
||||
finally:
|
||||
fresh_ctx.close()
|
||||
_prefs(cred["email"], kept) # 계정 화면 취향 되돌림
|
||||
|
||||
|
||||
def _drag_and_fetch(fresh_ctx, cred, got, on_response) -> None:
|
||||
login = {k: cred[k] for k in ("email", "password")}
|
||||
assert fresh_ctx.request.post(f"{BASE}/api/auth/login/request", data=login, timeout=60000).ok
|
||||
page = _open(fresh_ctx, on_response)
|
||||
@@ -309,6 +359,12 @@ def test_노선_크게_끌어_놓기_등고선_다시_받음(ctx):
|
||||
drops += 1
|
||||
page.wait_for_timeout(4000)
|
||||
if any(g["bbox"] for g in got):
|
||||
# 둘째 도엽(수 MB)은 늦게 옴 — 바쁜 서버(싱크 때 시험 묶음)면 4 초 안에 하나만(514-4)
|
||||
for _ in range(60):
|
||||
if sum(g["bbox"] for g in got) >= 2:
|
||||
break
|
||||
page.wait_for_timeout(500)
|
||||
page.wait_for_timeout(1500) # 더 오는 것(다시 받기 두 번)도 잡히게
|
||||
break
|
||||
page.mouse.move(1500, 900)
|
||||
page.mouse.down(button="middle")
|
||||
@@ -326,7 +382,6 @@ def test_노선_크게_끌어_놓기_등고선_다시_받음(ctx):
|
||||
page.screenshot(path=str(SHOTS / "sheet_follow_dark.png"))
|
||||
page.evaluate("() => document.documentElement.setAttribute('data-theme', 'light')")
|
||||
page.close()
|
||||
fresh_ctx.close()
|
||||
fresh = [g for g in after if g["bbox"]]
|
||||
print("first", first, "fresh", fresh, "drops", drops, "status", status)
|
||||
assert during == 0 # 끄는 동안 = 안 받음
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""PLAN 514-4 — 추천 저장본 접근 = 표준 횡단면 「없음」과 config 기본값을 같은 뜻으로.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_514_4_recommend_standard_default.py -q
|
||||
|
||||
전처리 때 표준 없음(None)으로 셈 → B05 · B06 [저장]이 손대지 않은 패널 기본값을 실음 → 화면은
|
||||
그 저장본을 꺼냄(409 아님 · 셈 0) · 반대 꼴도 · 사용자가 고친 표준은 옛 저장본을 안 끌어옴.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TESTER = Path(__file__).resolve().parent
|
||||
for path in (ROOT, TESTER):
|
||||
if str(path) not in sys.path:
|
||||
sys.path.insert(0, str(path))
|
||||
|
||||
from test_224_3_recommend_store import _ask, wired # noqa: E402, F401 — 가짜 세계 fixture
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Store import DEFAULT_MODES # noqa: E402
|
||||
from config.config_system import STANDARD_CROSS_SECTION # noqa: E402
|
||||
|
||||
|
||||
def _chain_then_screen(wired, before, after) -> list[dict]: # noqa: F811
|
||||
wired.standard = before
|
||||
for mode in DEFAULT_MODES:
|
||||
_ask(True, mode=mode)
|
||||
count = wired.count
|
||||
wired.standard = after
|
||||
answers = [_ask(False, mode=mode) for mode in DEFAULT_MODES]
|
||||
assert wired.count == count # 접근 = 셈 0
|
||||
return answers
|
||||
|
||||
|
||||
def test_전처리_없음_저장_뒤_기본값이면_꺼냄(wired) -> None: # noqa: F811
|
||||
answers = _chain_then_screen(wired, None, copy.deepcopy(STANDARD_CROSS_SECTION))
|
||||
assert all(isinstance(a, dict) and a["stored"] is True for a in answers), answers
|
||||
|
||||
|
||||
def test_전처리_기본값_뒤_없음이어도_꺼냄(wired) -> None: # noqa: F811
|
||||
answers = _chain_then_screen(wired, copy.deepcopy(STANDARD_CROSS_SECTION), None)
|
||||
assert all(isinstance(a, dict) and a["stored"] is True for a in answers), answers
|
||||
|
||||
|
||||
def test_고친_표준은_옛_저장본_안_끌어옴(wired) -> None: # noqa: F811
|
||||
custom = copy.deepcopy(STANDARD_CROSS_SECTION)
|
||||
custom["soil"]["road_width_m"] = 4.0
|
||||
answers = _chain_then_screen(wired, None, custom)
|
||||
assert all(getattr(a, "status_code", None) == 409 for a in answers), answers
|
||||
Reference in New Issue
Block a user