crates/gunnar-public-site/tests/sitemap.rs
//! **S-3: `/sitemap.xml`**: the public pages and every PUBLIC repository with
//! its `lastmod`, served without a login, built with the other pages on the
//! timer (never per request), and never naming a private repository.
mod common;
use common::*;
use http::{header, StatusCode};
#[tokio::test]
async fn a_crawler_reads_the_sitemap_without_a_login() {
let (site, calls) = site().await;
let before = calls.total();
let a = get(&app(site), "/sitemap.xml").await;
assert_eq!(a.status, StatusCode::OK, "{}", a.body);
assert!(a.headers[header::CONTENT_TYPE].to_str().unwrap().starts_with("application/xml"));
assert!(a.body.starts_with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"), "{}", a.body);
assert!(a.body.contains("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">") && a.body.trim_end().ends_with("</urlset>"));
assert!(a.body.contains("<url><loc>https://gunnar.example/</loc><lastmod>2025-10-25</lastmod></url>"), "{}", a.body);
assert!(
a.body.contains(&format!("<url><loc>https://gunnar.example/{NS}/{PUBLIC}</loc><lastmod>2025-10-25</lastmod></url>")),
"every public repository, with the day of its newest commit: {}",
a.body
);
assert!(a.body.contains(&format!("<loc>https://gunnar.example/{NS}</loc>")), "the namespace's page: {}", a.body);
assert_eq!(calls.total(), before, "the sitemap was built on request");
}
#[tokio::test]
async fn the_sitemap_names_no_private_repository() {
let (site, _) = site().await;
let body = get(&app(site), "/sitemap.xml").await.body;
assert!(!body.contains(PRIVATE) && !body.contains(PRIVATE_TEXT), "{body}");
}
#[tokio::test]
async fn the_sign_up_page_is_listed_only_where_people_sign_up() {
let (site, _) = site().await;
assert!(!get(&app(site), "/sitemap.xml").await.body.contains("/signup"));
let mut c = config();
c.signup = true;
let (site, _) = site_with(Fake::new(), c).await;
assert!(get(&app(site), "/sitemap.xml").await.body.contains("<loc>https://gunnar.example/signup</loc>"));
}
#[tokio::test]
async fn the_sitemap_lists_no_console_or_login_path() {
let (site, _) = site().await;
let body = get(&app(site), "/sitemap.xml").await.body;
for loc in body.split("<loc>").skip(1).map(|l| &l[..l.find("</loc>").unwrap()]) {
let path = loc.strip_prefix("https://gunnar.example").expect("every URL is this site's");
assert!(
!gunnar_public_site::robots::DISALLOWED.iter().any(|d| path.starts_with(d)),
"the sitemap lists {path}, which robots.txt disallows"
);
}
}
#[tokio::test]
async fn a_signed_in_person_reads_the_same_sitemap() {
let (site, _) = site().await;
let a = get_as(&app(site), "/sitemap.xml", Some(SESSION), None, Some("me")).await;
assert_eq!(a.status, StatusCode::OK);
assert!(a.body.contains("<urlset"));
}
/// The owner (2026-10-25): each lastmod is real, the time of that public
/// repository's own newest commit; the home's is the newest of them all.
#[tokio::test]
async fn each_lastmod_is_its_repositorys_newest_commit() {
let mut fake = Fake::new();
fake.extra.push(gunnar_public_site::RepoRef { namespace: "other".into(), name: "older".into(), description: String::new(), public: true });
fake.at.push(("older".into(), 1_700_000_000));
let (site, _) = site_with(fake, config()).await;
let body = get(&app(site), "/sitemap.xml").await.body;
assert!(body.contains("<url><loc>https://gunnar.example/other/older</loc><lastmod>2023-11-14</lastmod></url>"), "{body}");
assert!(body.contains("<url><loc>https://gunnar.example/other</loc><lastmod>2023-11-14</lastmod></url>"), "{body}");
assert!(body.contains(&format!("<url><loc>https://gunnar.example/{NS}/{PUBLIC}</loc><lastmod>2025-10-25</lastmod></url>")), "{body}");
assert!(body.contains("<url><loc>https://gunnar.example/</loc><lastmod>2025-10-25</lastmod></url>"), "{body}");
}