82 lines
3.9 KiB
TypeScript
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()
|
|
})
|
|
})
|