@atproto/bsky 0.0.103 → 0.0.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/api/blob-dispatcher.d.ts +4 -0
  3. package/dist/api/blob-dispatcher.d.ts.map +1 -0
  4. package/dist/api/blob-dispatcher.js +37 -0
  5. package/dist/api/blob-dispatcher.js.map +1 -0
  6. package/dist/api/blob-resolver.d.ts +17 -8
  7. package/dist/api/blob-resolver.d.ts.map +1 -1
  8. package/dist/api/blob-resolver.js +246 -99
  9. package/dist/api/blob-resolver.js.map +1 -1
  10. package/dist/api/well-known.d.ts.map +1 -1
  11. package/dist/api/well-known.js +30 -24
  12. package/dist/api/well-known.js.map +1 -1
  13. package/dist/config.d.ts +14 -1
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +40 -5
  16. package/dist/config.js.map +1 -1
  17. package/dist/context.d.ts +3 -0
  18. package/dist/context.d.ts.map +1 -1
  19. package/dist/context.js +3 -0
  20. package/dist/context.js.map +1 -1
  21. package/dist/data-plane/server/indexing/index.js +2 -2
  22. package/dist/image/server.d.ts +7 -13
  23. package/dist/image/server.d.ts.map +1 -1
  24. package/dist/image/server.js +119 -115
  25. package/dist/image/server.js.map +1 -1
  26. package/dist/image/sharp.d.ts +11 -2
  27. package/dist/image/sharp.d.ts.map +1 -1
  28. package/dist/image/sharp.js +35 -38
  29. package/dist/image/sharp.js.map +1 -1
  30. package/dist/image/util.d.ts +6 -4
  31. package/dist/image/util.d.ts.map +1 -1
  32. package/dist/image/util.js +14 -10
  33. package/dist/image/util.js.map +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +6 -10
  37. package/dist/index.js.map +1 -1
  38. package/dist/util/http.d.ts +12 -0
  39. package/dist/util/http.d.ts.map +1 -0
  40. package/dist/util/http.js +36 -0
  41. package/dist/util/http.js.map +1 -0
  42. package/dist/util/retry.d.ts +2 -5
  43. package/dist/util/retry.d.ts.map +1 -1
  44. package/dist/util/retry.js +8 -27
  45. package/dist/util/retry.js.map +1 -1
  46. package/package.json +18 -14
  47. package/src/api/blob-dispatcher.ts +38 -0
  48. package/src/api/blob-resolver.ts +341 -106
  49. package/src/api/well-known.ts +31 -24
  50. package/src/config.ts +63 -6
  51. package/src/context.ts +6 -0
  52. package/src/data-plane/server/indexing/index.ts +3 -3
  53. package/src/image/server.ts +131 -107
  54. package/src/image/sharp.ts +48 -52
  55. package/src/image/util.ts +20 -12
  56. package/src/index.ts +8 -15
  57. package/src/util/http.ts +41 -0
  58. package/src/util/retry.ts +8 -32
  59. package/tests/_util.ts +50 -3
  60. package/tests/blob-resolver.test.ts +62 -36
  61. package/tests/image/server.test.ts +40 -32
  62. package/tests/image/sharp.test.ts +17 -4
  63. package/tests/label-hydration.test.ts +6 -6
  64. package/tests/server.test.ts +41 -56
  65. package/tsconfig.build.tsbuildinfo +1 -1
package/tests/_util.ts CHANGED
@@ -1,14 +1,18 @@
1
- import { AtUri } from '@atproto/syntax'
1
+ import { AppBskyFeedGetPostThread } from '@atproto/api'
2
2
  import { lexToJson } from '@atproto/lexicon'
3
+ import { AtUri } from '@atproto/syntax'
4
+ import { type Express } from 'express'
3
5
  import { CID } from 'multiformats/cid'
