hub: S2 store — host-bound WG peers (register/re-key/adopt), one-per-host index, BumpHostDesired
allocateWGPeerTx extracted from addWGPeerOnce (behavior-neutral; S1 tests unmodified+green). RegisterWGPeerForHost: idempotent / re-key-in-place-keep-ip / adopt-unbound / ErrWGPubkeyBoundElsewhere. Partial unique index enforces one bound peer per host. BumpHostDesired touches ONLY the generation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -137,6 +137,119 @@ func TestRemoveWGPeer_UnknownIsNoRows(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- S2 Group A: host binding + generation bump ---
|
||||
|
||||
func TestRegisterWGPeerForHost_FreshIdempotentAndRekey(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
setTestEndpoint(t, s, "10.77.0.0/24", "10.77.0.1")
|
||||
if err := s.UpsertHost(&Host{HostID: "h1", CustomerID: "c1", APIKey: "k1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Fresh → allocates .2, changed=true.
|
||||
ip, changed, err := s.RegisterWGPeerForHost("h1", "P1")
|
||||
if err != nil || ip != "10.77.0.2" || !changed {
|
||||
t.Fatalf("fresh register = %q changed=%v err=%v, want .2/true", ip, changed, err)
|
||||
}
|
||||
// Idempotent: same pubkey → same ip, changed=false.
|
||||
ip, changed, err = s.RegisterWGPeerForHost("h1", "P1")
|
||||
if err != nil || ip != "10.77.0.2" || changed {
|
||||
t.Fatalf("idempotent register = %q changed=%v err=%v, want .2/false", ip, changed, err)
|
||||
}
|
||||
// Re-key: NEW pubkey → swapped in place, ip KEPT, changed=true.
|
||||
ip, changed, err = s.RegisterWGPeerForHost("h1", "P2")
|
||||
if err != nil || ip != "10.77.0.2" || !changed {
|
||||
t.Fatalf("re-key = %q changed=%v err=%v, want .2 kept/true", ip, changed, err)
|
||||
}
|
||||
p, err := s.GetWGPeerForHost("h1")
|
||||
if err != nil || p.Pubkey != "P2" || p.AssignedIP != "10.77.0.2" {
|
||||
t.Fatalf("after re-key peer = %+v err=%v", p, err)
|
||||
}
|
||||
if n := peerCount(t, s); n != 1 {
|
||||
t.Errorf("row count after re-key = %d, want 1", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterWGPeerForHost_AdoptsUnboundRow(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
setTestEndpoint(t, s, "10.77.0.0/24", "10.77.0.1")
|
||||
s.UpsertHost(&Host{HostID: "h1", CustomerID: "c1", APIKey: "k1"})
|
||||
// An S1 admin-added UNBOUND peer.
|
||||
ip0, _, err := s.AddWGPeer("P1", "", "s1-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ip, changed, err := s.RegisterWGPeerForHost("h1", "P1")
|
||||
if err != nil || !changed || ip != ip0 {
|
||||
t.Fatalf("adopt = %q changed=%v err=%v, want %q/true", ip, changed, err, ip0)
|
||||
}
|
||||
p, _ := s.GetWGPeerForHost("h1")
|
||||
if p == nil || p.Pubkey != "P1" {
|
||||
t.Fatalf("adopted peer = %+v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterWGPeerForHost_BoundElsewhereRefused(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
setTestEndpoint(t, s, "10.77.0.0/24", "10.77.0.1")
|
||||
s.UpsertHost(&Host{HostID: "h1", CustomerID: "c1", APIKey: "k1"})
|
||||
s.UpsertHost(&Host{HostID: "h2", CustomerID: "c2", APIKey: "k2"})
|
||||
s.RegisterWGPeerForHost("h2", "P1")
|
||||
|
||||
// Fresh-registration path: pubkey bound to h2 → refused.
|
||||
if _, _, err := s.RegisterWGPeerForHost("h1", "P1"); err != ErrWGPubkeyBoundElsewhere {
|
||||
t.Fatalf("register with h2's pubkey: err = %v, want ErrWGPubkeyBoundElsewhere", err)
|
||||
}
|
||||
if p, err := s.GetWGPeerForHost("h1"); err != sql.ErrNoRows {
|
||||
t.Errorf("h1 gained a peer despite refusal: %+v (err=%v)", p, err)
|
||||
}
|
||||
// Re-key path: h1 registers its own, then tries to re-key to h2's pubkey → refused, binding intact.
|
||||
s.RegisterWGPeerForHost("h1", "P9")
|
||||
if _, _, err := s.RegisterWGPeerForHost("h1", "P1"); err != ErrWGPubkeyBoundElsewhere {
|
||||
t.Fatalf("re-key onto h2's pubkey: err = %v, want ErrWGPubkeyBoundElsewhere", err)
|
||||
}
|
||||
p, _ := s.GetWGPeerForHost("h1")
|
||||
if p == nil || p.Pubkey != "P9" {
|
||||
t.Errorf("h1 binding disturbed by refused re-key: %+v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWGPeers_OneBoundPerHostIndex(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
setTestEndpoint(t, s, "10.77.0.0/24", "10.77.0.1")
|
||||
s.UpsertHost(&Host{HostID: "h1", CustomerID: "c1", APIKey: "k1"})
|
||||
s.RegisterWGPeerForHost("h1", "P1")
|
||||
// A second BOUND row for the same host via the S1 admin path → the partial index refuses.
|
||||
if _, _, err := s.AddWGPeer("P2", "h1", ""); err == nil {
|
||||
t.Fatal("second bound peer for h1 accepted — idx_wg_peers_host is not enforcing")
|
||||
}
|
||||
// Unbound rows are unaffected by the partial index.
|
||||
if _, _, err := s.AddWGPeer("P3", "", ""); err != nil {
|
||||
t.Fatalf("unbound add refused: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpHostDesired(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
s.UpsertHost(&Host{HostID: "h1", CustomerID: "c1", APIKey: "k1"})
|
||||
gen, err := s.BumpHostDesired("h1")
|
||||
if err != nil || gen != 1 {
|
||||
t.Fatalf("bump #1 = %d, %v", gen, err)
|
||||
}
|
||||
gen, err = s.BumpHostDesired("h1")
|
||||
if err != nil || gen != 2 {
|
||||
t.Fatalf("bump #2 = %d, %v", gen, err)
|
||||
}
|
||||
if _, err := s.BumpHostDesired("ghost"); err != sql.ErrNoRows {
|
||||
t.Errorf("unknown host bump err = %v, want ErrNoRows", err)
|
||||
}
|
||||
// desired_json untouched by the bump.
|
||||
h, _ := s.GetHost("h1")
|
||||
if h.DesiredJSON != "{}" && h.DesiredJSON != "" {
|
||||
t.Errorf("desired_json moved on bump: %q", h.DesiredJSON)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListWGPeers_DeterministicOrder(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
setTestEndpoint(t, s, "10.77.0.0/24", "10.77.0.1")
|
||||
|
||||
Reference in New Issue
Block a user