2fe634141f
gates / gates (push) Successful in 45s
R-1142: the HDD_PATH root is appdata/<own folder>, with a compiled-in folder map (paperless-ngx -> paperless). R-981: the allow-list is empty (homepage's socket served no Felhom feature). R-1063: finalizeDecommissionWith asks the agent before it marks the drive or moves the default; the 403/409 come back typed and reach the household as a plain 409. R-1069: the agent's no-durable-id format refusal is ErrNoDurableID and a plain sentence. R-1143: build.sh carries :latest only for x.y.z. Each pinned by a red-proofed test. MinAgent 0.158.1.
88 lines
3.5 KiB
Go
88 lines
3.5 KiB
Go
package agentapi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// R-856: GET /host/crash-guard decodes the crash guard's fields, and an agent that predates the route
|
|
// surfaces as a typed 404 (the caller reads it as unknown → the normal boot grace).
|
|
func TestR856_CrashGuardDecodesAndAnOlderAgentIs404(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("GET /host/crash-guard", func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"ok":true,"data":{"present":true,"last_boot_at":"2026-10-04T12:00:00Z","last_boot_unclean":true,"tripped":false}}`))
|
|
})
|
|
s := httptest.NewTLSServer(mux)
|
|
defer s.Close()
|
|
c := clientFor(t, s, strings.TrimPrefix(s.URL, "https://"))
|
|
st, err := c.CrashGuard(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !st.Present || !st.LastBootUnclean || st.LastBootAt != "2026-10-04T12:00:00Z" || st.Tripped {
|
|
t.Fatalf("state = %+v", st)
|
|
}
|
|
|
|
old := httptest.NewTLSServer(http.NewServeMux())
|
|
defer old.Close()
|
|
_, err = clientFor(t, old, strings.TrimPrefix(old.URL, "https://")).CrashGuard(context.Background())
|
|
var se *StatusError
|
|
if !errors.As(err, &se) || se.Code != http.StatusNotFound {
|
|
t.Fatalf("an older agent must answer a typed 404, got %v", err)
|
|
}
|
|
}
|
|
|
|
// R-1063: the agent's two decommission refusals come back TYPED (403 a protected role, 409 the whole-guest backup
|
|
// target), with the same text as before; any other failure is not that type.
|
|
func TestDecommission_PolicyRefusalsAreTyped(t *testing.T) {
|
|
for _, c := range []struct {
|
|
code int
|
|
typed bool
|
|
}{{http.StatusForbidden, true}, {http.StatusConflict, true}, {http.StatusBadGateway, false}} {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("POST /disks/decommission", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(c.code)
|
|
_, _ = w.Write([]byte(`{"ok":false,"error":"refused for the test"}`))
|
|
})
|
|
s := httptest.NewTLSServer(mux)
|
|
_, err := clientFor(t, s, strings.TrimPrefix(s.URL, "https://")).Decommission(context.Background(), "/mnt/x")
|
|
s.Close()
|
|
var dr *DecommissionRefusedError
|
|
if got := errors.As(err, &dr); got != c.typed {
|
|
t.Fatalf("HTTP %d: typed=%v, want %v (err %v)", c.code, got, c.typed, err)
|
|
}
|
|
if c.typed && (dr.Status != c.code || !strings.Contains(err.Error(), "HTTP "+strconv.Itoa(c.code)+": refused for the test")) {
|
|
t.Fatalf("HTTP %d: %+v / %q", c.code, dr, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// R-1069: the agent's anti-retarget refusal of a blank format (its exact text, a 409) comes back as ErrNoDurableID;
|
|
// another 409 does not.
|
|
func TestFormatDisk_NoDurableIDIsTyped(t *testing.T) {
|
|
for _, c := range []struct {
|
|
body string
|
|
typed bool
|
|
}{
|
|
{`{"ok":false,"error":"format refused: device has no durable id to bind the format to (path-only formats are not permitted)","data":{"formatted":false,"data_bearing":false}}`, true},
|
|
{`{"ok":false,"error":"format refused (device may have changed since inspection): gone","data":{"formatted":false}}`, false},
|
|
} {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("POST /disks/format", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusConflict)
|
|
_, _ = w.Write([]byte(c.body))
|
|
})
|
|
s := httptest.NewTLSServer(mux)
|
|
_, err := clientFor(t, s, strings.TrimPrefix(s.URL, "https://")).FormatDisk(context.Background(), "/dev/vdb", "ext4", false, "")
|
|
s.Close()
|
|
if got := errors.Is(err, ErrNoDurableID); got != c.typed || err == nil {
|
|
t.Fatalf("body %s: ErrNoDurableID=%v want %v (err %v)", c.body[:60], got, c.typed, err)
|
|
}
|
|
}
|
|
}
|