| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- package service
- import (
- "path/filepath"
- "testing"
- "time"
- "github.com/mhsanaei/3x-ui/v3/internal/database"
- "github.com/mhsanaei/3x-ui/v3/internal/eventbus"
- )
- // probe is one observatory sample: whether the outbound is alive and the
- // last_try_time xray reports for it (a new probe advances lastTry).
- type probe struct {
- alive bool
- lastTry int64
- }
- const testSentinel eventbus.EventType = "test.sentinel"
- // runObservatory feeds a probe sequence through applyObservatory with the given
- // threshold and returns the outbound.* events it published, in order.
- func runObservatory(t *testing.T, threshold int, seq []probe) []eventbus.EventType {
- t.Helper()
- ss := SettingService{}
- if err := ss.SetOutboundDownThreshold(threshold); err != nil {
- t.Fatalf("set threshold: %v", err)
- }
- bus := eventbus.New(256)
- events := make(chan eventbus.Event, 256)
- bus.Subscribe("test", func(e eventbus.Event) { events <- e })
- SetEventBus(bus)
- t.Cleanup(func() {
- SetEventBus(nil)
- bus.Stop()
- })
- s := &XrayMetricsService{settingService: ss}
- for _, p := range seq {
- s.applyObservatory(time.Unix(p.lastTry, 0), map[string]rawObsEntry{
- "proxy": {Alive: p.alive, Delay: 10, LastTryTime: p.lastTry, OutboundTag: "proxy"},
- })
- }
- bus.Publish(eventbus.Event{Type: testSentinel, Source: "x"})
- var got []eventbus.EventType
- for {
- select {
- case e := <-events:
- if e.Type == testSentinel {
- return got
- }
- got = append(got, e.Type)
- case <-time.After(2 * time.Second):
- t.Fatal("timed out waiting for events to drain")
- }
- }
- }
- func TestApplyObservatoryDebounce(t *testing.T) {
- if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
- t.Fatalf("init db: %v", err)
- }
- t.Cleanup(func() { _ = database.CloseDB() })
- tests := []struct {
- name string
- threshold int
- seq []probe
- want []eventbus.EventType
- }{
- {
- name: "notifies only after threshold consecutive failed probes",
- threshold: 3,
- seq: []probe{
- {true, 1},
- {false, 2},
- {false, 3},
- {false, 4},
- {false, 5},
- {true, 6},
- {false, 7},
- {true, 8},
- },
- want: []eventbus.EventType{eventbus.EventOutboundDown, eventbus.EventOutboundUp},
- },
- {
- name: "repeated samples of the same probe do not advance the streak",
- threshold: 3,
- seq: []probe{{false, 2}, {false, 2}, {false, 2}, {false, 2}, {false, 2}},
- want: nil,
- },
- {
- name: "single-probe blip never notifies",
- threshold: 3,
- seq: []probe{{true, 1}, {false, 2}, {true, 3}},
- want: nil,
- },
- {
- name: "threshold 1 keeps the legacy notify-on-first-failure behaviour",
- threshold: 1,
- seq: []probe{{true, 1}, {false, 2}},
- want: []eventbus.EventType{eventbus.EventOutboundDown},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := runObservatory(t, tt.threshold, tt.seq)
- if len(got) != len(tt.want) {
- t.Fatalf("events = %v, want %v", got, tt.want)
- }
- for i := range got {
- if got[i] != tt.want[i] {
- t.Fatalf("event[%d] = %q, want %q (full: %v)", i, got[i], tt.want[i], got)
- }
- }
- })
- }
- }
|