r""" Patient Info -> Sliding Fee Schedule: the read-only "is the sliding fee still valid on the claim's DOS" check (pages/sliding_fee.py). python tests\test_sliding_fee.py Browser cases run against tests/fixtures/sliding_fee.html; the verdict cases need no browser. """ import sys import pathlib import inspect import tempfile ROOT = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "src")) FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "sliding_fee.html" from ecw_bot.pages import sliding_fee # noqa: E402 from ecw_bot.selectors import claim_form as sel # noqa: E402 CASES = [] ASSIGNED = {"from_date": "03/10/2026", "to_date": "03/10/2027"} def case(fn): CASES.append(fn) return fn # ── verdict (no browser) ───────────────────────────────────────────────────── @case def dos_inside_the_range_is_active(): assert sliding_fee.evaluate(ASSIGNED, "08/31/2026")["verdict"] == "active" @case def both_range_ends_count_as_inside(): assert sliding_fee.evaluate(ASSIGNED, "03/10/2026")["verdict"] == "active" assert sliding_fee.evaluate(ASSIGNED, "03/10/2027")["verdict"] == "active" @case def dos_after_the_to_date_is_expired(): r = sliding_fee.evaluate(ASSIGNED, "03/11/2027") assert r["verdict"] == "expired", r @case def dos_before_the_from_date_is_not_started(): r = sliding_fee.evaluate(ASSIGNED, "03/09/2026") assert r["verdict"] == "not_started", r @case def only_the_dates_decide_other_fields_are_ignored(): """Operator, 2026-09-30: only the Date from/to against the DOS is checked — eCW's Status, poverty level, type are not.""" r = sliding_fee.evaluate(dict(ASSIGNED, status="Expired"), "08/31/2026") assert r["verdict"] == "active", r @case def disabled_button_or_no_dates_is_none(): assert sliding_fee.evaluate({"disabled": True}, "08/31/2026")["verdict"] == "none" blank = dict(ASSIGNED, from_date="", to_date="__/__/____") assert sliding_fee.evaluate(blank, "08/31/2026")["verdict"] == "none" @case def read_error_or_missing_dos_is_unknown(): assert sliding_fee.evaluate({"error": "x"}, "08/31/2026")["verdict"] == "unknown" assert sliding_fee.evaluate(None, "08/31/2026")["verdict"] == "unknown" assert sliding_fee.evaluate(ASSIGNED, "")["verdict"] == "unknown" @case def claim_dos_is_the_first_readable_sdos(): data = {"cpt": [{"sdos": "__/__/____"}, {"sdos": "08/31/2026"}, {"sdos": "09/01/2026"}]} assert sliding_fee.claim_dos(data) == "08/31/2026" assert sliding_fee.claim_dos({}) == "" # ── the screen (fixture) ───────────────────────────────────────────────────── def _reset(page, **knobs): page.goto(FIXTURE.as_uri()) page.evaluate("k => Object.assign(window.fixture, k)", knobs) def _all_closed_nothing_saved(page): assert not page.locator(sel.SEL_PT_INFO_MODAL).is_visible(), "ptInfo open" assert not page.locator(sel.SEL_SLIDING_FEE_MODAL).is_visible(), "modal open" assert page.evaluate("window.saved") is False, "a Save was clicked" @case def reads_the_assigned_dates_once_angular_fills_them(page): _reset(page, values=ASSIGNED) info = sliding_fee.read(page) assert info["from_date"] == "03/10/2026", info assert info["to_date"] == "03/10/2027", info assert set(info) == {"from_date", "to_date"}, info _all_closed_nothing_saved(page) @case def check_gives_the_verdict_for_the_claim_dos(page): _reset(page, values=ASSIGNED) r = sliding_fee.check(page, "T1", {"cpt": [{"sdos": "04/01/2027"}]}) assert r["verdict"] == "expired", r _all_closed_nothing_saved(page) @case def disabled_button_is_reported_and_still_closes(page): _reset(page, disabled=True, values=ASSIGNED) info = sliding_fee.read(page) assert info == {"disabled": True}, info _all_closed_nothing_saved(page) @case def unsaved_prompt_is_answered_dont_save(page): _reset(page, values=ASSIGNED, prompt=True) sliding_fee.read(page) assert not page.locator("#savePrompt").is_visible() _all_closed_nothing_saved(page) @case def a_hidden_leftover_info_button_first_in_the_dom_is_not_clicked(page): """ appears() waits on the VISIBLE Info button; the click that follows must use that one too. With a hidden copy first in the DOM, an unfiltered `.first` clicked the hidden copy and sat out its 15s click timeout, then reported "error" for a claim whose Info button was on screen. """ import time _reset(page, values=ASSIGNED) page.evaluate("""() => { const live = document.querySelector('#claimPatientInfo1790766147287'); const copy = live.cloneNode(true); copy.style.display = 'none'; document.body.insertBefore(copy, document.body.firstChild); }""") started = time.monotonic() info = sliding_fee.read(page) assert info.get("from_date") == "03/10/2026", info assert time.monotonic() - started < 10, "clicked the hidden copy first" _all_closed_nothing_saved(page) @case def a_hidden_leftover_sliding_fee_button_first_in_the_dom_is_not_clicked(page): """ Final review, 2026-10-05: appears() waits on the VISIBLE Sliding Fee Schedule button, and the click must take that one too. With a hidden copy first in the DOM, an unfiltered `.first` clicked the hidden copy and sat out its 15s click timeout, then reported "error". """ import time _reset(page, values=ASSIGNED) page.evaluate("""() => { const live = document.querySelector('#btnSlidingFeeschedule'); const copy = live.cloneNode(true); copy.style.display = 'none'; copy.removeAttribute('onclick'); copy.removeAttribute('data-ng-click'); document.body.insertBefore(copy, document.body.firstChild); }""") started = time.monotonic() info = sliding_fee.read(page) assert info.get("from_date") == "03/10/2026", info assert time.monotonic() - started < 10, "clicked the hidden copy first" _all_closed_nothing_saved(page) @case def hidden_leftover_close_and_cancel_buttons_are_not_clicked(page): """ _close_patient_info() checks with visible(), which answers True when ANY copy is on screen (P8) — so its clicks must take the visible copy too. A hidden leftover Cancel FIRST in Patient Information (what `.first` took) and a hidden leftover Close LAST in the Sliding Fee modal (what `.last` took) each sat out a 10s click timeout, and Patient Information stayed open over the claim form (review of unit C, 2026-10-05). """ import time _reset(page, values=ASSIGNED) page.evaluate("""() => { const hide = e => { e.style.display = 'none'; e.onclick = null; e.removeAttribute('id'); return e; }; const cancel = document.querySelector('#patient-demographicsBtn57'); cancel.parentNode.insertBefore(hide(cancel.cloneNode(true)), cancel.parentNode.firstChild); const close = document.querySelector('#slidingfeescheduleBtn8'); close.parentNode.appendChild(hide(close.cloneNode(true))); }""") started = time.monotonic() info = sliding_fee.read(page) assert info.get("from_date") == "03/10/2026", info took = time.monotonic() - started assert took < 10, f"took {took:.1f}s — clicked a hidden copy" _all_closed_nothing_saved(page) @case def no_info_button_is_an_error_not_a_crash(page): _reset(page, values=ASSIGNED) page.evaluate("document.querySelector('#claimPatientInfo1790766147287').remove()") info = sliding_fee.read(page) assert "error" in info, info assert sliding_fee.evaluate(info, "08/31/2026")["verdict"] == "unknown" @case def message_box_text_carries_the_verdict_dos_and_dates_only(): text = sliding_fee.message(sliding_fee.evaluate(ASSIGNED, "04/01/2027")) for want in ("EXPIRED", "04/01/2027", "03/10/2026", "03/10/2027"): assert want in text, (want, text) for gone in ("Status", "Poverty", "Scale type", "Fee schedule"): assert gone not in text, (gone, text) assert "UNKNOWN" in sliding_fee.message(sliding_fee.evaluate(None, "")) DENTAL_ROWS = """