signoz/pkg/query-service/rules/promrule_test.go
aniketio-ctrl f9a70a3a69
chore: notification routing | added notificaiton routing via expression based routes (#9195)
* chore: added custom distpatcher

* feat(notification-grouping): added notification grouping

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): linting and test cases

* feat(notification-grouping): linting and test cases

* feat(notification-grouping): linting and test cases

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): debug log lines

* feat(notification-grouping): debug log lines

* feat(notification-grouping): debug log lines

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): added structure changes

* feat(notification-grouping): added structure changes

* feat(notification-routing): added notification routing

* chore(notification-grouping): added notificaiton grouping

* Update pkg/alertmanager/nfmanager/rulebasednotification/provider.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-grouping): added renotification interval

* fix(notification-grouping): added fix for renotification

* chore(notificaiton-grouping): added no data renotify

* chore(notificaiton-grouping): added no data renotify

* chore(notificaiton-grouping): added no data renotify

* chore(notification-grouping): added no data renotify interval

* chore(notification-grouping): removed errors package from dispatcher

* chore(notification-grouping): removed errors package from dispatcher

* chore(notification-grouping): removed unwanted tests

* chore(notification-grouping): removed unwanted pkg name

* chore(notification-grouping): added delete notification setting

* chore(notification-grouping): added delete notification setting

* Update pkg/alertmanager/nfmanager/nfmanagertest/provider.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-grouping): removed nfmanager config| notification settings in postable rule

* chore(notification-grouping): removed nfmanager config| notification settings in postable rule

* chore(notification-grouping): added test for dispatcher

* chore(notification-grouping): added test for dispatcher

* chore(notification-grouping): go linting errors

* chore(notification-grouping): added test cases for aggGroupPerRoute

* chore(notification-grouping): added test cases for aggGroupPerRoute

* chore(notification-grouping): corrected get notification config logic

* Update pkg/alertmanager/nfmanager/rulebasednotification/provider_test.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-routing): added notification routing policies

* feat(notification-routing): added test cases for dispatcher

* chore(notification-routing): added notification routing policies

* chore(notification-routing): added notification routing policies

* Apply suggestions from code review

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-routing): added notification routing policies

* chore(notification-routing): added notification routing policies

* Update pkg/alertmanager/alertmanagerserver/distpatcher_test.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-routing): sorted imports

* chore(notification-routing): minor edit |pr resolve comments

* chore(notification-grouping): corrected dispatcher test cases

* chore(notification-routing): added notification routing policies

* chore(notification-routing): corrected race condition in test

* chore: resolved pr comments

* chore: passing threshold value to tempalte

* chore: completed delete rule functionality

* chore: added grouping disabled functionality

* chore: added grouping disabled functionality

* chore(notification-routing): resolved pr comments

* chore(notification-routing): resolved pr comments

* chore(notification-routing): resolved pr comments

* chore(notification-routing): sorted imports

* chore(notification-routing): fix linting errors

* chore(notification-routing): removed enabled flags

