Files
ecw-charge-validation/tests/test_msgbox.py
T
ManikandanandClaude Opus 5.5 ba15a574ab Initial import: eCW Charge Validation bot and results Portal
Snapshot of the working project as of 2026-10-23 (local 2d4d4fc), one
commit with no earlier history: the tracked files were de-identified
first (synthetic claim ids and patient data only; see Context.md
section 7). Credentials, run logs, screenshots, claim reports and client
workbooks are gitignored and not included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:38:17 +05:30

257 lines
8.4 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
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())