|
|
@@ -24,13 +24,11 @@ describe('workspaceFiles.readAll', () => {
|
|
|
expect(await harness.endpoint().readAll(harness.scope, 'empty', signal())).toMatchObject({ data: '', offset: 0, eof: true, bytes: 0 })
|
|
|
})
|
|
|
|
|
|
- it.each(['workspace', 'outside'] as const)('rejects a known oversized %s file before reading bytes', async (location) => {
|
|
|
+ it.each(['workspace', 'outside'] as const)('maps the filesystem size refusal for a %s file', async (location) => {
|
|
|
const path = join(harness[location], 'large')
|
|
|
await writeFile(path, 'abcde')
|
|
|
- const read = vi.spyOn(harness.ctx.fs, 'readByteRange')
|
|
|
expect(await failureOf(harness.endpoint({ maxFileBytes: 4 }).readAll(harness.scope, path, signal())))
|
|
|
.toEqual({ code: 'workspace-file/too-large', details: { path, limit: 4 } })
|
|
|
- expect(read).not.toHaveBeenCalled()
|
|
|
})
|
|
|
|
|
|
it.each([undefined, 1])('checks the actual bytes when stat reports %s', async (size) => {
|
|
|
@@ -51,15 +49,41 @@ describe('workspaceFiles.readAll', () => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
-it('applies the smaller Host reservation before reading a complete file', async () => {
|
|
|
+it.each([[8, 3], [3, 8]])('applies the smaller cap from deployment %s and Host reservation %s', async (maxFileBytes, reservation) => {
|
|
|
await writeFile(join(harness.workspace, 'reserved'), '1234')
|
|
|
- const read = vi.spyOn(harness.ctx.fs, 'readByteRange')
|
|
|
- const files = harness.endpoint({ maxFileBytes: 8 })
|
|
|
- expect(await failureOf(files.readAllBounded(harness.scope, 'reserved', 3, signal())))
|
|
|
+ const read = vi.spyOn(harness.ctx.fs, 'readBytes')
|
|
|
+ const files = harness.endpoint({ maxFileBytes })
|
|
|
+ const caller = signal()
|
|
|
+ expect(await failureOf(files.readAllBounded(harness.scope, 'reserved', reservation, caller)))
|
|
|
.toEqual({ code: 'workspace-file/too-large', details: { path: 'reserved', limit: 3 } })
|
|
|
- expect(read).not.toHaveBeenCalled()
|
|
|
- expect((await files.readAllBounded(harness.scope, 'reserved', 4, signal())).bytes).toBe(4)
|
|
|
- expect(read.mock.calls[0]![1]).toEqual({ offset: 0, length: 5 })
|
|
|
+ expect(read).toHaveBeenCalledExactlyOnceWith(expect.anything(), caller, 3)
|
|
|
+})
|
|
|
+
|
|
|
+it('returns the filesystem byte array directly to Host consumers at the exact cap', async () => {
|
|
|
+ await writeFile(join(harness.workspace, 'reserved'), '1234')
|
|
|
+ const read = vi.spyOn(harness.ctx.fs, 'readBytes')
|
|
|
+ const result = await harness.endpoint({ maxFileBytes: 8 }).readAllBounded(harness.scope, 'reserved', 4, signal())
|
|
|
+ expect(result.data).toBe(await read.mock.results[0]!.value)
|
|
|
+ expect(result.data).toEqual(Buffer.from('1234'))
|
|
|
+ expect(result.bytes).toBe(4)
|
|
|
+ expect(result).not.toHaveProperty('offset')
|
|
|
+ expect(result).not.toHaveProperty('eof')
|
|
|
+})
|
|
|
+
|
|
|
+it.each([new Error('Read denied'), new DOMException('Cancelled', 'AbortError'), null, 'backend failure', { code: 'FS_NOT_FOUND' }])(
|
|
|
+ 'preserves non-size filesystem failures: %s', async (failure) => {
|
|
|
+ await writeFile(join(harness.workspace, 'file'), '1234')
|
|
|
+ vi.spyOn(harness.ctx.fs, 'readBytes').mockRejectedValueOnce(failure)
|
|
|
+ await expect(harness.endpoint().readAllBounded(harness.scope, 'file', 4, signal())).rejects.toBe(failure)
|
|
|
+ },
|
|
|
+)
|
|
|
+
|
|
|
+it('maps a size refusal by code without requiring a shared error class', async () => {
|
|
|
+ await writeFile(join(harness.workspace, 'file'), '1234')
|
|
|
+ const failure = { code: 'FS_TOO_LARGE' }
|
|
|
+ vi.spyOn(harness.ctx.fs, 'readBytes').mockRejectedValueOnce(failure)
|
|
|
+ await expect(harness.endpoint().readAllBounded(harness.scope, 'file', 4, signal()))
|
|
|
+ .rejects.toMatchObject({ code: 'workspace-file/too-large', details: { path: 'file', limit: 4 }, cause: failure })
|
|
|
})
|
|
|
|
|
|
describe('workspaceFiles.readRelated', () => {
|