mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-05-09 03:22:37 +02:00
initial: Syntrex extraction from sentinel-community (615 files)
This commit is contained in:
commit
2c50c993b1
175 changed files with 32396 additions and 0 deletions
299
internal/transport/http/soc_handlers_test.go
Normal file
299
internal/transport/http/soc_handlers_test.go
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
package httpserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
appsoc "github.com/sentinel-community/gomcp/internal/application/soc"
|
||||
domsoc "github.com/sentinel-community/gomcp/internal/domain/soc"
|
||||
"github.com/sentinel-community/gomcp/internal/infrastructure/sqlite"
|
||||
)
|
||||
|
||||
// newTestServer creates an HTTP test server with a real SOC service backed by in-memory SQLite.
|
||||
func newTestServer(t *testing.T) (*httptest.Server, *appsoc.Service) {
|
||||
t.Helper()
|
||||
|
||||
// In-memory SQLite for SOC
|
||||
db, err := sqlite.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("open test db: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { db.Close() })
|
||||
|
||||
repo, err := sqlite.NewSOCRepo(db)
|
||||
if err != nil {
|
||||
t.Fatalf("create SOC repo: %v", err)
|
||||
}
|
||||
|
||||
socSvc := appsoc.NewService(repo, nil) // no decision logger for tests
|
||||
|
||||
srv := New(socSvc, 0) // port 0, we use httptest
|
||||
mux := http.NewServeMux()
|
||||
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/sensors", srv.handleSensors)
|
||||
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("GET /health", srv.handleHealth)
|
||||
|
||||
ts := httptest.NewServer(corsMiddleware(mux))
|
||||
t.Cleanup(ts.Close)
|
||||
|
||||
return ts, socSvc
|
||||
}
|
||||
|
||||
// TestHTTP_Dashboard_Returns200 verifies GET /api/soc/dashboard returns 200 with valid JSON.
|
||||
func TestHTTP_Dashboard_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/dashboard")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /api/soc/dashboard: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Verify JSON structure
|
||||
var result map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("decode JSON: %v", err)
|
||||
}
|
||||
|
||||
// Must contain total_events key
|
||||
if _, ok := result["total_events"]; !ok {
|
||||
t.Error("response missing 'total_events' field")
|
||||
}
|
||||
|
||||
// Verify CORS headers
|
||||
if origin := resp.Header.Get("Access-Control-Allow-Origin"); origin != "*" {
|
||||
t.Errorf("CORS: expected *, got %q", origin)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHTTP_Events_WithLimit verifies GET /api/soc/events?limit=5 returns at most 5 events.
|
||||
func TestHTTP_Events_WithLimit(t *testing.T) {
|
||||
ts, socSvc := newTestServer(t)
|
||||
|
||||
// Ingest 10 events
|
||||
for i := 0; i < 10; i++ {
|
||||
socSvc.IngestEvent(domsoc.SOCEvent{
|
||||
SensorID: "test-sensor",
|
||||
Category: "test",
|
||||
Severity: domsoc.SeverityLow,
|
||||
Payload: "test event payload",
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/events?limit=5")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /api/soc/events: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Events []any `json:"events"`
|
||||
Count int `json:"count"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("decode JSON: %v", err)
|
||||
}
|
||||
|
||||
if result.Limit != 5 {
|
||||
t.Errorf("expected limit=5, got %d", result.Limit)
|
||||
}
|
||||
if result.Count > 5 {
|
||||
t.Errorf("expected at most 5 events, got %d", result.Count)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHTTP_Incidents_FilterByStatus verifies GET /api/soc/incidents?status=open returns only open incidents.
|
||||
func TestHTTP_Incidents_FilterByStatus(t *testing.T) {
|
||||
ts, socSvc := newTestServer(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",
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/incidents?status=open")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /api/soc/incidents: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Incidents []any `json:"incidents"`
|
||||
Count int `json:"count"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("decode JSON: %v", err)
|
||||
}
|
||||
|
||||
if result.Status != "open" {
|
||||
t.Errorf("expected status filter 'open', got %q", result.Status)
|
||||
}
|
||||
|
||||
// After 3 jailbreak events, correlation should have created at least one open incident
|
||||
t.Logf("incidents: count=%d, status=%s", result.Count, result.Status)
|
||||
}
|
||||
|
||||
// TestHTTP_Health returns ok.
|
||||
func TestHTTP_Health(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
resp, err := http.Get(ts.URL + "/health")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /health: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result map[string]string
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("decode JSON: %v", err)
|
||||
}
|
||||
|
||||
if result["status"] != "ok" {
|
||||
t.Errorf("expected status 'ok', got %q", result["status"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestHTTP_Sensors_Returns200 verifies GET /api/soc/sensors returns 200.
|
||||
func TestHTTP_Sensors_Returns200(t *testing.T) {
|
||||
ts, socSvc := newTestServer(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,
|
||||
})
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/sensors")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /api/soc/sensors: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Sensors []any `json:"sensors"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("decode JSON: %v", err)
|
||||
}
|
||||
|
||||
if result.Count < 1 {
|
||||
t.Error("expected at least 1 sensor after event ingest")
|
||||
}
|
||||
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) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/threat-intel")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /api/soc/threat-intel: %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)
|
||||
}
|
||||
|
||||
// Without SetThreatIntel, should return enabled=false
|
||||
if enabled, ok := result["enabled"].(bool); !ok || enabled {
|
||||
t.Error("expected enabled=false when threat intel not configured")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHTTP_Analytics_Returns200 verifies GET /api/soc/analytics returns a valid report.
|
||||
func TestHTTP_Analytics_Returns200(t *testing.T) {
|
||||
ts, socSvc := newTestServer(t)
|
||||
|
||||
// Ingest some events for analytics
|
||||
for i := 0; i < 5; i++ {
|
||||
socSvc.IngestEvent(domsoc.SOCEvent{
|
||||
SensorID: "analytics-sensor",
|
||||
Source: domsoc.SourceShield,
|
||||
Category: "injection",
|
||||
Severity: domsoc.SeverityHigh,
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/analytics?window=1")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /api/soc/analytics: %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)
|
||||
}
|
||||
|
||||
// Must have analytics fields
|
||||
for _, field := range []string{"generated_at", "event_trend", "severity_distribution", "top_sources", "mttr_hours"} {
|
||||
if _, ok := result[field]; !ok {
|
||||
t.Errorf("response missing '%s' field", field)
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("analytics: events_per_hour=%.1f", result["events_per_hour"])
|
||||
}
|
||||
|
||||
// TestHTTP_WebhookStats_Returns200 verifies webhook-stats endpoint works.
|
||||
func TestHTTP_WebhookStats_Returns200(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
|
||||
resp, err := http.Get(ts.URL + "/api/soc/webhook-stats")
|
||||
if err != nil {
|
||||
t.Fatalf("GET /api/soc/webhook-stats: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue