Files
PortRelay/client/ratehistory_test.go
DeepFal 86e159b3f0 PortRelay:把内网服务临时、可控地开放给同事
边缘网关 + Agent 的端口中继:Agent 主动连出到网关,把内网目标挂到网关端口上;
通过邀请链接按设备授权,可选审批,首次连接起计时,到期、闲置自动收回。

- portrelay-server:边缘网关(端口池、白名单与授权租约、邀请与授权网页)
- portrelay-agent:Agent(yamux 隧道、本地 API 与网页面板、流量统计)
- mac-native:SwiftUI 菜单栏 App
2026-10-13 16:33:17 +08:00

53 lines
1.8 KiB
Go

package client
import (
"testing"
"time"
)
func TestRateHistoryBucketsAndWindow(t *testing.T) {
r := newRateRecorder()
base := time.Unix(1_700_000_000, 0).Truncate(rateStep)
// 同一个 5 秒桶内的多个采样取平均
r.record(base, map[string][2]int64{"a": {100, 1000}})
r.record(base.Add(1*time.Second), map[string][2]int64{"a": {300, 3000}})
// 跳过 3 个桶(中间无数据,应为 0)
r.record(base.Add(20*time.Second), map[string][2]int64{"a": {50, 500}})
s := r.series("a", base.Add(21*time.Second))
if len(s.Up) != ratePoints || len(s.Down) != ratePoints || s.Step != 5 {
t.Fatalf("series must be fixed length %d with step 5: %d/%d step %d", ratePoints, len(s.Up), len(s.Down), s.Step)
}
last := ratePoints - 1
if s.Up[last] != 50 || s.Down[last] != 500 {
t.Fatalf("last point should be the current bucket: %d/%d", s.Up[last], s.Down[last])
}
if s.Up[last-4] != 200 || s.Down[last-4] != 2000 {
t.Fatalf("first bucket should average samples: %d/%d", s.Up[last-4], s.Down[last-4])
}
for i := last - 3; i < last; i++ {
if s.Up[i] != 0 || s.Down[i] != 0 {
t.Fatalf("gap bucket %d should be zero: %d/%d", i, s.Up[i], s.Down[i])
}
}
// 合计曲线
total := r.series(totalSeries, base.Add(21*time.Second))
if total.Down[last] != 500 {
t.Fatalf("total should sum tunnels: %d", total.Down[last])
}
// 11 分钟后旧数据滑出窗口
later := base.Add(11 * time.Minute)
if s2 := r.series("a", later); s2.Up[0] != 0 || s2.Down[last] != 0 {
t.Fatalf("old data should slide out of the 10 minute window: first=%d last=%d", s2.Up[0], s2.Down[last])
}
// 关闭的通道清除曲线
r.retain(map[string]bool{})
if s3 := r.series("a", base.Add(21*time.Second)); s3.Down[last] != 0 {
t.Fatal("history of a disabled tunnel should be dropped")
}
}