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