92 行
2.8 KiB
TypeScript
92 行
2.8 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { Server } from 'node:http'
|
|
import { AddressInfo } from 'node:net'
|
|
import { afterEach, test } from 'node:test'
|
|
import { createApp } from '../src'
|
|
|
|
const servers: Server[] = []
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(
|
|
servers.splice(0).map(server => new Promise<void>((resolve, reject) => {
|
|
server.close(error => error ? reject(error) : resolve())
|
|
}))
|
|
)
|
|
})
|
|
|
|
async function start(options: Parameters<typeof createApp>[0] = {}) {
|
|
const server = createApp(options).listen(0, '127.0.0.1')
|
|
servers.push(server)
|
|
await new Promise<void>(resolve => server.once('listening', resolve))
|
|
const { port } = server.address() as AddressInfo
|
|
return `http://127.0.0.1:${port}`
|
|
}
|
|
|
|
test('health 保持稳定响应契约', async () => {
|
|
const baseUrl = await start()
|
|
const response = await fetch(`${baseUrl}/health`)
|
|
|
|
assert.equal(response.status, 200)
|
|
assert.deepEqual(await response.json(), {
|
|
status: 'ok',
|
|
service: 'xuqm-symbolicator',
|
|
version: '1.0.0',
|
|
})
|
|
assert.equal(response.headers.get('x-powered-by'), null)
|
|
})
|
|
|
|
test('配置 API Key 时拒绝未认证请求', async () => {
|
|
const baseUrl = await start({ apiKey: 'test-only-key' })
|
|
const response = await fetch(`${baseUrl}/api/symbolicate`, {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ platform: 'android', stacktrace: 'sample' }),
|
|
})
|
|
|
|
assert.equal(response.status, 401)
|
|
assert.deepEqual(await response.json(), {
|
|
success: false,
|
|
error: 'Unauthorized: invalid or missing API key',
|
|
})
|
|
})
|
|
|
|
test('Android 反混淆接口保持既有请求响应结构', async () => {
|
|
const baseUrl = await start()
|
|
const response = await fetch(`${baseUrl}/api/symbolicate`, {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({
|
|
platform: 'android',
|
|
stacktrace: 'at a.b.c.a(r8-map-id-sample:13)',
|
|
mappingFile: [
|
|
'com.example.Sample -> a.b.c:',
|
|
' 13:13:void com.example.Sample.run():42:42 -> a',
|
|
].join('\n'),
|
|
}),
|
|
})
|
|
|
|
assert.equal(response.status, 200)
|
|
const body = await response.json() as { success: boolean; symbolicated: string }
|
|
assert.equal(body.success, true)
|
|
assert.match(body.symbolicated, /com\.example\.Sample\.[\w$]+\(Sample\.kt:42\)/)
|
|
})
|
|
|
|
test('请求体超限返回受控错误且不进入业务处理', async () => {
|
|
const baseUrl = await start({ bodyLimit: '1kb' })
|
|
const response = await fetch(`${baseUrl}/api/symbolicate`, {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({
|
|
platform: 'rn',
|
|
stacktrace: 'x'.repeat(2048),
|
|
sourcemap: '{}',
|
|
}),
|
|
})
|
|
|
|
assert.equal(response.status, 413)
|
|
assert.deepEqual(await response.json(), {
|
|
success: false,
|
|
error: 'request body too large',
|
|
})
|
|
})
|