@atproto/xrpc-server 0.0.1 → 0.2.0

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 (46) hide show
  1. package/dist/auth.d.ts +15 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +40116 -29848
  4. package/dist/index.js.map +4 -4
  5. package/dist/server.d.ts +9 -3
  6. package/dist/src/index.d.ts +2 -0
  7. package/dist/src/logger.d.ts +2 -0
  8. package/dist/src/server.d.ts +19 -0
  9. package/dist/src/types.d.ts +115 -0
  10. package/dist/src/util.d.ts +10 -0
  11. package/dist/stream/frames.d.ts +25 -0
  12. package/dist/stream/index.d.ts +5 -0
  13. package/dist/stream/logger.d.ts +2 -0
  14. package/dist/stream/server.d.ts +11 -0
  15. package/dist/stream/stream.d.ts +5 -0
  16. package/dist/stream/subscription.d.ts +24 -0
  17. package/dist/stream/types.d.ts +64 -0
  18. package/dist/tsconfig.build.tsbuildinfo +1 -0
  19. package/dist/types.d.ts +16 -0
  20. package/dist/util.d.ts +3 -2
  21. package/package.json +14 -2
  22. package/src/auth.ts +111 -0
  23. package/src/index.ts +2 -0
  24. package/src/server.ts +148 -10
  25. package/src/stream/frames.ts +95 -0
  26. package/src/stream/index.ts +5 -0
  27. package/src/stream/logger.ts +5 -0
  28. package/src/stream/server.ts +65 -0
  29. package/src/stream/stream.ts +26 -0
  30. package/src/stream/subscription.ts +175 -0
  31. package/src/stream/types.ts +43 -0
  32. package/src/types.ts +27 -2
  33. package/src/util.ts +38 -7
  34. package/tests/_util.ts +36 -1
  35. package/tests/auth.test.ts +15 -36
  36. package/tests/bodies.test.ts +50 -9
  37. package/tests/errors.test.ts +38 -11
  38. package/tests/frames.test.ts +137 -0
  39. package/tests/ipld.test.ts +96 -0
  40. package/tests/parameters.test.ts +13 -45
  41. package/tests/procedures.test.ts +7 -3
  42. package/tests/queries.test.ts +7 -3
  43. package/tests/stream.test.ts +169 -0
  44. package/tests/subscriptions.test.ts +347 -0
  45. package/tsconfig.build.tsbuildinfo +1 -1
  46. package/tsconfig.json +1 -0
@@ -1,8 +1,10 @@
1
1
  import * as http from 'http'
2
+ import getPort from 'get-port'
2
3
  import { createServer, closeServer } from './_util'
3
4
  import * as xrpcServer from '../src'
4
5
  import xrpc, {
5
6
  Client,
7
+ ServiceClient,
6
8
  XRPCError,
7
9
  XRPCInvalidResponseError,
8
10
  } from '@atproto/xrpc'
@@ -24,6 +26,15 @@ const LEXICONS = [
24
26
  },
25
27
  },
26
28
  },
29
+ {
30
+ lexicon: 1,
31
+ id: 'io.example.throwFalsyValue',
32
+ defs: {
33
+ main: {
34
+ type: 'query',
35
+ },
36
+ },
37
+ },
27
38
  {
28
39
  lexicon: 1,
29
40
  id: 'io.example.query',
@@ -105,6 +116,9 @@ describe('Errors', () => {
105
116
  return { status: 400 }
106
117
  }
107
118
  })
119
+ server.method('io.example.throwFalsyValue', () => {
120
+ throw ''
121
+ })
108
122
  server.method('io.example.query', () => {
109
123
  return undefined
110
124
  })
