Something went wrong. Try again.
Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. forked from nekomimi.pet/wisp.place-monorepo
Something went wrong. Try again.
27 kB · 999 lines
TypeScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000import { describe, test, expect } from 'bun:test'import { shouldCompressFile, compressFile, processUploadedFiles, createManifest, updateFileBlobs, computeCID, extractBlobMap, type UploadedFile, type FileUploadResult,} from './wisp-utils'import type { Directory } from '../lexicons/types/place/wisp/fs'import { gunzipSync } from 'zlib'import { BlobRef } from '@atproto/api'import { CID } from 'multiformats/cid'
// Helper function to create a valid CID for testing// Using a real valid CID from actual AT Protocol usageconst TEST_CID_STRING = 'bafkreid7ybejd5s2vv2j7d4aajjlmdgazguemcnuliiyfn6coxpwp2mi6y'
function createMockBlobRef(mimeType: string, size: number): BlobRef { // Create a properly formatted CID const cid = CID.parse(TEST_CID_STRING) return new BlobRef(cid, mimeType, size)}
describe('shouldCompressFile', () => { test('should compress HTML files', () => { expect(shouldCompressFile('text/html')).toBe(true) expect(shouldCompressFile('text/html; charset=utf-8')).toBe(true) })
test('should compress CSS files', () => { expect(shouldCompressFile('text/css')).toBe(true) })
test('should compress JavaScript files', () => { expect(shouldCompressFile('text/javascript')).toBe(true) expect(shouldCompressFile('application/javascript')).toBe(true) expect(shouldCompressFile('application/x-javascript')).toBe(true) })
test('should compress JSON files', () => { expect(shouldCompressFile('application/json')).toBe(true) })
test('should compress SVG files', () => { expect(shouldCompressFile('image/svg+xml')).toBe(true) })
test('should compress XML files', () => { expect(shouldCompressFile('text/xml')).toBe(true) expect(shouldCompressFile('application/xml')).toBe(true) })
test('should compress plain text files', () => { expect(shouldCompressFile('text/plain')).toBe(true) })
test('should NOT compress images', () => { expect(shouldCompressFile('image/png')).toBe(false) expect(shouldCompressFile('image/jpeg')).toBe(false) expect(shouldCompressFile('image/jpg')).toBe(false) expect(shouldCompressFile('image/gif')).toBe(false) expect(shouldCompressFile('image/webp')).toBe(false) })
test('should NOT compress videos', () => { expect(shouldCompressFile('video/mp4')).toBe(false) expect(shouldCompressFile('video/webm')).toBe(false) })
test('should NOT compress already compressed formats', () => { expect(shouldCompressFile('application/zip')).toBe(false) expect(shouldCompressFile('application/gzip')).toBe(false) expect(shouldCompressFile('application/pdf')).toBe(false) })
test('should NOT compress fonts', () => { expect(shouldCompressFile('font/woff')).toBe(false) expect(shouldCompressFile('font/woff2')).toBe(false) expect(shouldCompressFile('font/ttf')).toBe(false) })})
describe('compressFile', () => { test('should compress text content', () => { const content = Buffer.from('Hello, World! '.repeat(100)) const compressed = compressFile(content)
expect(compressed.length).toBeLessThan(content.length)
// Verify we can decompress it back const decompressed = gunzipSync(compressed) expect(decompressed.toString()).toBe(content.toString()) })
test('should compress HTML content significantly', () => { const html = ` <!DOCTYPE html> <html> <head><title>Test</title></head> <body> ${'<p>Hello World!</p>\n'.repeat(50)} </body> </html> ` const content = Buffer.from(html) const compressed = compressFile(content)
expect(compressed.length).toBeLessThan(content.length) // Verify decompression const decompressed = gunzipSync(compressed) expect(decompressed.toString()).toBe(html) })
test('should handle empty content', () => { const content = Buffer.from('') const compressed = compressFile(content) const decompressed = gunzipSync(compressed) expect(decompressed.toString()).toBe('') })
test('should produce deterministic compression', () => { const content = Buffer.from('Test content') const compressed1 = compressFile(content) const compressed2 = compressFile(content) expect(compressed1.toString('base64')).toBe(compressed2.toString('base64')) })})
describe('processUploadedFiles', () => { test('should process single root-level file', () => { const files: UploadedFile[] = [ { name: 'index.html', content: Buffer.from('<html></html>'), mimeType: 'text/html', size: 13, }, ]
const result = processUploadedFiles(files)
expect(result.fileCount).toBe(1) expect(result.directory.type).toBe('directory') expect(result.directory.entries).toHaveLength(1) expect(result.directory.entries[0].name).toBe('index.html') const node = result.directory.entries[0].node expect('blob' in node).toBe(true) // It's a file node })
test('should process multiple root-level files', () => { const files: UploadedFile[] = [ { name: 'index.html', content: Buffer.from('<html></html>'), mimeType: 'text/html', size: 13, }, { name: 'styles.css', content: Buffer.from('body {}'), mimeType: 'text/css', size: 7, }, { name: 'script.js', content: Buffer.from('console.log("hi")'), mimeType: 'application/javascript', size: 17, }, ]
const result = processUploadedFiles(files)
expect(result.fileCount).toBe(3) expect(result.directory.entries).toHaveLength(3) const names = result.directory.entries.map(e => e.name) expect(names).toContain('index.html') expect(names).toContain('styles.css') expect(names).toContain('script.js') })
test('should process files with subdirectories', () => { const files: UploadedFile[] = [ { name: 'dist/index.html', content: Buffer.from('<html></html>'), mimeType: 'text/html', size: 13, }, { name: 'dist/css/styles.css', content: Buffer.from('body {}'), mimeType: 'text/css', size: 7, }, { name: 'dist/js/app.js', content: Buffer.from('console.log()'), mimeType: 'application/javascript', size: 13, }, ]
const result = processUploadedFiles(files)
expect(result.fileCount).toBe(3) expect(result.directory.entries).toHaveLength(3) // index.html, css/, js/
// Check root has index.html (after base folder removal) const indexEntry = result.directory.entries.find(e => e.name === 'index.html') expect(indexEntry).toBeDefined()
// Check css directory exists const cssDir = result.directory.entries.find(e => e.name === 'css') expect(cssDir).toBeDefined() expect('entries' in cssDir!.node).toBe(true) if ('entries' in cssDir!.node) { expect(cssDir!.node.entries).toHaveLength(1) expect(cssDir!.node.entries[0].name).toBe('styles.css') }
// Check js directory exists const jsDir = result.directory.entries.find(e => e.name === 'js') expect(jsDir).toBeDefined() expect('entries' in jsDir!.node).toBe(true) })
test('should handle deeply nested subdirectories', () => { const files: UploadedFile[] = [ { name: 'dist/deep/nested/folder/file.txt', content: Buffer.from('content'), mimeType: 'text/plain', size: 7, }, ]
const result = processUploadedFiles(files)
expect(result.fileCount).toBe(1)
// Navigate through the directory structure (base folder removed) const deepDir = result.directory.entries.find(e => e.name === 'deep') expect(deepDir).toBeDefined() expect('entries' in deepDir!.node).toBe(true)
if ('entries' in deepDir!.node) { const nestedDir = deepDir!.node.entries.find(e => e.name === 'nested') expect(nestedDir).toBeDefined()
if (nestedDir && 'entries' in nestedDir.node) { const folderDir = nestedDir.node.entries.find(e => e.name === 'folder') expect(folderDir).toBeDefined()
if (folderDir && 'entries' in folderDir.node) { expect(folderDir.node.entries).toHaveLength(1) expect(folderDir.node.entries[0].name).toBe('file.txt') } } } })
test('should remove base folder name from paths', () => { const files: UploadedFile[] = [ { name: 'dist/index.html', content: Buffer.from('<html></html>'), mimeType: 'text/html', size: 13, }, { name: 'dist/css/styles.css', content: Buffer.from('body {}'), mimeType: 'text/css', size: 7, }, ]
const result = processUploadedFiles(files)
// After removing 'dist/', we should have index.html and css/ at root expect(result.directory.entries.find(e => e.name === 'index.html')).toBeDefined() expect(result.directory.entries.find(e => e.name === 'css')).toBeDefined() expect(result.directory.entries.find(e => e.name === 'dist')).toBeUndefined() })
test('should handle empty file list', () => { const files: UploadedFile[] = [] const result = processUploadedFiles(files)
expect(result.fileCount).toBe(0) expect(result.directory.entries).toHaveLength(0) })
test('should handle multiple files in same subdirectory', () => { const files: UploadedFile[] = [ { name: 'dist/assets/image1.png', content: Buffer.from('png1'), mimeType: 'image/png', size: 4, }, { name: 'dist/assets/image2.png', content: Buffer.from('png2'), mimeType: 'image/png', size: 4, }, ]
const result = processUploadedFiles(files)
expect(result.fileCount).toBe(2) const assetsDir = result.directory.entries.find(e => e.name === 'assets') expect(assetsDir).toBeDefined() if ('entries' in assetsDir!.node) { expect(assetsDir!.node.entries).toHaveLength(2) const names = assetsDir!.node.entries.map(e => e.name) expect(names).toContain('image1.png') expect(names).toContain('image2.png') } })})
describe('createManifest', () => { test('should create valid manifest', () => { const root: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [], }
const manifest = createManifest('example.com', root, 0)
expect(manifest.$type).toBe('place.wisp.fs') expect(manifest.site).toBe('example.com') expect(manifest.root).toBe(root) expect(manifest.fileCount).toBe(0) expect(manifest.createdAt).toBeDefined() // Verify it's a valid ISO date string const date = new Date(manifest.createdAt) expect(date.toISOString()).toBe(manifest.createdAt) })
test('should create manifest with file count', () => { const root: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [], }
const manifest = createManifest('test-site', root, 42) expect(manifest.fileCount).toBe(42) expect(manifest.site).toBe('test-site') })
test('should create manifest with populated directory', () => { const mockBlob = createMockBlobRef('text/html', 100) const root: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'index.html', node: { $type: 'place.wisp.fs#file', type: 'file', blob: mockBlob, }, }, ], }
const manifest = createManifest('populated-site', root, 1) expect(manifest).toBeDefined() expect(manifest.site).toBe('populated-site') expect(manifest.root.entries).toHaveLength(1) })})
describe('updateFileBlobs', () => { test('should update single file blob at root', () => { const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'index.html', node: { $type: 'place.wisp.fs#file', type: 'file', blob: undefined as any, }, }, ], }
const mockBlob = createMockBlobRef('text/html', 100) const uploadResults: FileUploadResult[] = [ { hash: TEST_CID_STRING, blobRef: mockBlob, mimeType: 'text/html', }, ]
const filePaths = ['index.html']
const updated = updateFileBlobs(directory, uploadResults, filePaths)
expect(updated.entries).toHaveLength(1) const fileNode = updated.entries[0].node if ('blob' in fileNode) { expect(fileNode.blob).toBeDefined() expect(fileNode.blob.mimeType).toBe('text/html') expect(fileNode.blob.size).toBe(100) } else { throw new Error('Expected file node') } })
test('should update files in nested directories', () => { const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'css', node: { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'styles.css', node: { $type: 'place.wisp.fs#file', type: 'file', blob: undefined as any, }, }, ], }, }, ], }
const mockBlob = createMockBlobRef('text/css', 50) const uploadResults: FileUploadResult[] = [ { hash: TEST_CID_STRING, blobRef: mockBlob, mimeType: 'text/css', encoding: 'gzip', }, ]
const filePaths = ['css/styles.css']
const updated = updateFileBlobs(directory, uploadResults, filePaths)
const cssDir = updated.entries[0] expect(cssDir.name).toBe('css') if ('entries' in cssDir.node) { const cssFile = cssDir.node.entries[0] expect(cssFile.name).toBe('styles.css') if ('blob' in cssFile.node) { expect(cssFile.node.blob.mimeType).toBe('text/css') if ('encoding' in cssFile.node) { expect(cssFile.node.encoding).toBe('gzip') } } else { throw new Error('Expected file node') } } else { throw new Error('Expected directory node') } })
test('should handle normalized paths with base folder removed', () => { const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'index.html', node: { $type: 'place.wisp.fs#file', type: 'file', blob: undefined as any, }, }, ], }
const mockBlob = createMockBlobRef('text/html', 100) const uploadResults: FileUploadResult[] = [ { hash: TEST_CID_STRING, blobRef: mockBlob, }, ]
// Path includes base folder that should be normalized const filePaths = ['dist/index.html']
const updated = updateFileBlobs(directory, uploadResults, filePaths)
const fileNode = updated.entries[0].node if ('blob' in fileNode) { expect(fileNode.blob).toBeDefined() } else { throw new Error('Expected file node') } })
test('should preserve file metadata (encoding, mimeType, base64)', () => { const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'data.json', node: { $type: 'place.wisp.fs#file', type: 'file', blob: undefined as any, }, }, ], }
const mockBlob = createMockBlobRef('application/json', 200) const uploadResults: FileUploadResult[] = [ { hash: TEST_CID_STRING, blobRef: mockBlob, mimeType: 'application/json', encoding: 'gzip', base64: true, }, ]
const filePaths = ['data.json']
const updated = updateFileBlobs(directory, uploadResults, filePaths)
const fileNode = updated.entries[0].node if ('blob' in fileNode && 'mimeType' in fileNode && 'encoding' in fileNode && 'base64' in fileNode) { expect(fileNode.mimeType).toBe('application/json') expect(fileNode.encoding).toBe('gzip') expect(fileNode.base64).toBe(true) } else { throw new Error('Expected file node with metadata') } })
test('should handle multiple files at different directory levels', () => { const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'index.html', node: { $type: 'place.wisp.fs#file', type: 'file', blob: undefined as any, }, }, { name: 'assets', node: { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'logo.svg', node: { $type: 'place.wisp.fs#file', type: 'file', blob: undefined as any, }, }, ], }, }, ], }
const htmlBlob = createMockBlobRef('text/html', 100) const svgBlob = createMockBlobRef('image/svg+xml', 500) const uploadResults: FileUploadResult[] = [ { hash: TEST_CID_STRING, blobRef: htmlBlob, }, { hash: TEST_CID_STRING, blobRef: svgBlob, }, ]
const filePaths = ['index.html', 'assets/logo.svg']
const updated = updateFileBlobs(directory, uploadResults, filePaths)
// Check root file const indexNode = updated.entries[0].node if ('blob' in indexNode) { expect(indexNode.blob.mimeType).toBe('text/html') }
// Check nested file const assetsDir = updated.entries[1] if ('entries' in assetsDir.node) { const logoNode = assetsDir.node.entries[0].node if ('blob' in logoNode) { expect(logoNode.blob.mimeType).toBe('image/svg+xml') } } })})
describe('computeCID', () => { test('should compute CID for gzipped+base64 encoded content', () => { // This simulates the actual flow: gzip -> base64 -> compute CID const originalContent = Buffer.from('Hello, World!') const gzipped = compressFile(originalContent) const base64Content = Buffer.from(gzipped.toString('base64'), 'binary')
const cid = computeCID(base64Content)
// CID should be a valid CIDv1 string starting with 'bafkrei' expect(cid).toMatch(/^bafkrei[a-z0-9]+$/) expect(cid.length).toBeGreaterThan(10) })
test('should compute deterministic CIDs for identical content', () => { const content = Buffer.from('Test content for CID calculation') const gzipped = compressFile(content) const base64Content = Buffer.from(gzipped.toString('base64'), 'binary')
const cid1 = computeCID(base64Content) const cid2 = computeCID(base64Content)
expect(cid1).toBe(cid2) })
test('should compute different CIDs for different content', () => { const content1 = Buffer.from('Content A') const content2 = Buffer.from('Content B')
const gzipped1 = compressFile(content1) const gzipped2 = compressFile(content2)
const base64Content1 = Buffer.from(gzipped1.toString('base64'), 'binary') const base64Content2 = Buffer.from(gzipped2.toString('base64'), 'binary')
const cid1 = computeCID(base64Content1) const cid2 = computeCID(base64Content2)
expect(cid1).not.toBe(cid2) })
test('should handle empty content', () => { const emptyContent = Buffer.from('') const gzipped = compressFile(emptyContent) const base64Content = Buffer.from(gzipped.toString('base64'), 'binary')
const cid = computeCID(base64Content)
expect(cid).toMatch(/^bafkrei[a-z0-9]+$/) })
test('should compute same CID as PDS for base64-encoded content', () => { // Test that binary encoding produces correct bytes for CID calculation const testContent = Buffer.from('<!DOCTYPE html><html><body>Hello</body></html>') const gzipped = compressFile(testContent) const base64Content = Buffer.from(gzipped.toString('base64'), 'binary')
// Compute CID twice to ensure consistency const cid1 = computeCID(base64Content) const cid2 = computeCID(base64Content)
expect(cid1).toBe(cid2) expect(cid1).toMatch(/^bafkrei/) })
test('should use binary encoding for base64 strings', () => { // This test verifies we're using the correct encoding method // For base64 strings, 'binary' encoding ensures each character becomes exactly one byte const content = Buffer.from('Test content') const gzipped = compressFile(content) const base64String = gzipped.toString('base64')
// Using binary encoding (what we use in production) const base64Content = Buffer.from(base64String, 'binary')
// Verify the length matches the base64 string length expect(base64Content.length).toBe(base64String.length)
// Verify CID is computed correctly const cid = computeCID(base64Content) expect(cid).toMatch(/^bafkrei/) })})
describe('extractBlobMap', () => { test('should extract blob map from flat directory structure', () => { const mockCid = CID.parse(TEST_CID_STRING) const mockBlob = new BlobRef(mockCid, 'text/html', 100)
const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'index.html', node: { $type: 'place.wisp.fs#file', type: 'file', blob: mockBlob, }, }, ], }
const blobMap = extractBlobMap(directory)
expect(blobMap.size).toBe(1) expect(blobMap.has('index.html')).toBe(true)
const entry = blobMap.get('index.html') expect(entry?.cid).toBe(TEST_CID_STRING) expect(entry?.blobRef).toBe(mockBlob) })
test('should extract blob map from nested directory structure', () => { const mockCid1 = CID.parse(TEST_CID_STRING) const mockCid2 = CID.parse('bafkreiabaduc3573q6snt2xgxzpglwuaojkzflocncrh2vj5j3jykdpqhi')
const mockBlob1 = new BlobRef(mockCid1, 'text/html', 100) const mockBlob2 = new BlobRef(mockCid2, 'text/css', 50)
const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'index.html', node: { $type: 'place.wisp.fs#file', type: 'file', blob: mockBlob1, }, }, { name: 'assets', node: { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'styles.css', node: { $type: 'place.wisp.fs#file', type: 'file', blob: mockBlob2, }, }, ], }, }, ], }
const blobMap = extractBlobMap(directory)
expect(blobMap.size).toBe(2) expect(blobMap.has('index.html')).toBe(true) expect(blobMap.has('assets/styles.css')).toBe(true)
expect(blobMap.get('index.html')?.cid).toBe(TEST_CID_STRING) expect(blobMap.get('assets/styles.css')?.cid).toBe('bafkreiabaduc3573q6snt2xgxzpglwuaojkzflocncrh2vj5j3jykdpqhi') })
test('should handle deeply nested directory structures', () => { const mockCid = CID.parse(TEST_CID_STRING) const mockBlob = new BlobRef(mockCid, 'text/javascript', 200)
const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'src', node: { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'lib', node: { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'utils.js', node: { $type: 'place.wisp.fs#file', type: 'file', blob: mockBlob, }, }, ], }, }, ], }, }, ], }
const blobMap = extractBlobMap(directory)
expect(blobMap.size).toBe(1) expect(blobMap.has('src/lib/utils.js')).toBe(true) expect(blobMap.get('src/lib/utils.js')?.cid).toBe(TEST_CID_STRING) })
test('should handle empty directory', () => { const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [], }
const blobMap = extractBlobMap(directory)
expect(blobMap.size).toBe(0) })
test('should correctly extract CID from BlobRef instances (not plain objects)', () => { // This test verifies the fix: AT Protocol SDK returns BlobRef instances, // not plain objects with $type and $link properties const mockCid = CID.parse(TEST_CID_STRING) const mockBlob = new BlobRef(mockCid, 'application/octet-stream', 500)
const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'test.bin', node: { $type: 'place.wisp.fs#file', type: 'file', blob: mockBlob, }, }, ], }
const blobMap = extractBlobMap(directory)
// The fix: we call .toString() on the CID instance instead of accessing $link expect(blobMap.get('test.bin')?.cid).toBe(TEST_CID_STRING) expect(blobMap.get('test.bin')?.blobRef.ref.toString()).toBe(TEST_CID_STRING) })
test('should handle multiple files in same directory', () => { const mockCid1 = CID.parse(TEST_CID_STRING) const mockCid2 = CID.parse('bafkreiabaduc3573q6snt2xgxzpglwuaojkzflocncrh2vj5j3jykdpqhi') const mockCid3 = CID.parse('bafkreieb3ixgchss44kw7xiavnkns47emdfsqbhcdfluo3p6n3o53fl3vq')
const mockBlob1 = new BlobRef(mockCid1, 'image/png', 1000) const mockBlob2 = new BlobRef(mockCid2, 'image/png', 2000) const mockBlob3 = new BlobRef(mockCid3, 'image/png', 3000)
const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'images', node: { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'logo.png', node: { $type: 'place.wisp.fs#file', type: 'file', blob: mockBlob1, }, }, { name: 'banner.png', node: { $type: 'place.wisp.fs#file', type: 'file', blob: mockBlob2, }, }, { name: 'icon.png', node: { $type: 'place.wisp.fs#file', type: 'file', blob: mockBlob3, }, }, ], }, }, ], }
const blobMap = extractBlobMap(directory)
expect(blobMap.size).toBe(3) expect(blobMap.has('images/logo.png')).toBe(true) expect(blobMap.has('images/banner.png')).toBe(true) expect(blobMap.has('images/icon.png')).toBe(true) })
test('should handle mixed directory and file structure', () => { const mockCid1 = CID.parse(TEST_CID_STRING) const mockCid2 = CID.parse('bafkreiabaduc3573q6snt2xgxzpglwuaojkzflocncrh2vj5j3jykdpqhi') const mockCid3 = CID.parse('bafkreieb3ixgchss44kw7xiavnkns47emdfsqbhcdfluo3p6n3o53fl3vq')
const directory: Directory = { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'index.html', node: { $type: 'place.wisp.fs#file', type: 'file', blob: new BlobRef(mockCid1, 'text/html', 100), }, }, { name: 'assets', node: { $type: 'place.wisp.fs#directory', type: 'directory', entries: [ { name: 'styles.css', node: { $type: 'place.wisp.fs#file', type: 'file', blob: new BlobRef(mockCid2, 'text/css', 50), }, }, ], }, }, { name: 'README.md', node: { $type: 'place.wisp.fs#file', type: 'file', blob: new BlobRef(mockCid3, 'text/markdown', 200), }, }, ], }
const blobMap = extractBlobMap(directory)
expect(blobMap.size).toBe(3) expect(blobMap.has('index.html')).toBe(true) expect(blobMap.has('assets/styles.css')).toBe(true) expect(blobMap.has('README.md')).toBe(true) })})