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:
2026-10-22 21:53:30 +09:00
co-authored by Claude Opus 5.5
parent f195a0c72c
commit 3736e601ee
4 changed files with 137 additions and 14 deletions
+56 -1
View File
@@ -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