6
+ import { Server } from 'node:http'
7
+
8
+ import { AddressInfo } from 'node:net'
9
+ import { isViewRecord } from '../src/lexicon/types/app/bsky/embed/record'
4
10
  import {
5
11
  FeedViewPost,
6
12
  PostView,
7
13
  isPostView,
8
14
  isThreadViewPost,
9
15
  } from '../src/lexicon/types/app/bsky/feed/defs'
10
- import { isViewRecord } from '../src/lexicon/types/app/bsky/embed/record'
11
- import { AppBskyFeedGetPostThread } from '@atproto/api'
12
16
  import {
13
17
  LabelerView,
14
18
  isLabelerView,
@@ -234,3 +238,46 @@ export const stripViewerFromLabeler = (
234
238
  labeler.creator = stripViewer(labeler.creator)
235
239
  return stripViewer(labeler)
236
240
  }
241
+
242
+ export async function startServer(app: Express) {
243
+ return new Promise<{
244
+ origin: string
245
+ server: Server
246
+ stop: () => Promise<void>
247
+ }>((resolve, reject) => {
248
+ const onListen = () => {
249
+ const port = (server.address() as AddressInfo).port
250
+ resolve({
251
+ server,
252
+ origin: `http://localhost:${port}`,
253
+ stop: () => stopServer(server),
254
+ })
255
+ cleanup()
256
+ }
257
+ const onError = (err: Error) => {
258
+ reject(err)
259
+ cleanup()
260
+ }
261
+ const cleanup = () => {
262
+ server.removeListener('listening', onListen)
263
+ server.removeListener('error', onError)
264
+ }
265
+
266
+ const server = app
267
+ .listen(0)
268
+ .once('listening', onListen)
269
+ .once('error', onError)
270
+ })
271
+ }
272
+
273
+ export async function stopServer(server: Server) {
274
+ return new Promise<void>((resolve, reject) => {
275
+ server.close((err) => {
276
+ if (err) {
277
+ reject(err)
278
+ } else {
279
+ resolve()
280
+ }
281
+ })
282
+ })
283
+ }
@@ -1,14 +1,14 @@
1
- import axios, { AxiosInstance } from 'axios'
2
- import { CID } from 'multiformats/cid'
3
1
  import { cidForCbor, verifyCidForBytes } from '@atproto/common'
4
- import { TestNetwork, basicSeed } from '@atproto/dev-env'
5
2
  import { randomBytes } from '@atproto/crypto'
3
+ import { TestNetwork, basicSeed } from '@atproto/dev-env'
4
+ import { CID } from 'multiformats/cid'
5
+ import { request } from 'undici'
6
6
 
7
7
  describe('blob resolver', () => {
8
8
  let network: TestNetwork
9
- let client: AxiosInstance
10
9
  let fileDid: string
11
10
  let fileCid: CID
11
+ let fileSize: number
12
12
 
13
13
  beforeAll(async () => {
14
14
  network = await TestNetwork.create({
@@ -19,10 +19,7 @@ describe('blob resolver', () => {
19
19
  await network.processAll()
20
20
  fileDid = sc.dids.carol
21
21
  fileCid = sc.posts[fileDid][0].images[0].image.ref
22
- client = axios.create({
23
- baseURL: network.bsky.url,
24
- validateStatus: () => true,
25
- })
22
+ fileSize = sc.posts[fileDid][0].images[0].image.size
26
23
  })
27
24
 
28
25
  afterAll(async () => {
@@ -30,70 +27,99 @@ describe('blob resolver', () => {
30
27
  })
31
28
 
32
29
  it('resolves blob with good signature check.', async () => {
33
- const { data, status, headers } = await client.get(
34
- `/blob/${fileDid}/${fileCid.toString()}`,
35
- { responseType: 'arraybuffer' },
30
+ const response = await request(
31
+ new URL(`/blob/${fileDid}/${fileCid.toString()}`, network.bsky.url),
36
32
  )
37
- expect(status).toEqual(200)
38
- expect(headers['content-type']).toEqual('image/jpeg')
39
- expect(headers['content-security-policy']).toEqual(
33
+ expect(response.statusCode).toEqual(200)
34
+ expect(response.headers['content-type']).toEqual('image/jpeg')
35
+ expect(response.headers['content-security-policy']).toEqual(
40
36
  `default-src 'none'; sandbox`,
41
37
  )
42
- expect(headers['x-content-type-options']).toEqual('nosniff')
43
- await expect(verifyCidForBytes(fileCid, data)).resolves.toBeUndefined()
38
+ expect(response.headers['x-content-type-options']).toEqual('nosniff')
39
+
40
+ const bytes = new Uint8Array(await response.body.arrayBuffer())
41
+ await expect(verifyCidForBytes(fileCid, bytes)).resolves.toBeUndefined()
44
42
  })
45
43
 
46
44
  it('404s on missing blob.', async () => {
47
45
  const badCid = await cidForCbor({ unknown: true })
48
- const { data, status } = await client.get(
49
- `/blob/${fileDid}/${badCid.toString()}`,
46
+ const response = await request(
47
+ new URL(`/blob/${fileDid}/${badCid.toString()}`, network.bsky.url),
50
48
  )
51
- expect(status).toEqual(404)
52
- expect(data).toEqual({
49
+ expect(response.statusCode).toEqual(404)
50
+ await expect(response.body.json()).resolves.toEqual({
53
51
  error: 'NotFoundError',
54
52
  message: 'Blob not found',
55
53
  })
56
54
  })
57
55
 
58
56
  it('404s on missing identity.', async () => {
59
- const { data, status } = await client.get(
60
- `/blob/did:plc:unknown/${fileCid.toString()}`,
57
+ const nonExistingDid = `did:plc:${'a'.repeat(24)}`
58
+
59
+ const response = await request(
60
+ new URL(
61
+ `/blob/${nonExistingDid}/${fileCid.toString()}`,
62
+ network.bsky.url,
63
+ ),
61
64
  )
62
- expect(status).toEqual(404)
63
- expect(data).toEqual({
65
+ expect(response.statusCode).toEqual(404)
66
+ await expect(response.body.json()).resolves.toEqual({
64
67
  error: 'NotFoundError',
65
68
  message: 'Origin not found',
66
69
  })
67
70
  })
68
71
 
69
72
  it('400s on invalid did.', async () => {
70
- const { data, status } = await client.get(
71
- `/blob/did::/${fileCid.toString()}`,
73
+ const response = await request(
74
+ new URL(`/blob/did::/${fileCid.toString()}`, network.bsky.url),
72
75
  )
73
- expect(status).toEqual(400)
74
- expect(data).toEqual({
76
+ expect(response.statusCode).toEqual(400)
77
+ await expect(response.body.json()).resolves.toEqual({
75
78
  error: 'BadRequestError',
76
79
  message: 'Invalid did',
77
80
  })
78
81
  })
79
82
 
80
83
  it('400s on invalid cid.', async () => {
81
- const { data, status } = await client.get(`/blob/${fileDid}/barfy`)
82
- expect(status).toEqual(400)
83
- expect(data).toEqual({
84
+ const response = await request(
85
+ new URL(`/blob/${fileDid}/barfy`, network.bsky.url),
86
+ )
87
+ expect(response.statusCode).toEqual(400)
88
+ await expect(response.body.json()).resolves.toEqual({
84
89
  error: 'BadRequestError',
85
90
  message: 'Invalid cid',
86
91
  })
87
92
  })
88
93
 
89
- it('fails on blob with bad signature check.', async () => {
94
+ it('400s on missing file.', async () => {
95
+ const missingCid = await cidForCbor('missing-file')
96
+
97
+ const response = await request(
98
+ new URL(`/blob/${fileDid}/${missingCid}`, network.bsky.url),
99
+ )
100
+ expect(response.statusCode).toEqual(404)
101
+ await expect(response.body.json()).resolves.toEqual({
102
+ error: 'NotFoundError',
103
+ message: 'Blob not found',
104
+ })
105
+ })
106
+
107
+ it('replaces the file with invalid bytes.', async () => {
90
108
  await network.pds.ctx.blobstore(fileDid).delete(fileCid)
91
109
  await network.pds.ctx
92
110
  .blobstore(fileDid)
93
- .putPermanent(fileCid, randomBytes(100))
94
- const tryGetBlob = client.get(`/blob/${fileDid}/${fileCid.toString()}`)
95
- await expect(tryGetBlob).rejects.toThrow(
96
- 'maxContentLength size of -1 exceeded',
111
+ .putPermanent(fileCid, randomBytes(fileSize))
112
+ })
113
+
114
+ it('fails to fetch bytes on blob with bad signature check.', async () => {
115
+ const response = await request(
116
+ new URL(`/blob/${fileDid}/${fileCid.toString()}`, network.bsky.url),
97
117
  )
118
+
119
+ expect(response.statusCode).toEqual(404)
120
+ await expect(response.body.json()).resolves.toEqual({
121
+ error: 'NotFoundError',
122
+ message: 'Bad cid check',
123
+ })
98
124
  })
99
125
  })
@@ -1,13 +1,12 @@
1
- import axios, { AxiosInstance } from 'axios'
2
- import { CID } from 'multiformats/cid'
3
1
  import { cidForCbor } from '@atproto/common'
4
2
  import { TestNetwork, basicSeed } from '@atproto/dev-env'
3
+ import { CID } from 'multiformats/cid'
4
+ import { Readable } from 'node:stream'
5
5
  import { getInfo } from '../../src/image/sharp'
6
6
  import { ImageUriBuilder } from '../../src/image/uri'
7
7
 
8
8
  describe('image processing server', () => {
9
9
  let network: TestNetwork
10
- let client: AxiosInstance
11
10
  let fileDid: string
12
11
  let fileCid: CID
13
12
 
@@ -20,10 +19,6 @@ describe('image processing server', () => {
20
19
  await network.processAll()
21
20
  fileDid = sc.dids.carol
22
21
  fileCid = sc.posts[fileDid][0].images[0].image.ref
23
- client = axios.create({
24
- baseURL: `${network.bsky.url}/img`,
25
- validateStatus: () => true,
26
- })
27
22
  })
28
23
 
29
24
  afterAll(async () => {
@@ -31,16 +26,19 @@ describe('image processing server', () => {
31
26
  })
32
27
 
33
28
  it('processes image from blob resolver.', async () => {
34
- const res = await client.get(
35
- ImageUriBuilder.getPath({
36
- preset: 'feed_fullsize',
37
- did: fileDid,
38
- cid: fileCid.toString(),
39
- }),
40
- { responseType: 'stream' },
29
+ const res = await fetch(
30
+ new URL(
31
+ `/img${ImageUriBuilder.getPath({
32
+ preset: 'feed_fullsize',
33
+ did: fileDid,
34
+ cid: fileCid.toString(),
35
+ })}`,
36
+ network.bsky.url,
37
+ ),
41
38
  )
42
39
 
43
- const info = await getInfo(res.data)
40
+ const bytes = new Uint8Array(await res.arrayBuffer())
41
+ const info = await getInfo(Readable.from([bytes]))
44
42
 
45
43
  expect(info).toEqual({
46
44
  height: 580,
@@ -48,7 +46,7 @@ describe('image processing server', () => {
48
46
  size: 127578,
49
47
  mime: 'image/jpeg',
50
48
  })
51
- expect(res.headers).toEqual(
49
+ expect(Object.fromEntries(res.headers)).toEqual(
52
50
  expect.objectContaining({
53
51
  'content-type': 'image/jpeg',
54
52
  'cache-control': 'public, max-age=31536000',
@@ -63,26 +61,36 @@ describe('image processing server', () => {
63
61
  did: fileDid,
64
62
  cid: fileCid.toString(),
65
63
  })
66
- const res1 = await client.get(path, { responseType: 'arraybuffer' })
67
- expect(res1.headers['x-cache']).toEqual('miss')
68
- const res2 = await client.get(path, { responseType: 'arraybuffer' })
69
- expect(res2.headers['x-cache']).toEqual('hit')
70
- const res3 = await client.get(path, { responseType: 'arraybuffer' })
71
- expect(res3.headers['x-cache']).toEqual('hit')
72
- expect(Buffer.compare(res1.data, res2.data)).toEqual(0)
73
- expect(Buffer.compare(res1.data, res3.data)).toEqual(0)
64
+ const url = new URL(`/img${path}`, network.bsky.url)
65
+
66
+ const res1 = await fetch(url)
67
+ expect(res1.headers.get('x-cache')).toEqual('miss')
68
+ const bytes1 = new Uint8Array(await res1.arrayBuffer())
69
+ const res2 = await fetch(url)
70
+ expect(res2.headers.get('x-cache')).toEqual('hit')
71
+ const bytes2 = new Uint8Array(await res2.arrayBuffer())
72
+ const res3 = await fetch(url)
73
+ expect(res3.headers.get('x-cache')).toEqual('hit')
74
+ const bytes3 = new Uint8Array(await res3.arrayBuffer())
75
+ expect(Buffer.compare(bytes1, bytes2)).toEqual(0)
76
+ expect(Buffer.compare(bytes1, bytes3)).toEqual(0)
74
77
  })
75
78
 
76
79
  it('errors on missing file.', async () => {
77
80
  const missingCid = await cidForCbor('missing-file')
78
- const res = await client.get(
79
- ImageUriBuilder.getPath({
80
- preset: 'feed_fullsize',
81
- did: fileDid,
82
- cid: missingCid.toString(),
83
- }),
84
- )
81
+
82
+ const path = ImageUriBuilder.getPath({
83
+ preset: 'feed_fullsize',
84
+ did: fileDid,
85
+ cid: missingCid.toString(),
86
+ })
87
+
88
+ const url = new URL(`/img${path}`, network.bsky.url)
89
+
90
+ const res = await fetch(url)
85
91
  expect(res.status).toEqual(404)
86
- expect(res.data).toEqual({ message: 'Image not found' })
92
+ await expect(res.json()).resolves.toMatchObject({
93
+ message: 'Blob not found',
94
+ })
87
95
  })
88
96
  })
@@ -1,5 +1,11 @@
1
- import { createReadStream } from 'fs'
2
- import { Options, getInfo, resize } from '../../src/image/sharp'
1
+ import { createReadStream } from 'node:fs'
2
+ import { pipeline } from 'node:stream/promises'
3
+ import {
4
+ Options,
5
+ createImageProcessor,
6
+ createImageUpscaler,
7
+ getInfo,
8
+ } from '../../src/image/sharp'
3
9
 
4
10
  describe('sharp image processor', () => {
5
11
  it('scales up to cover.', async () => {
@@ -179,7 +185,14 @@ describe('sharp image processor', () => {
179
185
 
180
186
  async function processFixture(fixture: string, options: Options) {
181
187
  const image = createReadStream(`../dev-env/assets/${fixture}`)
182
- const resized = await resize(image, options)
183
- return await getInfo(resized)
188
+ const upscaler = createImageUpscaler(options)
189
+ const processor = createImageProcessor(options)
190
+
191
+ const [info] = await Promise.all([
192
+ getInfo(processor),
193
+ pipeline([image, upscaler, processor]),
194
+ ])
195
+
196
+ return info
184
197
  }
185
198
  })
@@ -1,6 +1,5 @@
1
1
  import { AtpAgent } from '@atproto/api'
2
2
  import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env'
3
- import axios from 'axios'
4
3
 
5
4
  describe('label hydration', () => {
6
5
  let network: TestNetwork
@@ -81,15 +80,16 @@ describe('label hydration', () => {
81
80
  })
82
81
 
83
82
  it('defaults to service labels when no labeler header is provided', async () => {
84
- const res = await axios.get(
83
+ const res = await fetch(
85
84
  `${network.pds.url}/xrpc/app.bsky.actor.getProfile?actor=${carol}`,
86
85
  { headers: sc.getHeaders(bob) },
87
86
  )
88
- expect(res.data.labels?.length).toBe(1)
89
- expect(res.data.labels?.[0].src).toBe(labelerDid)
90
- expect(res.data.labels?.[0].val).toBe('misleading')
87
+ const data = await res.json()
88
+ expect(data.labels?.length).toBe(1)
89
+ expect(data.labels?.[0].src).toBe(labelerDid)
90
+ expect(data.labels?.[0].val).toBe('misleading')
91
91
 
92
- expect(res.headers['atproto-content-labelers']).toEqual(
92
+ expect(res.headers.get('atproto-content-labelers')).toEqual(
93
93
  network.bsky.ctx.cfg.labelsFromIssuerDids
94
94
  .map((did) => `${did};redact`)
95
95
  .join(','),
@@ -1,9 +1,11 @@
1
- import { AddressInfo } from 'net'
2
- import express from 'express'
3
- import axios, { AxiosError } from 'axios'
4
1
  import { TestNetwork, basicSeed } from '@atproto/dev-env'
2
+ import express from 'express'
3
+ import { once } from 'node:events'
4
+ import { AddressInfo } from 'node:net'
5
+ import { finished } from 'node:stream/promises'
6
+ import { request } from 'undici'
5
7
  import { handler as errorHandler } from '../src/error'
6
- import { once } from 'events'
8
+ import { startServer } from './_util'
7
9
 
8
10
  describe('server', () => {
9
11
  let network: TestNetwork
@@ -24,8 +26,8 @@ describe('server', () => {
24
26
  })
25
27
 
26
28
  it('preserves 404s.', async () => {
27
- const promise = axios.get(`${network.bsky.url}/unknown`)
28
- await expect(promise).rejects.toThrow('failed with status code 404')
29
+ const response = await fetch(`${network.bsky.url}/unknown`)
30
+ expect(response.status).toEqual(404)
29
31
  })
30
32
 
31
33
  it('error handler turns unknown errors into 500s.', async () => {
@@ -34,99 +36,82 @@ describe('server', () => {
34
36
  throw new Error('Oops!')
35
37
  })
36
38
  app.use(errorHandler)
37
- const srv = app.listen()
38
- const port = (srv.address() as AddressInfo).port
39
- const promise = axios.get(`http://localhost:${port}/oops`)
40
- await expect(promise).rejects.toThrow('failed with status code 500')
41
- srv.close()
39
+ const { origin, stop } = await startServer(app)
42
40
  try {
43
- await promise
44
- } catch (err: unknown) {
45
- const axiosError = err as AxiosError
46
- expect(axiosError.response?.status).toEqual(500)
47
- expect(axiosError.response?.data).toEqual({
41
+ const response = await fetch(new URL(`/oops`, origin))
42
+ expect(response.status).toEqual(500)
43
+ await expect(response.json()).resolves.toEqual({
48
44
  error: 'InternalServerError',
49
45
  message: 'Internal Server Error',
50
46
  })
47
+ } finally {
48
+ await stop()
51
49
  }
52
50
  })
53
51
 
54
52
  it('healthcheck succeeds when database is available.', async () => {
55
- const { data, status } = await axios.get(`${network.bsky.url}/xrpc/_health`)
56
- expect(status).toEqual(200)
57
- expect(data).toEqual({ version: 'unknown' })
53
+ const response = await fetch(`${network.bsky.url}/xrpc/_health`)
54
+ expect(response.status).toEqual(200)
55
+ await expect(response.json()).resolves.toEqual({ version: 'unknown' })
58
56
  })
59
57
 
60
58
  // TODO(bsky) check on a different endpoint that accepts json, currently none.
61
59
  it.skip('limits size of json input.', async () => {
62
- let error: AxiosError
63
- try {
64
- await axios.post(
65
- `${network.bsky.url}/xrpc/com.atproto.repo.createRecord`,
66
- {
67
- data: 'x'.repeat(100 * 1024), // 100kb
68
- },
69
- // { headers: sc.getHeaders(alice) },
70
- )
71
- throw new Error('Request should have failed')
72
- } catch (err) {
73
- if (axios.isAxiosError(err)) {
74
- error = err
75
- } else {
76
- throw err
77
- }
78
- }
79
- expect(error.response?.status).toEqual(413)
80
- expect(error.response?.data).toEqual({
60
+ const response = await fetch(
61
+ `${network.bsky.url}/xrpc/com.atproto.repo.createRecord`,
62
+ {
63
+ body: 'x'.repeat(100 * 1024), // 100kb
64
+ },
65
+ )
66
+
67
+ expect(response.status).toEqual(413)
68
+ await expect(response.json()).resolves.toEqual({
81
69
  error: 'PayloadTooLargeError',
82
70
  message: 'request entity too large',
83
71
  })
84
72
  })
85
73
 
86
74
  it('compresses large json responses', async () => {
87
- const res = await axios.get(
75
+ const res = await request(
88
76
  `${network.bsky.url}/xrpc/app.bsky.feed.getTimeline`,
89
77
  {
90
- decompress: false,
91
78
  headers: {
92
79
  ...(await network.serviceHeaders(alice, 'app.bsky.feed.getTimeline')),
93
80
  'accept-encoding': 'gzip',
94
81
  },
95
82
  },
96
83
  )
84
+
85
+ await finished(res.body.resume())
86
+
97
87
  expect(res.headers['content-encoding']).toEqual('gzip')
98
88
  })
99
89
 
100
90
  it('does not compress small payloads', async () => {
101
- const res = await axios.get(`${network.bsky.url}/xrpc/_health`, {
102
- decompress: false,
91
+ const res = await request(`${network.bsky.url}/xrpc/_health`, {
103
92
  headers: { 'accept-encoding': 'gzip' },
104
93
  })
94
+
95
+ await finished(res.body.resume())
96
+
105
97
  expect(res.headers['content-encoding']).toBeUndefined()
106
98
  })
107
99
 
108
100
  it('healthcheck fails when dataplane is unavailable.', async () => {
109
101
  const { port } = network.bsky.dataplane.server.address() as AddressInfo
110
102
  await network.bsky.dataplane.destroy()
111
- let error: AxiosError
103
+
112
104
  try {
113
- await axios.get(`${network.bsky.url}/xrpc/_health`)
114
- throw new Error('Healthcheck should have failed')
115
- } catch (err) {
116
- if (axios.isAxiosError(err)) {
117
- error = err
118
- } else {
119
- throw err
120
- }
105
+ const response = await fetch(`${network.bsky.url}/xrpc/_health`)
106
+ expect(response.status).toEqual(503)
107
+ await expect(response.json()).resolves.toEqual({
108
+ version: 'unknown',
109
+ error: 'Service Unavailable',
110
+ })
121
111
  } finally {
122
112
  // restart dataplane server to allow test suite to cleanup
123
113
  network.bsky.dataplane.server.listen(port)
124
114
  await once(network.bsky.dataplane.server, 'listening')
125
115
  }
126
- expect(error.response?.status).toEqual(503)
127
- expect(error.response?.data).toEqual({
128
- version: 'unknown',
129
- error: 'Service Unavailable',
130
- })
131
116
  })
132
117
  })