mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-19 16:38:31 +00:00
- Cluster: forwardToNext offline detection (leader+non-leader), WatchLeader 1s heartbeat fallback, 409 for standalone nodes, Node.NodeKey key exchange via token ring, ClusterPeers persistence + auto-rejoin, Forward delegates to forwardToNext (bugfix) - Auth: Basic Auth (flag-creds fast path) + bcrypt users (admin/viewer) + Bearer API keys (read/write/admin scope) - Frontend: UsersView (accounts+API keys), ClusterView (ring/nodeKey/tasks/topology/log), StatusView (group management, per-proxy status), CanvasView (edge toggle/group), PortEdge (disabled/group labels) - API: handlers split (canvas/forwards/users/logs), canvas export/import, forwards group start/stop/assign/delete, cluster endpoints - Docs: comprehensive README rewrite (all flags/APIs/auth/cluster), docs/cluster-api.md (cluster management API reference) - Deploy: run-cluster.sh now 4-node ring + 1 isolated standalone, test-forward.sh updated for 4 nodes - Removed plan.md (design notes consolidated into README + API docs)
65 lines
2.2 KiB
Go
65 lines
2.2 KiB
Go
package cluster
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"webui4frpc/internal/store"
|
|
)
|
|
|
|
// TestRevokeTaskRemovesTopology: a revoke task published to the ring removes
|
|
// the forward from topology, calls Handler.Revoke, and logs forward.remove.
|
|
func TestRevokeTaskRemovesTopology(t *testing.T) {
|
|
revoked := false
|
|
eng := NewEngine("n1", "n1:7500", "u", "p", "0.71.0", nil,
|
|
&fakeHandler{load: Load{MemPct: 5, NetPct: 5},
|
|
revoke: func(ctx context.Context, tk *Task) error { revoked = true; return nil }},
|
|
func(ctx context.Context, next string, tk *Token) error { return nil },
|
|
"n1:7500", true, "")
|
|
|
|
// establish a forward
|
|
eng.state.AddPending(store.Local{Name: "web"}, store.Remote{Name: "frps1"}, store.Link{RemotePort: 18081})
|
|
// lowest-load is n1 (only node): it claims + builds topology
|
|
if _, err := eng.OnToken(context.Background(), &Token{Cycle: 1, State: eng.state}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(eng.state.TopologyList()) != 1 {
|
|
t.Fatalf("topology after claim = %+v", eng.state.TopologyList())
|
|
}
|
|
|
|
// publish a revoke task pointing at the same forward
|
|
eng.state.AddRevoke(store.Local{Name: "web"}, store.Remote{Name: "frps1"}, store.Link{RemotePort: 18081})
|
|
if _, err := eng.OnToken(context.Background(), &Token{Cycle: 2, State: eng.state}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(eng.state.TopologyList()) != 0 {
|
|
t.Fatalf("topology after revoke = %+v", eng.state.TopologyList())
|
|
}
|
|
if !revoked {
|
|
t.Fatal("Handler.Revoke was not called")
|
|
}
|
|
// log should contain forward.remove
|
|
var sawRemove bool
|
|
for _, e := range eng.Log.Snapshot() {
|
|
if e.Kind == LogForwardRemove {
|
|
sawRemove = true
|
|
}
|
|
}
|
|
if !sawRemove {
|
|
t.Fatalf("log missing forward.remove: %+v", eng.Log.Snapshot())
|
|
}
|
|
}
|
|
|
|
// TestRevokeIdempotent: revoking an already-missing forward does not error.
|
|
func TestRevokeIdempotent(t *testing.T) {
|
|
eng := newTestEngine("n1", true)
|
|
eng.state.AddRevoke(store.Local{Name: "ghost"}, store.Remote{Name: "frps1"}, store.Link{RemotePort: 1})
|
|
if _, err := eng.OnToken(context.Background(), &Token{Cycle: 1, State: eng.state}); err != nil {
|
|
t.Fatalf("revoke missing: %v", err)
|
|
}
|
|
// no topology entry, no panic
|
|
if len(eng.state.TopologyList()) != 0 {
|
|
t.Fatal("should be empty")
|
|
}
|
|
}
|