Skip to content

Add tests #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jan 2, 2025
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ sandbox.js
sandbox
coverage
package-lock.json
.vscode
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,60 @@ const link = server.getLink(key, {
})
```

## API

#### `const server = new BlobServer(store, options)`

`store` - Corestore instance

`options`:
```js
{
port // defaults to 49833,
host // defaults to '127.0.0.1',
token // server token
protocol // 'http' | 'https'
}
```

#### `await server.listen()`
Listen to requests

#### `const link = server.getLink(key, options)`

Generates the url used to fetch data

`key` - hypercore or hyperdrive key

`options`:
```js
{
host // custom host
port // custom port
protocol: 'http' | 'https',
filename | blob
}
```
`filename` - hyperdrive filename

`blob` - blob ID in the form of `{ blockOffset, blockLength, byteOffset, byteLength}`

When downloading blobs, you can set the `Range` header to download sections of data, implement pause/resume download functionality. Offsets are zero-indexed & inclusive

```
Range: bytes=<start>-<end>
Range: bytes=0-300
Range: bytes=2-
```

#### `await server.suspend()`

Let the instance know you wanna suspend so it can make relevant changes.

#### `await server.resume()`

Let the instance know you wanna resume from suspension. Will rebind the server etc.

## License

Apache-2.0
5 changes: 3 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ module.exports = class ServeBlobs {

_onconnection (socket) {
if (this.suspending) {
this.connection.destroy()
socket.destroy()
return
}

Expand Down Expand Up @@ -193,6 +193,7 @@ module.exports = class ServeBlobs {

async _resume () {
if (this.suspending) await this.suspending
this.suspending = null
if (this.server !== null) {
await this._closeAll(true)
this.server.ref()
Expand Down Expand Up @@ -273,7 +274,7 @@ module.exports = class ServeBlobs {
} = opts

if (!blob && !filename) {
throw new Error('Must specific a filename or blob')
throw new Error('Must specify a filename or blob')
}

const p = (protocol === 'http' && port === 80)
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
}
},
"scripts": {
"test": "standard"
"test": "standard && brittle test/*.js"
},
"dependencies": {
"bare-http1": "^4.0.2",
Expand All @@ -30,6 +30,11 @@
"z32": "^1.1.0"
},
"devDependencies": {
"brittle": "^3.7.0",
"corestore": "^6.18.4",
"hyperblobs": "^2.7.4",
"hyperdrive": "^11.13.3",
"random-access-memory": "^6.2.1",
"standard": "^17.1.2"
},
"repository": {
Expand Down
113 changes: 113 additions & 0 deletions test/blobs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const test = require('brittle')
const RAM = require('random-access-memory')
const Corestore = require('corestore')
const { testBlobServer, request, testHyperblobs } = require('./helpers')

test('can get blob from hypercore', async function (t) {
const store = new Corestore(RAM)

const blobs = testHyperblobs(t, store)

const id = await blobs.put(Buffer.from('Hello World'))

const server = testBlobServer(t, store)
await server.listen()

const res = await request(server, blobs.core.key, { blob: id })

t.is(res.status, 200)
t.is(res.data, 'Hello World')
})

test('can get blob from hypercore - multiple blocks', async function (t) {
const store = new Corestore(RAM)

const blobs = testHyperblobs(t, store)
blobs.blockSize = 4 // force multiple blocks

const id = await blobs.put(Buffer.from('Hello World'))
t.is(id.blockLength, 3) // 3 blocks

const server = testBlobServer(t, store)
await server.listen()

const res = await request(server, blobs.core.key, { blob: id })

t.is(res.status, 200)
t.is(res.data, 'Hello World')
})

test('can get a partial blob from hypercore', async function (t) {
const store = new Corestore(RAM)

const blobs = testHyperblobs(t, store)

const id = await blobs.put(Buffer.from('Hello World'))

const server = testBlobServer(t, store)
await server.listen()

const res = await request(server, blobs.core.key, { blob: id, range: 'bytes=3-7' })
t.is(res.status, 206)
t.is(res.data, 'lo Wo')
})

test('can get a partial blob from hypercore, but request the whole data', async function (t) {
const store = new Corestore(RAM)

const blobs = testHyperblobs(t, store)

const id = await blobs.put(Buffer.from('Hello World'))

const server = testBlobServer(t, store)
await server.listen()

const res = await request(server, blobs.core.key, { blob: id, range: 'bytes=0-10' })
t.is(res.status, 206)
t.is(res.data, 'Hello World')
})

test('handle out of range header end', async function (t) {
const store = new Corestore(RAM)

const blobs = testHyperblobs(t, store)

const id = await blobs.put(Buffer.from('Hello World'))

const server = testBlobServer(t, store)
await server.listen()

const res = await request(server, blobs.core.key, { blob: id, range: 'bytes=0-20' })
t.is(res.status, 206)
t.is(res.data, 'Hello World')
})

test('handle range header without end', async function (t) {
const store = new Corestore(RAM)

const blobs = testHyperblobs(t, store)

const id = await blobs.put(Buffer.from('Hello World'))

const server = testBlobServer(t, store)
await server.listen()

const res = await request(server, blobs.core.key, { blob: id, range: 'bytes=2-' })
t.is(res.status, 206)
t.is(res.data, 'llo World')
})

test('handle invalid range header', async function (t) {
const store = new Corestore(RAM)

const blobs = testHyperblobs(t, store)

const id = await blobs.put(Buffer.from('Hello World'))

const server = testBlobServer(t, store)
await server.listen()

const res = await request(server, blobs.core.key, { blob: id, range: 'testing' })
t.is(res.status, 200)
t.is(res.data, 'Hello World')
})
84 changes: 84 additions & 0 deletions test/drives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const test = require('brittle')
const RAM = require('random-access-memory')
const Corestore = require('corestore')
const { testHyperdrive, testBlobServer, request, get } = require('./helpers')

test('can get file from hyperdrive', async function (t) {
const store = new Corestore(RAM)

const drive = testHyperdrive(t, store)
await drive.put('/file.txt', 'Here')

const server = testBlobServer(t, store)
await server.listen()

const res = await request(server, drive.key, { filename: '/file.txt' })
t.is(res.status, 200)
t.is(res.data, 'Here')
})

test('404 if file not found', async function (t) {
const store = new Corestore(RAM)

const drive = testHyperdrive(t, store)
await drive.put('/file.txt', 'Here')

const server = testBlobServer(t, store)
await server.listen()

const res = await request(server, drive.key, { filename: '/testing.txt' })
t.is(res.status, 404)
t.is(res.data, '')
})

test('404 if token is invalid', async function (t) {
const store = new Corestore(RAM)

const drive = testHyperdrive(t, store)
await drive.put('/file.txt', 'Here')

const server = testBlobServer(t, store)
await server.listen()

const link = server.getLink(drive.key, { filename: '/testing.txt' })
const res = await get(link.replace('token=', 'token=breakme'))

t.is(res.status, 404)
t.is(res.data, '')
})

test('sending request while suspended', async function (t) {
const store = new Corestore(RAM)

const drive = testHyperdrive(t, store)
await drive.put('/file.txt', 'Here')

const server = testBlobServer(t, store)
await server.listen()

await server.suspend()

try {
await request(server, drive.key, { filename: '/file.txt' })
t.fail('request should fail')
} catch (err) {
t.ok(err)
}
})

test('sending request after resume', async function (t) {
const store = new Corestore(RAM)

const drive = testHyperdrive(t, store)
await drive.put('/file.txt', 'Here')

const server = testBlobServer(t, store)
await server.listen()

await server.suspend()
await server.resume()

const res = await request(server, drive.key, { filename: '/file.txt' })
t.is(res.status, 200)
t.is(res.data, 'Here')
})
68 changes: 68 additions & 0 deletions test/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const http = require('http')
const BlobServer = require('../../index.js')
const Hyperdrive = require('hyperdrive')
const Hyperblobs = require('hyperblobs')

module.exports = {
request,
get,
testBlobServer,
testHyperblobs,
testHyperdrive
}

function get (link, range = null) {
return new Promise((resolve, reject) => {
const req = http.get(link, {
headers: {
Connection: 'close',
range
}
})

req.on('error', reject)
req.on('response', function (res) {
if (res.statusCode === 307) {
// follow redirect
get(new URL(link).origin + res.headers.location).then(resolve).catch(reject)
} else {
let buf = ''
res.setEncoding('utf-8')
res.on('data', function (data) {
buf += data
})
res.on('end', function () {
resolve({ status: res.statusCode, data: buf })
})
res.on('close', function () {
resolve({ status: res.statusCode, data: buf })
})
}
})
})
}

async function request (server, key, opts) {
const link = server.getLink(key, opts)

return get(link, opts.range)
}

function testBlobServer (t, store, opts) {
const server = new BlobServer(store, opts)
t.teardown(() => server.close())
return server
}

function testHyperblobs (t, store) {
const core = store.get({ name: 'test' })
const blobs = new Hyperblobs(core)
t.teardown(() => blobs.close())
return blobs
}

function testHyperdrive (t, store) {
const drive = new Hyperdrive(store)
t.teardown(() => drive.close())
return drive
}
Loading