mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
existsSync answers true for a directory, so a parent that keeps its children and loses its own index read as served: 26 model pages shipped three dead links each while the gate that forbids exactly that stayed green. serveExport one file over already requires a file.
92 lines
4.2 KiB
TypeScript
92 lines
4.2 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import { OUT, pages } from './export'
|
|
|
|
/**
|
|
* A page never sends a reader to an address this site does not serve.
|
|
*
|
|
* Measured before this gate existed: `/trust` — the page a security reviewer
|
|
* reads — offered "The Data Processing Addendum" at `/dpa` and "The
|
|
* subprocessor register" at `/subprocessors`, described them as published so
|
|
* "a reviewer reads them without asking and without waiting", and both
|
|
* addresses 404'd. The documents were real and had been committed for months;
|
|
* `content/legal/*.md` held eleven of them and no route rendered any.
|
|
*
|
|
* Nothing caught it. The build was green because a static export has no idea
|
|
* what an `href` means, and every other gate here asks about a page's own
|
|
* bytes rather than about where it points. A promise to a reviewer that ends
|
|
* in a 404 is worse than no promise, which is why this reads the WHOLE export
|
|
* rather than a list of important pages.
|
|
*
|
|
* Only same-site paths are checked. An external URL is somebody else's uptime
|
|
* and a fetch of one would make this suite depend on the network; a fragment
|
|
* or a query names the same document either way; and a path ending in a file
|
|
* extension is an asset, which `out/` serves directly.
|
|
*/
|
|
|
|
/**
|
|
* How a static host resolves a path: a FILE, at one of three names.
|
|
*
|
|
* A bare `existsSync` answers true for a DIRECTORY, and a directory is the
|
|
* commonest way an export drops a page — a parent that keeps its children and
|
|
* loses its own index. `/models/~anthropic` is exactly that: the folder holds
|
|
* the model pages, no `~anthropic.html` is written, and the host 404s while this
|
|
* gate called it served. Twenty-six pages shipped three dead links each under a
|
|
* green gate that says "a page never sends a reader to an address this site does
|
|
* not serve". `serveExport()` one file over already requires a file, and records
|
|
* the same miss for /api.
|
|
*/
|
|
const isFile = (p: string): boolean => existsSync(p) && statSync(p).isFile()
|
|
|
|
function served(url: string): boolean {
|
|
const base = join(OUT, url)
|
|
return isFile(base) || isFile(base + '.html') || isFile(join(base, 'index.html'))
|
|
}
|
|
|
|
function internalLinks(html: string): string[] {
|
|
return [...html.matchAll(/href="(\/[^"#?]*)"/g)]
|
|
.map((m) => m[1].replace(/\/+$/, ''))
|
|
.filter((u) => u !== '' && !u.startsWith('//') && !/\.[a-z0-9]{2,5}$/i.test(u))
|
|
}
|
|
|
|
test('no page links to an address the export does not serve', () => {
|
|
const shipped = pages()
|
|
expect(shipped.length).toBeGreaterThan(500)
|
|
|
|
const dangling = new Map<string, Set<string>>()
|
|
for (const { route, file } of shipped) {
|
|
for (const url of internalLinks(readFileSync(file, 'utf8'))) {
|
|
if (served(url)) continue
|
|
if (!dangling.has(url)) dangling.set(url, new Set())
|
|
dangling.get(url)!.add(route)
|
|
}
|
|
}
|
|
|
|
const report = [...dangling.entries()]
|
|
.sort((a, b) => b[1].size - a[1].size)
|
|
.map(([url, from]) => ` ${url} <- ${[...from].slice(0, 4).join(', ')}${from.size > 4 ? ` (+${from.size - 4} more)` : ''}`)
|
|
expect(report.join('\n') || 'none', `addresses linked but not served:\n${report.join('\n')}`).toBe('none')
|
|
})
|
|
|
|
/**
|
|
* The legal documents are the reason the gate above exists, so they are also
|
|
* asserted directly: every file in `content/legal` is reachable, and each says
|
|
* whether it is in force. A document a reviewer relies on without knowing it
|
|
* is a draft is the failure /trust is written to avoid.
|
|
*/
|
|
test('every legal document is served, and says whether it is effective', () => {
|
|
const index = join(OUT, 'legal.html')
|
|
expect(existsSync(index), '/legal is not served').toBe(true)
|
|
|
|
const docs = ['acceptable-use', 'biometric-voice', 'consumer-health', 'cookies', 'dpa',
|
|
'privacy', 'research-api', 'research-contribution', 'subprocessors', 'terms']
|
|
for (const slug of docs) {
|
|
const file = join(OUT, 'legal', `${slug}.html`)
|
|
expect(existsSync(file), `/legal/${slug} is not served`).toBe(true)
|
|
const html = readFileSync(file, 'utf8')
|
|
expect(html.length, `/legal/${slug} shipped nearly empty`).toBeGreaterThan(4000)
|
|
expect(/not yet effective|Version /.test(html), `/legal/${slug} states no effective date`).toBe(true)
|
|
}
|
|
})
|