hub: offsite multi-endpoint management UI (v0.47.0 part 5)
- /offsite lists ALL wg_endpoints rows as cards (id, address, pubkey, subnet, PBS addr, peers-in-subnet count) + add/edit/delete forms - store: ListWGEndpoints (id order) + DeleteWGEndpoint (plain delete; the peers-in-subnet guard lives in the handler where the refusal is built); SetWGEndpoint upsert reused, single-expected comment updated - guards: subnet edit refused 409 while peers sit in the current subnet; endpoint delete refused 409 while peers sit in its subnet; full form validation (CIDR, pbs ip in subnet, port 1-65535, pubkey, id charset) -> 400 - peer table gains an Endpoint column (first id-ordered subnet match; em dash when none); pubkey-change edit gets a type-to-confirm noting pull-based convergence - allocation/reconciler/desired-state STAY lowest-endpoint-id (page notes the deferral); GetWGEndpoint semantics untouched - tests: E1-E6 incl. guard red-proofs; store list/delete test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vvz1NCu22p8dGkRCpeX9re
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
package store
|
||||
|
||||
// Group D (hub v0.47.0 offsite multi-endpoint) — the endpoint list/delete store surface.
|
||||
// GetWGEndpoint's LIMIT-1 lowest-id semantics stay untouched (allocation/sync contract).
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListWGEndpoints_OrderAndDelete(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
|
||||
// Empty → empty list, no error.
|
||||
eps, err := s.ListWGEndpoints()
|
||||
if err != nil || len(eps) != 0 {
|
||||
t.Fatalf("empty list = %v / %v", eps, err)
|
||||
}
|
||||
|
||||
// Insert out of order → returned ordered by endpoint_id.
|
||||
for _, id := range []string{"ep2", "ep0", "ep1"} {
|
||||
if err := s.SetWGEndpoint(&WGEndpoint{
|
||||
EndpointID: id, DNSName: id + ".felhom.eu", WGPort: 443,
|
||||
ServerPubkey: "pk-" + id, TunnelSubnet: "10.77.0.0/24", PBSTunnelIP: "10.77.0.1",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
eps, err = s.ListWGEndpoints()
|
||||
if err != nil || len(eps) != 3 {
|
||||
t.Fatalf("list = %d eps / %v, want 3", len(eps), err)
|
||||
}
|
||||
for i, want := range []string{"ep0", "ep1", "ep2"} {
|
||||
if eps[i].EndpointID != want {
|
||||
t.Errorf("eps[%d] = %s, want %s (id order)", i, eps[i].EndpointID, want)
|
||||
}
|
||||
}
|
||||
|
||||
// GetWGEndpoint (the allocation/sync endpoint) still returns the LOWEST id.
|
||||
ep, err := s.GetWGEndpoint()
|
||||
if err != nil || ep.EndpointID != "ep0" {
|
||||
t.Errorf("GetWGEndpoint = %+v / %v, want ep0 (lowest id wins)", ep, err)
|
||||
}
|
||||
|
||||
// Delete removes exactly the named row; unknown id → sql.ErrNoRows.
|
||||
if err := s.DeleteWGEndpoint("ep1"); err != nil {
|
||||
t.Fatalf("DeleteWGEndpoint: %v", err)
|
||||
}
|
||||
eps, _ = s.ListWGEndpoints()
|
||||
if len(eps) != 2 || eps[0].EndpointID != "ep0" || eps[1].EndpointID != "ep2" {
|
||||
t.Errorf("after delete = %+v, want [ep0 ep2]", eps)
|
||||
}
|
||||
if err := s.DeleteWGEndpoint("ghost"); err != sql.ErrNoRows {
|
||||
t.Errorf("unknown delete = %v, want sql.ErrNoRows", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user