v0.342.0 (unreleased): the upload watch reads the host's weekly line test (GET /host/line-test, agent >= 0.164.0); qBittorrent's limit = 50 % of the TESTED line only (D41), the watch uses the higher of test and push floor, the system page names the source; red-proved
gates / gates (push) Successful in 1m15s
gates / gates (push) Successful in 1m15s
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package upwatch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type memStore struct {
|
||||
mu sync.Mutex
|
||||
raw json.RawMessage
|
||||
}
|
||||
|
||||
func (m *memStore) GetUpwatch() json.RawMessage { m.mu.Lock(); defer m.mu.Unlock(); return m.raw }
|
||||
func (m *memStore) UpdateUpwatch(fn func(json.RawMessage) (json.RawMessage, error)) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n, err := fn(m.raw)
|
||||
if err == nil {
|
||||
m.raw = n
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// D41 (operator 2026-10-24): qBittorrent gets 50 % of the host's TESTED line — never of the push floor, which read
|
||||
// 10 Mbit/s on demo-hp's 90 Mbit/s line. The watch judges against the higher of the two.
|
||||
// RED-PROOF: let QBLimitNow use s.Line() → the push floor alone sets a limit → fails; let line() ignore the test →
|
||||
// Line() stays at the floor → fails.
|
||||
func TestService_QBLimitFromTheTestedLineOnly(t *testing.T) {
|
||||
logins, expire, limit := 0, false, int64(0)
|
||||
srv := fakeQBServer(t, "pw-canary", &logins, &expire, &limit)
|
||||
defer srv.Close()
|
||||
st := &memStore{}
|
||||
floor := LineRecord{Peak10Mbps: 10, Counts: true}
|
||||
if err := st.UpdateUpwatch(func(json.RawMessage) (json.RawMessage, error) { return json.Marshal(State{Line: []LineRecord{floor}}) }); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var tested float64
|
||||
at := time.Date(2026, 10, 24, 6, 31, 0, 0, time.UTC)
|
||||
svc := &Service{Store: st, QB: &QBittorrent{Box: envBox{"qbittorrent/QBT_ADMIN_PASSWORD": "pw-canary"}, URL: srv.URL},
|
||||
QBShare: 0.5, Measured: func(context.Context) (float64, time.Time, bool) { return tested, at, tested > 0 }}
|
||||
|
||||
svc.QBLimitNow(context.Background())
|
||||
if limit != 0 {
|
||||
t.Fatalf("no test yet, push floor 10 Mbit/s: qBittorrent limited to %d B/s — the floor must not set a limit", limit)
|
||||
}
|
||||
if l, ok := svc.Line(); !ok || l != 10 {
|
||||
t.Fatalf("watch line before a test = %.1f %v, want the push floor 10", l, ok)
|
||||
}
|
||||
if v := svc.View(); !v.LineKnown || v.LineTested || v.LineMbps != 10 {
|
||||
t.Fatalf("view before a test = %+v", v)
|
||||
}
|
||||
|
||||
tested = 90.7
|
||||
svc.QBLimitNow(context.Background())
|
||||
if want := int64(5668750) / 1024 * 1024; limit != want {
|
||||
t.Fatalf("tested 90.7 Mbit/s: limit %d B/s, want 50 %% = %d (stored in KiB/s)", limit, want)
|
||||
}
|
||||
if l, ok := svc.Line(); !ok || l != 90.7 {
|
||||
t.Fatalf("watch line after the test = %.1f %v, want 90.7", l, ok)
|
||||
}
|
||||
if v := svc.View(); !v.LineTested || v.LineMbps != 90.7 || v.QBLimitBps != limit {
|
||||
t.Fatalf("view after the test = %+v", v)
|
||||
}
|
||||
|
||||
// The agent stops answering: the last tested value stays (an agent restart must not drop the limit).
|
||||
tested = 0
|
||||
svc.RefreshMeasured(context.Background())
|
||||
if l, ok := svc.MeasuredLine(); !ok || l != 90.7 {
|
||||
t.Fatalf("after a failed read: %.1f %v, want the last value kept", l, ok)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user