diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-11 13:57:44 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-11 13:57:44 +0200 |
| commit | 7adbf5163f0193d80bb5578dc875eba811f864bb (patch) | |
| tree | 95b6fc62835536a41f1fc47e646e904b27f9a042 /packages/meshbay-hub/tests/test_asset_versioning.py | |
| parent | 4b74ff3fc54f35fbc41d9c0f7d2b4ec89475154a (diff) | |
| download | meshbay-7adbf5163f0193d80bb5578dc875eba811f864bb.tar.gz | |
fix(hub): the asset fingerprint covers every file under static/
Everything under static/ is served at /a/<hash>/ with a year's
`immutable`, but the hash was computed from a hand-kept list of 43
top-level modules. The ten catalogues and vendor/ were not on it, nor
was anything the guarding test could see: it globbed *.js at the top
level only. A change confined to the catalogues therefore kept the hash,
and a phone went on showing a heading that had been rewritten and
deployed - pull-to-refresh fetched the no-store shell, which was
current, and never refetched en.js at a URL that had not moved.
The fingerprint now hashes every file under static/, path and content,
so a change, a rename or a new file moves the version with nothing to
register. _ASSETS is gone, and CLAUDE.md, MESHBAY_DESIGN.md 9.4 step 6,
assets/brand/README.md and docs/playlists.md no longer ask for it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D9MCBBWSm9GhBESmqzJxNy
Diffstat (limited to 'packages/meshbay-hub/tests/test_asset_versioning.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_asset_versioning.py | 57 |
1 files changed, 36 insertions, 21 deletions
diff --git a/packages/meshbay-hub/tests/test_asset_versioning.py b/packages/meshbay-hub/tests/test_asset_versioning.py index 3d84d99..0c93f5a 100644 --- a/packages/meshbay-hub/tests/test_asset_versioning.py +++ b/packages/meshbay-hub/tests/test_asset_versioning.py @@ -23,7 +23,7 @@ import re import pytest from fastapi.testclient import TestClient -from meshbay_hub.api.webapp import _ASSETS, ASSET_V, STATIC_DIR, _asset_version +from meshbay_hub.api.webapp import ASSET_V, STATIC_DIR, _asset_version from meshbay_hub.app import create_app @@ -103,26 +103,41 @@ def test_the_fingerprint_follows_the_content(tmp_path, monkeypatch): assert _asset_version() == before, "the fingerprint is not reproducible" -def test_every_static_script_participates_in_the_fingerprint(): +@pytest.mark.parametrize("relative", ["locales/en.js", "vendor/htm-preact.js", "style.css"]) +def test_a_change_anywhere_under_the_prefix_moves_the_fingerprint(relative): """ - `_ASSETS` is hand-maintained, and forgetting an entry fails silently: the - file is imported by the page, so the browser fetches it, but it does not - feed the content hash — so a change confined to that one file ships at the - URL a cache already holds. Nothing errors, and the symptom is a fix that - "doesn't work" on exactly the machines that visited before. + Everything under the static directory is served at `/a/<hash>/` and cached + as immutable, so everything has to feed the hash — subdirectories included. - Found by `source-merge.js`, added to the Search view and left out of the - list. It happened to be harmless that day because `search-page.js` changed - in the same commit and *is* listed — which is the worst way for this to go - unnoticed. The checklist in docs/apps.md §4 step 5 names this trap; this - enforces it instead of relying on remembering. - - `sw.js` is the one deliberate exclusion — the service worker is served - unversioned on purpose (`test_the_service_worker_is_not_versioned`). + The fingerprint used to cover a hand-kept list of top-level modules, and + the check that guarded the list globbed `*.js` at the top level only. The + catalogues and `vendor/` were on neither: a change confined to the English + catalogue shipped at the URL a phone already held, and it went on showing a + heading that had been rewritten and deployed. Pull-to-refresh fetches the + shell, which is `no-store` and was current; it does not refetch an + immutable file whose URL has not moved. """ - on_disk = {p.name for p in STATIC_DIR.glob("*.js")} - {"sw.js"} - missing = sorted(on_disk - set(_ASSETS)) - assert not missing, ( - f"static scripts missing from webapp._ASSETS: {missing}. A change to " - "one of these will not move the asset URL, so a browser that cached " - "the page keeps running the old copy") + before = _asset_version() + target = STATIC_DIR / relative + original = target.read_bytes() + try: + target.write_bytes(original + b"\n/* touched */\n") + assert _asset_version() != before, ( + f"the fingerprint did not move when {relative} did, so a browser " + "that cached it keeps the old copy for a year") + finally: + target.write_bytes(original) + assert _asset_version() == before + + +def test_a_new_file_moves_the_fingerprint(): + """A rename or an addition changes what is served even when no existing + file's bytes do.""" + before = _asset_version() + extra = STATIC_DIR / "locales" / "zz-test-only.js" + try: + extra.write_text("export default {};\n") + assert _asset_version() != before + finally: + extra.unlink() + assert _asset_version() == before |