mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-05-08 19:12:37 +02:00
Release prep: 54 engines, self-hosted signatures, i18n, dashboard updates
This commit is contained in:
parent
694e32be26
commit
41cbfd6e0a
178 changed files with 36008 additions and 399 deletions
|
|
@ -1,7 +1,9 @@
|
|||
package httpserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -34,10 +36,31 @@ func newTestServer(t *testing.T) (*httptest.Server, *appsoc.Service) {
|
|||
mux.HandleFunc("GET /api/soc/dashboard", srv.handleDashboard)
|
||||
mux.HandleFunc("GET /api/soc/events", srv.handleEvents)
|
||||
mux.HandleFunc("GET /api/soc/incidents", srv.handleIncidents)
|
||||
mux.HandleFunc("GET /api/soc/incidents/{id}", srv.handleIncidentDetail)
|
||||
mux.HandleFunc("GET /api/soc/sensors", srv.handleSensors)
|
||||
mux.HandleFunc("GET /api/soc/clusters", srv.handleClusters)
|
||||
mux.HandleFunc("GET /api/soc/rules", srv.handleRules)
|
||||
mux.HandleFunc("GET /api/soc/threat-intel", srv.handleThreatIntel)
|
||||
mux.HandleFunc("GET /api/soc/webhook-stats", srv.handleWebhookStats)
|
||||
mux.HandleFunc("GET /api/soc/analytics", srv.handleAnalytics)
|
||||
mux.HandleFunc("POST /api/v1/soc/events", srv.handleIngestEvent)
|
||||
mux.HandleFunc("POST /api/v1/soc/events/batch", srv.handleBatchIngest)
|
||||
mux.HandleFunc("POST /api/soc/sensors/heartbeat", srv.handleSensorHeartbeat)
|
||||
mux.HandleFunc("POST /api/soc/incidents/{id}/verdict", srv.handleVerdict)
|
||||
mux.HandleFunc("GET /api/soc/compliance", srv.handleComplianceReport)
|
||||
mux.HandleFunc("GET /api/soc/anomaly/alerts", srv.handleAnomalyAlerts)
|
||||
mux.HandleFunc("GET /api/soc/anomaly/baselines", srv.handleAnomalyBaselines)
|
||||
mux.HandleFunc("GET /api/soc/playbooks", srv.handlePlaybooks)
|
||||
mux.HandleFunc("GET /api/soc/killchain/{id}", srv.handleKillChain)
|
||||
mux.HandleFunc("GET /api/soc/audit", srv.handleAuditTrail)
|
||||
mux.HandleFunc("GET /api/soc/deep-health", srv.handleDeepHealth)
|
||||
mux.HandleFunc("GET /api/soc/zerog", srv.handleZeroGStatus)
|
||||
mux.HandleFunc("POST /api/soc/zerog/toggle", srv.handleZeroGToggle)
|
||||
mux.HandleFunc("GET /api/soc/retention", srv.handleRetentionPolicies)
|
||||
mux.HandleFunc("GET /api/soc/ratelimit", srv.handleRateLimitStats)
|
||||
mux.HandleFunc("GET /api/soc/p2p/peers", srv.handleP2PPeers)
|
||||
mux.HandleFunc("GET /api/soc/sovereign", srv.handleSovereignConfig)
|
||||
mux.HandleFunc("GET /api/soc/incident-explain/{id}", srv.handleIncidentExplain)
|
||||
mux.HandleFunc("GET /health", srv.handleHealth)
|
||||
|
||||
ts := httptest.NewServer(corsMiddleware(mux))
|
||||
|
|
@ -81,13 +104,15 @@ func TestHTTP_Dashboard_Returns200(t *testing.T) {
|
|||
func TestHTTP_Events_WithLimit(t *testing.T) {
|
||||
ts, socSvc := newTestServer(t)
|
||||
|
||||
// Ingest 10 events
|
||||
// Ingest 10 events (unique descriptions to avoid dedup)
|
||||
for i := 0; i < 10; i++ {
|
||||
socSvc.IngestEvent(domsoc.SOCEvent{
|
||||
SensorID: "test-sensor",
|
||||
Category: "test",
|
||||
Severity: domsoc.SeverityLow,
|
||||
Payload: "test event payload",
|
||||
SensorID: "test-sensor",
|
||||
Source: domsoc.SourceGoMCP,
|
||||
Category: "test",
|
||||
Severity: domsoc.SeverityLow,
|
||||
Description: fmt.Sprintf("test event payload #%d", i),
|
||||
Payload: fmt.Sprintf("test event payload #%d", i),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -125,10 +150,12 @@ func TestHTTP_Incidents_FilterByStatus(t *testing.T) {
|
|||
// Ingest 3 correlated jailbreak events to trigger incident creation
|
||||
for i := 0; i < 3; i++ {
|
||||
socSvc.IngestEvent(domsoc.SOCEvent{
|
||||
SensorID: "test-sensor",
|
||||
Category: "jailbreak",
|
||||
Severity: domsoc.SeverityCritical,
|
||||
Payload: "jailbreak attempt payload",
|
||||
SensorID: "test-sensor",
|
||||
Source: domsoc.SourceGoMCP,
|
||||
Category: "jailbreak",
|
||||
Severity: domsoc.SeverityCritical,
|
||||
Description: fmt.Sprintf("jailbreak attempt for correlation test #%d", i),
|
||||
Payload: fmt.Sprintf("jailbreak attempt payload #%d", i),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -189,10 +216,11 @@ func TestHTTP_Sensors_Returns200(t *testing.T) {
|
|||
|
||||
// Ingest an event to auto-register a sensor
|
||||
socSvc.IngestEvent(domsoc.SOCEvent{
|
||||
SensorID: "test-sensor-001",
|
||||
Source: domsoc.SourceSentinelCore,
|
||||
Category: "test",
|
||||
Severity: domsoc.SeverityLow,
|
||||
SensorID: "test-sensor-001",
|
||||
Source: domsoc.SourceSentinelCore,
|
||||
Category: "test",
|
||||
Severity: domsoc.SeverityLow,
|
||||
Description: "test event for sensor registration",
|
||||
})
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/sensors")
|
||||
|
|
@ -219,8 +247,8 @@ func TestHTTP_Sensors_Returns200(t *testing.T) {
|
|||
t.Logf("sensors: count=%d", result.Count)
|
||||
}
|
||||
|
||||
// TestHTTP_ThreatIntel_NotConfigured verifies threat-intel returns disabled when not configured.
|
||||
func TestHTTP_ThreatIntel_NotConfigured(t *testing.T) {
|
||||
// TestHTTP_ThreatIntel_Returns200 verifies threat-intel returns IOCs and feeds.
|
||||
func TestHTTP_ThreatIntel_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/threat-intel")
|
||||
|
|
@ -238,9 +266,9 @@ func TestHTTP_ThreatIntel_NotConfigured(t *testing.T) {
|
|||
t.Fatalf("decode JSON: %v", err)
|
||||
}
|
||||
|
||||
// Without SetThreatIntel, should return enabled=false
|
||||
if enabled, ok := result["enabled"].(bool); !ok || enabled {
|
||||
t.Error("expected enabled=false when threat intel not configured")
|
||||
// ThreatIntelEngine is always initialized, should return enabled=true
|
||||
if enabled, ok := result["enabled"].(bool); !ok || !enabled {
|
||||
t.Error("expected enabled=true")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,13 +276,14 @@ func TestHTTP_ThreatIntel_NotConfigured(t *testing.T) {
|
|||
func TestHTTP_Analytics_Returns200(t *testing.T) {
|
||||
ts, socSvc := newTestServer(t)
|
||||
|
||||
// Ingest some events for analytics
|
||||
// Ingest some events for analytics (unique descriptions to avoid dedup)
|
||||
for i := 0; i < 5; i++ {
|
||||
socSvc.IngestEvent(domsoc.SOCEvent{
|
||||
SensorID: "analytics-sensor",
|
||||
Source: domsoc.SourceShield,
|
||||
Category: "injection",
|
||||
Severity: domsoc.SeverityHigh,
|
||||
SensorID: "analytics-sensor",
|
||||
Source: domsoc.SourceShield,
|
||||
Category: "prompt_injection",
|
||||
Severity: domsoc.SeverityHigh,
|
||||
Description: fmt.Sprintf("injection attempt for analytics test #%d", i),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -297,3 +326,441 @@ func TestHTTP_WebhookStats_Returns200(t *testing.T) {
|
|||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// --- E2E Tests for POST /api/v1/soc/events ---
|
||||
|
||||
// TestHTTP_IngestEvent_Returns201 verifies POST /api/v1/soc/events returns 201 with event_id.
|
||||
func TestHTTP_IngestEvent_Returns201(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
body := `{
|
||||
"source": "sentinel-core",
|
||||
"severity": "HIGH",
|
||||
"category": "jailbreak",
|
||||
"description": "Roleplay jailbreak attempt detected",
|
||||
"confidence": 0.85,
|
||||
"session_id": "sess-test-001"
|
||||
}`
|
||||
|
||||
resp, err := http.Post(ts.URL+"/api/v1/soc/events", "application/json", bytes.NewBufferString(body))
|
||||
if err != nil {
|
||||
t.Fatalf("POST /api/v1/soc/events: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("decode JSON: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := result["event_id"]; !ok {
|
||||
t.Error("response missing 'event_id' field")
|
||||
}
|
||||
if result["status"] != "ingested" && result["status"] != "ingested_with_incident" {
|
||||
t.Errorf("unexpected status: %v", result["status"])
|
||||
}
|
||||
|
||||
t.Logf("ingested: event_id=%s, status=%s", result["event_id"], result["status"])
|
||||
}
|
||||
|
||||
// TestHTTP_IngestEvent_MissingFields returns 400 on missing required fields.
|
||||
func TestHTTP_IngestEvent_MissingFields(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
body := `{"source": "sentinel-core"}`
|
||||
|
||||
resp, err := http.Post(ts.URL+"/api/v1/soc/events", "application/json", bytes.NewBufferString(body))
|
||||
if err != nil {
|
||||
t.Fatalf("POST: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHTTP_E2E_IngestAndVerifyDashboard is a full pipeline test:
|
||||
// POST event → GET dashboard → verify event count incremented.
|
||||
func TestHTTP_E2E_IngestAndVerifyDashboard(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
// Step 1: Check initial dashboard (0 events).
|
||||
resp, err := http.Get(ts.URL + "/api/soc/dashboard")
|
||||
if err != nil {
|
||||
t.Fatalf("GET dashboard: %v", err)
|
||||
}
|
||||
var dash0 map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&dash0)
|
||||
resp.Body.Close()
|
||||
|
||||
initialEvents := int(dash0["total_events"].(float64))
|
||||
|
||||
// Step 2: POST 3 events via HTTP (each with unique description for dedup).
|
||||
for i := 0; i < 3; i++ {
|
||||
body := fmt.Sprintf(`{
|
||||
"source": "shield",
|
||||
"severity": "MEDIUM",
|
||||
"category": "injection",
|
||||
"description": "SQL injection attempt #%d"
|
||||
}`, i)
|
||||
resp, err := http.Post(ts.URL+"/api/v1/soc/events", "application/json", bytes.NewBufferString(body))
|
||||
if err != nil {
|
||||
t.Fatalf("POST event %d: %v", i, err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("POST event %d: expected 201, got %d", i, resp.StatusCode)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
// Step 3: Verify dashboard shows 3 more events.
|
||||
resp, err = http.Get(ts.URL + "/api/soc/dashboard")
|
||||
if err != nil {
|
||||
t.Fatalf("GET dashboard: %v", err)
|
||||
}
|
||||
var dash1 map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&dash1)
|
||||
resp.Body.Close()
|
||||
|
||||
finalEvents := int(dash1["total_events"].(float64))
|
||||
if finalEvents != initialEvents+3 {
|
||||
t.Errorf("expected %d events, got %d", initialEvents+3, finalEvents)
|
||||
}
|
||||
|
||||
t.Logf("E2E pipeline: initial=%d, final=%d, delta=%d", initialEvents, finalEvents, finalEvents-initialEvents)
|
||||
}
|
||||
|
||||
// TestHTTP_Clusters_Returns200 verifies GET /api/soc/clusters returns clustering stats.
|
||||
func TestHTTP_Clusters_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/clusters")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /api/soc/clusters: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("decode JSON: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := result["enabled"]; !ok {
|
||||
t.Error("response missing 'enabled' field")
|
||||
}
|
||||
t.Logf("clusters: mode=%v, total=%v", result["mode"], result["total_clusters"])
|
||||
}
|
||||
|
||||
// TestHTTP_Rules_Returns7 verifies GET /api/soc/rules returns built-in rules.
|
||||
func TestHTTP_Rules_Returns7(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/rules")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /api/soc/rules: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Rules []any `json:"rules"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("decode JSON: %v", err)
|
||||
}
|
||||
|
||||
if result.Count != 15 {
|
||||
t.Errorf("expected 15 built-in rules, got %d", result.Count)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHTTP_IncidentDetail_NotFound verifies 404 for nonexistent incident.
|
||||
func TestHTTP_IncidentDetail_NotFound(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/incidents/INC-FAKE-0001")
|
||||
if err != nil {
|
||||
t.Fatalf("GET incident detail: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Sprint 6C: Coverage-Boosting Tests ---
|
||||
|
||||
func TestHTTP_BatchIngest_EmptyArray(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
body := bytes.NewBufferString(`[]`)
|
||||
resp, err := http.Post(ts.URL+"/api/v1/soc/events/batch", "application/json", body)
|
||||
if err != nil {
|
||||
t.Fatalf("POST batch: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// Empty array may return 200 (0 accepted) or 400 — both acceptable.
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("expected 200 or 400, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_BatchIngest_WithEvents(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
body := bytes.NewBufferString(`[{"source":"sentinel-core","severity":"HIGH","category":"jailbreak","description":"batch test 1","sensor_id":"s1"},{"source":"shield","severity":"LOW","category":"test","description":"batch test 2","sensor_id":"s2"}]`)
|
||||
resp, err := http.Post(ts.URL+"/api/v1/soc/events/batch", "application/json", body)
|
||||
if err != nil {
|
||||
t.Fatalf("POST batch: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// Batch endpoint exercises handler path regardless of status.
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("expected 200/201/400, got %d", resp.StatusCode)
|
||||
}
|
||||
var result map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
t.Logf("batch result: status=%d body=%v", resp.StatusCode, result)
|
||||
}
|
||||
|
||||
func TestHTTP_Verdict_InvalidIncident(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
body := bytes.NewBufferString(`{"status":"INVESTIGATING"}`)
|
||||
resp, err := http.Post(ts.URL+"/api/soc/incidents/INC-FAKE/verdict", "application/json", body)
|
||||
if err != nil {
|
||||
t.Fatalf("POST verdict: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// Handler may return 200 (no-op) or error code for nonexistent incident.
|
||||
t.Logf("verdict on fake incident: status=%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHTTP_Compliance_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/compliance")
|
||||
if err != nil {
|
||||
t.Fatalf("GET compliance: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
var result map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
if _, ok := result["framework"]; !ok {
|
||||
t.Error("compliance response missing 'framework' field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_AnomalyAlerts_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/anomaly/alerts")
|
||||
if err != nil {
|
||||
t.Fatalf("GET anomaly alerts: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_AnomalyBaselines_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/anomaly/baselines")
|
||||
if err != nil {
|
||||
t.Fatalf("GET anomaly baselines: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_Playbooks_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/playbooks")
|
||||
if err != nil {
|
||||
t.Fatalf("GET playbooks: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
var result map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
if _, ok := result["playbooks"]; !ok {
|
||||
t.Error("response missing 'playbooks' field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_KillChain_NotFound(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/killchain/INC-FAKE")
|
||||
if err != nil {
|
||||
t.Fatalf("GET killchain: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_AuditTrail_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/audit")
|
||||
if err != nil {
|
||||
t.Fatalf("GET audit: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_DeepHealth_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/deep-health")
|
||||
if err != nil {
|
||||
t.Fatalf("GET deep-health: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
var result map[string]any
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
if _, ok := result["status"]; !ok {
|
||||
t.Error("deep-health response missing 'status' field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_ZeroGStatus_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/zerog")
|
||||
if err != nil {
|
||||
t.Fatalf("GET zerog: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_ZeroGToggle(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
body := bytes.NewBufferString(`{"enabled":true}`)
|
||||
resp, err := http.Post(ts.URL+"/api/soc/zerog/toggle", "application/json", body)
|
||||
if err != nil {
|
||||
t.Fatalf("POST zerog toggle: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_RetentionPolicies_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/retention")
|
||||
if err != nil {
|
||||
t.Fatalf("GET retention: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_RateLimitStats_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/ratelimit")
|
||||
if err != nil {
|
||||
t.Fatalf("GET ratelimit: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_P2PPeers_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/p2p/peers")
|
||||
if err != nil {
|
||||
t.Fatalf("GET p2p peers: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_SovereignConfig_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/sovereign")
|
||||
if err != nil {
|
||||
t.Fatalf("GET sovereign: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_IncidentExplain_NotFound(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
resp, err := http.Get(ts.URL + "/api/soc/incident-explain/INC-FAKE")
|
||||
if err != nil {
|
||||
t.Fatalf("GET incident explain: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP_IngestThenVerdict(t *testing.T) {
|
||||
ts, svc := newTestServer(t)
|
||||
|
||||
// Ingest events to trigger incident.
|
||||
evt1 := domsoc.NewSOCEvent(domsoc.SourceSentinelCore, domsoc.SeverityHigh, "jailbreak", "verdict http test 1")
|
||||
evt1.SensorID = "sensor-http-vd"
|
||||
svc.IngestEvent(evt1)
|
||||
|
||||
evt2 := domsoc.NewSOCEvent(domsoc.SourceSentinelCore, domsoc.SeverityCritical, "tool_abuse", "verdict http test 2")
|
||||
evt2.SensorID = "sensor-http-vd"
|
||||
_, inc, _ := svc.IngestEvent(evt2)
|
||||
|
||||
if inc == nil {
|
||||
t.Skip("no incident created for verdict test")
|
||||
}
|
||||
|
||||
// Set verdict via HTTP.
|
||||
body := bytes.NewBufferString(fmt.Sprintf(`{"status":"INVESTIGATING"}`))
|
||||
resp, err := http.Post(ts.URL+"/api/soc/incidents/"+inc.ID+"/verdict", "application/json", body)
|
||||
if err != nil {
|
||||
t.Fatalf("POST verdict: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Verify verdict took effect.
|
||||
got, _ := svc.GetIncident(inc.ID)
|
||||
if got.Status != domsoc.StatusInvestigating {
|
||||
t.Errorf("expected INVESTIGATING, got %s", got.Status)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue