3cf49c7fd5
The customer sets + owns the dashboard password via a hub-emailed one-time claim code. An unclaimed box (code hash present, no password) serves ONLY the claim page — every other route → claim page (302) or 401, so a Day-0 box is never open on the internet. A set password disables the gate (auth wins). Reset rides the same code engine (login "Elfelejtett jelszó"). Legacy-open (no password, no hash) shows a red transition banner until the hub delivers a hash. Report ACK caches the code state idempotently by generation; report carries claimed (set-only). --print-reset-code root escape hatch. Requires hub v0.50.0. Gate-coverage signature test + 4 red-proofs proven.
63 lines
1.8 KiB
Go
63 lines
1.8 KiB
Go
package report
|
|
|
|
import (
|
|
"io"
|
|
"log"
|
|
"testing"
|
|
)
|
|
|
|
type fakeClaimSettings struct {
|
|
hash string
|
|
gen int
|
|
issued string
|
|
setCall int
|
|
}
|
|
|
|
func (f *fakeClaimSettings) GetClaimCode() (string, int, string) { return f.hash, f.gen, f.issued }
|
|
func (f *fakeClaimSettings) SetClaimCode(hash string, gen int, issued string) error {
|
|
f.hash, f.gen, f.issued = hash, gen, issued
|
|
f.setCall++
|
|
return nil
|
|
}
|
|
|
|
func newSync(f *fakeClaimSettings) *ClaimSync {
|
|
return &ClaimSync{Settings: f, Logger: log.New(io.Discard, "", 0)}
|
|
}
|
|
|
|
// A newer generation caches; the same/older generation and nil are no-ops (idempotent, one-way).
|
|
func TestClaimSync_IdempotentByGeneration(t *testing.T) {
|
|
f := &fakeClaimSettings{}
|
|
s := newSync(f)
|
|
|
|
s.Reconcile(&ClaimStatus{CodeHash: "h1", Generation: 1, IssuedAt: "t1"})
|
|
if f.gen != 1 || f.hash != "h1" || f.setCall != 1 {
|
|
t.Fatalf("first cache: %+v", f)
|
|
}
|
|
|
|
// Same generation → no write.
|
|
s.Reconcile(&ClaimStatus{CodeHash: "h1-again", Generation: 1, IssuedAt: "t1"})
|
|
if f.setCall != 1 || f.hash != "h1" {
|
|
t.Fatalf("same generation must not rewrite: %+v", f)
|
|
}
|
|
|
|
// Older generation → no write (a lagging ACK can't regress the cache).
|
|
s.Reconcile(&ClaimStatus{CodeHash: "h0", Generation: 0, IssuedAt: "t0"})
|
|
if f.setCall != 1 {
|
|
t.Fatalf("older generation must not rewrite: %+v", f)
|
|
}
|
|
|
|
// Newer generation (a resend) → cache advances.
|
|
s.Reconcile(&ClaimStatus{CodeHash: "h2", Generation: 2, IssuedAt: "t2"})
|
|
if f.gen != 2 || f.hash != "h2" || f.setCall != 2 {
|
|
t.Fatalf("newer generation should advance: %+v", f)
|
|
}
|
|
|
|
// nil / empty / non-positive generation → no-op (old hub, no claim row).
|
|
s.Reconcile(nil)
|
|
s.Reconcile(&ClaimStatus{CodeHash: "", Generation: 3})
|
|
s.Reconcile(&ClaimStatus{CodeHash: "h", Generation: 0})
|
|
if f.setCall != 2 {
|
|
t.Fatalf("nil/empty/zero-gen must be no-ops: %+v", f)
|
|
}
|
|
}
|