aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/tests/test_e2e_local.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-cli/tests/test_e2e_local.py')
-rw-r--r--packages/meshbay-cli/tests/test_e2e_local.py121
1 files changed, 111 insertions, 10 deletions
diff --git a/packages/meshbay-cli/tests/test_e2e_local.py b/packages/meshbay-cli/tests/test_e2e_local.py
index 8ce3ebc..19f5ca9 100644
--- a/packages/meshbay-cli/tests/test_e2e_local.py
+++ b/packages/meshbay-cli/tests/test_e2e_local.py
@@ -312,6 +312,27 @@ class App:
assert r.get("detail") == "keypair_bundle_deleted", r
await close()
+ async def revoke_the_others(self) -> None:
+ """Retire every device of the account but this one: the node admits five,
+ and the tests link more machines than that between them."""
+ from meshbay_cli.client.keys import b64
+ from meshbay_common import MNP_VERSION
+ from meshbay_common.device import device_revoke_transcript
+ ch, close, node_pk, nonce_s, account = await self.session()
+ listed = await ch.request({"type": "device_list", "v": MNP_VERSION},
+ "device_list_result")
+ for d in listed["devices"]:
+ if d["pk_ed25519"] == self.ident.pk_ed:
+ continue
+ ts = int(time.time())
+ sig = self.ident.sk_ed.sign(device_revoke_transcript(
+ node_pk_b64=node_pk, user_id=account.user_id,
+ pk_ed25519_b64=d["pk_ed25519"], nonce_node=nonce_s, ts=ts))
+ await ch.request({"type": "device_revoke", "v": MNP_VERSION,
+ "pk_ed25519": d["pk_ed25519"], "ts": ts, "sig": b64(sig)},
+ "device_add_ack")
+ await close()
+
async def approve(self, code: str) -> None:
"""Approve a device by the code it shows, as the group's Settings tab does."""
from meshbay_cli.client.keys import b64
@@ -668,7 +689,9 @@ _LINK_FORM = r"""(() => {
return { form: Boolean(form), button: Boolean(form && form.querySelector('button')),
code: code || '', minted: window.__minted,
error: (document.querySelector('.error-msg') || {}).innerText || '',
- opened: /No messages yet/.test(page), page: page.slice(-400) };
+ // The group open on its Chat tab, the composer ready.
+ opened: Boolean(document.querySelector('.chat-input:not([disabled])')),
+ page: page.slice(-400) };
})()"""
@@ -730,12 +753,10 @@ class _Chrome:
self.profile.cleanup()
-@pytest.mark.skipif(not shutil.which("google-chrome"),
- reason="google-chrome is not available")
-def test_the_application_links_itself_from_its_group_page(world):
+def _session_for_the_page(world: dict) -> dict:
+ """What the page keeps in `mb_auth` after signing in, for the account."""
from meshbay_cli.client.keys import auth_key
- app, hub_url = world["app"], world["hub_url"]
- asyncio.run(app.withdraw_bundle())
+ hub_url = world["hub_url"]
deadline = time.monotonic() + 90
while (r := httpx.post(f"{hub_url}/v1/users/login", json={
"username": USERNAME, "auth_key": auth_key(USERNAME, PASSPHRASE)})
@@ -745,12 +766,19 @@ def test_the_application_links_itself_from_its_group_page(world):
tokens = r.json()
me = httpx.get(f"{hub_url}/v1/users/me",
headers={"Authorization": f"Bearer {tokens['access_token']}"}).json()
- auth = {"username": USERNAME, "userId": me["user_id"], "role": "user",
+ return {"username": USERNAME, "userId": me["user_id"], "role": "user",
"token": tokens["access_token"], "refreshToken": tokens.get("refresh_token")}
- chrome = _Chrome(_KEYS_STUB.replace("__AUTH__", json.dumps(auth)))
+
+def _linked_group_page(world: dict) -> "_Chrome":
+ """The group page in Chrome as the desktop application, linked as a new
+ device by its own "Get a linking code" and open. The caller closes it."""
+ app = world["app"]
+ asyncio.run(app.withdraw_bundle())
+ chrome = _Chrome(_KEYS_STUB.replace("__AUTH__", json.dumps(_session_for_the_page(world))))
try:
- chrome.call("Page.navigate", url=f"{hub_url}/#/group/{world['group_id']}")
+ chrome.call("Page.navigate",
+ url=f"{world['hub_url']}/#/group/{world['group_id']}")
# Refused as an unknown device: the form, and nothing shown as an error.
s = chrome.until(_LINK_FORM, lambda s: s["button"])
assert not s["error"], s
@@ -763,9 +791,16 @@ def test_the_application_links_itself_from_its_group_page(world):
s = chrome.until(_LINK_FORM, lambda s: s["opened"] and not s["form"], timeout=60)
assert not s["error"], s
assert s["minted"] == 1, "the identity linked is not the one kept"
- finally:
+ return chrome
+ except BaseException:
chrome.close()
+ raise
+
+@pytest.mark.skipif(not shutil.which("google-chrome"),
+ reason="google-chrome is not available")
+def test_the_application_links_itself_from_its_group_page(world):
+ _linked_group_page(world).close()
# ── A second account: invited, joined, its devices, browser access, leaving ──
@@ -916,3 +951,69 @@ def test_search_finds_a_file_and_get_takes_its_path(world, tmp_path):
assert (tmp_path / "plage de l'ouest.jpg").stat().st_size == 3000
r = cli(world, "search", "no-such-thing-anywhere", home="cli-search")
assert r.returncode == 3 and "Nothing found" in r.stderr
+
+
+def _cli_signed_in(world: dict, home: str) -> None:
+ deadline = time.monotonic() + 90
+ while True:
+ r = cli(world, "--hub", world["hub_url"], "-u", USERNAME, "login",
+ "--password-file", "-", stdin=PASSPHRASE + "\n", home=home)
+ if r.returncode == 0:
+ return
+ assert "Too many attempts" in r.stderr and time.monotonic() < deadline, r.stderr
+ time.sleep(5)
+
+
+@pytest.mark.skipif(not shutil.which("google-chrome"),
+ reason="google-chrome is not available")
+def test_chat_between_the_command_line_and_the_application(world):
+ """Posted here, read on the group page in Chrome; answered there, read here —
+ the Python seal and the page's crypto.js, on a real node."""
+ group = _handle()
+ asyncio.run(world["app"].revoke_the_others()) # room for this machine and the page
+ _cli_signed_in(world, "cli-chat")
+ r = cli(world, "chat", group, "-m", "hello from the terminal 1", home="cli-chat")
+ if r.returncode == 5: # a new device here: approved first
+ found = re.search(r"enter\s+([0-9A-Z]{4}-[0-9A-Z]{4})", r.stderr)
+ assert found, r.stderr
+ code = found.group(1)
+ asyncio.run(world["app"].approve(code))
+ r = cli(world, "chat", group, "-m", "hello from the terminal 1", home="cli-chat")
+ assert r.returncode == 0, r.stderr
+ for i in range(2, 13):
+ r = cli(world, "chat", group, "-m", f"hello from the terminal {i}", home="cli-chat")
+ assert r.returncode == 0, r.stderr
+
+ r = cli(world, "chat", group, home="cli-chat")
+ assert r.returncode == 0, r.stderr
+ lines = r.stdout.splitlines()
+ assert len(lines) == 10
+ assert lines[0].endswith(f"{USERNAME}: hello from the terminal 3")
+ assert lines[-1].endswith(f"{USERNAME}: hello from the terminal 12")
+ r = cli(world, "chat", "-100", group, home="cli-chat")
+ assert len(r.stdout.splitlines()) == 12, r.stdout
+
+ chrome = _linked_group_page(world)
+ try:
+ # The page opens the terminal's messages: signature, epoch key, content.
+ chrome.until("document.body.innerText",
+ lambda text: "hello from the terminal 12" in (text or ""))
+ assert "cannot be read" not in chrome.js("document.body.innerText").lower()
+ chrome.js("""(() => {
+ const box = document.querySelector('.chat-input');
+ box.value = 'hello from the browser';
+ box.dispatchEvent(new Event('input', { bubbles: true }));
+ })()""")
+ chrome.until("!document.querySelector('.chat-send').disabled", bool)
+ chrome.js("document.querySelector('.chat-send').click()")
+ chrome.until("document.querySelector('.chat-input').value === ''", bool)
+ finally:
+ chrome.close()
+
+ deadline = time.monotonic() + 30
+ while True:
+ r = cli(world, "chat", "-1", group, home="cli-chat")
+ if r.stdout.strip().endswith(f"{USERNAME}: hello from the browser"):
+ break
+ assert time.monotonic() < deadline, (r.stdout, r.stderr)
+ time.sleep(1)