2752e12acc
- DeleteHost(deleteEscrow) demotes current host_escrow into host_escrow_superseded (copy-before-delete, same tx), spares existing; one shared demoteCurrentEscrowTx (reused by SaveHostEscrow). F-14 provenance/gate unchanged. - DeleteCustomerConfig now purges both escrow tables for all the customer's hosts incl. already-deleted (F-14 provenance UNION) — the one true purge point. - Wording: checkbox/refusal/Danger-zone → demotion. S6b OBSOLETE. Red-proofs TestDeleteHost_Demotes + TestDeleteCustomer_Purges + wording guard.
71 lines
2.6 KiB
Go
71 lines
2.6 KiB
Go
package store
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
// Scenario A (v0.60.1) — host deletion DEMOTES the current escrow blob to retained custody and SPARES
|
|
// existing superseded blobs; it never destroys custody. RED-PROOF: the v0.60.0 code deleted both escrow
|
|
// tables → after DeleteHost the blobs are gone → the retrieval assertions FAIL.
|
|
func TestDeleteHost_DemotesEscrowNeverDestroys(t *testing.T) {
|
|
s := newTestStore(t)
|
|
const hostID, cust = "dh1", "cust-dh"
|
|
if err := s.UpsertHost(&Host{HostID: hostID, CustomerID: cust, APIKey: "k"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// current escrow = SHA_A, one superseded = SHA_OLD (two uploads with different passphrases).
|
|
if _, err := s.SaveHostEscrow(hostID, []byte("blob-old"), "fp", "zk", "2026-07-09T00:00:00Z", "SHA_OLD"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.SaveHostEscrow(hostID, []byte("blob-A"), "fp", "zk", "2026-07-16T00:00:00Z", "SHA_A"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n, _ := s.CountSupersededEscrow(hostID); n != 1 {
|
|
t.Fatalf("precondition superseded=%d, want 1", n)
|
|
}
|
|
|
|
// Refusal without the flag is unchanged (escrow present + !deleteEscrow → ErrHostEscrowPresent).
|
|
if err := s.DeleteHost(hostID, false); !errors.Is(err, ErrHostEscrowPresent) {
|
|
t.Fatalf("delete without the flag: got %v, want ErrHostEscrowPresent", err)
|
|
}
|
|
|
|
if err := s.DeleteHost(hostID, true); err != nil {
|
|
t.Fatalf("DeleteHost: %v", err)
|
|
}
|
|
if h, _ := s.GetHost(hostID); h != nil {
|
|
t.Fatal("host row survived the delete")
|
|
}
|
|
// The current row is DEMOTED (moved), not kept.
|
|
if cur, _ := s.GetHostEscrow(hostID); cur != nil {
|
|
t.Fatal("current escrow row survived — should be demoted into superseded, not left as current")
|
|
}
|
|
// RED-PROOF: BOTH blobs are RETAINED (SHA_A demoted + SHA_OLD spared) — host delete never destroys.
|
|
sup, err := s.ListSupersededEscrow(hostID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(sup) != 2 {
|
|
t.Fatalf("retained superseded = %d, want 2 (SHA_A demoted + SHA_OLD spared) — host delete DESTROYED custody", len(sup))
|
|
}
|
|
shas := map[string]bool{}
|
|
for _, e := range sup {
|
|
shas[e.ResticPwSHA256] = true
|
|
}
|
|
if !shas["SHA_A"] || !shas["SHA_OLD"] {
|
|
t.Fatalf("retained shas = %v, want both SHA_A + SHA_OLD", shas)
|
|
}
|
|
|
|
// EDGE: a host with NO escrow → DeleteHost unchanged (no demote row, no error).
|
|
const h2 = "dh2"
|
|
if err := s.UpsertHost(&Host{HostID: h2, CustomerID: cust, APIKey: "k2"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.DeleteHost(h2, true); err != nil {
|
|
t.Fatalf("no-escrow delete: %v", err)
|
|
}
|
|
if n, _ := s.CountSupersededEscrow(h2); n != 0 {
|
|
t.Fatalf("a no-escrow host delete created a superseded row: %d", n)
|
|
}
|
|
}
|