Files
vue-seo-layer/tests/routes.test.ts
Jakub Zych 3abc986f42 feat: route report and drift check at server start
- the module writes #seo-routes (static paths and route patterns) from the
  resolved pages; options seo.routeReport, seo.pagination and
  seo.htmlSitemap reach the public runtime config
- seo-route-report: POST /registry/routes once per process with the
  route-report token, one retry after 60 s on failure, nothing without a
  token, with the layer off or with routeReport false
- seo-drift: one "[vue-seo-layer] drift:" warning per mismatch between
  the CMS's site answer (URL patterns, pagination, HTML sitemap paths)
  and the build
- GET /api/__seo__/site and useSeoSite()
- smoke grammar routereport and log, fixture registry route, drift run
2026-11-04 11:49:21 +01:00

118 lines
6.4 KiB
TypeScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { htmlSitemapOption, paginationOption } from '../lib/options.ts'
import { pageMetaRobots, robotsNoindex, routePatterns, routesTemplate, staticRoutes } from '../lib/routes.ts'
import { DRIFT_PREFIX, driftWarnings, routeServes } from '../server/lib/drift.ts'
import { ROUTE_REPORT_MAX_ROUTES, routeReportBody, routeReportOutcome } from '../server/lib/routeReport.ts'
const pages = [
{ path: '/', name: 'index' },
{ path: '/blog', name: 'blog' },
{ path: '/posts/:slug()', name: 'posts-slug' },
{ path: '/:slug(.*)*', name: 'slug' },
{ path: '/konto', name: 'konto', meta: { requiresAuth: true } },
{ path: '/szkic', name: 'szkic', meta: { seo: { robots: 'noindex, follow' } } },
{ path: '/otwarty', name: 'otwarty', meta: { seo: { robots: 'index, follow' } } },
{ path: '/prywatne', name: 'prywatne' },
{ path: '/panel', name: 'panel', mode: 'client' },
{ path: '/mapa-strony', name: 'seo-sitemap-pl' },
{ path: '/ustawienia', name: 'ustawienia', children: [{ path: '', name: 'ustawienia-index' }, { path: 'profil', name: 'ustawienia-profil' }] },
]
test('pageMetaRobots: seo.robots, then requiresAuth, else the CMS decides', () => {
assert.equal(pageMetaRobots({ requiresAuth: true }), 'noindex, nofollow')
assert.equal(pageMetaRobots({ requiresAuth: true, seo: { robots: 'noindex, follow' } }), 'noindex, follow')
assert.equal(pageMetaRobots({ seo: { robots: ' index, follow ' } }), 'index, follow')
assert.equal(pageMetaRobots({ requiresAuth: false }), null)
assert.equal(pageMetaRobots({ seo: { breadcrumbs: false } }), null)
assert.equal(pageMetaRobots(undefined), null)
})
test('robotsNoindex: noindex and none keep a page out', () => {
assert.equal(robotsNoindex('noindex, follow'), true)
assert.equal(robotsNoindex('none'), true)
assert.equal(robotsNoindex('index, follow'), false)
assert.equal(robotsNoindex(null), false)
})
test('staticRoutes: indexable paths without parameters', () => {
assert.deepEqual(staticRoutes(pages, ['/prywatne/**']), ['/', '/blog', '/otwarty', '/ustawienia', '/ustawienia/profil'])
})
test('routePatterns: page paths as patterns, catch-alls marked', () => {
const out = routePatterns(pages)
assert.deepEqual(out.find((p) => p.pattern === '/posts/:slug'), { pattern: '/posts/:slug', catchAll: false })
assert.deepEqual(out.find((p) => p.pattern === '/**'), { pattern: '/**', catchAll: true })
assert.equal(new Set(out.map((p) => p.pattern)).size, out.length)
})
test('routesTemplate: an ES module with both lists', () => {
const src = routesTemplate(['/'], [{ pattern: '/', catchAll: false }])
assert.match(src, /export const staticRoutes = \["\/"\]/)
assert.match(src, /export const routePatterns = \[\{"pattern":"\/","catchAll":false\}\]/)
})
test('routeReportBody: within the plugin limits', () => {
assert.deepEqual(routeReportBody(['/', '/blog', 'nope', '/' + 'a'.repeat(2048)]).routes, ['/', '/blog'])
assert.equal(routeReportBody(Array.from({ length: 2500 }, (_, i) => `/p${i}`)).routes.length, ROUTE_REPORT_MAX_ROUTES)
})
test('routeReportOutcome: sent, absent, refused or one retry', () => {
assert.equal(routeReportOutcome(204), 'sent')
assert.equal(routeReportOutcome(200), 'sent')
assert.equal(routeReportOutcome(404), 'absent')
assert.equal(routeReportOutcome(401), 'refused')
assert.equal(routeReportOutcome(403), 'refused')
assert.equal(routeReportOutcome(503), 'retry')
assert.equal(routeReportOutcome(null), 'retry')
})
test('options: pagination and HTML sitemap with their checks', () => {
assert.deepEqual(paginationOption({ form: 'path', param: 'p', segments: { pl: 'strona', en: 'a b' } }), { form: 'path', param: 'p', segments: { pl: 'strona' } })
assert.deepEqual(paginationOption(undefined), { form: 'query', param: 'page', segments: {} })
assert.equal(htmlSitemapOption(false), false)
assert.deepEqual(htmlSitemapOption({ paths: { pl: '/mapa-strony/', en: 'sitemap', de: '/x/:id' } }), { paths: { pl: '/mapa-strony' } })
})
const layer = { pagination: { form: 'query' as const, param: 'page', segments: { pl: 'strona', en: 'page' } }, htmlSitemap: { pl: '/mapa-strony', en: '/en/sitemap' } }
const routes = routePatterns(pages)
test('routeServes: a route of the same shape or a prefixed catch-all; not the root catch-all', () => {
assert.equal(routeServes({ pattern: '/posts/:slug', catchAll: false }, '/posts/:id'), true)
assert.equal(routeServes({ pattern: '/posts/:slug', catchAll: false }, '/posts/{slug}'), true)
assert.equal(routeServes({ pattern: '/docs/**', catchAll: true }, '/docs/:a/:b'), true)
assert.equal(routeServes({ pattern: '/**', catchAll: true }, '/wpisy/:slug'), false)
})
test('driftWarnings: nothing when the CMS and the build agree', () => {
const site = {
routes: [{ type: 'post', pattern: '/posts/:slug' }],
pagination: { form: 'query' as const, param: 'page', segments: {} },
html_sitemap: { enabled: true, paths: { pl: '/mapa-strony', en: '/en/sitemap' } },
}
assert.deepEqual(driftWarnings({ site, routes, ...layer }), [])
})
test('driftWarnings: one warning per mismatch', () => {
const site = {
routes: [{ type: 'journal-post', pattern: '/wpisy/:slug' }],
pagination: { form: 'path' as const, param: 'page', segments: { pl: 'strona' } },
html_sitemap: { enabled: true, paths: { pl: '/mapa', en: '/en/sitemap' } },
}
const out = driftWarnings({ site, routes, ...layer })
assert.equal(out.length, 3)
assert.ok(out.every((w) => w.startsWith(DRIFT_PREFIX)))
assert.match(out[0]!, /journal-post URLs \(\/wpisy\/:slug\) match no Nuxt route/)
assert.match(out[1]!, /path form/)
assert.match(out[2]!, /pl HTML sitemap at \/mapa/)
})
test('driftWarnings: segments and param, a switched-off layer sitemap, a switched-off CMS sitemap', () => {
const pathLayer = { ...layer, pagination: { form: 'path' as const, param: 'page', segments: { pl: 'strona' } } }
assert.equal(driftWarnings({ site: { pagination: { form: 'path', segments: { pl: 'str' } } }, routes, ...pathLayer }).length, 1)
assert.equal(driftWarnings({ site: { pagination: { form: 'query', param: 'p' } }, routes, ...layer }).length, 1)
assert.equal(driftWarnings({ site: { html_sitemap: { enabled: true, paths: { pl: '/mapa-strony' } } }, routes, ...layer, htmlSitemap: false }).length, 1)
assert.deepEqual(driftWarnings({ site: { html_sitemap: { enabled: false, paths: { pl: '/x' } } }, routes, ...layer }), [])
assert.deepEqual(driftWarnings({ site: {}, routes, ...layer }), [])
})