package store import ( "database/sql" "testing" ) // RestageHostPBSSecret re-arms a stored (consumed) secret WITHOUT changing its value, inserting a // row, or bumping any generation. Returns restaged=false when no row exists. func TestRestageHostPBSSecret(t *testing.T) { s := newTestStore(t) // No row → restaged=false, no error (the caller escalates to Re-issue). if restaged, err := s.RestageHostPBSSecret("h1"); err != nil || restaged { t.Fatalf("restage with nothing stored = (%v, %v), want (false, nil)", restaged, err) } // Store + consume, then re-stage: the SAME value is served once more. if _, err := s.SaveHostPBSSecret("h1", "the-secret"); err != nil { t.Fatalf("save: %v", err) } if _, err := s.ConsumeHostPBSSecret("h1"); err != nil { t.Fatalf("first consume: %v", err) } if _, err := s.ConsumeHostPBSSecret("h1"); err != sql.ErrNoRows { t.Fatalf("pre-restage second consume = %v, want ErrNoRows", err) } restaged, err := s.RestageHostPBSSecret("h1") if err != nil || !restaged { t.Fatalf("restage of a stored secret = (%v, %v), want (true, nil)", restaged, err) } // Red-proof: dropping the `SET consumed_at = NULL` makes this consume return ErrNoRows. got, err := s.ConsumeHostPBSSecret("h1") if err != nil || got != "the-secret" { t.Fatalf("post-restage consume = (%q, %v), want (the-secret, nil) — same value, re-armed", got, err) } } // A-gen guard: a re-stage touches ONLY host_pbs_secrets — never the host generation (a bump would // trigger an agent desired-state refetch loop). Red-proof: adding a SetHostDesired/gen bump to // RestageHostPBSSecret makes this assert fail. func TestRestageHostPBSSecret_NoGenerationBump(t *testing.T) { s := newTestStore(t) if err := s.UpsertHost(&Host{HostID: "h1", CustomerID: "c1", APIKey: "k"}); err != nil { t.Fatalf("host: %v", err) } genBefore, err := s.SetHostDesired("h1", []byte(`{"pbs_dr":{"enabled":true,"namespace":"c1"}}`)) if err != nil { t.Fatalf("set desired: %v", err) } if _, err := s.SaveHostPBSSecret("h1", "s"); err != nil { t.Fatalf("save secret: %v", err) } if _, err := s.RestageHostPBSSecret("h1"); err != nil { t.Fatalf("restage: %v", err) } h, err := s.GetHost("h1") if err != nil { t.Fatalf("get host: %v", err) } if h.DesiredGeneration != genBefore { t.Fatalf("generation changed by a re-stage: %d -> %d (a re-stage must never bump)", genBefore, h.DesiredGeneration) } } // PBSDRHealStates joins each host's descriptor (enabled/provisioned) to its LATEST report's // pbs_dr.state + report id. func TestPBSDRHealStates(t *testing.T) { s := newTestStore(t) // Host A: enabled + provisioned, latest report waiting_secret (after an older applied report). mustHost(t, s, "hA", "cA", `{"pbs_dr":{"enabled":true,"namespace":"cA","storage_id":"felhom-pbs"}}`) mustReport(t, s, "hA", "cA", `{"pbs_dr":{"state":"applied"}}`) mustReport(t, s, "hA", "cA", `{"pbs_dr":{"state":"waiting_secret"}}`) // newer → wins // Host B: descriptor disabled. mustHost(t, s, "hB", "cB", `{"pbs_dr":{"enabled":false,"namespace":"cB"}}`) mustReport(t, s, "hB", "cB", `{"pbs_dr":{"state":"disabled"}}`) // Host C: enabled but NOT provisioned (namespace empty); no report. mustHost(t, s, "hC", "cC", `{"pbs_dr":{"enabled":true}}`) rows, err := s.PBSDRHealStates() if err != nil { t.Fatalf("PBSDRHealStates: %v", err) } byHost := map[string]PBSDRHealRow{} for _, r := range rows { byHost[r.HostID] = r } a := byHost["hA"] if !a.DescriptorEnabled || !a.DescriptorProvisioned || a.ReportedState != "waiting_secret" || a.ReportID == 0 { t.Errorf("hA = %+v, want enabled+provisioned+waiting_secret+reportID>0", a) } b := byHost["hB"] if b.DescriptorEnabled || b.ReportedState != "disabled" { t.Errorf("hB = %+v, want disabled descriptor + state disabled", b) } c := byHost["hC"] if !c.DescriptorEnabled || c.DescriptorProvisioned || c.ReportedState != "" || c.ReportID != 0 { t.Errorf("hC = %+v, want enabled+unprovisioned+no-report", c) } } func mustHost(t *testing.T, s *Store, hostID, customerID, desiredJSON string) { t.Helper() if err := s.UpsertHost(&Host{HostID: hostID, CustomerID: customerID, APIKey: "k-" + hostID}); err != nil { t.Fatalf("UpsertHost %s: %v", hostID, err) } if _, err := s.SetHostDesired(hostID, []byte(desiredJSON)); err != nil { t.Fatalf("SetHostDesired %s: %v", hostID, err) } } func mustReport(t *testing.T, s *Store, hostID, customerID, reportJSON string) { t.Helper() if err := s.SaveHostReport(hostID, customerID, []byte(reportJSON), HostReportDenorm{AgentVersion: "0.88.0"}); err != nil { t.Fatalf("SaveHostReport %s: %v", hostID, err) } } // The host-scoped consume-once contract (PBS DR SLICE 1): exactly one read per stored value, // a re-save resets the consumed flag (re-issue supersedes), absence is sql.ErrNoRows. func TestHostPBSSecret_ConsumeOnce(t *testing.T) { s := newTestStore(t) if _, err := s.ConsumeHostPBSSecret("h1"); err != sql.ErrNoRows { t.Fatalf("consume with nothing stored = %v, want sql.ErrNoRows", err) } if _, err := s.SaveHostPBSSecret("h1", "secret-1"); err != nil { t.Fatalf("save: %v", err) } got, err := s.ConsumeHostPBSSecret("h1") if err != nil || got != "secret-1" { t.Fatalf("first consume = (%q, %v), want (secret-1, nil)", got, err) } // Single use: the second consume MUST be ErrNoRows (red-proof: dropping the consumed_at // UPDATE in ConsumeHostPBSSecret makes this assert fail with the value returned again). if got, err := s.ConsumeHostPBSSecret("h1"); err != sql.ErrNoRows { t.Fatalf("second consume = (%q, %v), want sql.ErrNoRows — consume-once broken", got, err) } // Re-issue path: a fresh save resets consumption and serves the NEW value once. if _, err := s.SaveHostPBSSecret("h1", "secret-2"); err != nil { t.Fatalf("re-save: %v", err) } got, err = s.ConsumeHostPBSSecret("h1") if err != nil || got != "secret-2" { t.Fatalf("post-reissue consume = (%q, %v), want (secret-2, nil)", got, err) } // Host isolation: h2 never sees h1's rows. if _, err := s.ConsumeHostPBSSecret("h2"); err != sql.ErrNoRows { t.Fatalf("foreign host consume = %v, want sql.ErrNoRows", err) } } // R-39 — the secret GENERATION is what makes a re-key visible to the agent. // // A re-issue rotates only the side-table secret: token_id, fingerprint, datastore and namespace all // come back byte-identical, so without this counter the descriptor never moves, the converged agent // short-circuits on its content hash, and the fresh secret is never consumed. That is the 2026-07-18 // N100 failure. These assertions are the store half of the guarantee. func TestSaveHostPBSSecret_GenerationIsMonotonicPerMint(t *testing.T) { s := newTestStore(t) // No secret ever stored → generation 0 (not an error). if g, err := s.HostPBSSecretGeneration("h1"); err != nil || g != 0 { t.Fatalf("generation with nothing stored = (%d, %v), want (0, nil)", g, err) } g1, err := s.SaveHostPBSSecret("h1", "secret-1") if err != nil { t.Fatalf("first mint: %v", err) } if g1 != 1 { t.Fatalf("first mint generation = %d, want 1", g1) } // THE FIX: a re-key with an identical descriptor still advances the generation. g2, err := s.SaveHostPBSSecret("h1", "secret-2") if err != nil { t.Fatalf("re-key mint: %v", err) } if g2 != 2 { t.Fatalf("re-key generation = %d, want 2 — a re-issue MUST advance it or the agent never re-applies", g2) } if g2 <= g1 { t.Fatalf("generation went backwards or stalled: %d -> %d", g1, g2) } if got, err := s.HostPBSSecretGeneration("h1"); err != nil || got != g2 { t.Fatalf("read-back generation = (%d, %v), want (%d, nil)", got, err, g2) } // Per-host, not global: another host starts at 1. if g, err := s.SaveHostPBSSecret("h2", "other"); err != nil || g != 1 { t.Fatalf("second host first mint = (%d, %v), want (1, nil) — the counter is per-host", g, err) } } // A RE-STAGE must NOT advance the generation: it re-arms the SAME secret, so the descriptor content // genuinely has not changed and a bump would cause a pointless agent refetch loop. This is the // counterpart to TestRestageHostPBSSecret_NoGenerationBump, one level down. func TestRestageHostPBSSecret_LeavesSecretGenerationAlone(t *testing.T) { s := newTestStore(t) g1, err := s.SaveHostPBSSecret("h1", "secret-1") if err != nil { t.Fatalf("mint: %v", err) } if _, err := s.ConsumeHostPBSSecret("h1"); err != nil { t.Fatalf("consume: %v", err) } if restaged, err := s.RestageHostPBSSecret("h1"); err != nil || !restaged { t.Fatalf("restage = (%v, %v), want (true, nil)", restaged, err) } g2, err := s.HostPBSSecretGeneration("h1") if err != nil { t.Fatalf("read generation: %v", err) } if g2 != g1 { t.Fatalf("restage moved the secret generation %d -> %d; a re-stage changes no descriptor content", g1, g2) } }