diff options
Diffstat (limited to 'packages/meshbay-hub/tests')
| -rw-r--r-- | packages/meshbay-hub/tests/test_hub_address_seam.py | 79 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_locales.py | 6 |
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( |