""" The Node page's "No operator paired" banner. A node publishes its roster only once it has signed in to the hub, and until then it has no way to know who is paired. It used to answer `false` in that window and the page took `!operator_paired` for "not paired" -- so a node stuck waiting for its account told its operator to pair again, about a pairing that was intact, and sent them after the wrong problem. """ import json import re import shutil import subprocess from pathlib import Path import pytest STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" NODE_PAGE = STATIC / "node-page.js" def _source() -> str: return NODE_PAGE.read_text(encoding="utf-8") @pytest.mark.skipif(shutil.which("node") is None, reason="node is not available") def test_paired_is_unknown_unless_the_node_says_true_or_false(tmp_path): m = re.search(r"^export function pairedFrom\(.*?^\}", _source(), re.M | re.S) assert m, "node-page.js no longer has pairedFrom" script = tmp_path / "case.js" cases = [{"operator_paired": True}, {"operator_paired": False}, {"operator_paired": None}, {}, None, {"operator_paired": "yes"}] script.write_text(m.group(0).replace("export ", "") + f"\nconsole.log(JSON.stringify({json.dumps(cases)}.map(pairedFrom)));", encoding="utf-8") out = subprocess.run(["node", str(script)], capture_output=True, encoding="utf-8", check=True) assert json.loads(out.stdout) == [True, False, None, None, None, None] def test_the_banner_needs_an_explicit_false(): src = _source() banner = src.index('class="node-pair-banner"') guard = src.rindex("${", 0, banner) assert src[guard:banner].startswith("${operatorPaired === false &&"), ( "the pairing banner must not show for a node that has not read its roster") assert "useState(null)" in src.split("const [operatorPaired", 1)[1].split("\n", 1)[0] assert "setOperatorPaired(!!" not in src def test_pair_this_browser_queues_the_code_instead_of_claiming_success(): """It stored the code where nothing read it, said "Paired successfully", and the banner came back on the next refresh. The code is redeemed over MNP by the next group of this node to open, as the wizard's is.""" src = _source() pair = src.split("const doPairOperator = useCallback(", 1)[1].split("}, [refresh]);", 1)[0] assert "session.pendingJoinCode = result.code;" in pair assert "setOperatorPaired(true)" not in pair assert "import { HUB, session } from './hub-client.js';" in src def test_no_pairing_banner_for_a_node_with_no_group_yet(): """A node set up at sign-in hosts nothing yet: there is nothing to pair for until a group exists, and creating one pairs.""" src = _source() banner = src.index('class="node-pair-banner"') guard = src.rindex("${", 0, banner) assert "nodeGroups.length > 0" in src[guard:banner]