import { describe, test, expect, beforeAll, afterAll } from 'bun:test'; import { sha256 } from '@noble/hashes/sha2.js'; import { NotFoundError, type FileEntry } from '../../src/index.js'; import type { UserReadResult, UserWriteArgs, WriteResult } from '../../src/server/io-types.js'; import type { OpContext } from '../../src/server/handler-context.js'; import { setupFileRig, type FileTestRig } from './helpers/rig.js'; interface StoredBlob { bytes: Uint8Array; contentType?: string; sha256: string; mtime: number; } function bytesToHex(b: Uint8Array): string { return Array.from(b, (x) => x.toString(16).padStart(2, '0')).join(''); } describe('Content I/O — inline read/write E2E', () => { let rig: FileTestRig; const blobs = new Map(); beforeAll(async () => { blobs.clear(); rig = await setupFileRig({ read: async (ctx: OpContext<{ path: string }>): Promise => { const blob = blobs.get(ctx.path); if (blob === undefined) throw new NotFoundError(ctx.path); return blob.contentType !== undefined ? { kind: 'inline', bytes: blob.bytes, sha256: blob.sha256, contentType: blob.contentType } : { kind: 'inline', bytes: blob.bytes, sha256: blob.sha256 }; }, write: async (ctx: OpContext): Promise => { const args = ctx.args; if (args.content.kind !== 'inline') { throw new Error('expected inline content for this test'); } if (blobs.has(args.path) && !args.overwrite) { throw new Error('exists'); } const stored: StoredBlob = { bytes: args.content.bytes, ...(args.contentType !== undefined ? { contentType: args.contentType } : {}), sha256: args.content.sha256, mtime: Date.now(), }; blobs.set(args.path, stored); const entry: FileEntry = { name: args.path.split('/').filter(Boolean).pop() ?? '', kind: 'file', size: args.content.size, mtime: stored.mtime, ...(args.contentType !== undefined ? { contentType: args.contentType } : {}), metadata: { sha256: args.content.sha256 }, }; return { entry }; }, }); }); afterAll(async () => { await rig.teardown(); }); test('write 1 KiB inline → read it back, sha256 matches', async () => { const data = new Uint8Array(1024); for (let i = 0; i < data.length; i++) data[i] = (i * 7) & 0xff; const expectedSha = bytesToHex(sha256(data)); const writeResult = await rig.fs.write('/small.bin', data, { contentType: 'application/octet-stream' }); expect(writeResult.entry.size).toBe(1024); expect(writeResult.entry.metadata.sha256).toBe(expectedSha); const readResult = await rig.fs.read('/small.bin'); expect(readResult.kind).toBe('inline'); if (readResult.kind === 'inline') { expect(readResult.bytes.byteLength).toBe(1024); expect(readResult.sha256).toBe(expectedSha); expect(readResult.contentType).toBe('application/octet-stream'); expect(Array.from(readResult.bytes)).toEqual(Array.from(data)); } }); test('write 100 KiB inline → read it back, sha256 matches', async () => { const data = new Uint8Array(100 * 1024); crypto.getRandomValues(data); const expectedSha = bytesToHex(sha256(data)); await rig.fs.write('/big.bin', data); const readResult = await rig.fs.read('/big.bin'); expect(readResult.kind).toBe('inline'); if (readResult.kind === 'inline') { expect(readResult.bytes.byteLength).toBe(100 * 1024); expect(readResult.sha256).toBe(expectedSha); expect(Array.from(readResult.bytes)).toEqual(Array.from(data)); } }); test('overwrite without flag → server-defined error; with flag → succeeds', async () => { const a = new Uint8Array([1, 2, 3]); const b = new Uint8Array([4, 5, 6]); await rig.fs.write('/dup.bin', a); await expect(rig.fs.write('/dup.bin', b)).rejects.toThrow(); await rig.fs.write('/dup.bin', b, { overwrite: true }); const out = await rig.fs.read('/dup.bin'); expect(out.kind).toBe('inline'); if (out.kind === 'inline') { expect(Array.from(out.bytes)).toEqual([4, 5, 6]); } }); test('write Blob input → inferred contentType, round-trips', async () => { const blob = new Blob([new Uint8Array([0xde, 0xad, 0xbe, 0xef])], { type: 'image/png' }); await rig.fs.write('/blobby.png', blob); const out = await rig.fs.read('/blobby.png'); expect(out.kind).toBe('inline'); if (out.kind === 'inline') { expect(out.contentType).toBe('image/png'); expect(Array.from(out.bytes)).toEqual([0xde, 0xad, 0xbe, 0xef]); } }); test('read non-existent → NotFoundError', async () => { await expect(rig.fs.read('/missing.bin')).rejects.toThrow(); }); test('inline path also handles 256 KiB exactly (boundary)', async () => { const data = new Uint8Array(256 * 1024); crypto.getRandomValues(data); const expectedSha = bytesToHex(sha256(data)); await rig.fs.write('/boundary.bin', data); const out = await rig.fs.read('/boundary.bin'); expect(out.kind).toBe('inline'); if (out.kind === 'inline') { expect(out.sha256).toBe(expectedSha); } }); });