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

169 lines
4.9 KiB
Python

r"""
flows/recovery.py — the run gets itself out of a stuck screen (operator,
2026-10-30: "you can close or refresh and re-login and continue the agent").
Live 2026-10-29: a Dental claim's sliding fee check left Patient Information
open; its overlay took every click, and the next 18 claims were each marked
"Bot could not search the claim" without being looked at.
Now a claim the bot could not search or read sets off a recovery:
1. the login page on screen = the session ended → PageStuck (re-login);
2. Escape, close Patient Information, clear the screen → search ready?
3. reopen the claim search page → search ready?
4. still blocked → PageStuck.
After a recovery the same claim is tried once more. Two claims in a row
that still fail after their recovery → PageStuck, so the supervisor
restarts Chrome and logs in again.
python tests\test_recovery.py
No browser: the page steps are stand-ins.
"""
import sys
import pathlib
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / "src"))
from ecw_bot.flows import recovery # noqa: E402
CASES = []
def case(fn):
CASES.append(fn)
return fn
class Steps:
"""Stand-ins for the page steps; `blocked` = what search_blocker says,
in order, one per look."""
def __init__(self, blocked, logged_out=False):
self.blocked = list(blocked)
self.logged_out = logged_out
self.done = []
def install(self):
recovery._logged_out = lambda page: self.logged_out
recovery._escape_dialogs = lambda page: self.done.append("escape")
recovery._clear = lambda page, cid, keep: self.done.append("clear")
recovery._blocker = lambda page: self.blocked.pop(0)
def reopen(self, page):
self.done.append("reopen")
@case
def a_dialog_cleared_by_escape_needs_no_reopen():
s = Steps([None])
s.install()
assert recovery.recover(None, "1", (), s.reopen) == "cleared"
assert s.done == ["escape", "clear"], s.done
@case
def a_page_still_blocked_is_reopened():
s = Steps(["covered by div.modalOverlay", None])
s.install()
assert recovery.recover(None, "1", (), s.reopen) == "reopened"
assert s.done == ["escape", "clear", "reopen"], s.done
@case
def a_page_blocked_after_reopen_is_stuck():
s = Steps(["covered", "covered"])
s.install()
try:
recovery.recover(None, "1", (), s.reopen)
except recovery.PageStuck as exc:
assert "covered" in str(exc), exc
else:
raise AssertionError("no PageStuck")
@case
def the_login_page_means_the_session_ended():
s = Steps([], logged_out=True)
s.install()
try:
recovery.recover(None, "1", (), s.reopen)
except recovery.PageStuck as exc:
assert "login page" in str(exc), exc
else:
raise AssertionError("no PageStuck")
assert s.done == [], s.done
def _attempts(*outcomes):
"""attempt() returning each outcome in turn: 'ok' or 'bot'."""
seq = list(outcomes)
def attempt():
o = seq.pop(0)
return ({"search_error": "x"}, {}) if o == "bot" else ({}, {"cpt": []})
return attempt
@case
def a_claim_that_fails_is_recovered_and_tried_again():
tried = []
state = {}
r, data = recovery.run_claim(_attempts("bot", "ok"),
lambda: tried.append(1), state)
assert tried == [1] and not r.get("search_error"), (tried, r)
assert state["streak"] == 0
@case
def a_claim_that_fails_again_is_kept_as_review():
state = {}
r, _ = recovery.run_claim(_attempts("bot", "bot"), lambda: None, state)
assert r.get("search_error") and state["streak"] == 1
@case
def two_claims_in_a_row_failing_after_recovery_stop_the_run():
state = {}
recovery.run_claim(_attempts("bot", "bot"), lambda: None, state)
try:
recovery.run_claim(_attempts("bot", "bot"), lambda: None, state)
except recovery.PageStuck:
pass
else:
raise AssertionError("no PageStuck after two in a row")
@case
def a_good_claim_resets_the_streak():
state = {}
recovery.run_claim(_attempts("bot", "bot"), lambda: None, state)
recovery.run_claim(_attempts("ok"), lambda: None, state)
assert state["streak"] == 0
def main() -> int:
failures = 0
real = {k: getattr(recovery, k, None) for k in
("_logged_out", "_escape_dialogs", "_clear", "_blocker")}
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
finally:
for k, v in real.items():
setattr(recovery, k, v)
print(f"\n{len(CASES) - failures}/{len(CASES)} passed")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())