Files
felhom-controller/controller/internal/web/r1151_escrow_confirm_test.go
T

124 lines
5.0 KiB
Go

package web
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// R-1151 — the wizard's typed-back words reach the agent, which stores the new package only when they match. Until
// then the ceremony is NOT complete: no „completed" stamp, no report push — the hub still holds the previous package.
func postConfirm(t *testing.T, s *Server, lang, body string) *httptest.ResponseRecorder {
t.Helper()
r := httptest.NewRequest("POST", "/api/escrow/confirm?lang="+lang, strings.NewReader(body))
r.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
s.escrowConfirmAPIHandler(w, r)
return w
}
func ceremonyStamped(t *testing.T, h *escrowWizardHarness) bool {
t.Helper()
o := h.sett.GetOffboxTarget()
return o != nil && o.CeremonyCompletedAt != ""
}
// COMPANION RED-PROOF (observed 2026-10-19): drop the `if claim.ConfirmRequired { … return }` block in
// escrowClaimAPIHandler → this fails with `claim: 200 {"data":{"recovery_code":"egy-ketto-harom-negy"},…}` — no
// confirm request reaches the page, and the claim stamps the ceremony completed. Restored.
func TestR1151_ClaimThatWaitsIsNotCompleteUntilTheWordsComeBack(t *testing.T) {
h := newEscrowWizardHarness(t)
h.configureOffbox(t, "pending")
h.agent.claimCode, h.agent.claimStatus = "egy-ketto-harom-negy", http.StatusOK
h.agent.claimConfirm, h.agent.claimPos = true, []int{2, 4}
w := httptest.NewRecorder()
h.s.escrowClaimAPIHandler(w, httptest.NewRequest("POST", "/api/escrow/claim", nil))
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), `"confirm_required":true`) || !strings.Contains(w.Body.String(), `"verify_positions":[2,4]`) {
t.Fatalf("claim: %d %s", w.Code, w.Body.String())
}
if ceremonyStamped(t, h) {
t.Fatal("a claim that waits for the typed-back words stamped the ceremony completed")
}
h.agent.confirmStatus = http.StatusOK
if w := postConfirm(t, h.s, "hu", `{"words":["ketto","negy"]}`); w.Code != http.StatusOK {
t.Fatalf("confirm: %d %s", w.Code, w.Body.String())
}
if got := strings.Join(h.agent.confirmWords, ","); got != "ketto,negy" {
t.Fatalf("the agent got the words %q, want ketto,negy", got)
}
if !ceremonyStamped(t, h) {
t.Fatal("the confirmed ceremony was not stamped completed")
}
if c := w.Header().Get("Cache-Control"); c != "no-store" {
t.Fatalf("confirm Cache-Control = %q", c)
}
}
// An agent older than 0.160.0 answers the claim without a confirm request: its package is on the hub, and the claim
// completes the ceremony as before.
func TestR1151_OldAgentClaimCompletesAtOnce(t *testing.T) {
h := newEscrowWizardHarness(t)
h.configureOffbox(t, "pending")
h.agent.claimCode, h.agent.claimStatus = "egy-ketto-harom-negy", http.StatusOK
w := httptest.NewRecorder()
h.s.escrowClaimAPIHandler(w, httptest.NewRequest("POST", "/api/escrow/claim", nil))
if w.Code != http.StatusOK || strings.Contains(w.Body.String(), "confirm_required") {
t.Fatalf("claim on an old agent: %d %s", w.Code, w.Body.String())
}
if !ceremonyStamped(t, h) {
t.Fatal("an old agent's claim must complete the ceremony")
}
for _, o := range h.order {
if o == "confirm" {
t.Fatal("a confirm was sent to an old agent")
}
}
}
// Every refusal of the confirm answers in the box's language, says the previous code still works where it does,
// and tells the page to start again only when the new code is void.
func TestR1151_ConfirmAnswersInTheBoxLanguage(t *testing.T) {
cases := []struct {
agent int
want int
restart bool
hu, en string
}{
{http.StatusUnprocessableEntity, http.StatusUnprocessableEntity, false, "A megadott szavak nem egyeznek", "The words do not match"},
{http.StatusGone, http.StatusGone, true, "Lejárt az idő", "has run out"},
{http.StatusNotFound, http.StatusGone, true, "már nem vár megerősítésre", "no longer waits"},
{http.StatusBadGateway, http.StatusBadGateway, false, "nem sikerült elmenteni", "could not be saved"},
{0, http.StatusBadGateway, false, "nem sikerült elmenteni", "could not be saved"},
}
for _, c := range cases {
for _, lang := range []string{"hu", "en"} {
h := newEscrowWizardHarness(t)
h.agent.confirmStatus, h.agent.confirmErr = c.agent, fmt.Errorf("agent said %d", c.agent)
w := postConfirm(t, h.s, lang, `{"words":["a","b"]}`)
want := map[string]string{"hu": c.hu, "en": c.en}[lang]
if w.Code != c.want || !strings.Contains(w.Body.String(), want) {
t.Errorf("agent %d, %s: got %d %s — want %d with %q", c.agent, lang, w.Code, w.Body.String(), c.want, want)
}
if got := strings.Contains(w.Body.String(), `"restart":true`); got != c.restart {
t.Errorf("agent %d: restart=%v, want %v", c.agent, got, c.restart)
}
if ceremonyStamped(t, h) {
t.Errorf("agent %d: a refused confirm stamped the ceremony", c.agent)
}
}
}
// A body without two words never reaches the agent.
h := newEscrowWizardHarness(t)
if w := postConfirm(t, h.s, "hu", `{"words":["a"]}`); w.Code != http.StatusBadRequest {
t.Fatalf("one word: got %d", w.Code)
}
if len(h.order) != 0 {
t.Fatalf("a malformed confirm reached the agent: %v", h.order)
}
}