"""The updater drives a page it does not own, so pin the two things that are actually ours: which rows get ticked, and finding a toolbar button across three Joomla generations. The browser itself is faked — this is about the logic, and a real run against a live site is a dry run away. """ import admin import mesh import pytest class Loc: """The slice of Playwright's Locator this module uses.""" def __init__(self, items=(), text=""): self.items = list(items) self.text = text self.checked = False self.clicked = 0 # a locator is both a set and, via .first/.nth, its members def count(self): return len(self.items) if self.items else (1 if self.text else 0) def all(self): return self.items def nth(self, i): return self.items[i] @property def first(self): return self.items[0] if self.items else self def inner_text(self): return self.text def check(self): self.checked = True def click(self): self.clicked += 1 def get_attribute(self, _): return self.text def locator(self, sel): return self.sub[sel] def _row(cid, name): cb = Loc(text=cid) cells = Loc([Loc(text="")] * 1 + [Loc(text=f"{name}\nhttps://update.example/x.xml")]) tr = Loc(text=name) tr.sub = {"td, th": cells} cb.sub = {"xpath=ancestor::tr[1]": tr} return cb class Page: def __init__(self, rows=(), buttons=(), alerts=()): self.rows = {r.text: r for r in rows} self.buttons = dict(buttons) self.alerts = list(alerts) self.waited = [] def locator(self, sel): if sel == 'input[name="cid[]"]': return Loc(list(self.rows.values())) if sel.startswith('input[name="cid[]"][value='): return Loc([self.rows[sel.split('"')[3]]]) if sel == "#system-message-container .alert": return Loc([Loc(text=a) for a in self.alerts]) return self.buttons.get(sel, Loc()) def wait_for_load_state(self, *a, **kw): self.waited.append(a) ROWS = [_row("101", "Regular Labs - Snippets"), _row("102", "YOOtheme - Joomla Template")] MODERN = 'joomla-toolbar-button[task="update.update"] button' ANCIENT = 'button[onclick*="update.update"]' def test_only_the_named_rows_are_ticked(): page = Page(ROWS, {MODERN: Loc([Loc(text="btn")])}, alerts=["Update completed."]) picked, messages = admin.apply_updates(page, only=["yootheme"]) assert [p["label"] for p in picked] == ["YOOtheme - Joomla Template"] assert page.rows["102"].checked is True assert page.rows["101"].checked is False # the one we did not ask for assert messages == ["Update completed."] def test_no_filter_means_every_pending_row(): page = Page(ROWS, {MODERN: Loc([Loc(text="btn")])}) picked, _ = admin.apply_updates(page) assert len(picked) == 2 and all(r.checked for r in page.rows.values()) def test_a_joomla_3_toolbar_is_still_found(): page = Page(ROWS, {ANCIENT: Loc([Loc(text="btn")])}) admin.apply_updates(page) assert page.buttons[ANCIENT].items[0].clicked == 1 def test_no_update_button_is_an_error_not_a_silent_no_op(): page = Page(ROWS) with pytest.raises(admin.AdminError): admin.apply_updates(page) def test_nothing_pending_never_presses_update(): page = Page([], {MODERN: Loc([Loc(text="btn")])}) picked, messages = admin.apply_updates(page) assert picked == [] and messages == [] assert page.buttons[MODERN].items[0].clicked == 0 def test_typed_credentials_beat_the_stored_ones(monkeypatch): monkeypatch.setattr(mesh, "env", lambda site=None: { "ADMIN_USER": "stored", "ADMIN_PASS": "storedpw"}) assert admin.credentials("x") == ("stored", "storedpw") assert admin.credentials("x", "typed", "typedpw") == ("typed", "typedpw") def test_sign_in_without_credentials_refuses_before_opening_the_site(): with pytest.raises(admin.AdminError): admin.sign_in(None, "https://x.test", "", "")