atproto/packages/pds/tests/server.test.ts
Matthieu Sieben 72eba67af1
Drop axios dependency (#3177)
* 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>
2025-01-06 18:34:11 +01:00

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',
})
})
})