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>
This commit is contained in:
commit
ba15a574ab
205 files changed
+56772
No files matched your search
@@ -0,0 +1,256 @@
|
||||
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())
|
||||
Reference in new issue
Block a user