nordisk/gunnar-ui
uitest/src/lfsfileview.rs
//! ★ **A file kept in Git LFS is shown as the file, in a real browser** (owner
//! 2026-10-15: on gunnar.rs, Code → mikronova/modgunn `docs/idiot_guide.pdf`
//! read "131 B · Plain Text · version https://git-lfs.github.com/spec/v1 …") —
//! the BUILT console (`trunk build`'s `dist`) behind a loopback stub that
//! answers the BFF's JSON the way the server now does for an LFS pointer,
//! driven by headless Chrome over the DevTools Protocol.
//!
//! * A PDF: its real size, "PDF", "stored in Git LFS"; Open and Download point
//! at the raw route; the pointer text is not on the page except inside the
//! closed "LFS pointer" detail.
//! * A picture: drawn inline from the raw route, and actually decoded.
//! * Blame of an LFS file says it is stored in LFS and draws no pointer lines.
//!
//! Needs `GUNNAR_UI_CHROME` (a Chrome binary) and `GUNNAR_UI_DIST` (the built
//! console's `dist`); without both each test says NOT RUN and returns.
#![cfg(test)]
use std::io::{BufRead, BufReader, Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::cdp::{Cdp, Chrome};
const STATUS: &str = r#"{"authenticated":true,"user":"rickard","role":"admin","passkey_enabled":true,"open":false,"account":"rickard","account_role":"admin"}"#;
const HEALTH: &str = r#"{"serving":true,"agent":"gunnar","version":"1.5.0","build_ref":"v1.4.0","uptime_seconds":60,"grpc_authentication_enabled":true,"grpc_bind":"127.0.0.1:1","ssh_bind":"127.0.0.1:2"}"#;
const REPO: &str = r#"{"path":"acme/app","namespace":"acme","name":"app","hash_kind":"sha256","default_branch":"refs/heads/main","visibility":"public","created_at_unix_ms":1760000000000,"description":"","encrypted":false}"#;
const REFS: &str = r#"{"items":[{"ref_name":"refs/heads/main","oid":"2222222222222222222222222222222222222222","symbolic_target":null,"peeled_oid":null}],"total":1,"truncated":false}"#;
const POINTER: &str = "version https://git-lfs.github.com/spec/v1\noid sha256:fdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfd\nsize 189950\n";
/// A real 1×1 PNG, so "decoded" is a fact about pixels and not a status code.
const PNG_1PX: &[u8] = &[
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4,
0x89, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0xf8, 0xcf, 0xc0, 0xf0,
0x1f, 0x00, 0x05, 0x00, 0x01, 0xff, 0x89, 0x99, 0x3d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
];
/// The BFF's answer for an LFS pointer, as `api::browse::read_lfs_blob` writes it.
fn lfs_blob(path: &str, size: u64, shown: &str) -> String {
let pointer = POINTER.replace('\n', "\\n");
format!(
r#"{{"kind":"blob","revision":{{"commit_oid":"2222222222222222222222222222222222222222","tree_oid":"t","subject":"docs","author":"rickard","at_unix_seconds":1760000000}},"path":"{path}","crumbs":[{{"name":"{path}","path":"{path}"}}],"oid":"cc00000000000000000000000000000000000033","size":{size},"truncated":false,"too_large":false,"binary":true,"syntax":null,"lines":[],"sealed":false,"lfs":{{"sha256":"fdfd","shown":"{shown}","pointer":"{pointer}"}}}}"#
)
}
fn query_path(target: &str) -> String {
target
.split_once('?')
.map(|(_, q)| q)
.unwrap_or("")
.split('&')
.find_map(|kv| kv.strip_prefix("path="))
.unwrap_or("")
.replace("%2F", "/")
}
fn mime(path: &Path) -> &'static str {
match path.extension().and_then(|e| e.to_str()) {
Some("html") => "text/html; charset=utf-8",
Some("js") => "text/javascript",
Some("wasm") => "application/wasm",
Some("css") => "text/css",
Some("svg") => "image/svg+xml",
_ => "application/octet-stream",
}
}
fn answer(mut conn: TcpStream, dist: &Path) {
let _ = conn.set_read_timeout(Some(Duration::from_secs(5)));
let mut r = BufReader::new(conn.try_clone().unwrap());
let mut line = String::new();
if r.read_line(&mut line).is_err() {
return;
}
let mut parts = line.split_whitespace();
let (method, target) = (
parts.next().unwrap_or("").to_owned(),
parts.next().unwrap_or("/").to_owned(),
);
let mut len = 0usize;
loop {
let mut h = String::new();
if r.read_line(&mut h).is_err() || h.trim().is_empty() {
break;
}
if let Some(v) = h.to_ascii_lowercase().strip_prefix("content-length:") {
len = v.trim().parse().unwrap_or(0);
}
}
let mut body = vec![0u8; len];
let _ = r.read_exact(&mut body);
let path = target.split('?').next().unwrap_or("/").to_owned();
let file = query_path(&target);
let json = |s: &str| (200u16, "application/json", s.as_bytes().to_vec());
let (code, kind, out): (u16, &str, Vec<u8>) = match (method.as_str(), path.as_str()) {
("GET", "/auth/status") => json(STATUS),
("GET", "/api/health") => json(HEALTH),
("GET", "/api/repos/acme/app") => json(REPO),
("GET", "/api/repos/acme/app/refs") => json(REFS),
("GET", "/api/repos/acme/app/browse") if file.ends_with(".png") => {
json(&lfs_blob(&file, 70, "image"))
}
("GET", "/api/repos/acme/app/browse") => json(&lfs_blob(&file, 189_950, "pdf")),
("GET", "/api/repos/acme/app/raw") if file.ends_with(".png") => {
(200, "image/png", PNG_1PX.to_vec())
}
("GET", "/api/repos/acme/app/raw") => {
(200, "application/pdf", b"%PDF-1.7\n%%EOF\n".to_vec())
}
("GET", "/api/repos/acme/app/blame") => json(&format!(
r#"{{"rev":"","path":"{file}","lines":[],"commits":["{}"],"verified":0,"truncated":false,"lfs":true}}"#,
"a".repeat(40)
)),
("GET", p) if p.starts_with("/api/") || p.starts_with("/auth/") => (
404,
"application/json",
br#"{"code":"notfound","message":"not in this stub"}"#.to_vec(),
),
("GET", p) => {
let f = dist.join(p.trim_start_matches('/'));
let f = if p != "/" && f.is_file() {
f
} else {
dist.join("index.html")
};
match std::fs::read(&f) {
Ok(bytes) => (200, mime(&f), bytes),
Err(_) => (404, "text/plain", b"no".to_vec()),
}
}
_ => (404, "text/plain", b"no".to_vec()),
};
let head = format!("HTTP/1.1 {code} X\r\ncontent-type: {kind}\r\ncontent-length: {}\r\nconnection: close\r\n\r\n", out.len());
let _ = conn.write_all(head.as_bytes());
let _ = conn.write_all(&out);
}
fn serve(dist: PathBuf) -> u16 {
let l = TcpListener::bind("127.0.0.1:0").expect("a loopback port");
let port = l.local_addr().unwrap().port();
std::thread::spawn(move || {
for conn in l.incoming().flatten() {
let dist = dist.clone();
std::thread::spawn(move || answer(conn, &dist));
}
});
port
}
/// The browser on `page` of acme/app once `ready` holds, or NOT RUN.
fn rig(tag: &str, page: &str, ready: &str) -> Option<(Chrome, Cdp, String)> {
let (Some(bin), Some(dist)) = (
std::env::var("GUNNAR_UI_CHROME")
.ok()
.filter(|v| !v.is_empty()),
std::env::var("GUNNAR_UI_DIST")
.ok()
.filter(|v| !v.is_empty()),
) else {
eprintln!("NOT RUN: set GUNNAR_UI_CHROME (a Chrome binary) and GUNNAR_UI_DIST (the console's built dist)");
return None;
};
assert!(
Path::new(&dist).join("index.html").is_file(),
"{dist} holds no built console"
);
let port = serve(PathBuf::from(dist));
let debug = TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap()
.port();
let profile = std::env::temp_dir().join(format!("lfsfileview-{tag}-{}", std::process::id()));
let chrome = Chrome::launch(&bin, &profile, debug).expect("chrome starts");
let mut cdp = Cdp::attach(debug).expect("cdp");
let sess = cdp.page().expect("a page");
cdp.navigate(&sess, &format!("http://127.0.0.1:{port}{page}"))
.expect("the console loads");
if let Err(e) = cdp.wait_for(&sess, ready, 30_000) {
let text = cdp
.eval_str(&sess, "document.body.innerText")
.unwrap_or_default();
panic!(
"{page} never drew what was waited for ({e}): {}",
crate::http::cut(&text, 800)
);
}
Some((chrome, cdp, sess))
}
/// The page's visible text, the closed pointer detail left out.
fn visible(c: &mut Cdp, sess: &str) -> String {
c.eval_str(
sess,
"(() => { const d = document.querySelector('details.lfs-pointer'); const open = d && d.open; \
const body = document.body.innerText; return open ? '' : body; })()",
)
.unwrap()
}
#[test]
fn an_lfs_pdf_is_shown_as_the_pdf_and_never_as_its_pointer() {
let Some((_chrome, mut c, sess)) = rig(
"pdf",
"/repo/acme/app/files?path=docs/idiot_guide.pdf",
"!!document.querySelector('.lfs-head')",
) else {
return;
};
let head = c
.eval_str(&sess, "document.querySelector('.lfs-head').innerText")
.unwrap();
assert!(head.contains("185.5 KiB"), "the real size: {head}");
assert!(head.contains("PDF"), "{head}");
assert!(head.contains("stored in Git LFS"), "{head}");
assert!(
!head.contains("131 B") && !head.contains("Plain Text"),
"{head}"
);
let open = c
.eval_str(
&sess,
"document.querySelector('a.lfs-open').getAttribute('href')",
)
.unwrap();
assert_eq!(open, "/api/repos/acme/app/raw?path=docs/idiot_guide.pdf");
let download = c
.eval_str(
&sess,
"document.querySelector('a.lfs-download').getAttribute('href')",
)
.unwrap();
assert_eq!(download, open);
let text = visible(&mut c, &sess);
assert!(
!text.contains("version https://git-lfs"),
"the pointer is shown as the file: {}",
crate::http::cut(&text, 600)
);
assert!(
text.contains("LFS pointer"),
"the pointer detail is offered"
);
}
#[test]
fn an_lfs_picture_is_drawn_inline_from_the_raw_route() {
let Some((_chrome, mut c, sess)) = rig(
"png",
"/repo/acme/app/files?path=assets/pic.png",
"(() => { const i = document.querySelector('img.lfs-image'); return !!i && i.complete && i.naturalWidth > 0; })()",
) else {
return;
};
let src = c
.eval_str(
&sess,
"document.querySelector('img.lfs-image').getAttribute('src')",
)
.unwrap();
assert_eq!(src, "/api/repos/acme/app/raw?path=assets/pic.png");
let head = c
.eval_str(&sess, "document.querySelector('.lfs-head').innerText")
.unwrap();
assert!(
head.contains("PNG image") && head.contains("stored in Git LFS"),
"{head}"
);
assert!(!visible(&mut c, &sess).contains("version https://git-lfs"));
}
#[test]
fn blame_of_an_lfs_file_says_it_is_in_lfs_and_blames_no_pointer_lines() {
let Some((_chrome, mut c, sess)) = rig(
"blame",
"/repo/acme/app/blame?path=docs/idiot_guide.pdf",
"!!document.querySelector('[data-blame-lfs]')",
) else {
return;
};
let said = c
.eval_str(
&sess,
"document.querySelector('[data-blame-lfs]').innerText",
)
.unwrap();
assert!(said.contains("stored in Git LFS"), "{said}");
let rows = c
.eval(
&sess,
"document.querySelectorAll('tr[data-blame-line]').length",
)
.unwrap()
.as_f64()
.unwrap();
assert_eq!(rows, 0.0, "pointer lines were blamed");
}