gomcp/internal/domain/soc/ghost_sinkhole_test.go

146 lines
3.7 KiB
Go

package soc
import (
"testing"
)
func TestGhostSinkhole_GenerateDecoy(t *testing.T) {
gs := NewGhostSinkhole()
resp := gs.GenerateDecoy("shadow_ai", "abc123hash", "10.0.0.1", "curl/7.0")
if resp.ID == "" {
t.Fatal("expected non-empty ID")
}
if resp.Category != "shadow_ai" {
t.Fatalf("expected category shadow_ai, got %s", resp.Category)
}
if resp.DecoyTemplate != "fake_api_key" {
t.Fatalf("expected fake_api_key template, got %s", resp.DecoyTemplate)
}
if resp.SourceIP != "10.0.0.1" {
t.Fatalf("expected source IP 10.0.0.1, got %s", resp.SourceIP)
}
if resp.DecoyContent == "" {
t.Fatal("expected non-empty decoy content")
}
}
func TestGhostSinkhole_CategoryTemplateMatching(t *testing.T) {
gs := NewGhostSinkhole()
tests := []struct {
category string
template string
}{
{"shadow_ai", "fake_api_key"},
{"jailbreak", "fake_model_response"},
{"exfiltration", "fake_data_export"},
{"auth_bypass", "fake_credentials"},
{"unknown_category", "generic_success"},
}
for _, tt := range tests {
t.Run(tt.category, func(t *testing.T) {
resp := gs.GenerateDecoy(tt.category, "hash", "", "")
if resp.DecoyTemplate != tt.template {
t.Errorf("category %q: got template %q, want %q", tt.category, resp.DecoyTemplate, tt.template)
}
})
}
}
func TestGhostSinkhole_GetResponses(t *testing.T) {
gs := NewGhostSinkhole()
for i := 0; i < 10; i++ {
gs.GenerateDecoy("shadow_ai", "hash", "", "")
}
// Get last 5
recent := gs.GetResponses(5)
if len(recent) != 5 {
t.Fatalf("expected 5 responses, got %d", len(recent))
}
// Most recent first
if recent[0].Timestamp.Before(recent[4].Timestamp) {
t.Fatal("responses should be ordered most recent first")
}
}
func TestGhostSinkhole_GetResponse_ByID(t *testing.T) {
gs := NewGhostSinkhole()
resp := gs.GenerateDecoy("jailbreak", "hash", "", "")
found, ok := gs.GetResponse(resp.ID)
if !ok {
t.Fatal("expected to find response by ID")
}
if found.Category != "jailbreak" {
t.Fatalf("expected jailbreak, got %s", found.Category)
}
_, ok = gs.GetResponse("nonexistent-id")
if ok {
t.Fatal("should not find nonexistent ID")
}
}
func TestGhostSinkhole_RingBuffer(t *testing.T) {
gs := &GhostSinkhole{maxStore: 5, templates: NewGhostSinkhole().templates}
for i := 0; i < 10; i++ {
gs.GenerateDecoy("shadow_ai", "hash", "", "")
}
all := gs.GetResponses(0)
if len(all) != 5 {
t.Fatalf("ring buffer should cap at 5, got %d", len(all))
}
}
func TestGhostSinkhole_Stats(t *testing.T) {
gs := NewGhostSinkhole()
gs.GenerateDecoy("shadow_ai", "h1", "", "")
gs.GenerateDecoy("shadow_ai", "h2", "", "")
gs.GenerateDecoy("jailbreak", "h3", "", "")
stats := gs.Stats()
if stats["total_decoys"].(int) != 3 {
t.Fatalf("expected 3 total, got %v", stats["total_decoys"])
}
byCat := stats["by_category"].(map[string]int)
if byCat["shadow_ai"] != 2 {
t.Fatalf("expected 2 shadow_ai, got %d", byCat["shadow_ai"])
}
if byCat["jailbreak"] != 1 {
t.Fatalf("expected 1 jailbreak, got %d", byCat["jailbreak"])
}
}
func TestGhostSinkhole_TTPs(t *testing.T) {
gs := NewGhostSinkhole()
resp := gs.GenerateDecoy("exfiltration", "hash", "192.168.1.1", "python-requests/2.28")
if resp.TTPs["technique"] != "exfiltration" {
t.Fatalf("expected technique=exfiltration, got %s", resp.TTPs["technique"])
}
if resp.TTPs["decoy_served"] != "fake_data_export" {
t.Fatalf("expected decoy_served=fake_data_export, got %s", resp.TTPs["decoy_served"])
}
}
func TestGhostSinkhole_UniqueIDs(t *testing.T) {
gs := NewGhostSinkhole()
ids := make(map[string]bool)
for i := 0; i < 100; i++ {
resp := gs.GenerateDecoy("shadow_ai", "hash", "", "")
if ids[resp.ID] {
t.Fatalf("duplicate ID generated: %s", resp.ID)
}
ids[resp.ID] = true
}
}