aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests')
-rw-r--r--packages/meshbay-hub/tests/test_hub_address_seam.py79
-rw-r--r--packages/meshbay-hub/tests/test_locales.py6
2 files changed, 82 insertions, 3 deletions
diff --git a/packages/meshbay-hub/tests/test_hub_address_seam.py b/packages/meshbay-hub/tests/test_hub_address_seam.py
new file mode 100644
index 0000000..392a21c
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_hub_address_seam.py
@@ -0,0 +1,79 @@
+"""
+One address for the hub, resolved in one place.
+
+`keyderive.js` carried its own `const HUB = '' // same origin`. True of a page
+the hub served; false of one loaded from a package, where the origin is
+`app://meshbay` — so `/v1/users/register` resolved against that and the
+application's own protocol handler answered 404. **Registration and sign-in, the
+first two things anybody does, failed with "Not found".**
+
+It was found by a person clicking Register, not by anything here, and it is the
+same shape as the duplicate `MNP_VERSION` in `protocol.py`: a second copy of a
+constant, harmless until something changes underneath it.
+
+So: no file that talks to the hub may decide for itself where the hub is.
+"""
+
+import re
+from pathlib import Path
+
+import pytest
+
+STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
+
+# Files that issue hub requests. `app.js` gets its base from `platform.hubBase()`
+# and the rest reach the adapter through the global it publishes.
+CALLERS = ["app.js", "keyderive.js", "transport.js", "crypto.js"]
+
+
+def _source(name: str) -> str:
+ return (STATIC / name).read_text(encoding="utf-8")
+
+
+@pytest.mark.parametrize("name", CALLERS)
+def test_no_file_decides_where_the_hub_is(name):
+ """
+ A literal empty base means "same origin", which is an assumption about how
+ the page was loaded — and it is wrong in the application.
+ """
+ for line in _source(name).splitlines():
+ stripped = line.strip()
+ if stripped.startswith("*") or stripped.startswith("//"):
+ continue # prose about the fix, not the fix
+ assert not re.match(r"const HUB\s*=\s*['\"]{2}\s*;", stripped), (
+ f"{name} hard-codes the hub as the current origin")
+
+
+@pytest.mark.parametrize("name", CALLERS)
+def test_hub_paths_are_never_fetched_against_the_page_origin(name):
+ """
+ `fetch('/v1/...')` resolves against whatever served the page. In a browser
+ that is the hub; in the application it is the package, and the request never
+ leaves the machine.
+ """
+ source = _source(name)
+ bad = re.findall(r"""\bfetch\(\s*['"`]/v1/""", source)
+ assert not bad, (
+ f"{name} fetches a hub path relative to the page origin — "
+ f"{len(bad)} site(s)")
+
+
+def test_the_adapter_is_reachable_from_a_classic_script():
+ """
+ `keyderive.js` and `transport.js` load before the module graph and cannot
+ import. The adapter therefore publishes a global, and they read it when a
+ call is made rather than when they load — by which time it exists.
+ """
+ platform = _source("platform.js")
+ assert "window.MeshBayPlatform" in platform
+
+ for name in ("keyderive.js", "transport.js"):
+ source = _source(name)
+ assert "MeshBayPlatform" in source, (
+ f"{name} does not reach the adapter, so it has an answer of its own")
+
+
+def test_the_adapter_is_the_only_thing_that_answers_where():
+ """One implementation, so a second cannot drift from it."""
+ platform = _source("platform.js")
+ assert platform.count("export function hubBase()") == 1
diff --git a/packages/meshbay-hub/tests/test_locales.py b/packages/meshbay-hub/tests/test_locales.py
index 4035f1f..59ea2bf 100644
--- a/packages/meshbay-hub/tests/test_locales.py
+++ b/packages/meshbay-hub/tests/test_locales.py
@@ -158,7 +158,7 @@ def test_locale_resolution_is_region_aware(tmp_path):
const out = {};
for (const tags of [['pt-BR'], ['pt'], ['zh-CN'], ['zh'], ['fr-CA'],
['de-AT'], ['ru', 'it'], ['ko']]) {
- globalThis.navigator = { languages: tags, language: tags[0] };
+ Object.defineProperty(globalThis, 'navigator', { value: { languages: tags, language: tags[0] }, configurable: true });
delete store.mb_lang;
out[tags.join(',')] = await i18n.initLocale();
}
@@ -186,7 +186,7 @@ def test_counted_string_picks_the_right_polish_form(tmp_path):
setItem: (k, v) => { store[k] = v; },
};
globalThis.document = { documentElement: {} };
- globalThis.navigator = { languages: ['pl'], language: 'pl' };
+ Object.defineProperty(globalThis, 'navigator', { value: { languages: ['pl'], language: 'pl' }, configurable: true });
const i18n = await import('./i18n.js');
await i18n.initLocale();
console.log(JSON.stringify(
@@ -205,7 +205,7 @@ def test_interpolated_value_is_not_read_as_a_replacement_pattern(tmp_path):
setItem: (k, v) => { store[k] = v; },
};
globalThis.document = { documentElement: {} };
- globalThis.navigator = { languages: ['en'], language: 'en' };
+ Object.defineProperty(globalThis, 'navigator', { value: { languages: ['en'], language: 'en' }, configurable: true });
const i18n = await import('./i18n.js');
await i18n.initLocale();
console.log(JSON.stringify(