Files
mihomo-patchbay/shared/connections.test.ts
T
im0o b1b251b897
release / release (push) Successful in 26s
legacy 入站可升级为命名入站
顶层端口字段没有 proxy 可写(listener/inbound/base.go:97),界面上
切不了订阅。升级删掉顶层键、建同端口的 listener:listen 按 allow-lan
推导后写死,全局规则里 MATCH 之前的部分复制成附加规则,来源照抄
MATCH 目标组的 use:,让流量去向尽量不变。udp 不写,命名入站解析时
默认就是开的(listener/parse.go:52)。
「默认」tag 随术语表改成 legacy,旧叫法进 avoid 列表。
2026-08-12 17:30:47 +08:00

172 lines
5.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'bun:test'
import {
chainLabel,
filterConnections,
formatDuration,
groupByInbound,
hostLabel,
ruleLabel,
sortConnections,
} from './connections.ts'
import type { ConnectionItem, ConnectionMeta } from './connections.ts'
const NOW = Date.parse('2026-08-11T18:40:00+08:00')
function meta(over: Partial<ConnectionMeta> = {}): ConnectionMeta {
return {
host: 'example.com',
destinationIP: '93.184.216.34',
destinationPort: '443',
inboundName: 'lxc-web',
inboundPort: '17891',
...over,
}
}
function conn(over: Partial<ConnectionItem> = {}): ConnectionItem {
return {
id: 'a',
upload: 0,
download: 0,
start: '2026-08-11T18:39:00+08:00',
chains: ['[vless]某线路', 'AUTO', 'PROXY'],
rule: 'DomainSuffix',
rulePayload: 'example.com',
metadata: meta(),
...over,
}
}
describe('主机标签', () => {
it('有域名用域名,带端口', () => {
expect(hostLabel(meta())).toBe('example.com:443')
})
it('没域名退回 IP', () => {
expect(hostLabel(meta({ host: '' }))).toBe('93.184.216.34:443')
})
it('没端口就只给主机', () => {
expect(hostLabel(meta({ destinationPort: '' }))).toBe('example.com')
})
})
describe('出站链', () => {
it('倒过来显示:最先接触的在左,最终出口在右', () => {
expect(chainLabel(['[vless]某线路', 'AUTO', 'PROXY'])).toBe('PROXY → AUTO → [vless]某线路')
})
it('单跳就是直连', () => {
expect(chainLabel(['DIRECT'])).toBe('DIRECT')
})
})
describe('规则标签', () => {
it('类型和载荷用空格连起来', () => {
expect(ruleLabel('DomainSuffix', 'example.com')).toBe('DomainSuffix example.com')
})
it('兜底规则没有载荷', () => {
expect(ruleLabel('Match', '')).toBe('Match')
})
})
describe('持续时间', () => {
it('不满一分钟按秒', () => {
expect(formatDuration('2026-08-11T18:39:18+08:00', NOW)).toBe('42 秒')
})
it('满一分钟带分', () => {
expect(formatDuration('2026-08-11T18:36:36+08:00', NOW)).toBe('3 分 24 秒')
})
it('满一小时不再给秒', () => {
expect(formatDuration('2026-08-11T16:35:00+08:00', NOW)).toBe('2 小时 5 分')
})
it('start 带时区偏移,跨时区解析不能算错', () => {
// 同一时刻写成 UTC 和 +08:00,算出来的持续时间必须一样
const local = formatDuration('2026-08-11T18:39:00+08:00', NOW)
const utc = formatDuration('2026-08-11T10:39:00Z', NOW)
expect(local).toBe(utc)
})
it('坏数据返回空串,未来的时间按 0 算', () => {
expect(formatDuration('not-a-date', NOW)).toBe('')
expect(formatDuration('2026-08-11T19:00:00+08:00', NOW)).toBe('0 秒')
})
})
describe('排序', () => {
const list = [
conn({ id: 'small-new', download: 100, start: '2026-08-11T18:39:50+08:00' }),
conn({ id: 'big-new', download: 9000, start: '2026-08-11T18:39:40+08:00' }),
conn({ id: 'small-old', download: 50, start: '2026-08-11T18:00:00+08:00' }),
]
it('按下载量降序', () => {
expect(sortConnections(list, 'download', NOW).map((c) => c.id)).toEqual([
'big-new',
'small-new',
'small-old',
])
})
it('按持续时间降序:活得久的排前面', () => {
expect(sortConnections(list, 'duration', NOW).map((c) => c.id)).toEqual([
'small-old',
'big-new',
'small-new',
])
})
it('不动原数组', () => {
const before = list.map((c) => c.id)
sortConnections(list, 'download', NOW)
expect(list.map((c) => c.id)).toEqual(before)
})
})
describe('筛选', () => {
const list = [
conn({ id: 'a', metadata: meta({ host: 'speed.cloudflare.com' }) }),
conn({ id: 'b', metadata: meta({ host: '', destinationIP: '1.2.3.4' }) }),
]
it('匹配主机名,大小写不敏感', () => {
expect(filterConnections(list, 'CLOUDFLARE').map((c) => c.id)).toEqual(['a'])
})
it('没域名时也能按 IP 搜', () => {
expect(filterConnections(list, '1.2').map((c) => c.id)).toEqual(['b'])
})
it('空串不筛,空白也不算条件', () => {
expect(filterConnections(list, ' ')).toHaveLength(2)
})
})
describe('按入站聚合', () => {
it('同一路入站计数、流量都累加,连接多的排前面', () => {
const list = [
conn({ id: 'a', download: 10, upload: 1 }),
conn({ id: 'b', download: 20, upload: 2 }),
conn({ id: 'c', metadata: meta({ inboundName: 'lxc-db', inboundPort: '17892' }) }),
]
expect(groupByInbound(list)).toEqual([
{ label: 'lxc-web(17891)', count: 2, upload: 3, download: 30 },
{ label: 'lxc-db(17892)', count: 1, upload: 0, download: 0 },
])
})
it('legacy 入站用内核给的内部名,附上端口', () => {
const list = [conn({ metadata: meta({ inboundName: 'DEFAULT-MIXED', inboundPort: '17890' }) })]
expect(groupByInbound(list)[0]?.label).toBe('DEFAULT-MIXED(17890)')
})
it('入站名字缺失时用端口凑', () => {
const list = [conn({ metadata: meta({ inboundName: '' }) })]
expect(groupByInbound(list)[0]?.label).toBe('17891')
})
})