package agentapi import ( "context" "net/http" "net/http/httptest" "reflect" "strings" "testing" ) // R-921: the pre-check reads the agent's real wire shapes — GET /backup/tiers, then GET /backup/status per // target — and reports exactly the tiers whose job is running or snapshotted (the agent's in-flight set). func TestBackupJobsInFlight_ReportsRunningAndSnapshottedTiers(t *testing.T) { phases := map[string]string{"local": "done", "felhom-pbs": "snapshotted", "extra": "running"} mux := http.NewServeMux() mux.HandleFunc("GET /backup/tiers", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"ok":true,"data":{"vmid":9201,"tiers":[` + `{"target":"local","cadence_seconds":86400,"primary":true,"storage":"present"},` + `{"target":"felhom-pbs","cadence_seconds":604800,"primary":false,"storage":"present"},` + `{"target":"extra","cadence_seconds":604800,"primary":false,"storage":"present"}]}}`)) }) mux.HandleFunc("GET /backup/status", func(w http.ResponseWriter, r *http.Request) { tg := r.URL.Query().Get("target") ph, ok := phases[tg] if !ok { http.Error(w, "unexpected target "+tg, http.StatusBadRequest) return } _, _ = w.Write([]byte(`{"ok":true,"data":{"vmid":9201,"phase":"` + ph + `","target":"` + tg + `"}}`)) }) s := httptest.NewTLSServer(mux) defer s.Close() c := clientFor(t, s, strings.TrimPrefix(s.URL, "https://")) got, err := c.BackupJobsInFlight(context.Background()) if err != nil { t.Fatalf("BackupJobsInFlight: %v", err) } if want := []string{"felhom-pbs", "extra"}; !reflect.DeepEqual(got, want) { t.Fatalf("in-flight tiers = %v, want %v (done must not count; running and snapshotted must)", got, want) } } // A pre-R-82 agent (no /backup/tiers → 404) has no per-tier jobs: nil, nil — never an error that would be // read as "cannot tell", and never a busy verdict. func TestBackupJobsInFlight_PreR82AgentIsNotBusy(t *testing.T) { mux := http.NewServeMux() s := httptest.NewTLSServer(mux) // every route 404s defer s.Close() c := clientFor(t, s, strings.TrimPrefix(s.URL, "https://")) got, err := c.BackupJobsInFlight(context.Background()) if err != nil || got != nil { t.Fatalf("pre-R-82 agent: got %v, %v — want nil, nil", got, err) } } // A status read that fails is returned, so the loop can fail toward backing up and say so. func TestBackupJobsInFlight_StatusErrorIsReturned(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("GET /backup/tiers", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"ok":true,"data":{"vmid":9201,"tiers":[{"target":"local","primary":true}]}}`)) }) mux.HandleFunc("GET /backup/status", func(w http.ResponseWriter, r *http.Request) { http.Error(w, "boom", http.StatusInternalServerError) }) s := httptest.NewTLSServer(mux) defer s.Close() c := clientFor(t, s, strings.TrimPrefix(s.URL, "https://")) if _, err := c.BackupJobsInFlight(context.Background()); err == nil { t.Fatal("a failed status read was swallowed — the caller could not tell it from 'nothing in flight'") } }