package localapi import ( "errors" "net/http" "strings" "testing" ) // R-1254 (the ceremony half, review round 2 HUNT-3 H2): the upload's answer can be lost or late (30 s) AFTER the hub // stored the new package. The household was then told „the previous code still works" — false: the hub now holds the // package the NEW code opens. The agent asks the hub which package it holds before it words the result: // - the hub holds the new package → success (the code is handed out / the confirm completes); // - the hub holds another package → the old sentence, now true; // - the hub cannot be asked → 504 and a sentence that does NOT promise the previous code; the wait goes on. // RED-PROOF: remove the hub question from the claim's failure branch → 502 „the previous code still works" while the // hub holds the new package → FAILS. func TestR1254_ALostAnswerAtTheClaimIsASuccessWhenTheHubHoldsTheNewPackage(t *testing.T) { hub := &fakeEscrowHub{lost: true} srv, _ := newEscrowTestServerHub(t, sequenceRunner(firstR), hub) h := srv.Handler() startAndWait(t, srv, h) code, body := claimCode(t, h) if !hub.opensWith(firstR) { t.Fatal("fixture: the hub must hold the new package (the answer was lost after the store)") } if code != http.StatusOK || !strings.Contains(body, firstR) { t.Fatalf("claim after a lost answer, the hub holding the new package: %d %s — want 200 with the code", code, body) } if strings.Contains(body, "previous code still works") { t.Fatalf("the household was told the previous code still works: %s", body) } if st := do(t, h, "GET", "/escrow/ceremony/status", "A", "").Body.String(); !strings.Contains(st, `"uploaded":true`) { t.Fatalf("the job must read uploaded: %s", st) } } // The hub cannot be asked: no promise about the previous code, 504, the code stays claimable (a retry decides). // RED-PROOF: as above → 502 with „the previous code still works" → FAILS. func TestR1254_AnUnknownOutcomeAtTheClaimPromisesNothing(t *testing.T) { hub := &fakeEscrowHub{lost: true, holdsErr: errors.New("hub unreachable")} srv, _ := newEscrowTestServerHub(t, sequenceRunner(firstR), hub) h := srv.Handler() startAndWait(t, srv, h) code, body := claimCode(t, h) if code != http.StatusGatewayTimeout { t.Fatalf("claim with an unknown outcome: %d %s — want 504", code, body) } if strings.Contains(body, "previous code still works") { t.Fatalf("an unknown outcome promised the previous code: %s", body) } assertNoRIn(t, "unknown-outcome claim payload", body, firstR) if st := do(t, h, "GET", "/escrow/ceremony/status", "A", "").Body.String(); !strings.Contains(st, `"claimable":true`) { t.Fatalf("the code must stay claimable: %s", st) } // The hub answers again: the retry finds the new package already stored and hands the code out. hub.mu.Lock() hub.holdsErr = nil hub.mu.Unlock() if code, body := claimCode(t, h); code != http.StatusOK || !strings.Contains(body, firstR) { t.Fatalf("retry: %d %s", code, body) } } // The hub holds ANOTHER package (the upload really failed): the old sentence stays, and is now true. func TestR1254_AFailedUploadTheHubDidNotStoreKeepsTheOldSentence(t *testing.T) { hub := &fakeEscrowHub{err: errors.New("hub returned HTTP 502")} srv, _ := newEscrowTestServerHub(t, sequenceRunner(firstR), hub) h := srv.Handler() startAndWait(t, srv, h) if code, body := claimCode(t, h); code != http.StatusBadGateway || !strings.Contains(body, "previous code still works") { t.Fatalf("a failed upload: %d %s — want 502 and the previous code named", code, body) } } // The same at the typed-back confirm (R-1151). // RED-PROOF: remove the hub question from the confirm's failure branch → 502 → FAILS. func TestR1254_ALostAnswerAtTheConfirmIsASuccessWhenTheHubHoldsTheNewPackage(t *testing.T) { hub := &fakeEscrowHub{lost: true} srv, _ := newEscrowTestServerHub(t, sequenceRunner(secondR), hub) h := srv.Handler() startAndWait(t, srv, h) c := claimDeferred(t, h) if code := confirm(t, h, c.typedBack(t)); code != http.StatusOK || !hub.opensWith(secondR) { t.Fatalf("confirm after a lost answer: %d (opens: %v) — want 200", code, hub.opensWith(secondR)) } hub.mu.Lock() hub.lost, hub.holdsErr = true, errors.New("hub unreachable") hub.mu.Unlock() srv2, _ := newEscrowTestServerHub(t, sequenceRunner(firstR), hub) h2 := srv2.Handler() startAndWait(t, srv2, h2) c2 := claimDeferred(t, h2) if code := confirm(t, h2, c2.typedBack(t)); code != http.StatusGatewayTimeout { t.Fatalf("confirm with an unknown outcome: %d — want 504", code) } }