CI / go-test (push) Waiting to run
CI / postgres-durable-first (push) Waiting to run
CI / codegen (push) Waiting to run
CI / govulncheck (push) Waiting to run
CI / race (push) Waiting to run
CI / fuzz-smoke (push) Waiting to run
CI / golangci (push) Waiting to run
CI / frontend (push) Waiting to run
Docs CI / build (push) Waiting to run
Docs Deploy (GitHub Pages) / build (push) Waiting to run
Docs Deploy (GitHub Pages) / deploy (push) Blocked by required conditions
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
Release 3X-UI / Build for Windows (amd64) (push) Waiting to run
Release 3X-UI / Publish rolling dev release (push) Blocked by required conditions
CodeQL Advanced / Analyze (go) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
- 0: sing-box gen overrides fragment listen/port from the inbound row (empty listen -> "::"), Update/Add recompute the stored fragment; non-loopback E2E against the real core binary. - 1: agent fetches the sing-box core from the Gitea release when missing, self-checks it, and only then latches the switch. - 2: node list shows the sing-box core version (heartbeat field, persisted on the node row), xray version as fallback. - 3: public-page fallback returns 404 to anonymous visitors when the page is disabled. - 4: settings restart hint bar removed, dead i18n keys cleaned. - 5: two PUT calls missing JSON headers fixed (audit: only those two), batch probe-task creation endpoint + UI. - 6: cf-probe retirement card and coverage pipeline removed. - 7: generated vless links no longer carry spx (panel export + sub); parser still accepts spx; golden fixtures updated. - probe batch: dedicated probe settings tab (public-page settings, tasks, per-node meta), retention_days editable, per-node traffic calibration + stat-interface pinning, runtime metrics split into a lightweight fast report (default 3s, 1-60s adjustable). - 8: sing-box core processes start with GOMEMLIMIT=32MiB, GOGC=50. - 9: dead /getTwoFactorEnable docs entries removed (frontend call and contract entry already gone at base; verified by grep).
109 lines
3.8 KiB
Go
109 lines
3.8 KiB
Go
package probe
|
|
|
|
import (
|
|
"database/sql"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// V2.0.1 probe batch, storage side: the calibration columns and the
|
|
// counted-interface pin round-trip through probe_meta, pre-V2.0.1
|
|
// databases gain the columns on open, and the traffic reads apply
|
|
// the calibration (floored at zero) on every figure.
|
|
|
|
func TestMetaCalibrationAndIfaceRoundTrip(t *testing.T) {
|
|
st := newTestStore(t)
|
|
meta := DefaultMeta(7)
|
|
meta.TrafficCalUpGB = 2.5
|
|
meta.TrafficCalDownGB = -1.25
|
|
meta.StatIface = "eth0"
|
|
if err := st.UpsertMeta(meta); err != nil {
|
|
t.Fatalf("upsert: %v", err)
|
|
}
|
|
back, found, err := st.Meta(7)
|
|
if err != nil || !found {
|
|
t.Fatalf("meta read: found=%v err=%v", found, err)
|
|
}
|
|
if back.TrafficCalUpGB != 2.5 || back.TrafficCalDownGB != -1.25 || back.StatIface != "eth0" {
|
|
t.Errorf("round trip = up %v down %v iface %q", back.TrafficCalUpGB, back.TrafficCalDownGB, back.StatIface)
|
|
}
|
|
}
|
|
|
|
func TestOpenMigratesLegacyMetaColumns(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "probe.db")
|
|
db, err := sql.Open("sqlite3", path)
|
|
if err != nil {
|
|
t.Fatalf("open raw: %v", err)
|
|
}
|
|
// A pre-V2.0.1 probe_meta: the original 16 columns only (shape
|
|
// copied from the V2.0.0 schema in git history).
|
|
if _, err := db.Exec(`CREATE TABLE probe_meta (
|
|
agent_id INTEGER PRIMARY KEY, price REAL NOT NULL DEFAULT 0,
|
|
currency TEXT NOT NULL DEFAULT 'USD', billing_cycle TEXT NOT NULL DEFAULT 'monthly',
|
|
expires_at TEXT, traffic_limit INTEGER NOT NULL DEFAULT 0,
|
|
traffic_mode TEXT NOT NULL DEFAULT 'sum', traffic_reset_day INTEGER NOT NULL DEFAULT 1,
|
|
public INTEGER NOT NULL DEFAULT 1, sort INTEGER NOT NULL DEFAULT 0,
|
|
group_name TEXT NOT NULL DEFAULT '', public_remark TEXT NOT NULL DEFAULT '',
|
|
notify INTEGER NOT NULL DEFAULT 0, country_pin TEXT NOT NULL DEFAULT '',
|
|
ipv4_pin TEXT NOT NULL DEFAULT '', ipv6_pin TEXT NOT NULL DEFAULT '')`); err != nil {
|
|
t.Fatalf("create legacy table: %v", err)
|
|
}
|
|
if _, err := db.Exec(`INSERT INTO probe_meta (agent_id, group_name) VALUES (3, 'legacy')`); err != nil {
|
|
t.Fatalf("seed legacy row: %v", err)
|
|
}
|
|
_ = db.Close()
|
|
|
|
st, err := Open(path)
|
|
if err != nil {
|
|
t.Fatalf("open legacy db: %v", err)
|
|
}
|
|
defer st.Close()
|
|
back, found, err := st.Meta(3)
|
|
if err != nil || !found {
|
|
t.Fatalf("meta read: found=%v err=%v", found, err)
|
|
}
|
|
if back.GroupName != "legacy" || back.TrafficCalUpGB != 0 || back.TrafficCalDownGB != 0 || back.StatIface != "" {
|
|
t.Errorf("legacy row after migration = %+v", back)
|
|
}
|
|
back.StatIface = "ens3"
|
|
if err := st.UpsertMeta(back); err != nil {
|
|
t.Fatalf("upsert after migration: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTrafficCalibrationAppliedAndFloored(t *testing.T) {
|
|
st := newTestStore(t)
|
|
now := time.Now()
|
|
// Book 3 GB down / 1 GB up of counted traffic (baseline, delta).
|
|
book(t, st, 5, "k1/fp", 0, 0, now, nil, 0)
|
|
book(t, st, 5, "k1/fp", 3_000_000_000, 1_000_000_000, now.Add(time.Minute), nil, now.Unix())
|
|
|
|
meta := DefaultMeta(5)
|
|
meta.TrafficCalUpGB = -2 // correction exceeds the counted upload
|
|
meta.TrafficCalDownGB = 0.5 // half a GB the counters missed
|
|
if err := st.UpsertMeta(meta); err != nil {
|
|
t.Fatalf("upsert: %v", err)
|
|
}
|
|
|
|
all, err := st.AllTraffic()
|
|
if err != nil {
|
|
t.Fatalf("AllTraffic: %v", err)
|
|
}
|
|
tr := all[5]
|
|
if tr.TotalRx != 3_500_000_000 || tr.MonthRx != 3_500_000_000 || tr.DayRx != 3_500_000_000 {
|
|
t.Errorf("rx figures after +0.5 GB calibration = %d/%d/%d, want 3500000000 each", tr.TotalRx, tr.MonthRx, tr.DayRx)
|
|
}
|
|
if tr.TotalTx != 0 || tr.MonthTx != 0 || tr.DayTx != 0 {
|
|
t.Errorf("tx figures after -2 GB calibration = %d/%d/%d, want 0 (floored)", tr.TotalTx, tr.MonthTx, tr.DayTx)
|
|
}
|
|
|
|
one, found, err := st.TrafficFor(5)
|
|
if err != nil || !found {
|
|
t.Fatalf("TrafficFor: found=%v err=%v", found, err)
|
|
}
|
|
if one.TotalRx != tr.TotalRx || one.TotalTx != tr.TotalTx {
|
|
t.Errorf("TrafficFor disagrees with AllTraffic: %+v vs %+v", one, tr)
|
|
}
|
|
}
|