release / release (push) Successful in 29s
判定按状态码白名单而不是只认 200:探测不带鉴权,OpenAI 回 401、Anthropic 回 405 才是正常应答,说明 TLS 握手、路由、目标服务全都正常。清单和白名单照 sub2api 的 代理质量检查抄来。 失败文案不照抄 Bun:本机端口没人监听时它给的是「Unable to connect. Is the computer able to access the url?」,把矛头指向目标网址,而真正的原因在本机。 对话框留在 InboundDetail 而不是提到 RouterPage——它不写配置文件也不改内核状态, 不需要走那条 commit 链路。firstPort 挪进 shared/inbounds.ts,两个页面都要用它了。
121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
||
import {
|
||
describeProbeFailure,
|
||
findTarget,
|
||
isChallenge,
|
||
judge,
|
||
rayId,
|
||
REACH_TARGETS,
|
||
} from './reachability.ts'
|
||
|
||
const openai = REACH_TARGETS[0]!
|
||
const gemini = REACH_TARGETS[2]!
|
||
|
||
const headers = (init: Record<string, string> = {}) => new Headers(init)
|
||
|
||
describe('REACH_TARGETS', () => {
|
||
test('标识不重名,否则接口按 id 取目标会取错', () => {
|
||
const ids = REACH_TARGETS.map((t) => t.id)
|
||
expect(new Set(ids).size).toBe(ids.length)
|
||
})
|
||
|
||
test('findTarget 认不出来的标识返回 null', () => {
|
||
expect(findTarget('openai')?.label).toBe('OpenAI')
|
||
expect(findTarget('nonesuch')).toBeNull()
|
||
})
|
||
})
|
||
|
||
describe('judge', () => {
|
||
test('无鉴权探测拿到 401 算可达,并说明这是预期结果', () => {
|
||
const { verdict, message } = judge(openai, 401, headers(), '')
|
||
expect(verdict).toBe('reachable')
|
||
expect(message).toBe('HTTP 401,目标可达')
|
||
})
|
||
|
||
test('2xx 不需要解释,只报状态码', () => {
|
||
expect(judge(gemini, 200, headers(), '{}')).toEqual({
|
||
verdict: 'reachable',
|
||
message: 'HTTP 200',
|
||
})
|
||
})
|
||
|
||
test('白名单之外的状态码算不通', () => {
|
||
const { verdict, message } = judge(gemini, 403, headers(), '')
|
||
expect(verdict).toBe('unreachable')
|
||
expect(message).toBe('HTTP 403,不是预期的状态码')
|
||
})
|
||
|
||
test('429 单独说是限流,不和连不上混在一起', () => {
|
||
expect(judge(openai, 429, headers(), '').verdict).toBe('throttled')
|
||
})
|
||
|
||
test('Cloudflare 拦截优先于状态码白名单', () => {
|
||
// 429 在 openai 的白名单外,但结论要是 challenge 而不是 throttled:
|
||
// 前者要换出口 IP,后者只要等
|
||
const { verdict, message } = judge(
|
||
openai,
|
||
429,
|
||
headers({ 'cf-mitigated': 'challenge', 'cf-ray': '8f0a1b2c3d4e5f60-LAX' }),
|
||
'',
|
||
)
|
||
expect(verdict).toBe('challenge')
|
||
expect(message).toContain('8f0a1b2c3d4e5f60-LAX')
|
||
})
|
||
})
|
||
|
||
describe('isChallenge', () => {
|
||
test('只有 403 和 429 才可能是拦截页', () => {
|
||
const body = '<html>window._cf_chl_opt</html>'
|
||
expect(isChallenge(403, headers(), body)).toBe(true)
|
||
expect(isChallenge(429, headers(), body)).toBe(true)
|
||
expect(isChallenge(200, headers(), body)).toBe(false)
|
||
})
|
||
|
||
test('没有 cf-mitigated 头时认正文里的标记', () => {
|
||
expect(isChallenge(403, headers(), '<title>Just a Moment...</title>')).toBe(true)
|
||
})
|
||
|
||
test('普通的 403 不是拦截页', () => {
|
||
expect(isChallenge(403, headers(), '{"error":"forbidden"}')).toBe(false)
|
||
})
|
||
|
||
test('正文只看前 4 KB,标记在那之后不算', () => {
|
||
expect(isChallenge(403, headers(), 'x'.repeat(5000) + 'just a moment')).toBe(false)
|
||
})
|
||
})
|
||
|
||
describe('rayId', () => {
|
||
test('优先取响应头', () => {
|
||
expect(rayId(headers({ 'cf-ray': ' abc123-LAX ' }), 'cf-ray: zzz')).toBe('abc123-LAX')
|
||
})
|
||
|
||
test('没有响应头时从正文里找', () => {
|
||
expect(rayId(headers(), 'Cloudflare Ray ID: <code>cf-ray: abc123</code>')).toBe('abc123')
|
||
})
|
||
|
||
test('都没有就是空串', () => {
|
||
expect(rayId(headers(), 'nothing here')).toBe('')
|
||
})
|
||
})
|
||
|
||
describe('describeProbeFailure', () => {
|
||
test('超时说等了多久', () => {
|
||
const error = new Error('timed out')
|
||
error.name = 'TimeoutError'
|
||
expect(describeProbeFailure(7890, 10_000, error)).toBe('10 秒内没有响应')
|
||
})
|
||
|
||
test('本机端口没人监听时指向内核,不照抄 Bun 那句指向目标网址的话', () => {
|
||
const error = Object.assign(new Error('Unable to connect. Is the computer able to access the url?'), {
|
||
code: 'ConnectionRefused',
|
||
})
|
||
expect(describeProbeFailure(7890, 10_000, error)).toBe('本机 7890 端口没有响应,内核可能没在运行')
|
||
})
|
||
|
||
test('其余原因照原文给出,不猜', () => {
|
||
expect(describeProbeFailure(7890, 10_000, new Error('TLS handshake failed'))).toBe(
|
||
'连接失败:TLS handshake failed',
|
||
)
|
||
})
|
||
})
|