package transform import ( "context" "testing" "time" "codeberg.org/pata1704/guenther/internal/config" "codeberg.org/pata1704/guenther/pkg/types" "github.com/stretchr/testify/assert" ) func TestTransformEngine_Fusion(t *testing.T) { logChan := make(chan types.LogEvent, 100) metricChan := make(chan types.MetricSnapshot, 100) serviceStatusChan := make(chan types.ServiceStatus, 100) featureChan := make(chan types.FeatureVector, 100) healthChan := make(chan types.StageHealth, 10) cfg := &config.Config{} cfg.Transformation.WindowSize = 1 * time.Second cfg.Transformation.DbPath = ":memory:" engine, err := NewTransformEngine(cfg, logChan, metricChan, serviceStatusChan, featureChan, healthChan) assert.NoError(t, err) baseTime := time.Date(2026, 1, 1, 12, 0, 0, 0, time.Local) // 1. Send data for first window metricChan <- types.MetricSnapshot{ Timestamp: baseTime, CPUPercent: 50.0, MemoryUsedMB: 1000, MemoryDirtyMB: 100, NetworkInMBps: 10.0, NetworkOutMBps: 20.0, TCPRetransPerS: 5, NetPacketsInPerS: 100, NetPacketsOutPerS: 200, } // 2. Start engine and wait for first window ctx, cancel := context.WithCancel(context.Background()) engine.Start(ctx) defer func() { cancel() engine.Wait() }() select { case fv := <-featureChan: assert.Equal(t, 50.0, fv.AvgCPUPercent) // Deltas are absolute value on first window because tracker starts at 0 assert.Equal(t, 10.0, fv.DeltaNetIn) case <-time.After(2 * time.Second): t.Fatal("Timeout waiting for first FeatureVector") } // 3. Send data for second window (triggers deltas) secondTime := baseTime.Add(cfg.Transformation.WindowSize) metricChan <- types.MetricSnapshot{ Timestamp: secondTime, CPUPercent: 60.0, MemoryUsedMB: 1000, MemoryDirtyMB: 200, NetworkInMBps: 15.0, // DeltaNetIn = 15.0 - 10.0 = 5.0 NetworkOutMBps: 20.0, TCPRetransPerS: 10, // DeltaTCPRetrans = 10.0 - 5.0 = 5.0 NetPacketsInPerS: 150, NetPacketsOutPerS: 200, } select { case fv := <-featureChan: // Check original logic assert.Equal(t, 60.0, fv.AvgCPUPercent) // Check new delta features assert.Equal(t, 5.0, fv.DeltaNetIn) assert.Equal(t, 5.0, fv.DeltaTCPRetrans) // Check ratio features // MemPressure = dirty / (used + 1) = 200/1001 expectedPressure := 200.0 / 1001.0 assert.InDelta(t, expectedPressure, fv.MemPressure, 1e-9) // NetAsymmetry = in / (out + 1e-3) = 15/20.001 expectedAsym := 15.0 / 20.001 assert.InDelta(t, expectedAsym, fv.NetAsymmetry, 1e-9) // Check NormalizedVector length (should be 45 base + params) assert.GreaterOrEqual(t, len(fv.NormalizedVector), 45) // Verify slots 39-44 (Engineered Features tail) nv := fv.NormalizedVector assert.Equal(t, 5.0, nv[39]) // DeltaNetIn assert.Equal(t, 5.0, nv[40]) // DeltaTCPRetrans // TcpRollStd and NetRollStd will have values (even if just 2 pts) assert.Greater(t, nv[41], 0.0) // TcpRollStd (10 and 5) assert.Equal(t, 0.0, nv[42]) // NetRollStd (20 and 20 -> std=0) assert.InDelta(t, expectedPressure, nv[43], 1e-9) // MemPressure assert.InDelta(t, expectedAsym, nv[44], 1e-9) // NetAsymmetry case <-time.After(2 * time.Second): t.Fatal("Timeout waiting for second FeatureVector") } }