iter 0001.tidy: registry.toml, cache-before-fetch, escape registry slugs
Resolves the actionable drift the cycle-close architect review found: - Ship registry.toml (the spec's acceptance artifact) for aura + data-server, so `docsite serve`/`check` run out of the box — the headline live-render was not runnable as shipped without it. - Cache before fetch: the page handler now resolves the branch SHA, serves from cache on a hit, and only fetches the source on a miss (the spec data-flow). The pre-tidy handler always fetched, paying a wasted Gitea read on every cache hit. A new e2e test (a counting fetcher) pins that a second request serves from cache without re-fetching. - Escape the registry slug/page in nav_html's href and the index links — operator-trusted data, but consistent with the documented no-injection invariant and the rest of the shell (which already escaped). cargo test green (41: 32 lib + 8 e2e + 1 gated live smoke); clippy clean. refs #1
This commit is contained in:
@@ -191,3 +191,49 @@ docs="docs"
|
||||
"index must list exactly the registry's projects, no more: {body}"
|
||||
);
|
||||
}
|
||||
|
||||
/// A Fetcher that counts `fetch_raw` calls, wrapping a MockFetcher. Lets the
|
||||
/// E2E layer prove the cache-before-fetch data-flow.
|
||||
struct CountingFetcher {
|
||||
inner: MockFetcher,
|
||||
fetches: Arc<std::sync::atomic::AtomicUsize>,
|
||||
}
|
||||
|
||||
impl Fetcher for CountingFetcher {
|
||||
fn branch_sha(&self, repo: &str, branch: &str) -> Result<String, GiteaError> {
|
||||
self.inner.branch_sha(repo, branch)
|
||||
}
|
||||
fn fetch_raw(&self, repo: &str, branch: &str, path: &str) -> Result<String, GiteaError> {
|
||||
self.fetches.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
self.inner.fetch_raw(repo, branch, path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Single source of truth + the revision-keyed cache (invariant 2) at the HTTP
|
||||
/// layer: two requests for the same page at an unchanged branch SHA must hit the
|
||||
/// source exactly ONCE — the second serves from cache without re-fetching. The
|
||||
/// pre-audit handler fetched before checking the cache, so a cache hit still
|
||||
/// paid the Gitea fetch; this pins the corrected hit -> serve, miss -> fetch flow.
|
||||
#[tokio::test]
|
||||
async fn second_request_serves_from_cache_without_refetching() {
|
||||
let fetches = Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||
let fetcher = CountingFetcher {
|
||||
inner: MockFetcher::new("a517899")
|
||||
.with("Brummel/aura", "docs/design/INDEX.md", "# Ledger\n"),
|
||||
fetches: fetches.clone(),
|
||||
};
|
||||
let app = router(AppState {
|
||||
reg: Arc::new(reg()),
|
||||
fetcher: Arc::new(fetcher),
|
||||
cache: Arc::new(Cache::new()),
|
||||
});
|
||||
let (s1, _) = get(app.clone(), "/aura/design/INDEX").await;
|
||||
let (s2, _) = get(app, "/aura/design/INDEX").await;
|
||||
assert_eq!(s1, StatusCode::OK);
|
||||
assert_eq!(s2, StatusCode::OK);
|
||||
assert_eq!(
|
||||
fetches.load(std::sync::atomic::Ordering::SeqCst),
|
||||
1,
|
||||
"the second request must serve from cache, not re-fetch the source",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user