91b9405c81
gates / gates (push) Successful in 55s
Unreleased; ships with tomorrow's release. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
107 lines
4.2 KiB
Go
107 lines
4.2 KiB
Go
package localapi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/escrow"
|
|
)
|
|
|
|
// R-224 — THE STATUS IS THE DISCRIMINATOR, and this test asserts the CONSEQUENCE (what the HTTP
|
|
// boundary answers) rather than the mechanism (that the sentinel exists).
|
|
//
|
|
// The controller one trust tier down classifies on the STATUS and must never parse the sentence. So
|
|
// the contract this pins is: four distinguishable situations, four distinct statuses, and the
|
|
// wrong-code message reachable ONLY from a real refusal.
|
|
//
|
|
// Before R-224 the first and last rows both answered 400 with the same sentence — which is how
|
|
// CAMPAIGN-11 F3 told a customer holding a CORRECT code that it did not open their package.
|
|
|
|
type fakeRecoverer struct{ err error }
|
|
|
|
func (f fakeRecoverer) RecoverOffsiteRepoPassword(context.Context, string) (string, error) {
|
|
if f.err != nil {
|
|
return "", f.err
|
|
}
|
|
return "0123456789abcdef0123456789abcdef", nil
|
|
}
|
|
|
|
func TestRecoverOffsitePassword_EachSituationGetsItsOwnStatus(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
err error
|
|
wantStatus int
|
|
// mustNotSay guards the specific misattribution each status exists to prevent.
|
|
mustNotSay []string
|
|
}{
|
|
{
|
|
name: "fetch failed — the code was NEVER used",
|
|
err: errors.Join(escrow.ErrBundleFetch, errors.New("hub: transport error: no route to host")),
|
|
wantStatus: 502,
|
|
mustNotSay: []string{"did not open"},
|
|
},
|
|
{
|
|
name: "wrong code — the bundle WAS fetched and refused it",
|
|
err: errors.New("escrow: the recovery code did not unwrap the identity escrow"),
|
|
wantStatus: 400,
|
|
mustNotSay: []string{"could not be fetched"},
|
|
},
|
|
{
|
|
name: "the hub holds no bundle",
|
|
err: escrow.ErrNoEscrowBlob,
|
|
wantStatus: 404,
|
|
mustNotSay: []string{"did not open"},
|
|
},
|
|
{
|
|
// R-304: earlier packages were not all tried — never „did not open the sealed bundle" (the wrong-code words).
|
|
name: "earlier packages not all checked — not a wrong code",
|
|
err: &escrow.RetainedUncheckedError{Unchecked: 2},
|
|
wantStatus: 424,
|
|
mustNotSay: []string{"did not open the sealed bundle", "could not be fetched"},
|
|
},
|
|
{
|
|
name: "the bundle predates the repository-password field",
|
|
err: escrow.ErrNoResticPassword,
|
|
wantStatus: 409,
|
|
mustNotSay: []string{"could not be fetched"},
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
srv := newTestServerS(t, &fakeGuests{}, &fakeBackups{}, &fakeStore{}, nil)
|
|
srv.escrowRecovery = fakeRecoverer{err: tc.err}
|
|
w := do(t, srv.Handler(), "POST", "/escrow/recover-offsite-password", "A",
|
|
`{"vmid":8200,"recovery_code":"correct horse battery staple sedative anaconda wobbly kingdom placard yodel"}`)
|
|
if w.Code != tc.wantStatus {
|
|
t.Fatalf("status: got %d, want %d — body=%s", w.Code, tc.wantStatus, w.Body.String())
|
|
}
|
|
for _, phrase := range tc.mustNotSay {
|
|
if strings.Contains(w.Body.String(), phrase) {
|
|
t.Fatalf("the %d answer must not say %q — body=%s", tc.wantStatus, phrase, w.Body.String())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The pair that matters most, stated as its own assertion so a regression cannot hide inside a table:
|
|
// a fetch failure and a wrong code must never answer with the SAME status. Collapsing them is the
|
|
// whole of R-224.
|
|
func TestRecoverOffsitePassword_FetchFailureAndWrongCodeDiffer(t *testing.T) {
|
|
status := func(err error) int {
|
|
srv := newTestServerS(t, &fakeGuests{}, &fakeBackups{}, &fakeStore{}, nil)
|
|
srv.escrowRecovery = fakeRecoverer{err: err}
|
|
return do(t, srv.Handler(), "POST", "/escrow/recover-offsite-password", "A",
|
|
`{"vmid":8200,"recovery_code":"correct horse battery staple sedative anaconda wobbly kingdom placard yodel"}`).Code
|
|
}
|
|
fetch := status(errors.Join(escrow.ErrBundleFetch, errors.New("no route to host")))
|
|
wrong := status(errors.New("escrow: the recovery code did not unwrap the identity escrow"))
|
|
// RED-PROOF: delete the ErrBundleFetch case from handleRecoverOffsitePassword → both become 400
|
|
// → this FAILS. That is the exact pre-R-224 code, and the exact defect CAMPAIGN-11 measured.
|
|
if fetch == wrong {
|
|
t.Fatalf("a failed fetch and a wrong code must not share a status (both %d)", fetch)
|
|
}
|
|
}
|