Files
felhom.eu/hub/internal/store/host_delete_demote_test.go
T
admin 2752e12acc hub v0.60.1: host deletion demotes escrow custody (never destroys) + customer-delete purge point + S6b obsolete
- 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.
2026-07-17 11:25:38 +02:00

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