Snapshot of D:\Ecw Process a11c9ff (2026-10-30). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
260 lines
8.6 KiB
Python
260 lines
8.6 KiB
Python
r"""
|
|
core/msgbox - which button Enter answers.
|
|
|
|
python tests\test_msgbox.py
|
|
|
|
Review item P1 (2026-10-01): the per-claim save box's default button was
|
|
OK, so pressing Enter - out of habit, or to dismiss what looked like one more
|
|
info box - would save the claim to live eCW. `default_cancel=True` adds
|
|
MB_DEFBUTTON2 so Enter/Space answers Cancel; every other caller (captcha,
|
|
security image) must keep the box it always had.
|
|
|
|
No real message box is shown: `_raw_call`, the one function that reaches
|
|
user32, is replaced by a recorder that answers with a chosen return code.
|
|
"""
|
|
|
|
import sys
|
|
import pathlib
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT / "src"))
|
|
|
|
from ecw_bot.core import msgbox # noqa: E402
|
|
|
|
CASES = []
|
|
|
|
|
|
def case(fn):
|
|
CASES.append(fn)
|
|
return fn
|
|
|
|
|
|
_DEFBUTTON2 = 0x00000100
|
|
_IDOK, _IDCANCEL = 1, 2
|
|
|
|
|
|
def _capture(fn, rc=_IDCANCEL):
|
|
"""Call fn() with _raw_call recording its flags. Returns (result, flags)."""
|
|
seen = []
|
|
orig = msgbox._raw_call
|
|
msgbox._raw_call = lambda text, caption, flags, timeout_s: (
|
|
seen.append(flags) or rc)
|
|
try:
|
|
result = fn()
|
|
finally:
|
|
msgbox._raw_call = orig
|
|
assert len(seen) == 1, seen
|
|
return result, seen[0]
|
|
|
|
|
|
@case
|
|
def defbutton2_constant_is_the_win32_value():
|
|
# Pinned: a typo here would silently leave OK as the default button.
|
|
assert msgbox._MB_DEFBUTTON2 == _DEFBUTTON2, hex(msgbox._MB_DEFBUTTON2)
|
|
|
|
|
|
@case
|
|
def confirm_default_keeps_ok_as_the_default_button():
|
|
_, flags = _capture(lambda: msgbox.confirm("t", "m", 5))
|
|
assert not flags & _DEFBUTTON2, hex(flags)
|
|
assert flags & msgbox._MB_OKCANCEL, hex(flags)
|
|
|
|
|
|
@case
|
|
def confirm_default_cancel_makes_cancel_the_default_button():
|
|
_, flags = _capture(
|
|
lambda: msgbox.confirm("t", "m", 5, default_cancel=True))
|
|
assert flags & _DEFBUTTON2, hex(flags)
|
|
# Still the same OK/Cancel warning box, on top of the browser.
|
|
assert flags & msgbox._MB_OKCANCEL, hex(flags)
|
|
assert flags & msgbox._MB_ICONWARN, hex(flags)
|
|
assert flags & msgbox._MB_TOPMOST, hex(flags)
|
|
|
|
|
|
@case
|
|
def ok_cancel_passes_default_cancel_through():
|
|
_, flags = _capture(lambda: msgbox.ok_cancel("t", "m", default_cancel=True))
|
|
assert flags & _DEFBUTTON2, hex(flags)
|
|
|
|
|
|
@case
|
|
def ok_cancel_default_is_unchanged_for_existing_callers():
|
|
"""captcha.py and login.py call ok_cancel(title, msg) - no Enter change."""
|
|
_, flags = _capture(lambda: msgbox.ok_cancel("t", "m"))
|
|
assert not flags & _DEFBUTTON2, hex(flags)
|
|
|
|
|
|
@case
|
|
def ok_cancel_default_cancel_still_true_only_on_ok():
|
|
"""The default button changes; what each answer means does not."""
|
|
ok, _ = _capture(lambda: msgbox.ok_cancel("t", "m", default_cancel=True),
|
|
rc=_IDOK)
|
|
assert ok is True, ok
|
|
cancel, _ = _capture(
|
|
lambda: msgbox.ok_cancel("t", "m", default_cancel=True), rc=_IDCANCEL)
|
|
assert cancel is False, cancel
|
|
timed_out, _ = _capture(
|
|
lambda: msgbox.ok_cancel("t", "m", default_cancel=True),
|
|
rc=msgbox._IDTIMEOUT)
|
|
assert timed_out is False, timed_out
|
|
|
|
|
|
@case
|
|
def info_box_never_gets_defbutton2():
|
|
_, flags = _capture(lambda: msgbox.info("t", "m", 5), rc=_IDOK)
|
|
assert not flags & _DEFBUTTON2, hex(flags)
|
|
|
|
|
|
# ── [TEST] title prefix (review item D9, 2026-10-05) ─────────────────────────
|
|
# A TEST_CLAIM_IDS_FILE run drives live eCW too, so every box it shows says
|
|
# so in its title. Applied in the raw info() / confirm() (and so in the
|
|
# info_box / ok_cancel wrappers built on them): the 2FA manual box, "Run the
|
|
# Lookup?" and the export report box call the raw ones directly, and the 2FA
|
|
# box is reached at login in a test run (review fix round 1, 2026-10-05).
|
|
|
|
def _captions(fn, test_file, rc=_IDOK):
|
|
seen = []
|
|
orig_raw, orig_file = msgbox._raw_call, msgbox.cfg.TEST_CLAIM_IDS_FILE
|
|
msgbox._raw_call = lambda text, caption, flags, timeout_s: (
|
|
seen.append(caption) or rc)
|
|
msgbox.cfg.TEST_CLAIM_IDS_FILE = test_file
|
|
try:
|
|
fn()
|
|
finally:
|
|
msgbox._raw_call = orig_raw
|
|
msgbox.cfg.TEST_CLAIM_IDS_FILE = orig_file
|
|
return seen
|
|
|
|
|
|
@case
|
|
def info_box_title_says_test_in_test_mode():
|
|
seen = _captions(lambda: msgbox.info_box("eCW Bot — Claim 1", "m"),
|
|
"C:/x/Standard Claims.xlsx")
|
|
assert seen == ["[TEST] eCW Bot — Claim 1"], seen
|
|
|
|
|
|
@case
|
|
def ok_cancel_title_says_test_in_test_mode():
|
|
seen = _captions(lambda: msgbox.ok_cancel("eCW Bot — Save?", "m",
|
|
default_cancel=True),
|
|
"C:/x/Standard Claims.xlsx", rc=_IDCANCEL)
|
|
assert seen == ["[TEST] eCW Bot — Save?"], seen
|
|
|
|
|
|
@case
|
|
def live_run_titles_are_unchanged():
|
|
seen = _captions(lambda: (msgbox.info_box("A", "m"),
|
|
msgbox.ok_cancel("B", "m")), "")
|
|
assert seen == ["A", "B"], seen
|
|
|
|
|
|
@case
|
|
def raw_confirm_title_says_test_in_test_mode():
|
|
"""auth/mailbox.py's 2FA manual box calls msgbox.confirm directly."""
|
|
seen = _captions(lambda: msgbox.confirm("eCW Bot — 2FA (Salud)", "m", 5),
|
|
"C:/x/Standard Claims.xlsx", rc=_IDCANCEL)
|
|
assert seen == ["[TEST] eCW Bot — 2FA (Salud)"], seen
|
|
|
|
|
|
@case
|
|
def raw_info_title_says_test_in_test_mode():
|
|
"""pages/claim_report.py's export report box calls msgbox.info directly."""
|
|
seen = _captions(lambda: msgbox.info("eCW Bot — EXCEL report", "m", 5),
|
|
"C:/x/Standard Claims.xlsx")
|
|
assert seen == ["[TEST] eCW Bot — EXCEL report"], seen
|
|
|
|
|
|
@case
|
|
def raw_boxes_are_unchanged_on_a_live_run():
|
|
seen = _captions(lambda: (msgbox.info("A", "m", 5),
|
|
msgbox.confirm("B", "m", 5)), "")
|
|
assert seen == ["A", "B"], seen
|
|
|
|
|
|
@case
|
|
def test_prefix_is_never_doubled():
|
|
# The wrappers apply it and so do the raw functions under them.
|
|
seen = _captions(lambda: (msgbox.info_box("[TEST] A", "m"),
|
|
msgbox.info_box("B", "m"),
|
|
msgbox.ok_cancel("C", "m")), "x.xlsx")
|
|
assert seen == ["[TEST] A", "[TEST] B", "[TEST] C"], seen
|
|
|
|
|
|
@case
|
|
def quiet_boxes_send_an_info_box_to_the_log_not_the_screen():
|
|
import io
|
|
import contextlib
|
|
shown = []
|
|
# TEST_CLAIM_IDS_FILE cleared: the operator's .env sets it, and then
|
|
# titled() puts "[TEST] " in front of the title.
|
|
orig = (msgbox._raw_call, msgbox.cfg.QUIET_BOXES,
|
|
msgbox.cfg.TEST_CLAIM_IDS_FILE)
|
|
msgbox._raw_call = lambda *a: shown.append(a) or 1
|
|
msgbox.cfg.QUIET_BOXES = True
|
|
msgbox.cfg.TEST_CLAIM_IDS_FILE = ""
|
|
out = io.StringIO()
|
|
try:
|
|
with contextlib.redirect_stdout(out):
|
|
msgbox.info_box("eCW Bot — Claim 1", "Summary : Pass\nmore")
|
|
rc = msgbox.info("eCW Bot — CSV report", "Saved.", 5)
|
|
finally:
|
|
(msgbox._raw_call, msgbox.cfg.QUIET_BOXES,
|
|
msgbox.cfg.TEST_CLAIM_IDS_FILE) = orig
|
|
assert shown == [], shown
|
|
assert rc == msgbox.OK
|
|
text = out.getvalue()
|
|
assert "[BOX] eCW Bot — Claim 1: Summary : Pass" in text, text
|
|
assert "more" not in text, text
|
|
|
|
|
|
@case
|
|
def quiet_boxes_still_show_a_box_that_needs_a_person():
|
|
shown = []
|
|
orig = (msgbox._raw_call, msgbox.cfg.QUIET_BOXES)
|
|
msgbox._raw_call = lambda *a: shown.append(a) or 1
|
|
msgbox.cfg.QUIET_BOXES = True
|
|
try:
|
|
msgbox.info_box("eCW Bot — Security Image", "Click Continue",
|
|
needs_person=True)
|
|
msgbox.confirm("eCW Bot — CAPTCHA", "Type it", 5)
|
|
finally:
|
|
msgbox._raw_call, msgbox.cfg.QUIET_BOXES = orig
|
|
assert len(shown) == 2, shown
|
|
|
|
|
|
@case
|
|
def without_quiet_boxes_an_info_box_is_shown():
|
|
shown = []
|
|
orig = (msgbox._raw_call, msgbox.cfg.QUIET_BOXES)
|
|
msgbox._raw_call = lambda *a: shown.append(a) or 1
|
|
msgbox.cfg.QUIET_BOXES = False
|
|
try:
|
|
msgbox.info_box("eCW Bot — Claim 1", "Summary : Pass")
|
|
finally:
|
|
msgbox._raw_call, msgbox.cfg.QUIET_BOXES = orig
|
|
assert len(shown) == 1, shown
|
|
|
|
|
|
def main() -> int:
|
|
failures = 0
|
|
# This PC's .env runs bulk with QUIET_BOXES=1 (2026-10-29); the cases
|
|
# that want it on set it themselves.
|
|
msgbox.cfg.QUIET_BOXES = False
|
|
for fn in CASES:
|
|
name = fn.__name__.replace("_", " ")
|
|
try:
|
|
fn()
|
|
print(f" ok {name}")
|
|
except AssertionError as exc:
|
|
print(f" FAIL {name}: {exc}")
|
|
failures += 1
|
|
except Exception as exc:
|
|
print(f" ERROR {name}: {type(exc).__name__}: {exc}")
|
|
failures += 1
|
|
print(f"\n{len(CASES) - failures}/{len(CASES)} passed")
|
|
return 1 if failures else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|