29bcf4403e
gates / gates (push) Successful in 1m37s
GET /host/line-test reports waiting_for_quiet (due, in the window, tunnel up, only the quiet missing); POST /host/line-test/run runs one measurement with a 5 s settle and a 10 s quiet check instead of 10 quiet minutes, runs 5 s apart (~55 s). Every other weekly rule holds. Commit only; the release rides the next train.
117 lines
3.7 KiB
Go
117 lines
3.7 KiB
Go
package localapi
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"log/slog"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/linetest"
|
|
)
|
|
|
|
type fixedLine struct {
|
|
mbps float64
|
|
ok bool
|
|
}
|
|
|
|
func (f fixedLine) Value() (float64, time.Time, bool) {
|
|
return f.mbps, time.Date(2026, 10, 24, 6, 31, 0, 0, time.UTC), f.ok
|
|
}
|
|
|
|
func lineTestBody(t *testing.T, s *Server) LineTestResponse {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
s.handleLineTest(rec, httptest.NewRequest("GET", "/host/line-test", nil), 9201)
|
|
var env struct {
|
|
OK bool `json:"ok"`
|
|
Data LineTestResponse `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &env); err != nil {
|
|
t.Fatalf("decode %q: %v", rec.Body.String(), err)
|
|
}
|
|
return env.Data
|
|
}
|
|
|
|
// R-1247: the controller sets qBittorrent's limit only from a PRESENT value; never measured and not wired must both
|
|
// read present:false, never a zero line.
|
|
func TestLineTestRoute(t *testing.T) {
|
|
s := &Server{logger: slog.Default()}
|
|
if got := lineTestBody(t, s); got.Present {
|
|
t.Fatalf("not wired: %+v, want present:false", got)
|
|
}
|
|
s.SetLineTest(fixedLine{ok: false})
|
|
if got := lineTestBody(t, s); got.Present || got.Mbps != 0 {
|
|
t.Fatalf("never measured: %+v, want present:false", got)
|
|
}
|
|
s.SetLineTest(fixedLine{mbps: 90.7, ok: true})
|
|
got := lineTestBody(t, s)
|
|
if !got.Present || got.Mbps != 90.7 || got.Method != "iperf3-tunnel" || !strings.HasPrefix(got.MeasuredAt, "2026-10-24T06:31") {
|
|
t.Fatalf("measured: %+v", got)
|
|
}
|
|
}
|
|
|
|
type requestableLine struct {
|
|
fixedLine
|
|
waiting bool
|
|
mbps float64
|
|
err error
|
|
calls int
|
|
}
|
|
|
|
func (r *requestableLine) WaitingForQuiet() bool { return r.waiting }
|
|
func (r *requestableLine) RunNow(context.Context) (float64, []float64, error) {
|
|
r.calls++
|
|
if r.err != nil {
|
|
return 0, nil, r.err
|
|
}
|
|
return r.mbps, []float64{r.mbps - 1, r.mbps}, nil
|
|
}
|
|
|
|
func lineTestRun(t *testing.T, s *Server) (int, LineTestRunResponse) {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
s.handleLineTestRun(rec, httptest.NewRequest("POST", "/host/line-test/run", nil), 9201)
|
|
var env struct {
|
|
OK bool `json:"ok"`
|
|
Data LineTestRunResponse `json:"data"`
|
|
}
|
|
_ = json.Unmarshal(rec.Body.Bytes(), &env)
|
|
return rec.Code, env.Data
|
|
}
|
|
|
|
// R-1250: the controller acts only on waiting_for_quiet, and reads a refusal by its code — never as a measurement.
|
|
func TestLineTestRunRoute(t *testing.T) {
|
|
s := &Server{logger: slog.Default()}
|
|
if code, _ := lineTestRun(t, s); code != 503 {
|
|
t.Fatalf("not wired: HTTP %d, want 503", code)
|
|
}
|
|
s.SetLineTest(fixedLine{ok: true, mbps: 90})
|
|
if code, _ := lineTestRun(t, s); code != 503 {
|
|
t.Fatalf("a reader without the request half: HTTP %d, want 503", code)
|
|
}
|
|
l := &requestableLine{fixedLine: fixedLine{ok: false}, waiting: true, mbps: 93.5}
|
|
s.SetLineTest(l)
|
|
if got := lineTestBody(t, s); got.Present || !got.WaitingForQuiet {
|
|
t.Fatalf("GET while waiting: %+v, want present:false waiting_for_quiet:true", got)
|
|
}
|
|
if code, got := lineTestRun(t, s); code != 200 || !got.OK || got.Mbps != 93.5 || len(got.RunsMbps) != 2 {
|
|
t.Fatalf("a measured request: HTTP %d %+v", code, got)
|
|
}
|
|
l.err = &linetest.Refusal{Code: "not-quiet", Msg: "the uplink sent 16.0 Mbit/s while held"}
|
|
if _, got := lineTestRun(t, s); got.OK || got.Refused != "not-quiet" || got.Mbps != 0 {
|
|
t.Fatalf("a refusal: %+v, want ok:false refused:not-quiet", got)
|
|
}
|
|
l.err = linetest.ErrBusy
|
|
if _, got := lineTestRun(t, s); got.OK || got.Refused != "server-busy" {
|
|
t.Fatalf("server busy: %+v, want refused:server-busy", got)
|
|
}
|
|
l.err = errors.New("dial tcp 10.77.0.1:5201: i/o timeout")
|
|
if _, got := lineTestRun(t, s); got.OK || got.Refused != "" || got.Error == "" {
|
|
t.Fatalf("a failed run: %+v, want ok:false, no refusal, the error", got)
|
|
}
|
|
}
|