* fix: test rule multiple threhsold (#9224)

* chore: corrected linting errors

* chore: corrected linting errors

* chore: corrected linting errors

* chore: corrected linting errors

* chore: corrected migration errors

* chore: corrected migration errors

* chore: corrected migration errors

* chore: corrected migration errors

* Update pkg/sqlmigration/049_add_route_policy.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore: added org_is as foreign key

* chore: resolved pr comments

* chore: removed route store unused

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-10-03 19:47:15 +05:30

729 lines
16 KiB
Go

package rules
import (
"testing"
"time"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
pql "github.com/prometheus/prometheus/promql"
"github.com/stretchr/testify/assert"
)
func getVectorValues(vectors []ruletypes.Sample) []float64 {
if len(vectors) == 0 {
return []float64{} // Return empty slice instead of nil
}
var values []float64
for _, v := range vectors {
values = append(values, v.V)
}
return values
}
func TestPromRuleShouldAlert(t *testing.T) {
postableRule := ruletypes.PostableRule{
AlertName: "Test Rule",
AlertType: ruletypes.AlertTypeMetric,
RuleType: ruletypes.RuleTypeProm,
Evaluation: &ruletypes.EvaluationEnvelope{Kind: ruletypes.RollingEvaluation, Spec: ruletypes.RollingWindow{
EvalWindow: ruletypes.Duration(5 * time.Minute),
Frequency: ruletypes.Duration(1 * time.Minute),
}},
RuleCondition: &ruletypes.RuleCondition{
CompositeQuery: &v3.CompositeQuery{
QueryType: v3.QueryTypePromQL,
PromQueries: map[string]*v3.PromQuery{
"A": {
Query: "dummy_query", // This is not used in the test
},
},
},
},
}
cases := []struct {
values pql.Series
expectAlert bool
compareOp string
matchType string
target float64
expectedAlertSample v3.Point
expectedVectorValues []float64 // Expected values in result vector
}{
// Test cases for Equals Always
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 0.0},
},
},
expectAlert: true,
compareOp: "3", // Equals
matchType: "2", // Always
target: 0.0,
expectedAlertSample: v3.Point{Value: 0.0},
expectedVectorValues: []float64{0.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 1.0},
},
},
expectAlert: false,
compareOp: "3", // Equals
matchType: "2", // Always
target: 0.0,
expectedVectorValues: []float64{},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 1.0},
{F: 0.0},
{F: 1.0},
{F: 1.0},
},
},
expectAlert: false,
compareOp: "3", // Equals
matchType: "2", // Always
target: 0.0,
expectedVectorValues: []float64{},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
},
},
expectAlert: false,
compareOp: "3", // Equals
matchType: "2", // Always
target: 0.0,
},
// Test cases for Equals Once
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 0.0},
},
},
expectAlert: true,
compareOp: "3", // Equals
matchType: "1", // Once
target: 0.0,
expectedAlertSample: v3.Point{Value: 0.0},
expectedVectorValues: []float64{0.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 1.0},
},
},
expectAlert: true,
compareOp: "3", // Equals
matchType: "1", // Once
target: 0.0,
expectedAlertSample: v3.Point{Value: 0.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 1.0},
{F: 0.0},
{F: 1.0},
{F: 1.0},
},
},
expectAlert: true,
compareOp: "3", // Equals
matchType: "1", // Once
target: 0.0,
expectedAlertSample: v3.Point{Value: 0.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
},
},
expectAlert: false,
compareOp: "3", // Equals
matchType: "1", // Once
target: 0.0,
expectedVectorValues: []float64{},
},
// Test cases for Greater Than Always
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: true,
compareOp: "1", // Greater Than
matchType: "2", // Always
target: 1.5,
expectedAlertSample: v3.Point{Value: 2.0},
expectedVectorValues: []float64{2.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 11.0},
{F: 4.0},
{F: 3.0},
{F: 7.0},
{F: 12.0},
},
},
expectAlert: true,
compareOp: "1", // Above
matchType: "2", // Always
target: 2.0,
expectedAlertSample: v3.Point{Value: 3.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 11.0},
{F: 4.0},
{F: 3.0},
{F: 7.0},
{F: 12.0},
},
},
expectAlert: true,
compareOp: "2", // Below
matchType: "2", // Always
target: 13.0,
expectedAlertSample: v3.Point{Value: 12.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: false,
compareOp: "1", // Greater Than
matchType: "2", // Always
target: 4.5,
},
// Test cases for Greater Than Once
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: true,
compareOp: "1", // Greater Than
matchType: "1", // Once
target: 4.5,
expectedAlertSample: v3.Point{Value: 10.0},
expectedVectorValues: []float64{10.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 4.0},
{F: 4.0},
{F: 4.0},
{F: 4.0},
{F: 4.0},
},
},
expectAlert: false,
compareOp: "1", // Greater Than
matchType: "1", // Once
target: 4.5,
},
// Test cases for Not Equals Always
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 1.0},
{F: 0.0},
{F: 1.0},
{F: 0.0},
},
},
expectAlert: false,
compareOp: "4", // Not Equals
matchType: "2", // Always
target: 0.0,
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 0.0},
},
},
expectAlert: false,
compareOp: "4", // Not Equals
matchType: "2", // Always
target: 0.0,
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
},
},
expectAlert: true,
compareOp: "4", // Not Equals
matchType: "2", // Always
target: 0.0,
expectedAlertSample: v3.Point{Value: 1.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 1.0},
{F: 0.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
},
},
expectAlert: false,
compareOp: "4", // Not Equals
matchType: "2", // Always
target: 0.0,
},
// Test cases for Not Equals Once
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 1.0},
{F: 0.0},
{F: 1.0},
{F: 0.0},
},
},
expectAlert: true,
compareOp: "4", // Not Equals
matchType: "1", // Once
target: 0.0,
expectedAlertSample: v3.Point{Value: 1.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 0.0},
{F: 0.0},
},
},
expectAlert: false,
compareOp: "4", // Not Equals
matchType: "1", // Once
target: 0.0,
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 0.0},
{F: 0.0},
{F: 1.0},
{F: 0.0},
{F: 1.0},
},
},
expectAlert: true,
compareOp: "4", // Not Equals
matchType: "1", // Once
target: 0.0,
expectedAlertSample: v3.Point{Value: 1.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
{F: 1.0},
},
},
expectAlert: true,
compareOp: "4", // Not Equals
matchType: "1", // Once
target: 0.0,
expectedAlertSample: v3.Point{Value: 1.0},
},
// Test cases for Less Than Always
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 1.5},
{F: 1.5},
{F: 1.5},
{F: 1.5},
{F: 1.5},
},
},
expectAlert: true,
compareOp: "2", // Less Than
matchType: "2", // Always
target: 4,
expectedAlertSample: v3.Point{Value: 1.5},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 4.5},
{F: 4.5},
{F: 4.5},
{F: 4.5},
{F: 4.5},
},
},
expectAlert: false,
compareOp: "2", // Less Than
matchType: "2", // Always
target: 4,
},
// Test cases for Less Than Once
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 4.5},
{F: 4.5},
{F: 4.5},
{F: 4.5},
{F: 2.5},
},
},
expectAlert: true,
compareOp: "2", // Less Than
matchType: "1", // Once
target: 4,
expectedAlertSample: v3.Point{Value: 2.5},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 4.5},
{F: 4.5},
{F: 4.5},
{F: 4.5},
{F: 4.5},
},
},
expectAlert: false,
compareOp: "2", // Less Than
matchType: "1", // Once
target: 4,
},
// Test cases for OnAverage
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: true,
compareOp: "3", // Equals
matchType: "3", // OnAverage
target: 6.0,
expectedAlertSample: v3.Point{Value: 6.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: false,
compareOp: "3", // Equals
matchType: "3", // OnAverage
target: 4.5,
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: true,
compareOp: "4", // Not Equals
matchType: "3", // OnAverage
target: 4.5,
expectedAlertSample: v3.Point{Value: 6.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: false,
compareOp: "4", // Not Equals
matchType: "3", // OnAverage
target: 6.0,
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: true,
compareOp: "1", // Greater Than
matchType: "3", // OnAverage
target: 4.5,
expectedAlertSample: v3.Point{Value: 6.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: true,
compareOp: "2", // Less Than
matchType: "3", // OnAverage
target: 12.0,
expectedAlertSample: v3.Point{Value: 6.0},
},
// Test cases for InTotal
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: true,
compareOp: "3", // Equals
matchType: "4", // InTotal
target: 30.0,
expectedAlertSample: v3.Point{Value: 30.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 4.0},
{F: 6.0},
{F: 8.0},
{F: 2.0},
},
},
expectAlert: false,
compareOp: "3", // Equals
matchType: "4", // InTotal
target: 20.0,
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
},
},
expectAlert: true,
compareOp: "4", // Not Equals
matchType: "4", // InTotal
target: 9.0,
expectedAlertSample: v3.Point{Value: 10.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
},
},
expectAlert: false,
compareOp: "4", // Not Equals
matchType: "4", // InTotal
target: 10.0,
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 10.0},
},
},
expectAlert: true,
compareOp: "1", // Greater Than
matchType: "4", // InTotal
target: 10.0,
expectedAlertSample: v3.Point{Value: 20.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 10.0},
},
},
expectAlert: false,
compareOp: "1", // Greater Than
matchType: "4", // InTotal
target: 20.0,
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 10.0},
},
},
expectAlert: true,
compareOp: "2", // Less Than
matchType: "4", // InTotal
target: 30.0,
expectedAlertSample: v3.Point{Value: 20.0},
},
{
values: pql.Series{
Floats: []pql.FPoint{
{F: 10.0},
{F: 10.0},
},
},
expectAlert: false,
compareOp: "2", // Less Than
matchType: "4", // InTotal
target: 20.0,
},
}
logger := instrumentationtest.New().Logger()
for idx, c := range cases {
postableRule.RuleCondition.CompareOp = ruletypes.CompareOp(c.compareOp)
postableRule.RuleCondition.MatchType = ruletypes.MatchType(c.matchType)
postableRule.RuleCondition.Target = &c.target
postableRule.RuleCondition.Thresholds = &ruletypes.RuleThresholdData{
Kind: ruletypes.BasicThresholdKind,
Spec: ruletypes.BasicRuleThresholds{
{
TargetValue: &c.target,
MatchType: ruletypes.MatchType(c.matchType),
CompareOp: ruletypes.CompareOp(c.compareOp),
},
},
}
rule, err := NewPromRule("69", valuer.GenerateUUID(), &postableRule, logger, nil, nil)
if err != nil {
assert.NoError(t, err)
}
resultVectors, err := rule.Threshold.ShouldAlert(toCommonSeries(c.values), rule.Unit())
assert.NoError(t, err)
// Compare full result vector with expected vector
actualValues := getVectorValues(resultVectors)
if c.expectedVectorValues != nil {
// If expected vector values are specified, compare them exactly
assert.Equal(t, c.expectedVectorValues, actualValues, "Result vector values don't match expected for case %d", idx)
} else {
// Fallback to the old logic for cases without expectedVectorValues
if c.expectAlert {
assert.NotEmpty(t, resultVectors, "Expected alert but got no result vectors for case %d", idx)
// Verify at least one of the result vectors matches the expected alert sample
if len(resultVectors) > 0 {
found := false
for _, sample := range resultVectors {
if sample.V == c.expectedAlertSample.Value {
found = true
break
}
}
assert.True(t, found, "Expected alert sample value %.2f not found in result vectors for case %d. Got values: %v", c.expectedAlertSample.Value, idx, actualValues)
}
} else {
assert.Empty(t, resultVectors, "Expected no alert but got result vectors for case %d", idx)
}
}
}
}