@@ -115,13 +129,17 @@ describe('Errors', () => {
115
129
  server.method('io.example.procedure', () => {
116
130
  return undefined
117
131
  })
118
- const client = xrpc.service(`http://localhost:8893`)
119
132
  xrpc.addLexicons(LEXICONS)
120
133
  const badXrpc = new Client()
121
- const badClient = badXrpc.service(`http://localhost:8893`)
122
134
  badXrpc.addLexicons(MISMATCHED_LEXICONS)
135
+
136
+ let client: ServiceClient
137
+ let badClient: ServiceClient
123
138
  beforeAll(async () => {
124
- s = await createServer(8893, server)
139
+ const port = await getPort()
140
+ s = await createServer(port, server)
141
+ client = xrpc.service(`http://localhost:${port}`)
142
+ badClient = badXrpc.service(`http://localhost:${port}`)
125
143
  })
126
144
  afterAll(async () => {
127
145
  await closeServer(s)
@@ -134,7 +152,7 @@ describe('Errors', () => {
134
152
  })
135
153
  throw new Error('Didnt throw')
136
154
  } catch (e) {
137
- expect(e instanceof XRPCError).toBeTruthy()
155
+ expect(e).toBeInstanceOf(XRPCError)
138
156
  expect((e as XRPCError).success).toBeFalsy()
139
157
  expect((e as XRPCError).error).toBe('Foo')
140
158
  expect((e as XRPCError).message).toBe('It was this one!')
@@ -145,18 +163,27 @@ describe('Errors', () => {
145
163
  })
146
164
  throw new Error('Didnt throw')
147
165
  } catch (e) {
148
- expect(e instanceof XRPCError).toBeTruthy()
166
+ expect(e).toBeInstanceOf(XRPCError)
149
167
  expect((e as XRPCError).success).toBeFalsy()
150
168
  expect((e as XRPCError).error).toBe('Bar')
151
169
  expect((e as XRPCError).message).toBe('It was that one!')
152
170
  }
171
+ try {
172
+ await client.call('io.example.throwFalsyValue')
173
+ throw new Error('Didnt throw')
174
+ } catch (e) {
175
+ expect(e instanceof XRPCError).toBeTruthy()
176
+ expect((e as XRPCError).success).toBeFalsy()
177
+ expect((e as XRPCError).error).toBe('InternalServerError')
178
+ expect((e as XRPCError).message).toBe('Internal Server Error')
179
+ }
153
180
  try {
154
181
  await client.call('io.example.error', {
155
182
  which: 'other',
156
183
  })
157
184
  throw new Error('Didnt throw')
158
185
  } catch (e) {
159
- expect(e instanceof XRPCError).toBeTruthy()
186
+ expect(e).toBeInstanceOf(XRPCError)
160
187
  expect((e as XRPCError).success).toBeFalsy()
161
188
  expect((e as XRPCError).error).toBe('InvalidRequest')
162
189
  expect((e as XRPCError).message).toBe('Invalid Request')
@@ -165,8 +192,8 @@ describe('Errors', () => {
165
192
  await client.call('io.example.invalidResponse')
166
193
  throw new Error('Didnt throw')
167
194
  } catch (e: any) {
168
- expect(e instanceof XRPCError).toBeTruthy()
169
- expect(e instanceof XRPCInvalidResponseError).toBeTruthy()
195
+ expect(e).toBeInstanceOf(XRPCError)
196
+ expect(e).toBeInstanceOf(XRPCInvalidResponseError)
170
197
  expect(e.success).toBeFalsy()
171
198
  expect(e.error).toBe('Invalid Response')
172
199
  expect(e.message).toBe(
@@ -187,7 +214,7 @@ describe('Errors', () => {
187
214
  await badClient.call('io.example.query')
188
215
  throw new Error('Didnt throw')
189
216
  } catch (e: any) {
190
- expect(e instanceof XRPCError).toBeTruthy()
217
+ expect(e).toBeInstanceOf(XRPCError)
191
218
  expect(e.success).toBeFalsy()
192
219
  expect(e.error).toBe('InvalidRequest')
193
220
  expect(e.message).toBe('Incorrect HTTP method (POST) expected GET')
@@ -196,7 +223,7 @@ describe('Errors', () => {
196
223
  await badClient.call('io.example.procedure')
197
224
  throw new Error('Didnt throw')
198
225
  } catch (e: any) {
199
- expect(e instanceof XRPCError).toBeTruthy()
226
+ expect(e).toBeInstanceOf(XRPCError)
200
227
  expect(e.success).toBeFalsy()
201
228
  expect(e.error).toBe('InvalidRequest')
202
229
  expect(e.message).toBe('Incorrect HTTP method (GET) expected POST')
@@ -205,7 +232,7 @@ describe('Errors', () => {
205
232
  await badClient.call('io.example.doesNotExist')
206
233
  throw new Error('Didnt throw')
207
234
  } catch (e: any) {
208
- expect(e instanceof XRPCError).toBeTruthy()
235
+ expect(e).toBeInstanceOf(XRPCError)
209
236
  expect(e.success).toBeFalsy()
210
237
  expect(e.error).toBe('MethodNotImplemented')
211
238
  expect(e.message).toBe('Method Not Implemented')
@@ -0,0 +1,137 @@
1
+ import * as cborx from 'cbor-x'
2
+ import * as uint8arrays from 'uint8arrays'
3
+ import { MessageFrame, ErrorFrame, Frame, FrameType } from '../src'
4
+
5
+ describe('Frames', () => {
6
+ it('creates and parses message frame.', async () => {
7
+ const messageFrame = new MessageFrame(
8
+ { a: 'b', c: [1, 2, 3] },
9
+ { type: '#d' },
10
+ )
11
+
12
+ expect(messageFrame.header).toEqual({
13
+ op: FrameType.Message,
14
+ t: '#d',
15
+ })
16
+ expect(messageFrame.op).toEqual(FrameType.Message)
17
+ expect(messageFrame.type).toEqual('#d')
18
+ expect(messageFrame.body).toEqual({ a: 'b', c: [1, 2, 3] })
19
+
20
+ const bytes = messageFrame.toBytes()
21
+ expect(
22
+ uint8arrays.equals(
23
+ bytes,
24
+ new Uint8Array([
25
+ /*header*/ 162, 97, 116, 98, 35, 100, 98, 111, 112, 1, /*body*/ 162,
26
+ 97, 97, 97, 98, 97, 99, 131, 1, 2, 3,
27
+ ]),
28
+ ),
29
+ ).toEqual(true)
30
+
31
+ const parsedFrame = Frame.fromBytes(bytes)
32
+ if (!(parsedFrame instanceof MessageFrame)) {
33
+ throw new Error('Did not parse as message frame')
34
+ }
35
+
36
+ expect(parsedFrame.header).toEqual(messageFrame.header)
37
+ expect(parsedFrame.op).toEqual(messageFrame.op)
38
+ expect(parsedFrame.type).toEqual(messageFrame.type)
39
+ expect(parsedFrame.body).toEqual(messageFrame.body)
40
+ })
41
+
42
+ it('creates and parses error frame.', async () => {
43
+ const errorFrame = new ErrorFrame({
44
+ error: 'BigOops',
45
+ message: 'Something went awry',
46
+ })
47
+
48
+ expect(errorFrame.header).toEqual({ op: FrameType.Error })
49
+ expect(errorFrame.op).toEqual(FrameType.Error)
50
+ expect(errorFrame.code).toEqual('BigOops')
51
+ expect(errorFrame.message).toEqual('Something went awry')
52
+ expect(errorFrame.body).toEqual({
53
+ error: 'BigOops',
54
+ message: 'Something went awry',
55
+ })
56
+
57
+ const bytes = errorFrame.toBytes()
58
+ expect(
59
+ uint8arrays.equals(
60
+ bytes,
61
+ new Uint8Array([
62
+ /*header*/ 161, 98, 111, 112, 32, /*body*/ 162, 101, 101, 114, 114,
63
+ 111, 114, 103, 66, 105, 103, 79, 111, 112, 115, 103, 109, 101, 115,
64
+ 115, 97, 103, 101, 115, 83, 111, 109, 101, 116, 104, 105, 110, 103,
65
+ 32, 119, 101, 110, 116, 32, 97, 119, 114, 121,
66
+ ]),
67
+ ),
68
+ ).toEqual(true)
69
+
70
+ const parsedFrame = Frame.fromBytes(bytes)
71
+ if (!(parsedFrame instanceof ErrorFrame)) {
72
+ throw new Error('Did not parse as error frame')
73
+ }
74
+
75
+ expect(parsedFrame.header).toEqual(errorFrame.header)
76
+ expect(parsedFrame.op).toEqual(errorFrame.op)
77
+ expect(parsedFrame.code).toEqual(errorFrame.code)
78
+ expect(parsedFrame.message).toEqual(errorFrame.message)
79
+ expect(parsedFrame.body).toEqual(errorFrame.body)
80
+ })
81
+
82
+ it('parsing fails when frame is not CBOR.', async () => {
83
+ const bytes = Buffer.from('some utf8 bytes')
84
+ const emptyBytes = Buffer.from('')
85
+ expect(() => Frame.fromBytes(bytes)).toThrow('Unexpected end of CBOR data')
86
+ expect(() => Frame.fromBytes(emptyBytes)).toThrow(
87
+ 'Unexpected end of CBOR data',
88
+ )
89
+ })
90
+
91
+ it('parsing fails when frame header is malformed.', async () => {
92
+ const bytes = uint8arrays.concat([
93
+ cborx.encode({ op: -2 }), // Unknown op
94
+ cborx.encode({ a: 'b', c: [1, 2, 3] }),
95
+ ])
96
+
97
+ expect(() => Frame.fromBytes(bytes)).toThrow('Invalid frame header:')
98
+ })
99
+
100
+ it('parsing fails when frame is missing body.', async () => {
101
+ const messageFrame = new MessageFrame(
102
+ { a: 'b', c: [1, 2, 3] },
103
+ { type: '#d' },
104
+ )
105
+
106
+ const headerBytes = cborx.encode(messageFrame.header)
107
+
108
+ expect(() => Frame.fromBytes(headerBytes)).toThrow('Missing frame body')
109
+ })
110
+
111
+ it('parsing fails when frame has too many data items.', async () => {
112
+ const messageFrame = new MessageFrame(
113
+ { a: 'b', c: [1, 2, 3] },
114
+ { type: '#d' },
115
+ )
116
+
117
+ const bytes = uint8arrays.concat([
118
+ messageFrame.toBytes(),
119
+ cborx.encode({ d: 'e', f: [4, 5, 6] }),
120
+ ])
121
+
122
+ expect(() => Frame.fromBytes(bytes)).toThrow(
123
+ 'Too many CBOR data items in frame',
124
+ )
125
+ })
126
+
127
+ it('parsing fails when error frame has invalid body.', async () => {
128
+ const errorFrame = new ErrorFrame({ error: 'BadOops' })
129
+
130
+ const bytes = uint8arrays.concat([
131
+ cborx.encode(errorFrame.header),
132
+ cborx.encode({ blah: 1 }),
133
+ ])
134
+
135
+ expect(() => Frame.fromBytes(bytes)).toThrow('Invalid error frame body:')
136
+ })
137
+ })
@@ -0,0 +1,96 @@
1
+ import * as http from 'http'
2
+ import xrpc, { ServiceClient } from '@atproto/xrpc'
3
+ import { CID } from 'multiformats/cid'
4
+ import getPort from 'get-port'
5
+ import { createServer, closeServer } from './_util'
6
+ import * as xrpcServer from '../src'
7
+
8
+ const LEXICONS = [
9
+ {
10
+ lexicon: 1,
11
+ id: 'io.example.ipld',
12
+ defs: {
13
+ main: {
14
+ type: 'procedure',
15
+ input: {
16
+ encoding: 'application/json',
17
+ schema: {
18
+ type: 'object',
19
+ properties: {
20
+ cid: {
21
+ type: 'cid-link',
22
+ },
23
+ bytes: {
24
+ type: 'bytes',
25
+ },
26
+ },
27
+ },
28
+ },
29
+ output: {
30
+ encoding: 'application/json',
31
+ schema: {
32
+ type: 'object',
33
+ properties: {
34
+ cid: {
35
+ type: 'cid-link',
36
+ },
37
+ bytes: {
38
+ type: 'bytes',
39
+ },
40
+ },
41
+ },
42
+ },
43
+ },
44
+ },
45
+ },
46
+ ]
47
+
48
+ describe('Ipld vals', () => {
49
+ let s: http.Server
50
+ const server = xrpcServer.createServer(LEXICONS)
51
+ server.method(
52
+ 'io.example.ipld',
53
+ (ctx: { input?: xrpcServer.HandlerInput }) => {
54
+ const asCid = CID.asCID(ctx.input?.body.cid)
55
+ if (!(asCid instanceof CID)) {
56
+ throw new Error('expected cid')
57
+ }
58
+ const bytes = ctx.input?.body.bytes
59
+ if (!(bytes instanceof Uint8Array)) {
60
+ throw new Error('expected bytes')
61
+ }
62
+ return { encoding: 'application/json', body: ctx.input?.body }
63
+ },
64
+ )
65
+ xrpc.addLexicons(LEXICONS)
66
+
67
+ let client: ServiceClient
68
+ beforeAll(async () => {
69
+ const port = await getPort()
70
+ s = await createServer(port, server)
71
+ client = xrpc.service(`http://localhost:${port}`)
72
+ })
73
+ afterAll(async () => {
74
+ await closeServer(s)
75
+ })
76
+
77
+ it('can send and receive ipld vals', async () => {
78
+ const cid = CID.parse(
79
+ 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
80
+ )
81
+ const bytes = new Uint8Array([0, 1, 2, 3])
82
+ const res = await client.call(
83
+ 'io.example.ipld',
84
+ {},
85
+ {
86
+ cid,
87
+ bytes,
88
+ },
89
+ { encoding: 'application/json' },
90
+ )
91
+ expect(res.success).toBeTruthy()
92
+ expect(res.headers['content-type']).toBe('application/json; charset=utf-8')
93
+ expect(cid.equals(res.data.cid)).toBeTruthy()
94
+ expect(bytes).toEqual(res.data.bytes)
95
+ })
96
+ })
@@ -1,7 +1,8 @@
1
1
  import * as http from 'http'
2
+ import getPort from 'get-port'
3
+ import xrpc, { ServiceClient } from '@atproto/xrpc'
2
4
  import { createServer, closeServer } from './_util'
3
5
  import * as xrpcServer from '../src'
4
- import xrpc from '@atproto/xrpc'
5
6
 
6
7
  const LEXICONS = [
7
8
  {
@@ -12,13 +13,13 @@ const LEXICONS = [
12
13
  type: 'query',
13
14
  parameters: {
14
15
  type: 'params',
15
- required: ['str', 'int', 'num', 'bool', 'arr'],
16
+ required: ['str', 'int', 'bool', 'arr'],
16
17
  properties: {
17
18
  str: { type: 'string', minLength: 2, maxLength: 10 },
18
19
  int: { type: 'integer', minimum: 2, maximum: 10 },
19
- num: { type: 'number', minimum: 2, maximum: 10 },
20
20
  bool: { type: 'boolean' },
21
21
  arr: { type: 'array', items: { type: 'integer' }, maxLength: 2 },
22
+ def: { type: 'integer', default: 0 },
22
23
  },
23
24
  },
24
25
  output: {
@@ -39,10 +40,13 @@ describe('Parameters', () => {
39
40
  body: ctx.params,
40
41
  }),
41
42
  )
42
- const client = xrpc.service(`http://localhost:8889`)
43
43
  xrpc.addLexicons(LEXICONS)
44
+
45
+ let client: ServiceClient
44
46
  beforeAll(async () => {
45
- s = await createServer(8889, server)
47
+ const port = await getPort()
48
+ s = await createServer(port, server)
49
+ client = xrpc.service(`http://localhost:${port}`)
46
50
  })
47
51
  afterAll(async () => {
48
52
  await closeServer(s)
@@ -52,36 +56,35 @@ describe('Parameters', () => {
52
56
  const res1 = await client.call('io.example.paramTest', {
53
57
  str: 'valid',
54
58
  int: 5,
55
- num: 5.5,
56
59
  bool: true,
57
60
  arr: [1, 2],
61
+ def: 5,
58
62
  })
59
63
  expect(res1.success).toBeTruthy()
60
64
  expect(res1.data.str).toBe('valid')
61
65
  expect(res1.data.int).toBe(5)
62
- expect(res1.data.num).toBe(5.5)
63
66
  expect(res1.data.bool).toBe(true)
64
67
  expect(res1.data.arr).toEqual([1, 2])
68
+ expect(res1.data.def).toEqual(5)
65
69
 
66
70
  const res2 = await client.call('io.example.paramTest', {
67
71
  str: 10,
68
72
  int: '5',
69
- num: '5.5',
70
73
  bool: 'foo',
71
74
  arr: '3',
72
75
  })
73
76
  expect(res2.success).toBeTruthy()
74
77
  expect(res2.data.str).toBe('10')
75
78
  expect(res2.data.int).toBe(5)
76
- expect(res2.data.num).toBe(5.5)
77
79
  expect(res2.data.bool).toBe(true)
78
80
  expect(res2.data.arr).toEqual([3])
81
+ expect(res2.data.def).toEqual(0)
79
82
 
83
+ // @TODO test sending blatantly bad types
80
84
  await expect(
81
85
  client.call('io.example.paramTest', {
82
86
  str: 'n',
83
87
  int: 5,
84
- num: 5.5,
85
88
  bool: true,
86
89
  arr: [1],
87
90
  }),
@@ -90,7 +93,6 @@ describe('Parameters', () => {
90
93
  client.call('io.example.paramTest', {
91
94
  str: 'loooooooooooooong',
92
95
  int: 5,
93
- num: 5.5,
94
96
  bool: true,
95
97
  arr: [1],
96
98
  }),
@@ -98,7 +100,6 @@ describe('Parameters', () => {
98
100
  await expect(
99
101
  client.call('io.example.paramTest', {
100
102
  int: 5,
101
- num: 5.5,
102
103
  bool: true,
103
104
  arr: [1],
104
105
  }),
@@ -108,7 +109,6 @@ describe('Parameters', () => {
108
109
  client.call('io.example.paramTest', {
109
110
  str: 'valid',
110
111
  int: -1,
111
- num: 5.5,
112
112
  bool: true,
113
113
  arr: [1],
114
114
  }),
@@ -117,7 +117,6 @@ describe('Parameters', () => {
117
117
  client.call('io.example.paramTest', {
118
118
  str: 'valid',
119
119
  int: 11,
120
- num: 5.5,
121
120
  bool: true,
122
121
  arr: [1],
123
122
  }),
@@ -125,7 +124,6 @@ describe('Parameters', () => {
125
124
  await expect(
126
125
  client.call('io.example.paramTest', {
127
126
  str: 'valid',
128
- num: 5.5,
129
127
  bool: true,
130
128
  arr: [1],
131
129
  }),
@@ -135,34 +133,6 @@ describe('Parameters', () => {
135
133
  client.call('io.example.paramTest', {
136
134
  str: 'valid',
137
135
  int: 5,
138
- num: -5.5,
139
- bool: true,
140
- arr: [1],
141
- }),
142
- ).rejects.toThrow('num can not be less than 2')
143
- await expect(
144
- client.call('io.example.paramTest', {
145
- str: 'valid',
146
- int: 5,
147
- num: 50.5,
148
- bool: true,
149
- arr: [1],
150
- }),
151
- ).rejects.toThrow('num can not be greater than 10')
152
- await expect(
153
- client.call('io.example.paramTest', {
154
- str: 'valid',
155
- int: 5,
156
- bool: true,
157
- arr: [1],
158
- }),
159
- ).rejects.toThrow(`Params must have the property "num"`)
160
-
161
- await expect(
162
- client.call('io.example.paramTest', {
163
- str: 'valid',
164
- int: 5,
165
- num: 5.5,
166
136
  arr: [1],
167
137
  }),
168
138
  ).rejects.toThrow(`Params must have the property "bool"`)
@@ -171,7 +141,6 @@ describe('Parameters', () => {
171
141
  client.call('io.example.paramTest', {
172
142
  str: 'valid',
173
143
  int: 5,
174
- num: 5.5,
175
144
  bool: true,
176
145
  arr: [],
177
146
  }),
@@ -180,7 +149,6 @@ describe('Parameters', () => {
180
149
  client.call('io.example.paramTest', {
181
150
  str: 'valid',
182
151
  int: 5,
183
- num: 5.5,
184
152
  bool: true,
185
153
  arr: [1, 2, 3],
186
154
  }),
@@ -1,6 +1,7 @@
1
1
  import * as http from 'http'
2
2
  import { Readable } from 'stream'
3
- import xrpc from '@atproto/xrpc'
3
+ import xrpc, { ServiceClient } from '@atproto/xrpc'
4
+ import getPort from 'get-port'
4
5
  import { createServer, closeServer } from './_util'
5
6
  import * as xrpcServer from '../src'
6
7
 
@@ -119,10 +120,13 @@ describe('Procedures', () => {
119
120
  }
120
121
  },
121
122
  )
122
- const client = xrpc.service(`http://localhost:8891`)
123
123
  xrpc.addLexicons(LEXICONS)
124
+
125
+ let client: ServiceClient
124
126
  beforeAll(async () => {
125
- s = await createServer(8891, server)
127
+ const port = await getPort()
128
+ s = await createServer(port, server)
129
+ client = xrpc.service(`http://localhost:${port}`)
126
130
  })
127
131
  afterAll(async () => {
128
132
  await closeServer(s)
@@ -1,7 +1,8 @@
1
1
  import * as http from 'http'
2
+ import getPort from 'get-port'
3
+ import xrpc, { ServiceClient } from '@atproto/xrpc'
2
4
  import { createServer, closeServer } from './_util'
3
5
  import * as xrpcServer from '../src'
4
- import xrpc from '@atproto/xrpc'
5
6
 
6
7
  const LEXICONS = [
7
8
  {
@@ -83,10 +84,13 @@ describe('Queries', () => {
83
84
  body: { message: ctx.params.message },
84
85
  }
85
86
  })
86
- const client = xrpc.service(`http://localhost:8890`)
87
87
  xrpc.addLexicons(LEXICONS)
88
+
89
+ let client: ServiceClient
88
90
  beforeAll(async () => {
89
- s = await createServer(8890, server)
91
+ const port = await getPort()
92
+ s = await createServer(port, server)
93
+ client = xrpc.service(`http://localhost:${port}`)
90
94
  })
91
95
  afterAll(async () => {
92
96
  await closeServer(s)