crates/gunnar-public-site/tests/issue_pages.rs
//! **PUB-ISSUES: a public repository's issues for AI tools and crawlers** (owner order
//! 2026-10-29, the OpenSSF badge's "public bug tracker with a searchable archive"): the whole
//! archive as Markdown, linked from llms.txt and the sitemap, built on the refresh timer
//! from what the grantless guest may read, and served from memory. The list, one issue and
//! the search are the console's own pages since GUN-GUEST (owner 2026-10-30), pre-rendered
//! for a crawler by the console (`gunnar-ui-server`).
mod common;
use common::*;
use http::{header, StatusCode};
fn issues(leaf: &str) -> String {
format!("/{NS}/{PUBLIC}/issues{leaf}")
}
#[tokio::test]
async fn the_whole_archive_is_one_markdown_file() {
let (site, _) = site().await;
let a = get(&app(site), &issues(".md")).await;
assert_eq!(a.status, StatusCode::OK, "{}", a.body);
assert!(a.headers[header::CONTENT_TYPE].to_str().unwrap().starts_with("text/markdown"));
for want in [&format!("# {NS}/{PUBLIC}: issues") as &str, &format!("## #1 {ISSUE_OPEN} (open"), &format!("## #2 {ISSUE_CLOSED} (closed"), ISSUE_COMMENT, ISSUE_REASON] {
assert!(a.body.contains(want), "{want}: {}", a.body);
}
}
#[tokio::test]
async fn llms_txt_and_the_sitemap_link_the_issues() {
let (site, _) = site().await;
let app = app(site);
let llms = get(&app, "/llms.txt").await;
assert!(llms.body.contains(&format!("issues: https://gunnar.example/{NS}/{PUBLIC}/issues.md")), "{}", llms.body);
let map = get(&app, "/sitemap.xml").await;
for want in [format!("<loc>https://gunnar.example/{NS}/{PUBLIC}/issues</loc>"), format!("<loc>https://gunnar.example/{NS}/{PUBLIC}/issues/2</loc><lastmod>2025-10-26</lastmod>")] {
assert!(map.body.contains(&want), "{want}: {}", map.body);
}
}
/// ★ **Private stays exactly as today**: a private repository has no archive (the console
/// answers as it does now), and its issues were never even asked for (the fake asserts it).
#[tokio::test]
async fn a_private_repository_has_no_archive_and_its_issues_are_never_read() {
let (site, _) = site().await;
let app = app(site.clone());
let a = get(&app, &format!("/{NS}/{PRIVATE}/issues.md")).await;
assert_eq!((a.status, a.body.as_str()), (StatusCode::SEE_OTHER, TODAY));
for (path, body) in every_page(&site) {
assert!(!body.contains(PRIVATE) && !body.contains(PRIVATE_ISSUE), "{path} names the private repository");
}
}
/// ★ **Switched off, the archive is not served.**
#[tokio::test]
async fn switched_off_the_archive_answers_as_today() {
let (site, _) = site().await;
site.switch().set(false);
let a = get(&app(site), &issues(".md")).await;
assert_eq!((a.status, a.body.as_str()), (StatusCode::SEE_OTHER, TODAY));
}
/// **No expensive work for anonymous clients**: a refresh lists each public
/// repository's issues once, and reads a thread again only where the list says
/// it moved (a new comment or a close).
#[tokio::test]
async fn a_refresh_reads_a_thread_again_only_where_it_moved() {
let fake = Fake::new();
let calls = fake.calls.clone();
let fake = std::sync::Arc::new(fake);
let site = gunnar_public_site::Site::new(config(), fake.clone(), hooks(), true);
site.refresh().await.unwrap();
let threads = |c: &Calls| c.threads.load(std::sync::atomic::Ordering::SeqCst);
let listed = |c: &Calls| c.issues.load(std::sync::atomic::Ordering::SeqCst);
assert_eq!((listed(&calls), threads(&calls)), (1, 2), "the first refresh lists once and reads both threads");
site.refresh().await.unwrap();
assert_eq!((listed(&calls), threads(&calls)), (2, 2), "nothing moved: no thread is read again");
fake.issues.lock().unwrap()[0].comments = 2;
fake.threads.lock().unwrap().get_mut(&1).unwrap().push(gunnar_public_site::IssueNote { at_unix: 1_761_400_000, body: "And on Tuesdays.".into(), closed: false });
site.refresh().await.unwrap();
assert_eq!((listed(&calls), threads(&calls)), (3, 3), "only the issue that moved is read again");
let a = get(&app(site), &issues(".md")).await;
assert!(a.body.contains("And on Tuesdays."), "{}", a.body);
}