Files
ecw-charge-validation/tests/test_twofa_late_approval.py
2026-10-30 10:58:35 +05:30

135 lines
4.4 KiB
Python

r"""
auth/twofactor.py `handle_2fa()` — an approval the login page never picks up.
Live 2026-10-29, three logins in a row: the push mail came 6 to 8.5 minutes
late, the bot opened its link and clicked "Yes, it's me", and the login page
stayed on "eClinicalWorks sent a notification to your email" with Resend OTP
still there. The bot waited out LOGIN_TIMEOUT and gave up ("Login not
confirmed"). Now it presses Resend, waits for the new mail and approves that
one, up to OTP_PICKUP_RESENDS times (operator, 2026-10-30: no manual step).
python tests\test_twofa_late_approval.py
Local headless chromium, `page.set_content()` only. The mailbox and the
approval tab are stand-ins: no eCW, no mail.
"""
import sys
import pathlib
import tempfile
ROOT = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "src"))
from ecw_bot import settings as cfg # noqa: E402
from ecw_bot.auth import twofactor # noqa: E402
from ecw_bot.auth.mailbox import Challenge # noqa: E402
CASES = []
def case(fn):
CASES.append(fn)
return fn
# The push screen as seen live: the notice in #dispMessage, Resend enabled.
_PAGE = """
<div id="dispMessage">eClinicalWorks sent a notification to your email.
Please confirm to login.</div>
<input type="button" id="resendOtpBtn" value="Resend OTP"
onclick="window.resends = (window.resends || 0) + 1">
<a id="jellybean-panelLink4" style="display:none">Home</a>
<script>window.resends = 0</script>
"""
def _go_home(page):
page.evaluate("document.getElementById('jellybean-panelLink4')"
".style.display = ''; document.getElementById('dispMessage')"
".style.display = 'none'")
def _fakes(page, picked_up_on):
"""Mailbox hands out links in order; approval number `picked_up_on`
(1-based) is the one the page takes."""
seen = {"approvals": 0, "mails": 0}
def get_challenge(**kw):
seen["mails"] += 1
return Challenge("link", f"https://example.test/approve/{seen['mails']}")
def approve(context, url):
seen["approvals"] += 1
if seen["approvals"] == picked_up_on:
_go_home(page)
return True
twofactor.mailbox.get_challenge = get_challenge
twofactor._approve_in_new_tab = approve
return seen
@case
def an_approval_not_picked_up_is_resent_and_approved_again(page):
page.set_content(_PAGE)
seen = _fakes(page, picked_up_on=2)
twofactor.handle_2fa(page, None, "Test", None)
assert seen["approvals"] == 2, seen
assert page.evaluate("window.resends") == 1
assert page.locator("#jellybean-panelLink4").is_visible()
@case
def an_approval_picked_up_at_once_needs_no_resend(page):
page.set_content(_PAGE)
seen = _fakes(page, picked_up_on=1)
twofactor.handle_2fa(page, None, "Test", None)
assert seen["approvals"] == 1, seen
assert page.evaluate("window.resends || 0") == 0
@case
def resends_stop_at_the_limit(page):
page.set_content(_PAGE)
seen = _fakes(page, picked_up_on=99)
twofactor.handle_2fa(page, None, "Test", None)
assert page.evaluate("window.resends") == cfg.OTP_PICKUP_RESENDS
assert seen["approvals"] == cfg.OTP_PICKUP_RESENDS + 1, seen
def main() -> int:
from playwright.sync_api import sync_playwright
failures = 0
real = (twofactor.mailbox.get_challenge, twofactor._approve_in_new_tab)
with tempfile.TemporaryDirectory() as tmp:
cfg.SCREENSHOTS = pathlib.Path(tmp) # never the real folder
cfg.ECW_SECRET_KEY = ""
cfg.OTP_PICKUP_SECONDS = 2
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
page = browser.new_page()
for fn in CASES:
name = fn.__name__.replace("_", " ")
try:
fn(page)
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
finally:
(twofactor.mailbox.get_challenge,
twofactor._approve_in_new_tab) = real
browser.close()
print(f"\n{len(CASES) - failures}/{len(CASES)} passed")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())