mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
525 lines
12 KiB
Go
525 lines
12 KiB
Go
package audit
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func TestNewSQLiteLogger(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
if logger.GetRetentionDays() != 30 {
|
|
t.Errorf("Expected retention days 30, got %d", logger.GetRetentionDays())
|
|
}
|
|
}
|
|
|
|
func TestNewSQLiteLoggerDefaultRetention(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
// RetentionDays not set
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
if logger.GetRetentionDays() != 90 {
|
|
t.Errorf("Expected default retention days 90, got %d", logger.GetRetentionDays())
|
|
}
|
|
}
|
|
|
|
func TestSQLiteLoggerLog(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
event := Event{
|
|
ID: uuid.NewString(),
|
|
Timestamp: time.Now(),
|
|
EventType: "test_event",
|
|
User: "testuser",
|
|
IP: "192.168.1.1",
|
|
Path: "/api/test",
|
|
Success: true,
|
|
Details: "test details",
|
|
}
|
|
|
|
err = logger.Log(event)
|
|
if err != nil {
|
|
t.Fatalf("Log failed: %v", err)
|
|
}
|
|
|
|
// Query the event back
|
|
events, err := logger.Query(QueryFilter{ID: event.ID})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
|
|
if len(events) != 1 {
|
|
t.Fatalf("Expected 1 event, got %d", len(events))
|
|
}
|
|
|
|
retrieved := events[0]
|
|
if retrieved.ID != event.ID {
|
|
t.Errorf("ID mismatch: expected %s, got %s", event.ID, retrieved.ID)
|
|
}
|
|
if retrieved.EventType != event.EventType {
|
|
t.Errorf("EventType mismatch: expected %s, got %s", event.EventType, retrieved.EventType)
|
|
}
|
|
if retrieved.User != event.User {
|
|
t.Errorf("User mismatch: expected %s, got %s", event.User, retrieved.User)
|
|
}
|
|
if retrieved.Success != event.Success {
|
|
t.Errorf("Success mismatch: expected %v, got %v", event.Success, retrieved.Success)
|
|
}
|
|
|
|
// Event should have a signature
|
|
if retrieved.Signature == "" {
|
|
t.Error("Expected event to have a signature")
|
|
}
|
|
}
|
|
|
|
func TestSQLiteLoggerQuery(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
// Log several events
|
|
now := time.Now()
|
|
events := []Event{
|
|
{ID: "e1", Timestamp: now.Add(-2 * time.Hour), EventType: "login", User: "alice", Success: true},
|
|
{ID: "e2", Timestamp: now.Add(-1 * time.Hour), EventType: "login", User: "bob", Success: true},
|
|
{ID: "e3", Timestamp: now, EventType: "logout", User: "alice", Success: true},
|
|
{ID: "e4", Timestamp: now, EventType: "login", User: "charlie", Success: false},
|
|
}
|
|
|
|
for _, e := range events {
|
|
if err := logger.Log(e); err != nil {
|
|
t.Fatalf("Log failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// Test event type filter
|
|
t.Run("FilterByEventType", func(t *testing.T) {
|
|
results, err := logger.Query(QueryFilter{EventType: "login"})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
if len(results) != 3 {
|
|
t.Errorf("Expected 3 login events, got %d", len(results))
|
|
}
|
|
})
|
|
|
|
// Test user filter
|
|
t.Run("FilterByUser", func(t *testing.T) {
|
|
results, err := logger.Query(QueryFilter{User: "alice"})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
if len(results) != 2 {
|
|
t.Errorf("Expected 2 events for alice, got %d", len(results))
|
|
}
|
|
})
|
|
|
|
// Test success filter
|
|
t.Run("FilterBySuccess", func(t *testing.T) {
|
|
success := false
|
|
results, err := logger.Query(QueryFilter{Success: &success})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
if len(results) != 1 {
|
|
t.Errorf("Expected 1 failed event, got %d", len(results))
|
|
}
|
|
})
|
|
|
|
// Test time range filter
|
|
t.Run("FilterByTimeRange", func(t *testing.T) {
|
|
start := now.Add(-90 * time.Minute)
|
|
end := now.Add(-30 * time.Minute)
|
|
results, err := logger.Query(QueryFilter{StartTime: &start, EndTime: &end})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
if len(results) != 1 {
|
|
t.Errorf("Expected 1 event in time range, got %d", len(results))
|
|
}
|
|
})
|
|
|
|
// Test limit and offset
|
|
t.Run("LimitAndOffset", func(t *testing.T) {
|
|
results, err := logger.Query(QueryFilter{Limit: 2, Offset: 1})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
if len(results) != 2 {
|
|
t.Errorf("Expected 2 events with limit, got %d", len(results))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSQLiteLoggerCount(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
// Log several events
|
|
for i := 0; i < 5; i++ {
|
|
event := Event{
|
|
ID: uuid.NewString(),
|
|
Timestamp: time.Now(),
|
|
EventType: "test",
|
|
Success: i%2 == 0,
|
|
}
|
|
if err := logger.Log(event); err != nil {
|
|
t.Fatalf("Log failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// Count all
|
|
count, err := logger.Count(QueryFilter{})
|
|
if err != nil {
|
|
t.Fatalf("Count failed: %v", err)
|
|
}
|
|
if count != 5 {
|
|
t.Errorf("Expected count 5, got %d", count)
|
|
}
|
|
|
|
// Count successful
|
|
success := true
|
|
count, err = logger.Count(QueryFilter{Success: &success})
|
|
if err != nil {
|
|
t.Fatalf("Count failed: %v", err)
|
|
}
|
|
if count != 3 {
|
|
t.Errorf("Expected 3 successful events, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestSQLiteLoggerVerifySignature(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
event := Event{
|
|
ID: uuid.NewString(),
|
|
Timestamp: time.Now(),
|
|
EventType: "verify_test",
|
|
User: "testuser",
|
|
Success: true,
|
|
}
|
|
|
|
if err := logger.Log(event); err != nil {
|
|
t.Fatalf("Log failed: %v", err)
|
|
}
|
|
|
|
// Query the event back
|
|
events, err := logger.Query(QueryFilter{ID: event.ID})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
|
|
if len(events) != 1 {
|
|
t.Fatalf("Expected 1 event, got %d", len(events))
|
|
}
|
|
|
|
// Verify should succeed
|
|
if !logger.VerifySignature(events[0]) {
|
|
t.Error("VerifySignature should return true for valid event")
|
|
}
|
|
|
|
// Tamper with event and verify should fail
|
|
tamperedEvent := events[0]
|
|
tamperedEvent.Details = "tampered"
|
|
if logger.VerifySignature(tamperedEvent) {
|
|
t.Error("VerifySignature should return false for tampered event")
|
|
}
|
|
}
|
|
|
|
func TestSQLiteLoggerWebhooks(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
// Initially no webhooks
|
|
urls := logger.GetWebhookURLs()
|
|
if len(urls) != 0 {
|
|
t.Errorf("Expected no webhooks initially, got %d", len(urls))
|
|
}
|
|
|
|
// Add webhooks
|
|
testURLs := []string{"https://example.com/webhook1", "https://example.com/webhook2"}
|
|
err = logger.UpdateWebhookURLs(testURLs)
|
|
if err != nil {
|
|
t.Fatalf("UpdateWebhookURLs failed: %v", err)
|
|
}
|
|
|
|
urls = logger.GetWebhookURLs()
|
|
if len(urls) != 2 {
|
|
t.Errorf("Expected 2 webhooks, got %d", len(urls))
|
|
}
|
|
|
|
// Clear webhooks
|
|
err = logger.UpdateWebhookURLs([]string{})
|
|
if err != nil {
|
|
t.Fatalf("UpdateWebhookURLs (clear) failed: %v", err)
|
|
}
|
|
|
|
urls = logger.GetWebhookURLs()
|
|
if len(urls) != 0 {
|
|
t.Errorf("Expected 0 webhooks after clear, got %d", len(urls))
|
|
}
|
|
}
|
|
|
|
func TestSQLiteLoggerRetention(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 1, // 1 day retention for testing
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
// Log an old event (2 days ago)
|
|
oldEvent := Event{
|
|
ID: "old-event",
|
|
Timestamp: time.Now().Add(-48 * time.Hour),
|
|
EventType: "old",
|
|
Success: true,
|
|
}
|
|
if err := logger.Log(oldEvent); err != nil {
|
|
t.Fatalf("Log old event failed: %v", err)
|
|
}
|
|
|
|
// Log a recent event
|
|
newEvent := Event{
|
|
ID: "new-event",
|
|
Timestamp: time.Now(),
|
|
EventType: "new",
|
|
Success: true,
|
|
}
|
|
if err := logger.Log(newEvent); err != nil {
|
|
t.Fatalf("Log new event failed: %v", err)
|
|
}
|
|
|
|
// Run cleanup
|
|
logger.cleanupOldEvents()
|
|
|
|
// Old event should be deleted
|
|
events, err := logger.Query(QueryFilter{ID: "old-event"})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
if len(events) != 0 {
|
|
t.Error("Old event should have been deleted")
|
|
}
|
|
|
|
// New event should still exist
|
|
events, err = logger.Query(QueryFilter{ID: "new-event"})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
if len(events) != 1 {
|
|
t.Error("New event should still exist")
|
|
}
|
|
}
|
|
|
|
func TestSQLiteLoggerSetRetentionDays(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
logger.SetRetentionDays(60)
|
|
if logger.GetRetentionDays() != 60 {
|
|
t.Errorf("Expected retention days 60, got %d", logger.GetRetentionDays())
|
|
}
|
|
}
|
|
|
|
func TestSQLiteLoggerPersistence(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
// Create logger and log an event
|
|
logger1, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
|
|
event := Event{
|
|
ID: "persist-test",
|
|
Timestamp: time.Now(),
|
|
EventType: "persistence_test",
|
|
User: "testuser",
|
|
Success: true,
|
|
}
|
|
if err := logger1.Log(event); err != nil {
|
|
t.Fatalf("Log failed: %v", err)
|
|
}
|
|
|
|
// Close the logger
|
|
logger1.Close()
|
|
|
|
// Create a new logger with same data dir
|
|
logger2, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger (reload) failed: %v", err)
|
|
}
|
|
defer logger2.Close()
|
|
|
|
// Query the event - should still exist
|
|
events, err := logger2.Query(QueryFilter{ID: "persist-test"})
|
|
if err != nil {
|
|
t.Fatalf("Query failed: %v", err)
|
|
}
|
|
if len(events) != 1 {
|
|
t.Error("Event should persist across logger restarts")
|
|
}
|
|
|
|
// Signature should still verify
|
|
if len(events) > 0 && !logger2.VerifySignature(events[0]) {
|
|
t.Error("Signature should still verify after restart")
|
|
}
|
|
}
|
|
|
|
func TestSQLiteLoggerConcurrentAccess(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
defer logger.Close()
|
|
|
|
// Concurrent writes
|
|
done := make(chan bool)
|
|
for i := 0; i < 10; i++ {
|
|
go func(n int) {
|
|
for j := 0; j < 10; j++ {
|
|
event := Event{
|
|
ID: uuid.NewString(),
|
|
Timestamp: time.Now(),
|
|
EventType: "concurrent_test",
|
|
User: "user",
|
|
Success: true,
|
|
}
|
|
if err := logger.Log(event); err != nil {
|
|
t.Errorf("Concurrent log failed: %v", err)
|
|
}
|
|
}
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
// Wait for all goroutines
|
|
for i := 0; i < 10; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Verify count
|
|
count, err := logger.Count(QueryFilter{EventType: "concurrent_test"})
|
|
if err != nil {
|
|
t.Fatalf("Count failed: %v", err)
|
|
}
|
|
if count != 100 {
|
|
t.Errorf("Expected 100 events, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestSQLiteLoggerCloseIdempotent(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
|
DataDir: tempDir,
|
|
CryptoMgr: newMockCryptoManager(),
|
|
RetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
|
}
|
|
|
|
if err := logger.Close(); err != nil {
|
|
t.Fatalf("first Close failed: %v", err)
|
|
}
|
|
if err := logger.Close(); err != nil {
|
|
t.Fatalf("second Close failed: %v", err)
|
|
}
|
|
}
|