72eba67af1
* Minor adaptation of VerifyCidTransform implementation * refactor: factorize content-encoding negotiation into new lib * bsky: Use undici to stream blob * fixup! bsky: Use undici to stream blob * disable ssrf bsky protection in dev-env * remove http requests to self to host "/img/" * drop axios from tests * fixes * fix tests * reviex changes * properly handle HEAD requests * handle client disconnection * fix tests * drop unrelated change * tidy * tidy * tidy * remove axios from dev-env * remove axios from identity package * use undici 6 * remove axios dependency from ozone * tidy * remove axios from PDS package * avoid killing bsky-pds connections * improve debugging data * Better handle invalid CID * tidy * tidy * refactor "allFulfilled" util in @atproto/common * tidy --------- Co-authored-by: devin ivy <devinivy@gmail.com>
146 lines
4.1 KiB
TypeScript
146 lines
4.1 KiB
TypeScript
import { AtpAgent, AtUri } from '@atproto/api'
|
|
import { randomStr } from '@atproto/crypto'
|
|
import { SeedClient, TestNetworkNoAppView } from '@atproto/dev-env'
|
|
import express from 'express'
|
|
import { finished } from 'node:stream/promises'
|
|
import { request } from 'undici'
|
|
import { handler as errorHandler } from '../src/error'
|
|
import { startServer } from './_util'
|
|
import basicSeed from './seeds/basic'
|
|
|
|
describe('server', () => {
|
|
let network: TestNetworkNoAppView
|
|
let agent: AtpAgent
|
|
let sc: SeedClient
|
|
let alice: string
|
|
|
|
beforeAll(async () => {
|
|
network = await TestNetworkNoAppView.create({
|
|
dbPostgresSchema: 'server',
|
|
pds: {
|
|
version: '0.0.0',
|
|
},
|
|
})
|
|
agent = network.pds.getClient()
|
|
sc = network.getSeedClient()
|
|
await basicSeed(sc)
|
|
alice = sc.dids.alice
|
|
})
|
|
|
|
afterAll(async () => {
|
|
await network.close()
|
|
})
|
|
|
|
it('preserves 404s.', async () => {
|
|
const res = await fetch(`${network.pds.url}/unknown`)
|
|
expect(res.status).toEqual(404)
|
|
})
|
|
|
|
it('error handler turns unknown errors into 500s.', async () => {
|
|
const app = express()
|
|
.get('/oops', () => {
|
|
throw new Error('Oops!')
|
|
})
|
|
.use(errorHandler)
|
|
|
|
const { origin, stop } = await startServer(app)
|
|
try {
|
|
const res = await fetch(new URL(`/oops`, origin))
|
|
expect(res.status).toEqual(500)
|
|
await expect(res.json()).resolves.toEqual({
|
|
error: 'InternalServerError',
|
|
message: 'Internal Server Error',
|
|
})
|
|
} finally {
|
|
await stop()
|
|
}
|
|
})
|
|
|
|
it('limits size of json input.', async () => {
|
|
const res = await fetch(
|
|
`${network.pds.url}/xrpc/com.atproto.repo.createRecord`,
|
|
{
|
|
method: 'POST',
|
|
body: 'x'.repeat(150 * 1024), // 150kb
|
|
headers: sc.getHeaders(alice),
|
|
},
|
|
)
|
|
|
|
expect(res.status).toEqual(413)
|
|
await expect(res.json()).resolves.toEqual({
|
|
error: 'PayloadTooLargeError',
|
|
message: 'request entity too large',
|
|
})
|
|
})
|
|
|
|
it('compresses large json responses', async () => {
|
|
// first create a large record
|
|
const record = {
|
|
text: 'blahblabh',
|
|
createdAt: new Date().toISOString(),
|
|
}
|
|
for (let i = 0; i < 100; i++) {
|
|
record[randomStr(8, 'base32')] = randomStr(32, 'base32')
|
|
}
|
|
const createRes = await agent.com.atproto.repo.createRecord(
|
|
{
|
|
repo: alice,
|
|
collection: 'app.bsky.feed.post',
|
|
record,
|
|
},
|
|
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
|
|
)
|
|
const uri = new AtUri(createRes.data.uri)
|
|
|
|
const res = await request(
|
|
`${network.pds.url}/xrpc/com.atproto.repo.getRecord?repo=${uri.host}&collection=${uri.collection}&rkey=${uri.rkey}`,
|
|
{
|
|
headers: { ...sc.getHeaders(alice), 'accept-encoding': 'gzip' },
|
|
},
|
|
)
|
|
|
|
await finished(res.body.resume())
|
|
|
|
expect(res.headers['content-encoding']).toEqual('gzip')
|
|
})
|
|
|
|
it('compresses large car file responses', async () => {
|
|
const res = await request(
|
|
`${network.pds.url}/xrpc/com.atproto.sync.getRepo?did=${alice}`,
|
|
{ headers: { 'accept-encoding': 'gzip' } },
|
|
)
|
|
|
|
await finished(res.body.resume())
|
|
|
|
expect(res.headers['content-encoding']).toEqual('gzip')
|
|
})
|
|
|
|
it('does not compress small payloads', async () => {
|
|
const res = await request(`${network.pds.url}/xrpc/_health`, {
|
|
headers: { 'accept-encoding': 'gzip' },
|
|
})
|
|
|
|
await finished(res.body.resume())
|
|
|
|
expect(res.headers['content-encoding']).toBeUndefined()
|
|
})
|
|
|
|
it('healthcheck succeeds when database is available.', async () => {
|
|
const res = await fetch(`${network.pds.url}/xrpc/_health`)
|
|
expect(res.status).toEqual(200)
|
|
await expect(res.json()).resolves.toEqual({ version: '0.0.0' })
|
|
})
|
|
|
|
// @TODO this is hanging for some unknown reason
|
|
it.skip('healthcheck fails when database is unavailable.', async () => {
|
|
await network.pds.ctx.accountManager.db.close()
|
|
|
|
const response = await fetch(`${network.pds.url}/xrpc/_health`)
|
|
expect(response.status).toEqual(503)
|
|
await expect(response.json()).resolves.toEqual({
|
|
version: 'unknown',
|
|
error: 'Service Unavailable',
|
|
})
|
|
})
|
|
})
|