2026-10-20 18:36:45 +08:00
|
|
|
|
import { describe, it, expect } from 'vitest'
|
|
|
|
|
|
import {
|
|
|
|
|
|
ROUTE_MANIFEST_VERSION, RENDER_SNAPSHOT_VERSION, THEME_MANIFEST_VERSION,
|
|
|
|
|
|
RENDER_SNAPSHOT_FILES, isRouteManifest, isThemeManifest,
|
|
|
|
|
|
} from '../src/index.js'
|
|
|
|
|
|
|
|
|
|
|
|
const validRouteManifest: any = {
|
|
|
|
|
|
manifest_version: 1, site_id: 's1', build_job_id: 'b1', origin: 'https://fixture.example',
|
|
|
|
|
|
generated_from_route_snapshot_hash: 'abc',
|
|
|
|
|
|
routes: [{ exact_key: '/', query_policy: 'ignore', kind: 'html', target_file: 'index.html', status: 200, indexable: true }],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const validThemeManifest: any = {
|
|
|
|
|
|
key: 'pilot-minimal', version: '0.1.0', renderContractVersion: '1',
|
|
|
|
|
|
supportedContentTypes: ['news_article', 'product', 'basic_page'],
|
|
|
|
|
|
requiredSiteConfig: ['origin', 'listPaths'],
|
|
|
|
|
|
templates: [{ key: 'home', appliesTo: 'home' }],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
describe('manifest 契约', () => {
|
|
|
|
|
|
it('三个版本号冻结为 1', () => {
|
|
|
|
|
|
expect([ROUTE_MANIFEST_VERSION, RENDER_SNAPSHOT_VERSION, THEME_MANIFEST_VERSION]).toEqual([1, 1, 1])
|
|
|
|
|
|
})
|
|
|
|
|
|
it('Render Snapshot 文件清单冻结(spec §9.2)', () => {
|
|
|
|
|
|
expect(RENDER_SNAPSHOT_FILES).toEqual([
|
|
|
|
|
|
'site.json', 'content.json', 'taxonomies.json', 'routes.json', 'assets.json', 'render-manifest.json',
|
|
|
|
|
|
])
|
|
|
|
|
|
})
|
|
|
|
|
|
it('合法 RouteManifest 通过守卫', () => {
|
|
|
|
|
|
expect(isRouteManifest({
|
|
|
|
|
|
manifest_version: 1, site_id: 's1', build_job_id: 'b1', origin: 'https://fixture.example',
|
|
|
|
|
|
generated_from_route_snapshot_hash: 'abc',
|
|
|
|
|
|
routes: [{ exact_key: '/', query_policy: 'ignore', kind: 'html', target_file: 'index.html', status: 200, indexable: true }],
|
|
|
|
|
|
})).toBe(true)
|
|
|
|
|
|
})
|
|
|
|
|
|
it('非法 kind 拒绝', () => {
|
|
|
|
|
|
const m: any = { manifest_version: 1, site_id: 's', build_job_id: 'b', origin: 'o', generated_from_route_snapshot_hash: 'h', routes: [{ exact_key: '/', query_policy: 'ignore', kind: 'weird', status: 200, indexable: true }] }
|
|
|
|
|
|
expect(isRouteManifest(m)).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
it('合法 ThemeManifest 通过守卫', () => {
|
|
|
|
|
|
expect(isThemeManifest({
|
|
|
|
|
|
key: 'pilot-minimal', version: '0.1.0', renderContractVersion: '1',
|
|
|
|
|
|
supportedContentTypes: ['news_article', 'product', 'basic_page'],
|
|
|
|
|
|
requiredSiteConfig: ['origin', 'listPaths'],
|
|
|
|
|
|
templates: [{ key: 'home', appliesTo: 'home' }],
|
|
|
|
|
|
})).toBe(true)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
describe('RouteManifest 守卫拒绝路径', () => {
|
|
|
|
|
|
it('manifest_version ≠ 1 拒绝(2 与字符串 "1")', () => {
|
|
|
|
|
|
expect(isRouteManifest({ ...validRouteManifest, manifest_version: 2 })).toBe(false)
|
|
|
|
|
|
expect(isRouteManifest({ ...validRouteManifest, manifest_version: '1' })).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('routes 非数组拒绝(字符串)', () => {
|
|
|
|
|
|
expect(isRouteManifest({ ...validRouteManifest, routes: 'x' })).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-10-20 18:47:42 +08:00
|
|
|
|
it('routes 元素缺 exact_key / 元素为 null 拒绝', () => {
|
2026-10-20 18:36:45 +08:00
|
|
|
|
const { exact_key: _omit, ...route } = validRouteManifest.routes[0]
|
|
|
|
|
|
expect(isRouteManifest({ ...validRouteManifest, routes: [route] })).toBe(false)
|
2026-10-20 18:47:42 +08:00
|
|
|
|
expect(isRouteManifest({ ...validRouteManifest, routes: [null] })).toBe(false)
|
2026-10-20 18:36:45 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('status 非数字拒绝(status: "200")', () => {
|
|
|
|
|
|
expect(isRouteManifest({
|
|
|
|
|
|
...validRouteManifest,
|
|
|
|
|
|
routes: [{ ...validRouteManifest.routes[0], status: '200' }],
|
|
|
|
|
|
})).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-10-20 18:47:42 +08:00
|
|
|
|
it('status 非整数拒绝(200.5)', () => {
|
|
|
|
|
|
expect(isRouteManifest({
|
|
|
|
|
|
...validRouteManifest,
|
|
|
|
|
|
routes: [{ ...validRouteManifest.routes[0], status: 200.5 }],
|
|
|
|
|
|
})).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('status 超域拒绝(999)', () => {
|
|
|
|
|
|
expect(isRouteManifest({
|
|
|
|
|
|
...validRouteManifest,
|
|
|
|
|
|
routes: [{ ...validRouteManifest.routes[0], status: 999 }],
|
|
|
|
|
|
})).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-10-20 18:36:45 +08:00
|
|
|
|
it('indexable 非布尔拒绝(indexable: 1)', () => {
|
|
|
|
|
|
expect(isRouteManifest({
|
|
|
|
|
|
...validRouteManifest,
|
|
|
|
|
|
routes: [{ ...validRouteManifest.routes[0], indexable: 1 }],
|
|
|
|
|
|
})).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('query_policy 非四值之一拒绝', () => {
|
|
|
|
|
|
expect(isRouteManifest({
|
|
|
|
|
|
...validRouteManifest,
|
|
|
|
|
|
routes: [{ ...validRouteManifest.routes[0], query_policy: 'loose' }],
|
|
|
|
|
|
})).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('顶层必填字符串字段非字符串拒绝(site_id 数字)', () => {
|
|
|
|
|
|
expect(isRouteManifest({ ...validRouteManifest, site_id: 101 })).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('非对象输入拒绝(null / number / array)', () => {
|
|
|
|
|
|
expect(isRouteManifest(null)).toBe(false)
|
|
|
|
|
|
expect(isRouteManifest(42)).toBe(false)
|
|
|
|
|
|
expect(isRouteManifest([])).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
describe('ThemeManifest 守卫拒绝路径', () => {
|
|
|
|
|
|
it('version 非 semver 拒绝("abc" 与 "1")', () => {
|
|
|
|
|
|
expect(isThemeManifest({ ...validThemeManifest, version: 'abc' })).toBe(false)
|
|
|
|
|
|
expect(isThemeManifest({ ...validThemeManifest, version: '1' })).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('key 非字符串拒绝(数字)', () => {
|
|
|
|
|
|
expect(isThemeManifest({ ...validThemeManifest, key: 42 })).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('templates 非数组拒绝', () => {
|
|
|
|
|
|
expect(isThemeManifest({ ...validThemeManifest, templates: 'x' })).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('templates 元素缺 appliesTo 拒绝(查值类型,不只查键存在)', () => {
|
|
|
|
|
|
expect(isThemeManifest({
|
|
|
|
|
|
...validThemeManifest,
|
|
|
|
|
|
templates: [{ key: 'home', appliesTo: 42 }],
|
|
|
|
|
|
})).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('renderContractVersion 非字符串拒绝', () => {
|
|
|
|
|
|
expect(isThemeManifest({ ...validThemeManifest, renderContractVersion: 1 })).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('非对象输入拒绝(null / number)', () => {
|
|
|
|
|
|
expect(isThemeManifest(null)).toBe(false)
|
|
|
|
|
|
expect(isThemeManifest(42)).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|