crates/gunnar-public-site/tests/home.rs
//! **PUB-HOME: `/` is the console's own landing page** (owner 2026-10-29):
//! gunnar.rs looks exactly as it did before the public site was switched on.
//! The site draws nothing on it; it only puts its machine-only tags (meta
//! description, canonical, Open Graph, Twitter, the llms.txt link and the
//! JSON-LD) in the landing's `<head>` and gives it the site's `<title>`. A
//! signed-in person still lands on their console, and a console that hands
//! the site no landing gets no `/` from it at all.
mod common;
use common::*;
use gunnar_public_site::{Offer, OfferItem, GUNNAR};
use http::{header, StatusCode};
/// ★ The owner's words, pinned (2026-10-29).
#[test]
fn the_summary_is_the_owners_words_exactly() {
assert_eq!(GUNNAR.tagline, "a European git server in Rust");
assert_eq!(GUNNAR.description, "Gunnar is a European git server in Rust: self-hosted or SaaS, with SLSA Level 3 releases.");
let c = config();
assert_eq!(format!("{} — {}", c.site_name, c.tagline), "Gunnar — a European git server in Rust");
assert_eq!(c.description, GUNNAR.description);
}
/// The landing with the site's head tags taken back out: what is left must be
/// the landing, byte for byte, but for its `<title>`.
fn without_the_tags(body: &str) -> String {
let open = body.find("<meta name=\"description\"").expect("the tags are in");
let close = body.find("</script>\n</head>").expect("the JSON-LD ends the head") + "</script>\n".len();
let body = format!("{}{}", &body[..open], &body[close..]);
body.replace("<title>Gunnar — a European git server in Rust</title>", "<title>Gunnar — log in</title>")
}
#[tokio::test]
async fn a_visitor_gets_the_consoles_landing_with_the_search_tags_in_its_head() {
let (site, _) = site().await;
let a = get(&app(site), "/").await;
assert_eq!(a.status, StatusCode::OK, "{}", a.body);
assert!(a.headers[header::CONTENT_TYPE].to_str().unwrap().starts_with("text/html"));
let b = &a.body;
// The landing's look: its picture, its page, untouched.
assert_eq!(without_the_tags(b), LANDING, "the landing must be served as it is, only its head added to");
assert!(b.contains("url(\"/gunnar_silver.webp\")"), "the picture: {b}");
// The machine-only tags, inside the head.
let head = &b[..b.find("</head>").unwrap()];
for tag in [
"<title>Gunnar — a European git server in Rust</title>",
"<meta name=\"description\" content=\"Gunnar is a European git server in Rust: self-hosted or SaaS, with SLSA Level 3 releases.\">",
"<link rel=\"canonical\" href=\"https://gunnar.example/\">",
"<meta property=\"og:description\" content=\"Gunnar is a European git server in Rust: self-hosted or SaaS, with SLSA Level 3 releases.\">",
"<meta name=\"twitter:description\" content=\"Gunnar is a European git server in Rust: self-hosted or SaaS, with SLSA Level 3 releases.\">",
"<link rel=\"alternate\" type=\"text/plain\" title=\"llms.txt\" href=\"https://gunnar.example/llms.txt\">",
"<script type=\"application/ld+json\">",
] {
assert_eq!(head.matches(tag).count(), 1, "{tag} once, in the head: {b}");
}
assert!(b.contains("\"description\":\"Gunnar is a European git server in Rust: self-hosted or SaaS, with SLSA Level 3 releases.\""), "the JSON-LD's words: {b}");
// Nothing of the old text home is drawn.
for gone in ["What it does", "Signed pushes", "Public repositories", "Release signing", "Get started", "<ul class=\"cards"] {
assert!(!b.contains(gone), "{gone:?} is drawn on /: {b}");
}
}
#[tokio::test]
async fn without_a_landing_the_site_builds_no_home_and_the_console_answers() {
let mut c = config();
c.landing = None;
let (site, _) = site_with(Fake::new(), c).await;
let a = get(&app(site), "/").await;
assert_eq!((a.status, a.body.as_str()), (StatusCode::SEE_OTHER, TODAY));
}
#[tokio::test]
async fn a_signed_in_person_still_lands_on_their_console() {
let (site, _) = site().await;
let a = get_as(&app(site), "/", Some(SESSION), None, Some("me")).await;
assert_eq!((a.status, a.body.as_str()), (StatusCode::SEE_OTHER, TODAY));
}
#[tokio::test]
async fn no_offer_is_drawn_on_the_home() {
let mut fake = Fake::new();
fake.offer = Some(Offer { heading: "Accounts".into(), items: vec![OfferItem { title: "Small".into(), lines: vec!["10 GB".into()] }] });
let (site, _) = site_with(fake, config()).await;
let body = get(&app(site), "/").await.body;
assert!(!body.contains("Accounts") && !body.contains("Small"), "{body}");
}
#[tokio::test]
async fn the_home_names_no_private_repository() {
let (site, _) = site().await;
let body = get(&app(site), "/").await.body;
assert!(!body.contains(PRIVATE) && !body.contains(PRIVATE_TEXT), "{body}");
}
/// ★ **GUN-LANDING (owner 2026-10-30, "it stands flashing"): a page whose answer
/// depends on the session cookie is never kept by the browser.** `/` is the
/// landing for a visitor and the console for a signed-in person; served
/// `public, max-age=300`, the browser kept the landing and handed it back after
/// the sign-in, whose own "already signed in, go to /" then reloaded the same
/// kept page over and over (on gunnar.rs, the host holding the cookie, never on
/// www). Every page with a signed-in variant is `no-store` and varies on the
/// cookie; a page that is the same for everybody keeps its five minutes.
#[tokio::test]
async fn a_page_that_differs_for_a_signed_in_person_is_never_kept_by_the_browser() {
let (site, _) = site().await;
let app = app(site);
for path in ["/".to_owned()] {
let a = get(&app, &path).await;
assert_eq!(a.status, StatusCode::OK, "{path}: {}", a.body);
assert_eq!(a.headers.get(header::CACHE_CONTROL).and_then(|v| v.to_str().ok()), Some("no-store"), "{path} is kept by the browser");
assert_eq!(a.headers.get(header::VARY).and_then(|v| v.to_str().ok()), Some("Cookie"), "{path} does not vary on the cookie");
}
let a = get(&app, "/robots.txt").await;
assert_eq!(a.headers.get(header::CACHE_CONTROL).and_then(|v| v.to_str().ok()), Some("public, max-age=300"), "the same page for everybody keeps its five minutes");
}