Files
mihomo-patchbay/shared/connections.test.ts
T
im0o c9a0b87af4 连接页:每秒轮询的连接表格,能按入站看、能断开
/connections 没有流式版本,只能全量轮询;断开是运行时动作,不走
落盘那条保护链路。两个容易做反的地方都收在 shared/connections.ts:
chains 数组第一个是最终出口,显示要倒过来;没有连接时 connections
字段是 null 而不是空数组。虚拟滚动用 @tanstack/vue-virtual,ADR 0006
留的两个例外之一,它只有 virtual-core 一个运行时依赖。
2026-08-11 18:48:55 +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('默认入站用内核给的内部名,附上端口', () => {
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')
})
})