diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 1cc1ca6..003f7eb 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -77,7 +77,7 @@ jobs:
import os, re, sys
sys.path.insert(0, os.getcwd())
import html_generator
- page = html_generator.get_html_template("ci", {"items": []}, "1")
+ page = html_generator.get_html_template("ci", {"items": []}, "1", inline_assets=True)
bodies = re.findall(r"", page, re.S)
assert bodies, "no inline "
+ else:
+ css_tag = f''
+ js_tag = f''
+
# 3. Inject Data
wrapped_json = f"/*__JSON_START__*/\nlet fullManifest = {json_str};\n/*__JSON_END__*/"
- # 4. Final Replacements
- final_html = html_template \
- .replace("__TITLE__", str(title)) \
- .replace("__NODE_ID__", str(node_id)) \
- .replace("__STANDALONE_FLAG__", "true" if standalone else "false") \
- .replace("let fullManifest = __JSON_DATA__;", wrapped_json) \
- .replace("__CSS_CONTENT__", css_content) \
- .replace("__JS_CONTENT__", js_content)
+ # 4. Final Replacements (asset tags LAST so resource text is never placeholder-substituted)
+ final_html = html_template .replace("__TITLE__", str(title)) .replace("__NODE_ID__", str(node_id)) .replace("__STANDALONE_FLAG__", "true" if standalone else "false") .replace("let fullManifest = __JSON_DATA__;", wrapped_json) .replace("__CSS_TAG__", css_tag) .replace("__JS_TAG__", js_tag)
- return final_html
\ No newline at end of file
+ return final_html
diff --git a/resources/template.html b/resources/template.html
index 8afff8e..d8ffac8 100644
--- a/resources/template.html
+++ b/resources/template.html
@@ -4,10 +4,7 @@
USCG - __TITLE__
-
+ __CSS_TAG__
@@ -930,16 +927,13 @@
+ __JS_TAG__
" in page
+
+
+def test_shell_references_bundles_with_hash():
+ page = _shell(standalone=True)
+ jh = hg.get_dashboard_bundle("js").hash
+ ch = hg.get_dashboard_bundle("css").hash
+ tag = f''
+ assert page.count(tag) == 1
+ assert f'' in page
+ assert "/* --- logic_" not in page
+ size = len(page.encode("utf-8"))
+ print(f"measured shell size: {size} bytes")
+ assert size < 100 * 1024
+ assert page.index("const TARGET_NODE_ID") < page.index(tag) < page.index('id="upscale-modal-overlay"')
+ for tok in TOKENS:
+ assert tok not in page
+
+
+def test_inline_manifest_implies_inline_assets_and_overrides():
+ m = {"items": []}
+ implied = hg.get_html_template("t", m, "1", inline_manifest=True)
+ assert "/* INJECTED LOGIC */" in implied and "dashboard_bundle_js" not in implied
+ assert "/* INJECTED LOGIC */" in hg.get_html_template("t", m, "1", inline_assets=True)
+ off = hg.get_html_template("t", m, "1", inline_manifest=True, inline_assets=False)
+ assert "/* INJECTED LOGIC */" not in off and "dashboard_bundle_js" in off
+
+
+def test_env_forces_inline(monkeypatch):
+ monkeypatch.setenv("USCG_INLINE_DASHBOARD_ASSETS", "1")
+ page = _shell()
+ assert "/* INJECTED LOGIC */" in page and "dashboard_bundle_js" not in page
+
+
+def test_hash_follows_resource_changes(tmp_path, monkeypatch):
+ dst = tmp_path / "resources"
+ shutil.copytree(hg.RESOURCE_DIR, dst)
+ monkeypatch.setattr(hg, "RESOURCE_DIR", str(dst))
+ hg._bundle_cache.clear()
+ try:
+ b1 = hg.get_dashboard_bundle("js")
+ assert hg.get_dashboard_bundle("js") is b1 # unchanged signature: cached object
+ url1 = hg.bundle_url("js")
+ init = dst / "logic_init.js"
+ st = init.stat()
+ os.utime(init, ns=(st.st_atime_ns, st.st_mtime_ns + 5_000_000_000)) # mtime only
+ b2 = hg.get_dashboard_bundle("js")
+ assert b2 is not b1 and b2.hash == b1.hash
+ with open(init, "ab") as f:
+ f.write(b"\n// hot reload edit\n")
+ b3 = hg.get_dashboard_bundle("js")
+ assert b3.hash != b1.hash
+ assert hg.bundle_url("js") != url1 and b3.hash in hg.bundle_url("js")
+ finally:
+ hg._bundle_cache.clear()
+
+
+def test_gzip_round_trip_and_deterministic():
+ for kind in ("js", "css"):
+ b = hg.get_dashboard_bundle(kind)
+ assert gzip.decompress(b.gzip) == b.body
+ assert b.gzip == gzip.compress(b.body, 9, mtime=0)
+ assert b.body == b.text.encode("utf-8")
+ assert len(b.gzip) < len(b.body)
+
+
+def test_unknown_kind_raises():
+ with pytest.raises(KeyError):
+ hg.get_dashboard_bundle("../x")
+
+
+def test_bundle_response_headers():
+ b = hg.get_dashboard_bundle("js")
+ st, body, h = run_api.bundle_response(b, b.hash, None, "gzip, br")
+ assert st == 200 and body == b.gzip
+ assert h["Content-Encoding"] == "gzip"
+ assert h["Cache-Control"] == "public, max-age=31536000, immutable"
+ assert h["ETag"] == f'W/"{b.hash}"'
+ assert h["Vary"] == "Accept-Encoding" and h["X-Content-Type-Options"] == "nosniff"
+ st, body, h = run_api.bundle_response(b, b.hash, None, "")
+ assert st == 200 and body == b.body and "Content-Encoding" not in h
+ assert h["Vary"] == "Accept-Encoding" and h["X-Content-Type-Options"] == "nosniff"
+ for v in ("bogus", "", None):
+ _, _, h = run_api.bundle_response(b, v, None, "gzip")
+ assert h["Cache-Control"] == "no-cache"
+ for inm in (f'W/"{b.hash}"', f'"{b.hash}"', f'"zzz", W/"{b.hash}"'):
+ st, body, h = run_api.bundle_response(b, b.hash, inm, "gzip")
+ assert st == 304 and body is None and h["ETag"] == f'W/"{b.hash}"'
+ st, _, _ = run_api.bundle_response(b, b.hash, 'W/"other"', "gzip")
+ assert st == 200
+
+
+@pytest.mark.parametrize("v", ["../../etc/passwd", "logic_ui.js", "..\\..\\windows\\win.ini"])
+def test_v_is_never_a_path(v):
+ b = hg.get_dashboard_bundle("js")
+ st, body, h = run_api.bundle_response(b, v, None, "")
+ assert st == 200 and body == b.body and h["Cache-Control"] == "no-cache"
+
+
+def test_routes_are_fixed_paths():
+ with open(os.path.join(_ROOT, "__init__.py"), encoding="utf-8") as f:
+ src = f.read()
+ assert '"/config_tester/dashboard_bundle_js"' in src
+ assert '"/config_tester/dashboard_bundle_css"' in src
+ for path in re.findall(r'routes\.(?:get|post|put|delete)\(\s*"([^"]+)"', src):
+ assert "{" not in path, path
+ m = re.search(r"def _bundle_http\(.*?(?=\n@server)", src, re.S)
+ assert m
+ handlers = [m.group(0)]
+ for fn in ("dashboard_bundle_js", "dashboard_bundle_css"):
+ handlers.append(re.search(r"async def %s\(.*?(?=\n\n\n|\Z)" % fn, src, re.S).group(0))
+ for body in handlers:
+ for banned in ("match_info", "open(", "FileResponse", "enable_compression"):
+ assert banned not in body, banned
+
+
+@pytest.mark.skipif(NODE is None, reason="node not installed")
+def test_bundle_parses_and_composes_with_globals():
+ page = _shell(standalone=True)
+ globals_js = re.search(r"", page).group(1)
+ js = hg.get_dashboard_bundle("js").text
+ with tempfile.TemporaryDirectory() as d:
+ for name, code in (("bundle.js", js), ("all.js", globals_js + "\n" + js)):
+ path = os.path.join(d, name)
+ with open(path, "w", encoding="utf-8") as f:
+ f.write(code)
+ out = subprocess.run([NODE, "--check", path], capture_output=True, timeout=60)
+ assert out.returncode == 0, out.stderr.decode()
diff --git a/tests/test_docs_consistency.py b/tests/test_docs_consistency.py
index e1230a3..dd82fda 100644
--- a/tests/test_docs_consistency.py
+++ b/tests/test_docs_consistency.py
@@ -165,7 +165,7 @@ def test_events_documented():
def test_js_files_order():
src = _read("html_generator.py")
- m = re.search(r"js_files\s*=\s*\[(.*?)\n\s*\]", src, re.S)
+ m = re.search(r"(?:JS_FILES|js_files)\s*=\s*[\[(](.*?)\n\s*[\])]", src, re.S)
assert m
files = re.findall(r'^\s*"([\w\-]+\.js)"', m.group(1), re.M)
assert files and files[-1] == "logic_standalone.js"
diff --git a/tests/test_standalone_html.py b/tests/test_standalone_html.py
index a3c7e8d..f66b923 100644
--- a/tests/test_standalone_html.py
+++ b/tests/test_standalone_html.py
@@ -75,9 +75,24 @@ def test_escaped_manifest_still_parses_with_dashboard_js_extraction():
@pytest.mark.parametrize("standalone", [True, False])
def test_concatenated_script_parses_as_one_script(standalone):
page = _page(standalone=standalone)
- scripts = re.findall(r"", page)
+ scripts = re.findall(r"", page)
assert scripts, "no inline script found"
- js = max(scripts, key=len)
+ js = "\n;\n".join(scripts)
+ with tempfile.TemporaryDirectory() as d:
+ path = os.path.join(d, "page.js")
+ with open(path, "w", encoding="utf-8") as f:
+ f.write(js)
+ out = subprocess.run([NODE, "--check", path], capture_output=True, timeout=60)
+ assert out.returncode == 0, out.stderr.decode()
+
+
+@pytest.mark.skipif(NODE is None, reason="node not installed")
+def test_shell_script_parses_as_one_script():
+ """The shell's globals script + the bundle route's JS share one global lexical scope."""
+ page = _shell(standalone=True)
+ scripts = re.findall(r"", page)
+ assert scripts
+ js = "\n;\n".join(scripts) + "\n;\n" + html_generator.get_dashboard_bundle("js").text
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "page.js")
with open(path, "w", encoding="utf-8") as f:
@@ -106,7 +121,7 @@ def test_shell_inlines_no_item_data():
def test_shell_size_does_not_grow_with_the_session():
big = [{"id": i, "note": "x" * 2500, "positive": "p" * 300} for i in range(1000)]
- assert len(_shell(big).encode("utf-8")) < 560 * 1024
+ assert len(_shell(big).encode("utf-8")) < 100 * 1024
assert len(_shell(big)) == len(_shell([]))
@@ -115,7 +130,8 @@ def test_shell_placeholders_all_replaced():
assert 'const TARGET_NODE_ID = "42";' in page
assert "const USCG_STANDALONE = true" in page
assert 'id="session-input" class="session-name-input" type="text" value="my-sess"' in page
- for token in ("__TITLE__", "__NODE_ID__", "__STANDALONE_FLAG__", "__CSS_CONTENT__", "__JS_CONTENT__"):
+ for token in ("__TITLE__", "__NODE_ID__", "__STANDALONE_FLAG__", "__CSS_CONTENT__", "__JS_CONTENT__",
+ "__CSS_TAG__", "__JS_TAG__"):
assert token not in page
@@ -133,18 +149,6 @@ def test_shell_parses_with_dashboard_js_extraction_as_lazy():
assert out.stdout.decode() == '[true,0,"abc"]'
-@pytest.mark.skipif(NODE is None, reason="node not installed")
-def test_shell_script_parses_as_one_script():
- scripts = re.findall(r"", _shell(standalone=True))
- js = max(scripts, key=len)
- with tempfile.TemporaryDirectory() as d:
- path = os.path.join(d, "page.js")
- with open(path, "w", encoding="utf-8") as f:
- f.write(js)
- out = subprocess.run([NODE, "--check", path], capture_output=True, timeout=60)
- assert out.returncode == 0, out.stderr.decode()
-
-
def test_inline_manifest_flag_keeps_the_old_behaviour():
page = _page()
assert "__uscg_lazy__" not in page.split("/*__JSON_START__*/")[1].split("/*__JSON_END__*/")[0]