Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar-ui

crates/gunnar-public-site/tests/head.rs

//! **S-4: the head tags on every public page**, from ONE shared helper: the
//! title, the meta description, the canonical URL, Open Graph, the Twitter card
//! and JSON-LD (Organization and SoftwareApplication on the home).

mod common;
use common::*;

use http::StatusCode;

/// The `content` of `<meta {attr}="{key}" content="…">`.
fn meta<'a>(html: &'a str, attr: &str, key: &str) -> Option<&'a str> {
    let start = format!("<meta {attr}=\"{key}\" content=\"");
    let at = html.find(&start)? + start.len();
    Some(&html[at..at + html[at..].find('"')?])
}

/// Every JSON-LD block of a page, parsed.
fn json_ld(html: &str) -> Vec<serde_json::Value> {
    let open = "<script type=\"application/ld+json\">";
    html.match_indices(open)
        .map(|(i, _)| {
            let rest = &html[i + open.len()..];
            let body = &rest[..rest.find("</script>").expect("the block is closed")];
            serde_json::from_str(body).unwrap_or_else(|e| panic!("JSON-LD does not parse ({e}): {body}"))
        })
        .collect()
}

fn types(ld: &[serde_json::Value]) -> Vec<String> {
    let mut out = Vec::new();
    for v in ld {
        let items = v.as_array().cloned().unwrap_or_else(|| vec![v.clone()]);
        for i in items {
            if let Some(t) = i.get("@type").and_then(|t| t.as_str()) {
                out.push(t.to_owned());
            }
        }
    }
    out
}

#[tokio::test]
async fn the_home_carries_every_head_tag() {
    let (site, _) = site().await;
    let a = get(&app(site), "/").await;
    assert_eq!(a.status, StatusCode::OK);
    let h = &a.body;
    assert!(h.contains("<title>Gunnar — a European git server in Rust</title>"), "{h}");
    assert_eq!(h.matches("<title>").count(), 1, "{h}");
    assert!(meta(h, "name", "description").is_some_and(|d| d == "Gunnar is a European git server in Rust: self-hosted or SaaS, with SLSA Level 3 releases."), "{h}");
    assert!(h.contains("<link rel=\"canonical\" href=\"https://gunnar.example/\">"), "{h}");
    assert_eq!(meta(h, "property", "og:type"), Some("website"));
    assert_eq!(meta(h, "property", "og:site_name"), Some("Gunnar"));
    assert_eq!(meta(h, "property", "og:url"), Some("https://gunnar.example/"));
    assert!(meta(h, "property", "og:title").is_some_and(|t| t.starts_with("Gunnar")));
    assert!(meta(h, "property", "og:description").is_some_and(|d| !d.is_empty()));
    assert_eq!(meta(h, "name", "twitter:card"), Some("summary"));
    assert!(meta(h, "name", "twitter:title").is_some() && meta(h, "name", "twitter:description").is_some());
    let ld = json_ld(h);
    let t = types(&ld);
    assert!(t.contains(&"Organization".to_owned()) && t.contains(&"SoftwareApplication".to_owned()), "{t:?}");
}

#[tokio::test]
async fn every_html_page_has_the_tags_from_the_one_helper() {
    let (site, _) = site().await;
    let built = site.built();
    let mut html = 0;
    for (path, page) in &built.pages {
        if !page.content_type.starts_with("text/html") {
            continue;
        }
        html += 1;
        let b = &page.body;
        for tag in ["<title>", "<meta name=\"description\"", "<link rel=\"canonical\" href=\"https://gunnar.example", "<meta property=\"og:title\"", "<meta name=\"twitter:card\"", "<script type=\"application/ld+json\">"] {
            assert_eq!(b.matches(tag).count(), 1, "{path}: {tag} once");
        }
        assert!(!json_ld(b).is_empty(), "{path}");
    }
    assert!(html >= 1, "no HTML page was built");
}

#[tokio::test]
async fn a_hostile_description_cannot_break_out_of_the_head() {
    let mut c = config();
    c.description = "Fast \"quoted\" & <b>bold</b> </script><script>alert(1)</script>".into();
    let (site, _) = site_with(Fake::new(), c).await;
    let h = get(&app(site), "/").await.body;
    assert_eq!(meta(&h, "name", "description"), Some("Fast &quot;quoted&quot; &amp; &lt;b&gt;bold&lt;/b&gt; &lt;/script&gt;&lt;script&gt;alert(1)&lt;/script&gt;"), "{h}");
    assert!(!h.contains("<script>alert"), "{h}");
    let ld = json_ld(&h);
    let app = ld.iter().flat_map(|v| v.as_array().cloned().unwrap_or_else(|| vec![v.clone()])).find(|i| i["@type"] == "SoftwareApplication").expect("SoftwareApplication");
    assert_eq!(app["description"].as_str(), Some("Fast \"quoted\" & <b>bold</b> </script><script>alert(1)</script>"), "JSON-LD keeps the words, escaped for the script element");
}