"""The settings sidebar footer that names the running build. The admin panel had no way to answer "which build is this?" from the UI: the version and the source commit were only reachable by hand-querying ``/api/version``. A footer pinned under the sidebar nav now shows the version the build registers plus the commit its process loaded. Three things can break independently, so each is covered on its own: * ``formatSettingsBuildInfo`` decides what the two lines say, including what to do with the ``"unknown"`` the backend reports when it cannot resolve a commit. That is real logic, so it is exercised in node against the shipped module rather than asserted on as text. * the footer's *position* is the requirement — below the scrolling nav, inside the sidebar. Only the markup's structure records that. * the layouts that have no bottom-left to write in (the collapsed rail, the narrow tab rail) must hide it, and the ``[hidden]`` attribute only works here because the CSS restates it against the element's own ``display`` rule. """ import json import re import shutil import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[1] INDEX_HTML = (ROOT / "static" / "index.html").read_text(encoding="utf-8") SIDEBAR_CSS = ( ROOT / "static" / "css" / "cookbook-research-memory-settings.css" ).read_text(encoding="utf-8") requires_node = pytest.mark.skipif( not shutil.which("node"), reason="node binary not on PATH" ) def _format_build_info(payload): """Run the shipped formatter over one payload and return its result.""" source = ( "import { formatSettingsBuildInfo } from " "'./static/js/settings/sidebar.js';\n" f"console.log(JSON.stringify(formatSettingsBuildInfo({json.dumps(payload)})));\n" ) result = subprocess.run( ["node", "--input-type=module", "-e", source], cwd=ROOT, check=True, capture_output=True, text=True, ) return json.loads(result.stdout) # --------------------------------------------------------------------------- # What the footer says # --------------------------------------------------------------------------- @requires_node def test_shows_registered_version_and_short_commit(): info = _format_build_info( { "version": "1.0.3", "build": "0.20.19", "source_commit": "8a28c5f9c0ffee1234567890abcdef1234567890", } ) assert info["versionLabel"] == "v1.0.3 · build 0.20.19" assert info["commitLabel"] == "8a28c5f9" # The short hash is what fits the sidebar; the full one stays on hover. assert info["commitTitle"] == "8a28c5f9c0ffee1234567890abcdef1234567890" @requires_node def test_build_equal_to_version_is_not_printed_twice(): info = _format_build_info( {"version": "1.0.3", "build": "1.0.3", "source_commit": "abcdef1234"} ) assert info["versionLabel"] == "v1.0.3" @requires_node def test_unresolvable_commit_leaves_only_the_version(): """`/api/version` reports the string "unknown" when `git rev-parse` fails — a read-only Docker tree with no `.git`. Printing it would be worse than printing nothing.""" info = _format_build_info( {"version": "1.0.3", "build": "unknown", "source_commit": "unknown"} ) assert info["versionLabel"] == "v1.0.3" assert info["commitLabel"] == "" assert info["commitTitle"] == "" @requires_node def test_commit_only_build_still_gets_a_footer(): info = _format_build_info({"source_commit": "0123456789abcdef"}) assert info["versionLabel"] == "" assert info["commitLabel"] == "01234567" @requires_node def test_non_sha_commit_override_is_left_intact(): """`ODYSSEUS_SOURCE_COMMIT` can be any string; truncating a tag to eight characters would corrupt it, so only real hashes get shortened.""" info = _format_build_info( {"version": "1.0.3", "source_commit": "release-2026-09-30"} ) assert info["commitLabel"] == "release-2026-09-30" @requires_node @pytest.mark.parametrize( "payload", [ {}, {"version": "", "build": "", "source_commit": ""}, {"version": "unknown", "build": "unknown", "source_commit": "unknown"}, ], ) def test_nothing_to_report_means_no_footer(payload): assert _format_build_info(payload) is None # --------------------------------------------------------------------------- # Where the footer sits # --------------------------------------------------------------------------- def _settings_sidebar_markup(): start = INDEX_HTML.index('