7f26b17652
* Improve error message when using invalid client_id during code exchange * Extract SPA example OAuth client in own package * wip * remove dependency on get-port * Properly configure jest to only transpile "get-port" from node_modules https://jestjs.io/docs/configuration#transformignorepatterns-arraystring * Use dynamically assigned port number during tests * use puppeteer to run tests * remove login input "id" attribute * code style * add missing declaration * tidy * headless * remove get-port dependency * fix tests/proxied/admin.test.ts * fix tests * Allow unsecure oauth providers through configuration * transpile "lande" during ozone tests * Cache Puppeteer browser binaries * Use puppeteer cache during all workflow steps * remove use of set-output * use get-port in xrpc-server tests * Renamed to allowHttp * tidy * tidy
235 lines
6.3 KiB
TypeScript
235 lines
6.3 KiB
TypeScript
import * as http from 'node:http'
|
|
import { AddressInfo } from 'node:net'
|
|
import { LexiconDoc } from '@atproto/lexicon'
|
|
import { XRPCError, XRPCInvalidResponseError, XrpcClient } from '@atproto/xrpc'
|
|
import { createServer, closeServer } from './_util'
|
|
import * as xrpcServer from '../src'
|
|
|
|
const LEXICONS: LexiconDoc[] = [
|
|
{
|
|
lexicon: 1,
|
|
id: 'io.example.error',
|
|
defs: {
|
|
main: {
|
|
type: 'query',
|
|
parameters: {
|
|
type: 'params',
|
|
properties: {
|
|
which: { type: 'string', default: 'foo' },
|
|
},
|
|
},
|
|
errors: [{ name: 'Foo' }, { name: 'Bar' }],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
lexicon: 1,
|
|
id: 'io.example.throwFalsyValue',
|
|
defs: {
|
|
main: {
|
|
type: 'query',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
lexicon: 1,
|
|
id: 'io.example.query',
|
|
defs: {
|
|
main: {
|
|
type: 'query',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
lexicon: 1,
|
|
id: 'io.example.procedure',
|
|
defs: {
|
|
main: {
|
|
type: 'procedure',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
lexicon: 1,
|
|
id: 'io.example.invalidResponse',
|
|
defs: {
|
|
main: {
|
|
type: 'query',
|
|
output: {
|
|
encoding: 'application/json',
|
|
schema: {
|
|
type: 'object',
|
|
required: ['expectedValue'],
|
|
properties: {
|
|
expectedValue: { type: 'string' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
]
|
|
|
|
const MISMATCHED_LEXICONS: LexiconDoc[] = [
|
|
{
|
|
lexicon: 1,
|
|
id: 'io.example.query',
|
|
defs: {
|
|
main: {
|
|
type: 'procedure',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
lexicon: 1,
|
|
id: 'io.example.procedure',
|
|
defs: {
|
|
main: {
|
|
type: 'query',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
lexicon: 1,
|
|
id: 'io.example.doesNotExist',
|
|
defs: {
|
|
main: {
|
|
type: 'query',
|
|
},
|
|
},
|
|
},
|
|
]
|
|
|
|
describe('Errors', () => {
|
|
let s: http.Server
|
|
const server = xrpcServer.createServer(LEXICONS, { validateResponse: false }) // disable validateResponse to test client validation
|
|
server.method('io.example.error', (ctx: { params: xrpcServer.Params }) => {
|
|
if (ctx.params.which === 'foo') {
|
|
throw new xrpcServer.InvalidRequestError('It was this one!', 'Foo')
|
|
} else if (ctx.params.which === 'bar') {
|
|
return { status: 400, error: 'Bar', message: 'It was that one!' }
|
|
} else {
|
|
return { status: 400 }
|
|
}
|
|
})
|
|
server.method('io.example.throwFalsyValue', () => {
|
|
throw ''
|
|
})
|
|
server.method('io.example.query', () => {
|
|
return undefined
|
|
})
|
|
// @ts-ignore We're intentionally giving the wrong response! -prf
|
|
server.method('io.example.invalidResponse', () => {
|
|
return { encoding: 'json', body: { something: 'else' } }
|
|
})
|
|
server.method('io.example.procedure', () => {
|
|
return undefined
|
|
})
|
|
|
|
let client: XrpcClient
|
|
let badClient: XrpcClient
|
|
beforeAll(async () => {
|
|
s = await createServer(server)
|
|
const { port } = s.address() as AddressInfo
|
|
client = new XrpcClient(`http://localhost:${port}`, LEXICONS)
|
|
badClient = new XrpcClient(`http://localhost:${port}`, MISMATCHED_LEXICONS)
|
|
})
|
|
afterAll(async () => {
|
|
await closeServer(s)
|
|
})
|
|
|
|
it('serves requests', async () => {
|
|
try {
|
|
await client.call('io.example.error', {
|
|
which: 'foo',
|
|
})
|
|
throw new Error('Didnt throw')
|
|
} catch (e) {
|
|
expect(e).toBeInstanceOf(XRPCError)
|
|
expect((e as XRPCError).success).toBeFalsy()
|
|
expect((e as XRPCError).error).toBe('Foo')
|
|
expect((e as XRPCError).message).toBe('It was this one!')
|
|
}
|
|
try {
|
|
await client.call('io.example.error', {
|
|
which: 'bar',
|
|
})
|
|
throw new Error('Didnt throw')
|
|
} catch (e) {
|
|
expect(e).toBeInstanceOf(XRPCError)
|
|
expect((e as XRPCError).success).toBeFalsy()
|
|
expect((e as XRPCError).error).toBe('Bar')
|
|
expect((e as XRPCError).message).toBe('It was that one!')
|
|
}
|
|
try {
|
|
await client.call('io.example.throwFalsyValue')
|
|
throw new Error('Didnt throw')
|
|
} catch (e) {
|
|
expect(e instanceof XRPCError).toBeTruthy()
|
|
expect((e as XRPCError).success).toBeFalsy()
|
|
expect((e as XRPCError).error).toBe('InternalServerError')
|
|
expect((e as XRPCError).message).toBe('Internal Server Error')
|
|
}
|
|
try {
|
|
await client.call('io.example.error', {
|
|
which: 'other',
|
|
})
|
|
throw new Error('Didnt throw')
|
|
} catch (e) {
|
|
expect(e).toBeInstanceOf(XRPCError)
|
|
expect((e as XRPCError).success).toBeFalsy()
|
|
expect((e as XRPCError).error).toBe('InvalidRequest')
|
|
expect((e as XRPCError).message).toBe('Invalid Request')
|
|
}
|
|
try {
|
|
await client.call('io.example.invalidResponse')
|
|
throw new Error('Didnt throw')
|
|
} catch (e: any) {
|
|
expect(e).toBeInstanceOf(XRPCError)
|
|
expect(e).toBeInstanceOf(XRPCInvalidResponseError)
|
|
expect(e.success).toBeFalsy()
|
|
expect(e.error).toBe('Invalid Response')
|
|
expect(e.message).toBe(
|
|
'The server gave an invalid response and may be out of date.',
|
|
)
|
|
const err = e as XRPCInvalidResponseError
|
|
expect(err.validationError.message).toBe(
|
|
'Output must have the property "expectedValue"',
|
|
)
|
|
expect(err.responseBody).toStrictEqual({ something: 'else' })
|
|
}
|
|
})
|
|
|
|
it('serves error for missing/mismatch schemas', async () => {
|
|
await client.call('io.example.query') // No error
|
|
await client.call('io.example.procedure') // No error
|
|
try {
|
|
await badClient.call('io.example.query')
|
|
throw new Error('Didnt throw')
|
|
} catch (e: any) {
|
|
expect(e).toBeInstanceOf(XRPCError)
|
|
expect(e.success).toBeFalsy()
|
|
expect(e.error).toBe('InvalidRequest')
|
|
expect(e.message).toBe('Incorrect HTTP method (POST) expected GET')
|
|
}
|
|
try {
|
|
await badClient.call('io.example.procedure')
|
|
throw new Error('Didnt throw')
|
|
} catch (e: any) {
|
|
expect(e).toBeInstanceOf(XRPCError)
|
|
expect(e.success).toBeFalsy()
|
|
expect(e.error).toBe('InvalidRequest')
|
|
expect(e.message).toBe('Incorrect HTTP method (GET) expected POST')
|
|
}
|
|
try {
|
|
await badClient.call('io.example.doesNotExist')
|
|
throw new Error('Didnt throw')
|
|
} catch (e: any) {
|
|
expect(e).toBeInstanceOf(XRPCError)
|
|
expect(e.success).toBeFalsy()
|
|
expect(e.error).toBe('MethodNotImplemented')
|
|
expect(e.message).toBe('Method Not Implemented')
|
|
}
|
|
})
|
|
})
|