chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
import { describe, expect } from 'vitest'
|
||||
import { NotFoundError, VolumeError, VolumeFileType } from '../../src'
|
||||
import { volumeTest } from '../setup'
|
||||
|
||||
describe('Volume File Operations', () => {
|
||||
describe('writeFile and readFile', () => {
|
||||
volumeTest('should write and read a text file', async ({ volume }) => {
|
||||
const path = '/test.txt'
|
||||
const content = 'Hello, World!'
|
||||
|
||||
const stat = await volume.writeFile(path, content)
|
||||
expect(stat.name).toBe('test.txt')
|
||||
expect(stat.type).toBe(VolumeFileType.FILE)
|
||||
expect(stat.path).toBe(path)
|
||||
expect(stat.atime).toBeInstanceOf(Date)
|
||||
expect(stat.mtime).toBeInstanceOf(Date)
|
||||
expect(stat.ctime).toBeInstanceOf(Date)
|
||||
|
||||
const readContent = await volume.readFile(path, { format: 'text' })
|
||||
expect(readContent).toBe(content)
|
||||
})
|
||||
|
||||
volumeTest(
|
||||
'should write and read a file with bytes format',
|
||||
async ({ volume }) => {
|
||||
const path = '/test-bytes.txt'
|
||||
const content = 'Test bytes content'
|
||||
const contentBytes = new TextEncoder().encode(content)
|
||||
|
||||
await volume.writeFile(path, contentBytes.buffer)
|
||||
const readBytes = await volume.readFile(path, { format: 'bytes' })
|
||||
|
||||
expect(readBytes).toEqual(contentBytes)
|
||||
}
|
||||
)
|
||||
|
||||
volumeTest('should write and read binary data', async ({ volume }) => {
|
||||
const path = '/binary.bin'
|
||||
const binaryData = new Uint8Array([0, 1, 2, 3, 4])
|
||||
|
||||
await volume.writeFile(path, binaryData.buffer)
|
||||
const readBytes = await volume.readFile(path, { format: 'bytes' })
|
||||
|
||||
expect(readBytes).toEqual(binaryData)
|
||||
})
|
||||
|
||||
volumeTest(
|
||||
'should write and read a file with blob format',
|
||||
async ({ volume }) => {
|
||||
const path = '/test-blob.txt'
|
||||
const content = 'Test blob content'
|
||||
const blob = new Blob([content], { type: 'text/plain' })
|
||||
|
||||
await volume.writeFile(path, blob)
|
||||
const readBlob = await volume.readFile(path, { format: 'blob' })
|
||||
|
||||
expect(await readBlob.text()).toBe(content)
|
||||
}
|
||||
)
|
||||
|
||||
volumeTest(
|
||||
'should write and read a file from a ReadableStream',
|
||||
async ({ volume }) => {
|
||||
const path = '/test-stream.txt'
|
||||
const content = 'Test stream content'
|
||||
const stream = new Blob([content]).stream()
|
||||
|
||||
await volume.writeFile(path, stream)
|
||||
const readContent = await volume.readFile(path, { format: 'text' })
|
||||
|
||||
expect(readContent).toBe(content)
|
||||
}
|
||||
)
|
||||
|
||||
volumeTest('should write and read an empty file', async ({ volume }) => {
|
||||
const path = '/empty.txt'
|
||||
const content = ''
|
||||
|
||||
await volume.writeFile(path, content)
|
||||
const readContent = await volume.readFile(path, { format: 'text' })
|
||||
|
||||
expect(readContent).toBe(content)
|
||||
})
|
||||
|
||||
volumeTest(
|
||||
'should read an empty file in all formats',
|
||||
async ({ volume }) => {
|
||||
const path = '/empty-formats.txt'
|
||||
await volume.writeFile(path, '')
|
||||
|
||||
const bytes = await volume.readFile(path, { format: 'bytes' })
|
||||
expect(bytes).toBeInstanceOf(Uint8Array)
|
||||
expect(bytes.length).toBe(0)
|
||||
|
||||
const blob = await volume.readFile(path, { format: 'blob' })
|
||||
expect(blob).toBeInstanceOf(Blob)
|
||||
expect(blob.size).toBe(0)
|
||||
|
||||
const stream = await volume.readFile(path, { format: 'stream' })
|
||||
expect(stream).toBeInstanceOf(ReadableStream)
|
||||
const chunks: Uint8Array[] = []
|
||||
for await (const chunk of stream as unknown as AsyncIterable<Uint8Array>) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
expect(chunks.reduce((n, c) => n + c.length, 0)).toBe(0)
|
||||
}
|
||||
)
|
||||
|
||||
volumeTest(
|
||||
'should overwrite an existing file with force option',
|
||||
async ({ volume }) => {
|
||||
const path = '/overwrite.txt'
|
||||
const initialContent = 'Initial content'
|
||||
const newContent = 'New content'
|
||||
|
||||
await volume.writeFile(path, initialContent)
|
||||
await volume.writeFile(path, newContent, { force: true })
|
||||
const readContent = await volume.readFile(path, { format: 'text' })
|
||||
|
||||
expect(readContent).toBe(newContent)
|
||||
}
|
||||
)
|
||||
|
||||
volumeTest(
|
||||
'should throw VolumeError when writing to existing file with force: false',
|
||||
async ({ volume }) => {
|
||||
const path = '/no-overwrite.txt'
|
||||
const initialContent = 'Initial content'
|
||||
const newContent = 'New content'
|
||||
|
||||
await volume.writeFile(path, initialContent)
|
||||
await expect(
|
||||
volume.writeFile(path, newContent, { force: false })
|
||||
).rejects.toThrow(VolumeError)
|
||||
}
|
||||
)
|
||||
|
||||
volumeTest(
|
||||
'should write file with metadata (uid, gid, mode)',
|
||||
async ({ volume }) => {
|
||||
const path = '/metadata.txt'
|
||||
const content = 'File with metadata'
|
||||
|
||||
const stat = await volume.writeFile(path, content, {
|
||||
uid: 1000,
|
||||
gid: 1000,
|
||||
mode: 0o644,
|
||||
})
|
||||
|
||||
expect(stat.type).toBe(VolumeFileType.FILE)
|
||||
expect(stat.uid).toBe(1000)
|
||||
expect(stat.gid).toBe(1000)
|
||||
expect(stat.mode).toBe(0o644)
|
||||
}
|
||||
)
|
||||
|
||||
volumeTest('should write file in nested directory', async ({ volume }) => {
|
||||
const dirPath = '/nested/deep/path'
|
||||
const filePath = `${dirPath}/file.txt`
|
||||
const content = 'Nested file content'
|
||||
|
||||
await volume.makeDir(dirPath, { force: true })
|
||||
await volume.writeFile(filePath, content)
|
||||
const readContent = await volume.readFile(filePath, { format: 'text' })
|
||||
|
||||
expect(readContent).toBe(content)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInfo', () => {
|
||||
volumeTest('should get info for a file', async ({ volume }) => {
|
||||
const path = '/info-file.txt'
|
||||
const content = 'File for info test'
|
||||
|
||||
await volume.writeFile(path, content)
|
||||
const info = await volume.getInfo(path)
|
||||
|
||||
expect(info.name).toBe('info-file.txt')
|
||||
expect(info.type).toBe(VolumeFileType.FILE)
|
||||
expect(info.path).toBe(path)
|
||||
expect(info.atime).toBeInstanceOf(Date)
|
||||
expect(info.mtime).toBeInstanceOf(Date)
|
||||
expect(info.ctime).toBeInstanceOf(Date)
|
||||
})
|
||||
|
||||
volumeTest('should get info for a directory', async ({ volume }) => {
|
||||
const path = '/info-dir'
|
||||
|
||||
await volume.makeDir(path)
|
||||
const info = await volume.getInfo(path)
|
||||
|
||||
expect(info.name).toBe('info-dir')
|
||||
expect(info.type).toBe(VolumeFileType.DIRECTORY)
|
||||
expect(info.path).toBe(path)
|
||||
})
|
||||
|
||||
volumeTest(
|
||||
'should return false from exists for non-existent file',
|
||||
async ({ volume }) => {
|
||||
expect(await volume.exists('/non-existent.txt')).toBe(false)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('updateMetadata', () => {
|
||||
volumeTest('should update file metadata', async ({ volume }) => {
|
||||
const path = '/metadata-update.txt'
|
||||
await volume.writeFile(path, 'Content')
|
||||
|
||||
const updated = await volume.updateMetadata(path, {
|
||||
uid: 1001,
|
||||
gid: 1001,
|
||||
mode: 0o755,
|
||||
})
|
||||
|
||||
expect(updated.path).toBe(path)
|
||||
expect(updated.type).toBe(VolumeFileType.FILE)
|
||||
expect(updated.uid).toBe(1001)
|
||||
expect(updated.gid).toBe(1001)
|
||||
expect(updated.mode).toBe(0o755)
|
||||
})
|
||||
|
||||
volumeTest(
|
||||
'should throw NotFoundError when updating non-existent file',
|
||||
async ({ volume }) => {
|
||||
await expect(
|
||||
volume.updateMetadata('/non-existent.txt', { mode: 0o644 })
|
||||
).rejects.toThrow(NotFoundError)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('makeDir', () => {
|
||||
volumeTest('should create a directory', async ({ volume }) => {
|
||||
const path = '/test-dir'
|
||||
|
||||
const stat = await volume.makeDir(path)
|
||||
|
||||
expect(stat.type).toBe(VolumeFileType.DIRECTORY)
|
||||
expect(stat.path).toBe(path)
|
||||
expect(stat.atime).toBeInstanceOf(Date)
|
||||
expect(stat.mtime).toBeInstanceOf(Date)
|
||||
expect(stat.ctime).toBeInstanceOf(Date)
|
||||
})
|
||||
|
||||
volumeTest(
|
||||
'should create nested directories with force option',
|
||||
async ({ volume }) => {
|
||||
const path = '/nested/deep/directory'
|
||||
|
||||
const stat = await volume.makeDir(path, { force: true })
|
||||
|
||||
expect(stat.type).toBe(VolumeFileType.DIRECTORY)
|
||||
}
|
||||
)
|
||||
|
||||
volumeTest(
|
||||
'should throw VolumeError when creating existing directory with force: false',
|
||||
async ({ volume }) => {
|
||||
const path = '/existing-dir'
|
||||
|
||||
await volume.makeDir(path)
|
||||
await expect(volume.makeDir(path, { force: false })).rejects.toThrow(
|
||||
VolumeError
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
volumeTest('should create directory with metadata', async ({ volume }) => {
|
||||
const path = '/dir-with-metadata'
|
||||
|
||||
const stat = await volume.makeDir(path, {
|
||||
uid: 1000,
|
||||
gid: 1000,
|
||||
mode: 0o755,
|
||||
})
|
||||
|
||||
expect(stat.type).toBe(VolumeFileType.DIRECTORY)
|
||||
expect(stat.uid).toBe(1000)
|
||||
expect(stat.gid).toBe(1000)
|
||||
expect(stat.mode & 0o777).toBe(0o755)
|
||||
})
|
||||
})
|
||||
|
||||
describe('list', () => {
|
||||
volumeTest('should list directory contents', async ({ volume }) => {
|
||||
await volume.writeFile('/file1.txt', 'Content 1')
|
||||
await volume.writeFile('/file2.txt', 'Content 2')
|
||||
await volume.makeDir('/dir1')
|
||||
|
||||
const entries = await volume.list('/')
|
||||
|
||||
expect(entries.length).toBeGreaterThanOrEqual(3)
|
||||
const fileNames = entries.map((e) => e.name).sort()
|
||||
expect(fileNames).toContain('file1.txt')
|
||||
expect(fileNames).toContain('file2.txt')
|
||||
expect(fileNames).toContain('dir1')
|
||||
})
|
||||
|
||||
volumeTest('should list nested directory contents', async ({ volume }) => {
|
||||
await volume.makeDir('/nested', { force: true })
|
||||
await volume.writeFile('/nested/file.txt', 'Content')
|
||||
|
||||
const entries = await volume.list('/nested')
|
||||
|
||||
expect(entries.length).toBeGreaterThanOrEqual(1)
|
||||
expect(entries.some((e) => e.name === 'file.txt')).toBe(true)
|
||||
})
|
||||
|
||||
volumeTest.skip('should list with depth option', async ({ volume }) => {
|
||||
await volume.makeDir('/deep/nested/structure', { force: true })
|
||||
await volume.writeFile('/deep/nested/structure/file.txt', 'Content')
|
||||
|
||||
const entries = await volume.list('/deep', { depth: 2 })
|
||||
|
||||
expect(entries.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
volumeTest(
|
||||
'should throw NotFoundError for non-existent directory',
|
||||
async ({ volume }) => {
|
||||
await expect(volume.list('/non-existent')).rejects.toThrow(
|
||||
NotFoundError
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('remove', () => {
|
||||
volumeTest('should remove a file', async ({ volume }) => {
|
||||
const path = '/to-remove.txt'
|
||||
await volume.writeFile(path, 'Content')
|
||||
|
||||
await volume.remove(path)
|
||||
|
||||
expect(await volume.exists(path)).toBe(false)
|
||||
})
|
||||
|
||||
volumeTest('should remove a directory', async ({ volume }) => {
|
||||
const path = '/to-remove-dir'
|
||||
await volume.makeDir(path)
|
||||
|
||||
await volume.remove(path)
|
||||
|
||||
expect(await volume.exists(path)).toBe(false)
|
||||
})
|
||||
|
||||
volumeTest('should remove a directory recursively', async ({ volume }) => {
|
||||
const dirPath = '/recursive-dir'
|
||||
await volume.makeDir(`${dirPath}/nested`, { force: true })
|
||||
await volume.writeFile(`${dirPath}/nested/file.txt`, 'Content')
|
||||
|
||||
await volume.remove(dirPath)
|
||||
|
||||
expect(await volume.exists(dirPath)).toBe(false)
|
||||
})
|
||||
|
||||
volumeTest(
|
||||
'should throw NotFoundError when removing non-existent file',
|
||||
async ({ volume }) => {
|
||||
await expect(volume.remove('/non-existent.txt')).rejects.toThrow(
|
||||
NotFoundError
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('file operations lifecycle', () => {
|
||||
volumeTest(
|
||||
'should handle directory with multiple files',
|
||||
async ({ volume }) => {
|
||||
const dirPath = '/multi-file-dir'
|
||||
await volume.makeDir(dirPath)
|
||||
|
||||
const files = ['file1.txt', 'file2.txt', 'file3.txt']
|
||||
for (const fileName of files) {
|
||||
await volume.writeFile(
|
||||
`${dirPath}/${fileName}`,
|
||||
`Content of ${fileName}`
|
||||
)
|
||||
}
|
||||
|
||||
const entries = await volume.list(dirPath)
|
||||
expect(entries.length).toBeGreaterThanOrEqual(files.length)
|
||||
|
||||
for (const fileName of files) {
|
||||
const content = await volume.readFile(`${dirPath}/${fileName}`, {
|
||||
format: 'text',
|
||||
})
|
||||
expect(content).toBe(`Content of ${fileName}`)
|
||||
}
|
||||
|
||||
await volume.remove(dirPath)
|
||||
expect(await volume.exists(dirPath)).toBe(false)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,290 @@
|
||||
import { describe, it, expect, afterAll, afterEach, beforeAll } from 'vitest'
|
||||
import { http, HttpResponse } from 'msw'
|
||||
import { setupServer } from 'msw/node'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Volume, NotFoundError, VolumeError } from '../../src'
|
||||
import { VolumeConnectionConfig } from '../../src/volume/client'
|
||||
import { apiUrl } from '../setup'
|
||||
|
||||
// In-memory store for mock volumes
|
||||
const volumes = new Map<
|
||||
string,
|
||||
{ volumeID: string; name: string; token: string }
|
||||
>()
|
||||
|
||||
// In-memory store for mock volume file contents, keyed by path
|
||||
const volumeFiles = new Map<string, string>()
|
||||
|
||||
const restHandlers = [
|
||||
// POST /volumes - create (returns VolumeAndToken)
|
||||
http.post(apiUrl('/volumes'), async ({ request }) => {
|
||||
const { name } = (await request.clone().json()) as { name: string }
|
||||
const volumeID = randomUUID()
|
||||
const token = `vol-token-${randomUUID()}`
|
||||
volumes.set(volumeID, { volumeID, name, token })
|
||||
return HttpResponse.json({ volumeID, name, token }, { status: 201 })
|
||||
}),
|
||||
|
||||
// GET /volumes - list (returns Volume[] without tokens)
|
||||
http.get(apiUrl('/volumes'), () => {
|
||||
const list = Array.from(volumes.values()).map(({ volumeID, name }) => ({
|
||||
volumeID,
|
||||
name,
|
||||
}))
|
||||
return HttpResponse.json(list)
|
||||
}),
|
||||
|
||||
// GET /volumes/:volumeID - get info (returns VolumeAndToken with token)
|
||||
http.get<{ volumeID: string }>(apiUrl('/volumes/:volumeID'), ({ params }) => {
|
||||
const vol = volumes.get(params.volumeID)
|
||||
if (!vol) {
|
||||
return HttpResponse.json(
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
return HttpResponse.json(vol)
|
||||
}),
|
||||
|
||||
// DELETE /volumes/:volumeID - destroy
|
||||
http.delete<{ volumeID: string }>(
|
||||
apiUrl('/volumes/:volumeID'),
|
||||
({ params }) => {
|
||||
const existed = volumes.delete(params.volumeID)
|
||||
if (!existed) {
|
||||
return HttpResponse.json(
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
return new HttpResponse(null, { status: 204 })
|
||||
}
|
||||
),
|
||||
|
||||
// GET /volumecontent/:volumeID/file - read file content
|
||||
http.get(apiUrl('/volumecontent/:volumeID/file'), ({ request }) => {
|
||||
const path = new URL(request.url).searchParams.get('path') ?? ''
|
||||
const content = volumeFiles.get(path)
|
||||
if (content === undefined) {
|
||||
return HttpResponse.json(
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
return new HttpResponse(content.length > 0 ? content : null, {
|
||||
status: 200,
|
||||
headers: { 'Content-Length': String(content.length) },
|
||||
})
|
||||
}),
|
||||
]
|
||||
|
||||
const server = setupServer(...restHandlers)
|
||||
|
||||
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
|
||||
afterAll(() => server.close())
|
||||
afterEach(() => {
|
||||
server.resetHandlers()
|
||||
volumes.clear()
|
||||
volumeFiles.clear()
|
||||
})
|
||||
|
||||
describe('Volume CRUD', () => {
|
||||
it('should create a volume', async () => {
|
||||
const vol = await Volume.create('test-volume')
|
||||
|
||||
expect(vol).toBeDefined()
|
||||
expect(vol.volumeId).toBeDefined()
|
||||
expect(vol.name).toBe('test-volume')
|
||||
expect(vol.token).toBeDefined()
|
||||
expect(vol.token).not.toBe('undefined')
|
||||
})
|
||||
|
||||
it('should get volume info', async () => {
|
||||
const created = await Volume.create('info-volume')
|
||||
const info = await Volume.getInfo(created.volumeId)
|
||||
|
||||
expect(info.volumeId).toBe(created.volumeId)
|
||||
expect(info.name).toBe('info-volume')
|
||||
})
|
||||
|
||||
it('should list volumes', async () => {
|
||||
await Volume.create('vol-a')
|
||||
await Volume.create('vol-b')
|
||||
|
||||
const list = await Volume.list()
|
||||
|
||||
expect(list).toHaveLength(2)
|
||||
expect(list.map((v) => v.name).sort()).toEqual(['vol-a', 'vol-b'])
|
||||
})
|
||||
|
||||
it('should return empty list when no volumes exist', async () => {
|
||||
const list = await Volume.list()
|
||||
expect(list).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should destroy a volume', async () => {
|
||||
const vol = await Volume.create('to-delete')
|
||||
const result = await Volume.destroy(vol.volumeId)
|
||||
|
||||
expect(result).toBe(true)
|
||||
|
||||
// Verify it's gone
|
||||
const list = await Volume.list()
|
||||
expect(list).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should return false when destroying a non-existent volume', async () => {
|
||||
const result = await Volume.destroy('non-existent-id')
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('should throw NotFoundError when getting info of non-existent volume', async () => {
|
||||
await expect(Volume.getInfo('non-existent-id')).rejects.toThrow(
|
||||
NotFoundError
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw VolumeError for a non-2xx response without content', async () => {
|
||||
server.use(
|
||||
http.post(
|
||||
apiUrl('/volumes'),
|
||||
() =>
|
||||
new HttpResponse(null, {
|
||||
status: 500,
|
||||
headers: { 'Content-Length': '0' },
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
await expect(Volume.create('error-volume')).rejects.toThrow(VolumeError)
|
||||
})
|
||||
|
||||
it('should keep the proxy on the instance so content calls reuse it', async () => {
|
||||
const proxy = 'http://user:pass@127.0.0.1:8080'
|
||||
const vol = await Volume.create('proxy-volume', { proxy })
|
||||
|
||||
// The proxy is stored on the instance...
|
||||
expect(vol.proxy).toBe(proxy)
|
||||
|
||||
// ...and instance methods (which build a VolumeConnectionConfig with no
|
||||
// per-call proxy) pick it up rather than falling back to no proxy.
|
||||
const config = new VolumeConnectionConfig(vol)
|
||||
expect(config.proxy).toBe(proxy)
|
||||
})
|
||||
|
||||
it('should let a per-call proxy override the instance proxy', async () => {
|
||||
const vol = await Volume.create('proxy-volume', {
|
||||
proxy: 'http://127.0.0.1:8080',
|
||||
})
|
||||
|
||||
const config = new VolumeConnectionConfig(vol, {
|
||||
proxy: 'http://127.0.0.1:9090',
|
||||
})
|
||||
expect(config.proxy).toBe('http://127.0.0.1:9090')
|
||||
})
|
||||
|
||||
it('should handle full lifecycle: create, get, list, destroy', async () => {
|
||||
// Create
|
||||
const vol = await Volume.create('lifecycle-vol')
|
||||
expect(vol.name).toBe('lifecycle-vol')
|
||||
|
||||
// Get info
|
||||
const info = await Volume.getInfo(vol.volumeId)
|
||||
expect(info.name).toBe('lifecycle-vol')
|
||||
|
||||
// List - should contain the volume
|
||||
const list = await Volume.list()
|
||||
expect(list).toHaveLength(1)
|
||||
expect(list[0].volumeId).toBe(vol.volumeId)
|
||||
|
||||
// Destroy
|
||||
const destroyed = await Volume.destroy(vol.volumeId)
|
||||
expect(destroyed).toBe(true)
|
||||
|
||||
// List again - should be empty
|
||||
const listAfter = await Volume.list()
|
||||
expect(listAfter).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Volume content readFile', () => {
|
||||
it('should return content for a non-empty file in every format', async () => {
|
||||
volumeFiles.set('hello.txt', 'hello world')
|
||||
const vol = await Volume.create('content-volume')
|
||||
|
||||
const text = await vol.readFile('hello.txt')
|
||||
expect(text).toBe('hello world')
|
||||
|
||||
const bytes = await vol.readFile('hello.txt', { format: 'bytes' })
|
||||
expect(bytes).toBeInstanceOf(Uint8Array)
|
||||
expect(new TextDecoder().decode(bytes)).toBe('hello world')
|
||||
|
||||
const blob = await vol.readFile('hello.txt', { format: 'blob' })
|
||||
expect(blob).toBeInstanceOf(Blob)
|
||||
expect(await blob.text()).toBe('hello world')
|
||||
|
||||
const stream = await vol.readFile('hello.txt', { format: 'stream' })
|
||||
expect(stream).toBeInstanceOf(ReadableStream)
|
||||
expect(await new Response(stream).text()).toBe('hello world')
|
||||
})
|
||||
|
||||
it('should return empty values for an empty file in every format', async () => {
|
||||
volumeFiles.set('empty.txt', '')
|
||||
const vol = await Volume.create('content-volume')
|
||||
|
||||
const text = await vol.readFile('empty.txt')
|
||||
expect(text).toBe('')
|
||||
|
||||
const bytes = await vol.readFile('empty.txt', { format: 'bytes' })
|
||||
expect(bytes).toBeInstanceOf(Uint8Array)
|
||||
expect(bytes.length).toBe(0)
|
||||
|
||||
const blob = await vol.readFile('empty.txt', { format: 'blob' })
|
||||
expect(blob).toBeInstanceOf(Blob)
|
||||
expect(blob.size).toBe(0)
|
||||
|
||||
const stream = await vol.readFile('empty.txt', { format: 'stream' })
|
||||
expect(stream).toBeInstanceOf(ReadableStream)
|
||||
expect(await new Response(stream).text()).toBe('')
|
||||
})
|
||||
|
||||
it('should throw NotFoundError for a missing file', async () => {
|
||||
const vol = await Volume.create('content-volume')
|
||||
|
||||
await expect(vol.readFile('missing.txt')).rejects.toThrow(NotFoundError)
|
||||
})
|
||||
|
||||
it('should reject at call time for a missing file with stream format', async () => {
|
||||
const vol = await Volume.create('content-volume')
|
||||
|
||||
await expect(
|
||||
vol.readFile('missing.txt', { format: 'stream' })
|
||||
).rejects.toThrow(NotFoundError)
|
||||
})
|
||||
})
|
||||
|
||||
describe('VolumeConnectionConfig request timeout', () => {
|
||||
it('should default the request timeout to 60 seconds', async () => {
|
||||
const vol = await Volume.create('timeout-volume')
|
||||
|
||||
const config = new VolumeConnectionConfig(vol)
|
||||
expect(config.requestTimeoutMs).toBe(60_000)
|
||||
expect(config.getSignal()).toBeInstanceOf(AbortSignal)
|
||||
})
|
||||
|
||||
it('should let a per-call timeout override the default', async () => {
|
||||
const vol = await Volume.create('timeout-volume')
|
||||
|
||||
const config = new VolumeConnectionConfig(vol, { requestTimeoutMs: 1_000 })
|
||||
expect(config.requestTimeoutMs).toBe(1_000)
|
||||
})
|
||||
|
||||
it('should disable the timeout when requestTimeoutMs is 0', async () => {
|
||||
const vol = await Volume.create('timeout-volume')
|
||||
|
||||
const config = new VolumeConnectionConfig(vol, { requestTimeoutMs: 0 })
|
||||
expect(config.requestTimeoutMs).toBe(0)
|
||||
expect(config.getSignal()).toBeUndefined()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user