69 lines
2.6 KiB
Go
69 lines
2.6 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// v0.126.4 — two defects surfaced by the 0.87.0 wizard leg's decommission attempt:
|
|
// 1. writeDiskJSON emitted app-level errors as 502 — Cloudflare replaces origin 502/504
|
|
// bodies with its own HTML error page, so the browser saw "<!DOCTYPE ..." instead of the
|
|
// Hungarian refusal and threw a JSON SyntaxError alert.
|
|
// 2. The M1 last-usable-drive refusal (a POLICY verdict) was indistinguishable from a real
|
|
// agent-gateway failure.
|
|
// COMPANION red-proofs: (1) remove the 502→500 map in writeDiskJSON → the status assertions
|
|
// fail; (2) return a non-sentinel fmt.Errorf from the mustBlock branch → the errors.Is
|
|
// assertion fails.
|
|
|
|
func TestWriteDiskJSON_EdgeSafeStatus(t *testing.T) {
|
|
cases := []struct {
|
|
in int
|
|
want int
|
|
}{
|
|
{http.StatusBadGateway, http.StatusInternalServerError}, // CF would swallow 502
|
|
{http.StatusGatewayTimeout, http.StatusInternalServerError}, // CF would swallow 504
|
|
{http.StatusConflict, http.StatusConflict}, // policy refusals pass as-is
|
|
{http.StatusBadRequest, http.StatusBadRequest},
|
|
{http.StatusOK, http.StatusOK},
|
|
}
|
|
for _, c := range cases {
|
|
rr := httptest.NewRecorder()
|
|
writeDiskJSON(rr, c.in, false, "hiba szöveg", nil)
|
|
if rr.Code != c.want {
|
|
t.Errorf("writeDiskJSON(%d): got status %d, want %d (502/504 must never leave the origin — CF replaces their body)", c.in, rr.Code, c.want)
|
|
}
|
|
if !strings.Contains(rr.Body.String(), `"hiba szöveg"`) {
|
|
t.Errorf("writeDiskJSON(%d): JSON body lost", c.in)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The M1 refusal is the TYPED sentinel (so the handler can map it to 409, which crosses the
|
|
// CF edge with its body intact), and it must refuse BEFORE any side-effect: no soft-mark, no
|
|
// agent call.
|
|
func TestFinalizeDecommission_LastDriveRefusalIsTypedAndSideEffectFree(t *testing.T) {
|
|
s := testServer(t)
|
|
_ = s.settings.AddStoragePath(settings.StoragePath{Path: "/mnt/only", Schedulable: true, IsDefault: true})
|
|
agent := &mockAgent{}
|
|
|
|
err := s.finalizeDecommissionWith(context.Background(), agent, "/mnt/only", "")
|
|
if err == nil {
|
|
t.Fatal("decommissioning the last usable drive must be refused (M1)")
|
|
}
|
|
if !errors.Is(err, errLastUsableDrive) {
|
|
t.Fatalf("refusal must be the typed errLastUsableDrive sentinel (409 mapping), got: %v", err)
|
|
}
|
|
if s.settings.IsDecommissioned("/mnt/only") {
|
|
t.Error("refusal must precede the soft-mark side-effect")
|
|
}
|
|
if len(agent.decommissionCalls) != 0 {
|
|
t.Errorf("agent must not be called on refusal, got %v", agent.decommissionCalls)
|
|
}
|
|
}
|