chore: release v1.0.2 with critical fixes and test improvements

- fix: add missing ClickHouse driver dependency
- fix: resolve race condition in orchestrator (single goroutine per source)
- feat: add explicit source_type config for Unix socket sources
- test: improve coverage from 50.6% to 62.0%
- docs: add CHANGELOG.md with release notes
- build: update version to 1.0.2 in build scripts and Dockerfiles

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Jacquin Antoine
2026-02-28 21:45:00 +01:00
parent 5f97af3627
commit 180c57c35b
15 changed files with 1178 additions and 30 deletions

View File

@ -155,3 +155,188 @@ func TestCorrelationService_Flush(t *testing.T) {
t.Errorf("expected 0 flushed events, got %d", len(flushed))
}
}
func TestCorrelationService_GetBufferSizes(t *testing.T) {
now := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
timeProvider := &mockTimeProvider{now: now}
config := CorrelationConfig{
TimeWindow: time.Second,
ApacheAlwaysEmit: false,
NetworkEmit: false,
}
svc := NewCorrelationService(config, timeProvider)
// Empty buffers
a, b := svc.GetBufferSizes()
if a != 0 || b != 0 {
t.Errorf("expected empty buffers, got A=%d, B=%d", a, b)
}
// Add event to buffer A
apacheEvent := &NormalizedEvent{
Source: SourceA,
Timestamp: now,
SrcIP: "192.168.1.1",
SrcPort: 8080,
}
svc.ProcessEvent(apacheEvent)
a, b = svc.GetBufferSizes()
if a != 1 || b != 0 {
t.Errorf("expected A=1, B=0, got A=%d, B=%d", a, b)
}
}
func TestCorrelationService_FlushWithEvents(t *testing.T) {
now := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
timeProvider := &mockTimeProvider{now: now}
// Flush only emits events if ApacheAlwaysEmit and NetworkEmit are true
config := CorrelationConfig{
TimeWindow: time.Second,
ApacheAlwaysEmit: true,
NetworkEmit: true,
}
svc := NewCorrelationService(config, timeProvider)
// We need to bypass the normal ProcessEvent logic to get events into buffers
// Add events directly to buffers for testing Flush
keyA := "192.168.1.1:8080"
keyB := "192.168.1.2:9090"
apacheEvent := &NormalizedEvent{
Source: SourceA,
Timestamp: now,
SrcIP: "192.168.1.1",
SrcPort: 8080,
}
networkEvent := &NormalizedEvent{
Source: SourceB,
Timestamp: now,
SrcIP: "192.168.1.2",
SrcPort: 9090,
}
// Manually add to buffers (simulating events that couldn't be matched)
elemA := svc.bufferA.events.PushBack(apacheEvent)
svc.pendingA[keyA] = append(svc.pendingA[keyA], elemA)
elemB := svc.bufferB.events.PushBack(networkEvent)
svc.pendingB[keyB] = append(svc.pendingB[keyB], elemB)
flushed := svc.Flush()
if len(flushed) != 2 {
t.Errorf("expected 2 flushed events, got %d", len(flushed))
}
// Verify buffers are cleared
a, b := svc.GetBufferSizes()
if a != 0 || b != 0 {
t.Errorf("expected empty buffers after flush, got A=%d, B=%d", a, b)
}
}
func TestCorrelationService_BufferOverflow(t *testing.T) {
now := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
timeProvider := &mockTimeProvider{now: now}
config := CorrelationConfig{
TimeWindow: time.Second,
ApacheAlwaysEmit: false,
NetworkEmit: false,
MaxBufferSize: 2,
}
svc := NewCorrelationService(config, timeProvider)
// Fill buffer A
for i := 0; i < 2; i++ {
event := &NormalizedEvent{
Source: SourceA,
Timestamp: now,
SrcIP: "192.168.1.1",
SrcPort: 8080 + i,
}
svc.ProcessEvent(event)
}
// Buffer full, next event should be dropped (not emitted since ApacheAlwaysEmit=false but buffer full)
overflowEvent := &NormalizedEvent{
Source: SourceA,
Timestamp: now,
SrcIP: "192.168.1.1",
SrcPort: 9999,
}
results := svc.ProcessEvent(overflowEvent)
if len(results) != 0 {
t.Errorf("expected 0 results on buffer overflow, got %d", len(results))
}
}
func TestCorrelationService_DefaultConfig(t *testing.T) {
timeProvider := &RealTimeProvider{}
// Test with zero config - should use defaults
config := CorrelationConfig{}
svc := NewCorrelationService(config, timeProvider)
if svc.config.MaxBufferSize != DefaultMaxBufferSize {
t.Errorf("expected MaxBufferSize %d, got %d", DefaultMaxBufferSize, svc.config.MaxBufferSize)
}
if svc.config.TimeWindow != DefaultTimeWindow {
t.Errorf("expected TimeWindow %v, got %v", DefaultTimeWindow, svc.config.TimeWindow)
}
}
func TestCorrelationService_NilTimeProvider(t *testing.T) {
config := CorrelationConfig{
TimeWindow: time.Second,
}
// Should not panic with nil time provider
svc := NewCorrelationService(config, nil)
if svc == nil {
t.Error("expected non-nil service")
}
}
func TestCorrelationService_DifferentSourceTypes(t *testing.T) {
now := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
timeProvider := &mockTimeProvider{now: now}
config := CorrelationConfig{
TimeWindow: time.Second,
ApacheAlwaysEmit: false,
NetworkEmit: false,
}
svc := NewCorrelationService(config, timeProvider)
// Send B first, then A - should still match
networkEvent := &NormalizedEvent{
Source: SourceB,
Timestamp: now,
SrcIP: "192.168.1.1",
SrcPort: 8080,
}
results := svc.ProcessEvent(networkEvent)
if len(results) != 0 {
t.Errorf("expected 0 results (buffered B), got %d", len(results))
}
apacheEvent := &NormalizedEvent{
Source: SourceA,
Timestamp: now.Add(500 * time.Millisecond),
SrcIP: "192.168.1.1",
SrcPort: 8080,
}
results = svc.ProcessEvent(apacheEvent)
if len(results) != 1 {
t.Errorf("expected 1 result (correlated), got %d", len(results))
}
if !results[0].Correlated {
t.Error("expected correlated result")
}
}