mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-03 08:33:26 +00:00
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
## Description - add mocks for tokenizer - rename mocks in alertmanager to alertmanagertest - add makefile command for generating mocks
279 lines
10 KiB
Go
279 lines
10 KiB
Go
package rules
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"math"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/alertmanager"
|
|
alertmanagermock "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagertest"
|
|
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
|
"github.com/SigNoz/signoz/pkg/prometheus"
|
|
"github.com/SigNoz/signoz/pkg/prometheus/prometheustest"
|
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
|
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstoretest"
|
|
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
|
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
|
|
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
|
"github.com/SigNoz/signoz/pkg/types/metrictypes"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
cmock "github.com/srikanthccv/ClickHouse-go-mock"
|
|
)
|
|
|
|
func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
|
|
target := 10.0
|
|
recovery := 5.0
|
|
|
|
for _, tc := range TcTestNotiSendUnmatchedThresholdRule {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
rule := ThresholdRuleAtLeastOnceValueAbove(target, &recovery)
|
|
|
|
// Marshal rule to JSON as TestNotification expects
|
|
ruleBytes, err := json.Marshal(rule)
|
|
require.NoError(t, err)
|
|
|
|
orgID := valuer.GenerateUUID()
|
|
|
|
// for saving temp alerts that are triggered via TestNotification
|
|
triggeredTestAlerts := []map[*alertmanagertypes.PostableAlert][]string{}
|
|
|
|
// Create manager using test factory with hooks
|
|
mgr := NewTestManager(t, &TestManagerOptions{
|
|
AlertmanagerHook: func(am alertmanager.Alertmanager) {
|
|
mockAM := am.(*alertmanagermock.MockAlertmanager)
|
|
// mock set notification config
|
|
mockAM.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
// for saving temp alerts that are triggered via TestNotification
|
|
if tc.ExpectAlerts > 0 {
|
|
mockAM.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
|
triggeredTestAlerts = append(triggeredTestAlerts, args.Get(3).(map[*alertmanagertypes.PostableAlert][]string))
|
|
}).Return(nil).Times(tc.ExpectAlerts)
|
|
}
|
|
},
|
|
SqlStoreHook: func(store sqlstore.SQLStore) {
|
|
mockStore := store.(*sqlstoretest.Provider)
|
|
// Mock the organizations query that SendAlerts makes
|
|
// Bun generates: SELECT id FROM organizations LIMIT 1 (or SELECT "id" FROM "organizations" LIMIT 1)
|
|
orgRows := mockStore.Mock().NewRows([]string{"id"}).AddRow(orgID.StringValue())
|
|
// Match bun's generated query pattern - bun may quote identifiers
|
|
mockStore.Mock().ExpectQuery("SELECT (.+) FROM (.+)organizations(.+) LIMIT (.+)").WillReturnRows(orgRows)
|
|
},
|
|
TelemetryStoreHook: func(store telemetrystore.TelemetryStore) {
|
|
mockStore := store.(*telemetrystoretest.Provider)
|
|
// Set up mock data for telemetry store
|
|
cols := make([]cmock.ColumnType, 0)
|
|
cols = append(cols, cmock.ColumnType{Name: "value", Type: "Float64"})
|
|
cols = append(cols, cmock.ColumnType{Name: "attr", Type: "String"})
|
|
cols = append(cols, cmock.ColumnType{Name: "ts", Type: "DateTime"})
|
|
|
|
alertDataRows := cmock.NewRows(cols, tc.Values)
|
|
|
|
mock := mockStore.Mock()
|
|
|
|
// Generate query arguments for the metric query
|
|
evalTime := time.Now().UTC()
|
|
evalWindow := 5 * time.Minute
|
|
evalDelay := time.Duration(0)
|
|
queryArgs := GenerateMetricQueryCHArgs(
|
|
evalTime,
|
|
evalWindow,
|
|
evalDelay,
|
|
"probe_success",
|
|
metrictypes.Unspecified,
|
|
)
|
|
|
|
mock.ExpectQuery("*WITH __temporal_aggregation_cte*").
|
|
WithArgs(queryArgs...).
|
|
WillReturnRows(alertDataRows)
|
|
},
|
|
})
|
|
|
|
count, apiErr := mgr.TestNotification(context.Background(), orgID, string(ruleBytes))
|
|
if apiErr != nil {
|
|
t.Logf("TestNotification error: %v, type: %s", apiErr.Err, apiErr.Typ)
|
|
}
|
|
require.Nil(t, apiErr)
|
|
assert.Equal(t, tc.ExpectAlerts, count)
|
|
|
|
if tc.ExpectAlerts > 0 {
|
|
// check if the alert has been triggered
|
|
require.Len(t, triggeredTestAlerts, 1)
|
|
var gotAlerts []*alertmanagertypes.PostableAlert
|
|
for a := range triggeredTestAlerts[0] {
|
|
gotAlerts = append(gotAlerts, a)
|
|
}
|
|
require.Len(t, gotAlerts, tc.ExpectAlerts)
|
|
// check if the alert has triggered with correct threshold value
|
|
if tc.ExpectValue != 0 {
|
|
assert.Equal(t, strconv.FormatFloat(tc.ExpectValue, 'f', -1, 64), gotAlerts[0].Annotations["value"])
|
|
}
|
|
} else {
|
|
// check if no alerts have been triggered
|
|
assert.Empty(t, triggeredTestAlerts)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManager_TestNotification_SendUnmatched_PromRule(t *testing.T) {
|
|
target := 10.0
|
|
|
|
for _, tc := range TcTestNotificationSendUnmatchedPromRule {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
// Capture base time once per test case to ensure consistent timestamps
|
|
baseTime := time.Now().UTC()
|
|
|
|
rule := BuildPromAtLeastOnceValueAbove(target, nil)
|
|
|
|
// Marshal rule to JSON as TestNotification expects
|
|
ruleBytes, err := json.Marshal(rule)
|
|
require.NoError(t, err)
|
|
|
|
orgID := valuer.GenerateUUID()
|
|
|
|
// for saving temp alerts that are triggered via TestNotification
|
|
triggeredTestAlerts := []map[*alertmanagertypes.PostableAlert][]string{}
|
|
|
|
// Variable to store promProvider for cleanup
|
|
var promProvider *prometheustest.Provider
|
|
|
|
// Create manager using test factory with hooks
|
|
mgr := NewTestManager(t, &TestManagerOptions{
|
|
AlertmanagerHook: func(am alertmanager.Alertmanager) {
|
|
mockAM := am.(*alertmanagermock.MockAlertmanager)
|
|
// mock set notification config
|
|
mockAM.On("SetNotificationConfig", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
// for saving temp alerts that are triggered via TestNotification
|
|
if tc.ExpectAlerts > 0 {
|
|
mockAM.On("TestAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
|
triggeredTestAlerts = append(triggeredTestAlerts, args.Get(3).(map[*alertmanagertypes.PostableAlert][]string))
|
|
}).Return(nil).Times(tc.ExpectAlerts)
|
|
}
|
|
},
|
|
SqlStoreHook: func(store sqlstore.SQLStore) {
|
|
mockStore := store.(*sqlstoretest.Provider)
|
|
// Mock the organizations query that SendAlerts makes
|
|
orgRows := mockStore.Mock().NewRows([]string{"id"}).AddRow(orgID.StringValue())
|
|
mockStore.Mock().ExpectQuery("SELECT (.+) FROM (.+)organizations(.+) LIMIT (.+)").WillReturnRows(orgRows)
|
|
},
|
|
TelemetryStoreHook: func(store telemetrystore.TelemetryStore) {
|
|
mockStore := store.(*telemetrystoretest.Provider)
|
|
|
|
// Set up Prometheus-specific mock data
|
|
// Fingerprint columns for Prometheus queries
|
|
fingerprintCols := []cmock.ColumnType{
|
|
{Name: "fingerprint", Type: "UInt64"},
|
|
{Name: "any(labels)", Type: "String"},
|
|
}
|
|
|
|
// Samples columns for Prometheus queries
|
|
samplesCols := []cmock.ColumnType{
|
|
{Name: "metric_name", Type: "String"},
|
|
{Name: "fingerprint", Type: "UInt64"},
|
|
{Name: "unix_milli", Type: "Int64"},
|
|
{Name: "value", Type: "Float64"},
|
|
{Name: "flags", Type: "UInt32"},
|
|
}
|
|
|
|
// Calculate query time range similar to Prometheus rule tests
|
|
// TestNotification uses time.Now().UTC() for evaluation
|
|
// We calculate the query window based on current time to match what the actual evaluation will use
|
|
evalTime := baseTime
|
|
evalWindowMs := int64(5 * 60 * 1000) // 5 minutes in ms
|
|
evalTimeMs := evalTime.UnixMilli()
|
|
queryStart := ((evalTimeMs-2*evalWindowMs)/60000)*60000 + 1 // truncate to minute + 1ms
|
|
queryEnd := (evalTimeMs / 60000) * 60000 // truncate to minute
|
|
|
|
// Create fingerprint data
|
|
fingerprint := uint64(12345)
|
|
labelsJSON := `{"__name__":"test_metric"}`
|
|
fingerprintData := [][]interface{}{
|
|
{fingerprint, labelsJSON},
|
|
}
|
|
fingerprintRows := cmock.NewRows(fingerprintCols, fingerprintData)
|
|
|
|
// Create samples data from test case values, calculating timestamps relative to baseTime
|
|
validSamplesData := make([][]interface{}, 0)
|
|
for _, v := range tc.Values {
|
|
// Skip NaN and Inf values in the samples data
|
|
if math.IsNaN(v.Value) || math.IsInf(v.Value, 0) {
|
|
continue
|
|
}
|
|
// Calculate timestamp relative to baseTime
|
|
sampleTimestamp := baseTime.Add(v.Offset).UnixMilli()
|
|
validSamplesData = append(validSamplesData, []interface{}{
|
|
"test_metric",
|
|
fingerprint,
|
|
sampleTimestamp,
|
|
v.Value,
|
|
uint32(0), // flags - 0 means normal value
|
|
})
|
|
}
|
|
samplesRows := cmock.NewRows(samplesCols, validSamplesData)
|
|
|
|
mock := mockStore.Mock()
|
|
|
|
// Mock the fingerprint query (for Prometheus label matching)
|
|
mock.ExpectQuery("SELECT fingerprint, any").
|
|
WithArgs("test_metric", "__name__", "test_metric").
|
|
WillReturnRows(fingerprintRows)
|
|
|
|
// Mock the samples query (for Prometheus metric data)
|
|
mock.ExpectQuery("SELECT metric_name, fingerprint, unix_milli").
|
|
WithArgs(
|
|
"test_metric",
|
|
"test_metric",
|
|
"__name__",
|
|
"test_metric",
|
|
queryStart,
|
|
queryEnd,
|
|
).
|
|
WillReturnRows(samplesRows)
|
|
|
|
// Create Prometheus provider for this test
|
|
promProvider = prometheustest.New(context.Background(), instrumentationtest.New().ToProviderSettings(), prometheus.Config{}, store)
|
|
},
|
|
ManagerOptionsHook: func(opts *ManagerOptions) {
|
|
// Set Prometheus provider for PromQL queries
|
|
if promProvider != nil {
|
|
opts.Prometheus = promProvider
|
|
}
|
|
},
|
|
})
|
|
|
|
count, apiErr := mgr.TestNotification(context.Background(), orgID, string(ruleBytes))
|
|
if apiErr != nil {
|
|
t.Logf("TestNotification error: %v, type: %s", apiErr.Err, apiErr.Typ)
|
|
}
|
|
require.Nil(t, apiErr)
|
|
assert.Equal(t, tc.ExpectAlerts, count)
|
|
|
|
if tc.ExpectAlerts > 0 {
|
|
// check if the alert has been triggered
|
|
require.Len(t, triggeredTestAlerts, 1)
|
|
var gotAlerts []*alertmanagertypes.PostableAlert
|
|
for a := range triggeredTestAlerts[0] {
|
|
gotAlerts = append(gotAlerts, a)
|
|
}
|
|
require.Len(t, gotAlerts, tc.ExpectAlerts)
|
|
// check if the alert has triggered with correct threshold value
|
|
if tc.ExpectValue != 0 && !math.IsNaN(tc.ExpectValue) && !math.IsInf(tc.ExpectValue, 0) {
|
|
assert.Equal(t, strconv.FormatFloat(tc.ExpectValue, 'f', -1, 64), gotAlerts[0].Annotations["value"])
|
|
}
|
|
} else {
|
|
// check if no alerts have been triggered
|
|
assert.Empty(t, triggeredTestAlerts)
|
|
}
|
|
|
|
promProvider.Close()
|
|
})
|
|
}
|
|
}
|