046b808792
gates / gates (push) Successful in 1m22s
When the agent waits for quiet and qBittorrent or Transmission runs: record the limit first, hold at 1 KiB/s, ask the agent for a test on request, put the old limit back (deferred; a household change during the test stays). Start-up puts back a limit a killed controller left; the 50 % rule skips while a hold record exists. Commit only.
103 lines
4.6 KiB
Go
103 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/notify"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/upwatch"
|
|
)
|
|
|
|
// The upload watch is built, started, handed to the off-site push BEFORE the scheduler can start one, and its view
|
|
// handed to the pages — a seam built and never wired is this project's commonest defect.
|
|
// RED-PROOF: delete the SetOffboxPushObserver line → fails; move it below the scheduler's start → fails; pass nil
|
|
// instead of agentLineTest(...) → the qBittorrent limit never gets a tested line → fails.
|
|
func TestUpwatchIsWiredAtStartup(t *testing.T) {
|
|
lines, _, _ := slice4CallLines(t)
|
|
for _, fn := range []string{"newUpwatch", "agentLineTest", "SetOffboxPushObserver", "startUpwatch", "SetUpwatchView"} {
|
|
if len(lines[fn]) == 0 {
|
|
t.Fatalf("main.go never calls %s", fn)
|
|
}
|
|
}
|
|
if lines["SetOffboxPushObserver"][0] > lines["Daily"][0] {
|
|
t.Fatalf("SetOffboxPushObserver (line %d) comes after the first scheduled job (line %d)", lines["SetOffboxPushObserver"][0], lines["Daily"][0])
|
|
}
|
|
}
|
|
|
|
// newUpwatch asks the three apps the spike proved, reports through the notifier, and reads exact counters.
|
|
// RED-PROOF: drop the transmission entry → fails.
|
|
func TestNewUpwatch_ExplainersAndEmit(t *testing.T) {
|
|
lg := log.New(io.Discard, "", 0)
|
|
sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), lg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
svc := newUpwatch(sett, nil, nil, nil, notify.New("", "", "", sett, lg, false), agentLine{}, lg)
|
|
for _, a := range []string{"qbittorrent", "transmission", "plex"} {
|
|
if _, ok := svc.Watcher.Explainers[a]; !ok {
|
|
t.Errorf("no explainer for %s", a)
|
|
}
|
|
}
|
|
if svc.Watcher.Emit == nil || svc.Watcher.Line == nil || svc.QB == nil || svc.QBShare != 0.5 {
|
|
t.Fatalf("service not wired: %+v", svc)
|
|
}
|
|
if _, ok := svc.Watcher.Source.(*upwatch.DockerAPI); !ok {
|
|
t.Fatal("the watch must read the Engine API's exact counters, not docker stats' text")
|
|
}
|
|
// R-1250: both torrent apps can be held, qBittorrent first; without a local API there is no hold at all.
|
|
if len(svc.Torrents) != 2 || svc.Torrents[0].Stack() != "qbittorrent" || svc.Torrents[1].Stack() != "transmission" ||
|
|
svc.TorrentRunning == nil || svc.LineWaiting != nil || svc.LineRun != nil {
|
|
t.Fatalf("line-test hold not wired: %d torrents, waiting=%v run=%v", len(svc.Torrents), svc.LineWaiting != nil, svc.LineRun != nil)
|
|
}
|
|
if svc.Watcher.Baseline != nil {
|
|
t.Fatal("no metrics store, yet a baseline: an app with no explainer would be judged without history")
|
|
}
|
|
}
|
|
|
|
// R-1250: the start-up check puts a held limit back BEFORE the first rule reads it, and every tick runs the hold
|
|
// before the 50 % rule. Asserted on startUpwatch's source order.
|
|
// RED-PROOF: move svc.RecoverHold below the ticker, or drop SeedLineTestNow → fails.
|
|
func TestStartUpwatchRecoversTheHoldFirst(t *testing.T) {
|
|
src, err := os.ReadFile("upwatch_wiring.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body := string(src)
|
|
i := strings.Index(body, "func startUpwatch(")
|
|
if i < 0 {
|
|
t.Fatal("no startUpwatch")
|
|
}
|
|
body = body[i:]
|
|
rec, ticker := strings.Index(body, "svc.RecoverHold(ctx)"), strings.Index(body, "time.NewTicker(")
|
|
seed, rule := strings.Index(body, "svc.SeedLineTestNow(ctx)"), strings.Index(body, "svc.QBLimitNow(ctx)")
|
|
if rec < 0 || rec > ticker {
|
|
t.Fatalf("RecoverHold at %d, the ticker at %d: a held limit must go back before anything else runs", rec, ticker)
|
|
}
|
|
if seed < 0 || seed > rule {
|
|
t.Fatalf("SeedLineTestNow at %d, QBLimitNow at %d: the hold must run before the 50 %% rule", seed, rule)
|
|
}
|
|
}
|
|
|
|
// With a local API, agentLineTest yields all three halves, and newUpwatch hands the hold both agent calls.
|
|
// RED-PROOF: drop LineRun from newUpwatch's Service, or Run from agentLineTest → fails.
|
|
func TestUpwatchHoldGetsBothAgentCalls(t *testing.T) {
|
|
lg := log.New(io.Discard, "", 0)
|
|
line := agentLineTest("127.0.0.1:1", "token-canary", strings.Repeat("ab", 32), lg)
|
|
if line.Measured == nil || line.Waiting == nil || line.Run == nil {
|
|
t.Fatalf("agentLineTest with a local API: measured=%v waiting=%v run=%v", line.Measured != nil, line.Waiting != nil, line.Run != nil)
|
|
}
|
|
sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), lg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
svc := newUpwatch(sett, nil, nil, nil, notify.New("", "", "", sett, lg, false), line, lg)
|
|
if svc.Measured == nil || svc.LineWaiting == nil || svc.LineRun == nil {
|
|
t.Fatalf("newUpwatch dropped an agent call: measured=%v waiting=%v run=%v", svc.Measured != nil, svc.LineWaiting != nil, svc.LineRun != nil)
|
|
}
|
|
}
|