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) } }