Files
landing-builder/lib/exporter/verify.integrity.test.ts

82 lines
3.9 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { AssetManager } from '@/lib/assets/assetManager'
import { buildAssetsIntegrityIndex } from '@/lib/exporter/assets.integrity.index'
import { verifyIntegrity } from '@/lib/exporter/verify'
function u8(n: number): Uint8Array { const a = new Uint8Array(n); for (let i=0;i<n;i++) a[i]=i&0xff; return a }
function td(b: Uint8Array){ return new TextDecoder().decode(b) }
function te(s: string){ return new TextEncoder().encode(s) }
describe('verifyIntegrity', () => {
it('returns ok=true for valid assets and integrity index', async () => {
const am = new AssetManager({ maxBytes: 1024*1024, allowedExts: ['png','txt'] })
const a = await am.add({ name: 'a.png', type: 'image/png', bytes: u8(5) })
const b = await am.add({ name: 'b.txt', type: 'text/plain', bytes: u8(7) })
const assets = am.toZipStructure()
const idx = buildAssetsIntegrityIndex(am)
const extras: Record<string, Uint8Array> = { 'assets.integrity.index.json': idx }
const res = verifyIntegrity(extras, assets)
expect(res.ok).toBe(true)
expect(res.errors.length).toBe(0)
// summary
const parsedIdx = JSON.parse(td(idx)) as { entries: Array<{ path: string }> }
expect(res.summary.total).toBe(parsedIdx.entries.length)
expect(res.summary.missing).toBe(0)
expect(res.summary.integrityMismatches).toBe(0)
expect(res.summary.sizeMismatches).toBe(0)
expect(res.summary.bundleHashMismatch).toBe(false)
// sanity: index contains both paths
const parsed = JSON.parse(td(idx)) as { entries: Array<{ path: string }> }
const paths = parsed.entries.map(e => e.path)
expect(paths).toContain(a.path)
expect(paths).toContain(b.path)
})
it('detects byte tampering in an asset', async () => {
const am = new AssetManager({ maxBytes: 1024*1024, allowedExts: ['png','txt'] })
await am.add({ name: 'a.png', type: 'image/png', bytes: u8(5) })
await am.add({ name: 'b.txt', type: 'text/plain', bytes: u8(7) })
const assets = am.toZipStructure()
const idx = buildAssetsIntegrityIndex(am)
// tamper one asset byte
const somePath = Object.keys(assets)[0]
const tampered = assets[somePath].slice()
tampered[0] = (tampered[0] ^ 0xff) & 0xff
assets[somePath] = tampered
const res = verifyIntegrity({ 'assets.integrity.index.json': idx }, assets)
expect(res.ok).toBe(false)
expect(res.errors.some((e: string) => e.includes(somePath) && e.toLowerCase().includes('mismatch'))).toBe(true)
expect(res.summary.integrityMismatches).toBeGreaterThanOrEqual(1)
})
it('detects missing entry or missing asset referenced by index', async () => {
const am = new AssetManager({ maxBytes: 1024*1024, allowedExts: ['png','txt'] })
await am.add({ name: 'a.png', type: 'image/png', bytes: u8(5) })
await am.add({ name: 'b.txt', type: 'text/plain', bytes: u8(7) })
const assets = am.toZipStructure()
const idxRaw = buildAssetsIntegrityIndex(am)
const obj = JSON.parse(td(idxRaw)) as { entries: Array<{ path: string; integrity: string }>, bundleHash: string }
// remove one entry
const removed = obj.entries.shift()!
const idx2 = te(JSON.stringify(obj))
const res1 = verifyIntegrity({ 'assets.integrity.index.json': idx2 }, assets)
expect(res1.ok).toBe(false)
expect(res1.errors.some((e: string) => e.toLowerCase().includes('bundlehash'))).toBe(true)
expect(res1.summary.bundleHashMismatch).toBe(true)
// reference a missing path in index
obj.entries.unshift({ path: 'assets/missing.bin', integrity: 'sha256-AAAA' })
const idx3 = te(JSON.stringify(obj))
const res2 = verifyIntegrity({ 'assets.integrity.index.json': idx3 }, assets)
expect(res2.ok).toBe(false)
expect(res2.errors.some((e: string) => e.includes('assets/missing.bin') && e.toLowerCase().includes('missing'))).toBe(true)
expect(res2.summary.missing).toBeGreaterThanOrEqual(1)
// keep lints happy
expect(removed).toBeTruthy()
})
})