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] // V2.2.1 ⑥: calibration converts on the binary convention — // +0.5 GB = 0.5 x 2^30 = 536870912 B (was 5e8 at the old 1e9). if tr.TotalRx != 3_536_870_912 || tr.MonthRx != 3_536_870_912 || tr.DayRx != 3_536_870_912 { t.Errorf("rx figures after +0.5 GB calibration = %d/%d/%d, want 3536870912 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) } }