|
@@ -17,7 +17,8 @@ import (
|
|
|
func TestReconcileInbound_SkipsUnchanged(t *testing.T) {
|
|
func TestReconcileInbound_SkipsUnchanged(t *testing.T) {
|
|
|
var pushes atomic.Int32
|
|
var pushes atomic.Int32
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
- if r.Method == http.MethodPost && strings.Contains(r.URL.Path, "/panel/api/inbounds/update/") {
|
|
|
|
|
|
|
+ if r.Method == http.MethodPost && (strings.Contains(r.URL.Path, "/panel/api/inbounds/update/") ||
|
|
|
|
|
+ strings.Contains(r.URL.Path, "/panel/api/inbounds/add")) {
|
|
|
pushes.Add(1)
|
|
pushes.Add(1)
|
|
|
}
|
|
}
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
@@ -283,7 +284,7 @@ func TestDelInboundDropsReconcileFingerprint(t *testing.T) {
|
|
|
ib := &model.Inbound{Tag: "in-del", Protocol: model.VLESS, Port: 443, Settings: `{"clients":[]}`}
|
|
ib := &model.Inbound{Tag: "in-del", Protocol: model.VLESS, Port: 443, Settings: `{"clients":[]}`}
|
|
|
r.cacheSet(ib.Tag, 7)
|
|
r.cacheSet(ib.Tag, 7)
|
|
|
|
|
|
|
|
- if pushed, err := r.ReconcileInbound(context.Background(), ib, false); err != nil || !pushed {
|
|
|
|
|
|
|
+ if pushed, err := r.ReconcileInbound(context.Background(), ib, true); err != nil || !pushed {
|
|
|
t.Fatalf("initial reconcile: pushed=%v err=%v, want push", pushed, err)
|
|
t.Fatalf("initial reconcile: pushed=%v err=%v, want push", pushed, err)
|
|
|
}
|
|
}
|
|
|
if err := r.DelInbound(context.Background(), ib); err != nil {
|
|
if err := r.DelInbound(context.Background(), ib); err != nil {
|
|
@@ -317,3 +318,36 @@ func TestUpdateInboundFallbackAddSeedsReconcileFingerprint(t *testing.T) {
|
|
|
t.Fatalf("reconcile sent %d full inbound updates, want 0", got)
|
|
t.Fatalf("reconcile sent %d full inbound updates, want 0", got)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// An inbound deleted on the node must be re-created by the next reconcile; a
|
|
|
|
|
+// cached tag→id from before the delete used to send update/<gone id> forever.
|
|
|
|
|
+func TestReconcileInbound_RecreatesInboundTheNodeLost(t *testing.T) {
|
|
|
|
|
+ var adds, staleUpdates atomic.Int32
|
|
|
|
|
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
+ w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
+ switch {
|
|
|
|
|
+ case strings.Contains(r.URL.Path, "/panel/api/inbounds/list"):
|
|
|
|
|
+ _, _ = w.Write([]byte(`{"success":true,"obj":[]}`))
|
|
|
|
|
+ case strings.Contains(r.URL.Path, "/panel/api/inbounds/update/"):
|
|
|
|
|
+ staleUpdates.Add(1)
|
|
|
|
|
+ _, _ = w.Write([]byte(`{"success":false,"msg":"record not found"}`))
|
|
|
|
|
+ case strings.Contains(r.URL.Path, "/panel/api/inbounds/add"):
|
|
|
|
|
+ adds.Add(1)
|
|
|
|
|
+ _, _ = w.Write([]byte(`{"success":true,"obj":{"id":9,"tag":"in-1"}}`))
|
|
|
|
|
+ default:
|
|
|
|
|
+ _, _ = w.Write([]byte(`{"success":true}`))
|
|
|
|
|
+ }
|
|
|
|
|
+ }))
|
|
|
|
|
+ defer srv.Close()
|
|
|
|
|
+
|
|
|
|
|
+ r := NewRemote(nodeForPlainServer(t, srv, "verify", "tok"), nil)
|
|
|
|
|
+ ib := &model.Inbound{Tag: "n1-in-1", Protocol: model.VLESS, Port: 443, Settings: `{"clients":[]}`}
|
|
|
|
|
+ r.cacheSet("in-1", 7)
|
|
|
|
|
+
|
|
|
|
|
+ if pushed, err := r.ReconcileInbound(context.Background(), ib, false); err != nil || !pushed {
|
|
|
|
|
+ t.Fatalf("reconcile of a lost inbound: pushed=%v err=%v, want a re-create", pushed, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ if staleUpdates.Load() != 0 || adds.Load() != 1 {
|
|
|
|
|
+ t.Fatalf("updates to the stale id=%d adds=%d, want 0 and 1", staleUpdates.Load(), adds.Load())
|
|
|
|
|
+ }
|
|
|
|
|
+}
|