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