From b01042391f8bc56952b5077b3782699a0536fac5 Mon Sep 17 00:00:00 2001 From: Robbie Court Date: Tue, 22 Sep 2026 11:56:43 +0000 Subject: [PATCH] Add get_preview and get_preview_image for link unfurling Slack, Bluesky, Teams and the rest build a link card from the og:* tags in a page's raw HTML, and none of them run JavaScript. The viewer sets those tags per term from JS only, so a pasted term link previews as the site logo whatever the term. get_preview?id= renders the same tags server-side from get_term_info -- title, description, thumbnail, canonical to /reports/ -- with a meta-refresh into the viewer so a human landing here still arrives where the link pointed. Intended for an IRI-routing branch that hands unfurler user-agents this instead of the redirect. get_preview_image?id= redirects to the term's thumbnail, or the logo when it has none: a stable id-keyed image URL for og:image, cards and the MCP. Both reuse the term_info: cache key, so a preview costs no extra term lookup and a burst of unfurlers for one link coalesces to one query; the HTML is a 2KB response v3-cached stores like any other. Title and description logic mirrors pageMetadata.js in geppetto-vfb so a term reads the same wherever its preview is built -- except that ';' in Types becomes ', ' rather than ',', which reads as a list; the JS wants the same fix on its next release. Ontology text is HTML-escaped, and ids are validated against the VFB id shape before any lookup. --- src/test/test_link_preview.py | 152 ++++++++++++++++++++++++++++++++++ src/vfbquery/ha_api.py | 57 +++++++++++++ src/vfbquery/link_preview.py | 148 +++++++++++++++++++++++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 src/test/test_link_preview.py create mode 100644 src/vfbquery/link_preview.py diff --git a/src/test/test_link_preview.py b/src/test/test_link_preview.py new file mode 100644 index 0000000..5ba3d6b --- /dev/null +++ b/src/test/test_link_preview.py @@ -0,0 +1,152 @@ +"""Unit tests for the link-preview page (get_preview / get_preview_image).""" + +import html +import re + +import pytest + +from vfbquery import link_preview + + +def _neuron(): + return { + "Id": "VFB_jrchjrch", + "Name": "5-HTPLP01_R", + "IsIndividual": True, + "IsClass": False, + "Meta": { + "Description": "", + "Comment": "tracing status-Roughly traced, cropped-False", + "Types": "[adult glutamatergic neuron](FBbt_00058220);[adult serotonergic PLP neuron](FBbt_00110925)", + }, + "Technique": ["Hemibrain-Reconstruction"], + "Tags": ["Adult", "Nervous_system"], + "Images": { + "VFB_00101567": [ + {"id": "VFB_00101384", "thumbnail": "https://www.virtualflybrain.org/data/VFB/i/jrch/jrch/VFB_00101384/thumbnail.png"}, + {"id": "VFB_00101385", "thumbnail": "https://x/second.png"}, + ] + }, + } + + +def _class_with_example(): + return { + "Id": "FBbt_00003748", + "Name": "medulla", + "IsClass": True, + "IsIndividual": False, + "Meta": {"Description": "The second optic neuropil, [see](FBbt_00003701) *the* lobula."}, + "Images": {}, + "Examples": {"VFB_00017894": [{"id": "VFB_00030624", "thumbnail": "https://x/medulla.png"}]}, + } + + +# --- id validation ----------------------------------------------------------- + +@pytest.mark.parametrize("value", ["FBbt_00003748", "VFB_jrchjrch", "VFBexp_FBtp0000001", "GO_0001872"]) +def test_is_term_id_accepts_vfb_shapes(value): + assert link_preview.is_term_id(value) + + +@pytest.mark.parametrize("value", ["", None, "medulla", "' + info["Meta"]["Description"] = "a & b < c" + page = link_preview.render_preview_html(info) + assert "