mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
Re-sync the entire SigNoz-derived Go tree to a clean fork of latest SigNoz main (upstream 3e6339019), eliminating the version-skew that broke `go build ./...` (mixed-era packages, ~14 imported-but-absent packages, partial signoz->o11y / clickhouse->datastore rebrands, self-import cycle, duplicate package decls). - pkg/ + cmd/ wholesale replaced with latest SigNoz; imports rewritten github.com/SigNoz/signoz/ -> github.com/hanzoai/o11y/. - Adopt SigNoz's CURRENT structure (resourcefilter->telemetryresourcefilter, integrationtypes->cloudintegrationtypes, roletypes/grammar/times folded in upstream); old paths dropped. - Fork deps repathed to hanzoai + bumped: signoz-otel-collector v0.144.6, clickhouse-go-mock v0.14.1, govaluate v0.1.0; expr-lang/expr => hanzoai/expr v1.17.8 (replace). go.mod reconciled from latest SigNoz; go.sum re-tidied. - ee/ + cmd/enterprise stripped — o11y stays MIT-only; community edition imports no ee packages. - Preserved o11y additions: module path github.com/hanzoai/o11y, mount.go (cloud.Register/zip mount adapter, HIP-0106), NOTICE + LICENSE (SigNoz §4c attribution retained; upstream pkg files are headerless per SigNoz convention). - Reverted o11y cosmetic renames (o11y*/datastore*) and experimental originals (authz/iamauthz, billing, zapreceiver, zapmetricreceiver) to SigNoz canonical to kill the skew; snapshots retained in git history for re-layering. go build ./... = exit 0. go vet clean except benign "unreachable code" in generated ANTLR parser files (inherited from upstream).
928 lines
24 KiB
Go
928 lines
24 KiB
Go
package rulebasednotification
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoai/o11y/pkg/alertmanager/nfmanager"
|
|
"github.com/hanzoai/o11y/pkg/alertmanager/nfmanager/nfroutingstore/nfroutingstoretest"
|
|
"github.com/hanzoai/o11y/pkg/factory"
|
|
"github.com/hanzoai/o11y/pkg/instrumentation/instrumentationtest"
|
|
"github.com/hanzoai/o11y/pkg/types"
|
|
"github.com/hanzoai/o11y/pkg/types/alertmanagertypes"
|
|
"github.com/hanzoai/o11y/pkg/valuer"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/prometheus/common/model"
|
|
)
|
|
|
|
func createTestProviderSettings() factory.ProviderSettings {
|
|
return instrumentationtest.New().ToProviderSettings()
|
|
}
|
|
|
|
func TestNewFactory(t *testing.T) {
|
|
routeStore := nfroutingstoretest.NewMockSQLRouteStore()
|
|
providerFactory := NewFactory(routeStore)
|
|
assert.NotNil(t, providerFactory)
|
|
assert.Equal(t, "rulebased", providerFactory.Name().String())
|
|
}
|
|
|
|
func TestNew(t *testing.T) {
|
|
ctx := context.Background()
|
|
providerSettings := createTestProviderSettings()
|
|
config := nfmanager.Config{}
|
|
|
|
routeStore := nfroutingstoretest.NewMockSQLRouteStore()
|
|
provider, err := New(ctx, providerSettings, config, routeStore)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, provider)
|
|
|
|
// Verify provider implements the interface correctly
|
|
assert.Implements(t, (*nfmanager.NotificationManager)(nil), provider)
|
|
}
|
|
|
|
func TestProvider_SetNotificationConfig(t *testing.T) {
|
|
ctx := context.Background()
|
|
providerSettings := createTestProviderSettings()
|
|
config := nfmanager.Config{}
|
|
|
|
routeStore := nfroutingstoretest.NewMockSQLRouteStore()
|
|
provider, err := New(ctx, providerSettings, config, routeStore)
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
orgID string
|
|
ruleID string
|
|
config *alertmanagertypes.NotificationConfig
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid parameters",
|
|
orgID: "org1",
|
|
ruleID: "rule1",
|
|
config: &alertmanagertypes.NotificationConfig{
|
|
Renotify: alertmanagertypes.ReNotificationConfig{
|
|
RenotifyInterval: 2 * time.Hour,
|
|
NoDataInterval: 2 * time.Hour,
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty orgID",
|
|
orgID: "",
|
|
ruleID: "rule1",
|
|
config: &alertmanagertypes.NotificationConfig{
|
|
Renotify: alertmanagertypes.ReNotificationConfig{
|
|
RenotifyInterval: time.Hour,
|
|
NoDataInterval: time.Hour,
|
|
},
|
|
},
|
|
wantErr: true, // Should error due to validation
|
|
},
|
|
{
|
|
name: "empty ruleID",
|
|
orgID: "org1",
|
|
ruleID: "",
|
|
config: &alertmanagertypes.NotificationConfig{
|
|
Renotify: alertmanagertypes.ReNotificationConfig{
|
|
RenotifyInterval: time.Hour,
|
|
NoDataInterval: time.Hour,
|
|
},
|
|
},
|
|
wantErr: true, // Should error due to validation
|
|
},
|
|
{
|
|
name: "nil config",
|
|
orgID: "org1",
|
|
ruleID: "rule1",
|
|
config: nil,
|
|
wantErr: true, // Should error due to nil config
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := provider.SetNotificationConfig(tt.orgID, tt.ruleID, tt.config)
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
|
|
// If we set a config successfully, we should be able to retrieve it
|
|
if tt.orgID != "" && tt.ruleID != "" && tt.config != nil {
|
|
retrievedConfig, retrieveErr := provider.GetNotificationConfig(tt.orgID, tt.ruleID)
|
|
assert.NoError(t, retrieveErr)
|
|
assert.NotNil(t, retrievedConfig)
|
|
assert.Equal(t, tt.config.Renotify, retrievedConfig.Renotify)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProvider_GetNotificationConfig(t *testing.T) {
|
|
ctx := context.Background()
|
|
providerSettings := createTestProviderSettings()
|
|
config := nfmanager.Config{}
|
|
|
|
routeStore := nfroutingstoretest.NewMockSQLRouteStore()
|
|
provider, err := New(ctx, providerSettings, config, routeStore)
|
|
require.NoError(t, err)
|
|
|
|
orgID := "test-org"
|
|
ruleID := "ruleId"
|
|
customConfig := &alertmanagertypes.NotificationConfig{
|
|
Renotify: alertmanagertypes.ReNotificationConfig{
|
|
RenotifyInterval: 30 * time.Minute,
|
|
NoDataInterval: 30 * time.Minute,
|
|
},
|
|
}
|
|
|
|
ruleId1 := "rule-1"
|
|
customConfig1 := &alertmanagertypes.NotificationConfig{
|
|
NotificationGroup: map[model.LabelName]struct{}{
|
|
model.LabelName("group1"): {},
|
|
model.LabelName("group2"): {},
|
|
},
|
|
}
|
|
|
|
err = provider.SetNotificationConfig(orgID, ruleID, customConfig)
|
|
require.NoError(t, err)
|
|
|
|
err = provider.SetNotificationConfig(orgID, ruleId1, customConfig1)
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
orgID string
|
|
ruleID string
|
|
alert *alertmanagertypes.Alert
|
|
expectedConfig *alertmanagertypes.NotificationConfig
|
|
shouldFallback bool
|
|
}{
|
|
{
|
|
name: "existing config",
|
|
orgID: orgID,
|
|
ruleID: ruleID,
|
|
expectedConfig: &alertmanagertypes.NotificationConfig{
|
|
NotificationGroup: map[model.LabelName]struct{}{
|
|
model.LabelName(ruleID): {},
|
|
},
|
|
Renotify: alertmanagertypes.ReNotificationConfig{
|
|
RenotifyInterval: 30 * time.Minute,
|
|
NoDataInterval: 30 * time.Minute,
|
|
},
|
|
},
|
|
shouldFallback: false,
|
|
},
|
|
{
|
|
name: "non-existing config - fallback",
|
|
orgID: orgID,
|
|
ruleID: ruleId1,
|
|
expectedConfig: &alertmanagertypes.NotificationConfig{
|
|
NotificationGroup: map[model.LabelName]struct{}{
|
|
model.LabelName("group1"): {},
|
|
model.LabelName("group2"): {},
|
|
model.LabelName(ruleID): {},
|
|
},
|
|
Renotify: alertmanagertypes.ReNotificationConfig{
|
|
RenotifyInterval: 4 * time.Hour,
|
|
NoDataInterval: 4 * time.Hour,
|
|
},
|
|
},
|
|
shouldFallback: false,
|
|
},
|
|
{
|
|
name: "empty orgID - fallback",
|
|
orgID: "",
|
|
ruleID: ruleID,
|
|
expectedConfig: nil, // Will get fallback
|
|
shouldFallback: true,
|
|
},
|
|
{
|
|
name: "nil alert - fallback",
|
|
orgID: orgID,
|
|
ruleID: "rule3", // Different ruleID to get fallback
|
|
alert: nil,
|
|
expectedConfig: nil, // Will get fallback
|
|
shouldFallback: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
config, err := provider.GetNotificationConfig(tt.orgID, tt.ruleID)
|
|
assert.NoError(t, err)
|
|
|
|
if tt.shouldFallback {
|
|
// Should get fallback config (4 hour default)
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, 4*time.Hour, config.Renotify.RenotifyInterval)
|
|
} else {
|
|
// Should get our custom config
|
|
assert.NotNil(t, config)
|
|
assert.Equal(t, tt.expectedConfig, config)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProvider_ConcurrentAccess(t *testing.T) {
|
|
ctx := context.Background()
|
|
providerSettings := createTestProviderSettings()
|
|
config := nfmanager.Config{}
|
|
|
|
routeStore := nfroutingstoretest.NewMockSQLRouteStore()
|
|
provider, err := New(ctx, providerSettings, config, routeStore)
|
|
require.NoError(t, err)
|
|
|
|
orgID := "test-org"
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
// Writer goroutine
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < 50; i++ {
|
|
config := &alertmanagertypes.NotificationConfig{
|
|
Renotify: alertmanagertypes.ReNotificationConfig{
|
|
RenotifyInterval: time.Duration(i+1) * time.Minute,
|
|
NoDataInterval: time.Duration(i+1) * time.Minute,
|
|
},
|
|
}
|
|
err := provider.SetNotificationConfig(orgID, "rule1", config)
|
|
assert.NoError(t, err)
|
|
}
|
|
}()
|
|
|
|
// Reader goroutine
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < 50; i++ {
|
|
config, err := provider.GetNotificationConfig(orgID, "rule1")
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, config)
|
|
}
|
|
}()
|
|
|
|
// Wait for both goroutines to complete
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestProvider_EvaluateExpression(t *testing.T) {
|
|
provider := &provider{
|
|
settings: factory.NewScopedProviderSettings(createTestProviderSettings(), "provider_test"),
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
expression string
|
|
labelSet model.LabelSet
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "simple equality check - match",
|
|
expression: `threshold.name == 'auth' && ruleId == 'rule1'`,
|
|
labelSet: model.LabelSet{
|
|
"threshold.name": "auth",
|
|
"ruleId": "rule1",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "simple equality check - match",
|
|
expression: `threshold.name = 'auth' AND ruleId = 'rule1'`,
|
|
labelSet: model.LabelSet{
|
|
"threshold.name": "auth",
|
|
"ruleId": "rule1",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "simple equality check - no match",
|
|
expression: `service == "payment"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "simple equality check - no match",
|
|
expression: `service = "payment"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "multiple conditions with AND - both match",
|
|
expression: `service == "auth" && env == "production"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "multiple conditions with AND - both match",
|
|
expression: `service = "auth" AND env = "production"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "multiple conditions with AND - one doesn't match",
|
|
expression: `service == "auth" && env == "staging"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "multiple conditions with AND - one doesn't match",
|
|
expression: `service = "auth" AND env = "staging"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "multiple conditions with OR - one matches",
|
|
expression: `service == "payment" || env == "production"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "multiple conditions with OR - one matches",
|
|
expression: `service = "payment" OR env = "production"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "multiple conditions with OR - none match",
|
|
expression: `service == "payment" || env == "staging"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "multiple conditions with OR - none match",
|
|
expression: `service = "payment" OR env = "staging"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
"env": "production",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "in operator - value in list",
|
|
expression: `service in ["auth", "payment", "notification"]`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "in operator - value in list",
|
|
expression: `service IN ["auth", "payment", "notification"]`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "in operator - value not in list",
|
|
expression: `service in ["payment", "notification"]`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "in operator - value not in list",
|
|
expression: `service IN ["payment", "notification"]`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "contains operator - substring match",
|
|
expression: `host contains "prod"`,
|
|
labelSet: model.LabelSet{
|
|
"host": "prod-server-01",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "contains operator - substring match",
|
|
expression: `host CONTAINS "prod"`,
|
|
labelSet: model.LabelSet{
|
|
"host": "prod-server-01",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "contains operator - no substring match",
|
|
expression: `host contains "staging"`,
|
|
labelSet: model.LabelSet{
|
|
"host": "prod-server-01",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "contains operator - no substring match",
|
|
expression: `host CONTAINS "staging"`,
|
|
labelSet: model.LabelSet{
|
|
"host": "prod-server-01",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "complex expression with parentheses",
|
|
expression: `(service == "auth" && env == "production") || critical == "true"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "payment",
|
|
"env": "staging",
|
|
"critical": "true",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "complex expression with parentheses",
|
|
expression: `(service = "auth" AND env = "production") OR critical = "true"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "payment",
|
|
"env": "staging",
|
|
"critical": "true",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "missing label key",
|
|
expression: `"missing_key" == "value"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "missing label key",
|
|
expression: `"missing_key" = "value"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "rule-based expression with threshold name and ruleId",
|
|
expression: `'threshold.name' == "high-cpu" && ruleId == "rule-123"`,
|
|
labelSet: model.LabelSet{
|
|
"threshold.name": "high-cpu",
|
|
"ruleId": "rule-123",
|
|
"service": "auth",
|
|
},
|
|
expected: false, //no commas
|
|
},
|
|
{
|
|
name: "rule-based expression with threshold name and ruleId",
|
|
expression: `'threshold.name' = "high-cpu" AND ruleId == "rule-123"`,
|
|
labelSet: model.LabelSet{
|
|
"threshold.name": "high-cpu",
|
|
"ruleId": "rule-123",
|
|
"service": "auth",
|
|
},
|
|
expected: false, //no commas
|
|
},
|
|
{
|
|
name: "alertname and ruleId combination",
|
|
expression: `alertname == "HighCPUUsage" && ruleId == "cpu-alert-001"`,
|
|
labelSet: model.LabelSet{
|
|
"alertname": "HighCPUUsage",
|
|
"ruleId": "cpu-alert-001",
|
|
"severity": "critical",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "alertname and ruleId combination",
|
|
expression: `alertname = "HighCPUUsage" AND ruleId = "cpu-alert-001"`,
|
|
labelSet: model.LabelSet{
|
|
"alertname": "HighCPUUsage",
|
|
"ruleId": "cpu-alert-001",
|
|
"severity": "critical",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "kubernetes namespace filtering",
|
|
expression: `k8s.namespace.name == "auth" && service in ["auth", "payment"]`,
|
|
labelSet: model.LabelSet{
|
|
"k8s.namespace.name": "auth",
|
|
"service": "auth",
|
|
"host": "k8s-node-1",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "kubernetes namespace filtering",
|
|
expression: `k8s.namespace.name = "auth" && service IN ["auth", "payment"]`,
|
|
labelSet: model.LabelSet{
|
|
"k8s.namespace.name": "auth",
|
|
"service": "auth",
|
|
"host": "k8s-node-1",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "migration expression format from SQL migration",
|
|
expression: `threshold.name == "HighCPUUsage" && ruleId == "rule-uuid-123"`,
|
|
labelSet: model.LabelSet{
|
|
"threshold.name": "HighCPUUsage",
|
|
"ruleId": "rule-uuid-123",
|
|
"severity": "warning",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "migration expression format from SQL migration",
|
|
expression: `threshold.name = "HighCPUUsage" && ruleId = "rule-uuid-123"`,
|
|
labelSet: model.LabelSet{
|
|
"threshold.name": "HighCPUUsage",
|
|
"ruleId": "rule-uuid-123",
|
|
"severity": "warning",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "case sensitive matching",
|
|
expression: `service == "Auth"`, // capital A
|
|
labelSet: model.LabelSet{
|
|
"service": "auth", // lowercase a
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "case sensitive matching",
|
|
expression: `service = "Auth"`, // capital A
|
|
labelSet: model.LabelSet{
|
|
"service": "auth", // lowercase a
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "numeric comparison as strings",
|
|
expression: `port == "8080"`,
|
|
labelSet: model.LabelSet{
|
|
"port": "8080",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "numeric comparison as strings",
|
|
expression: `port = "8080"`,
|
|
labelSet: model.LabelSet{
|
|
"port": "8080",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "quoted string with special characters",
|
|
expression: `service == "auth-service-v2"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth-service-v2",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "quoted string with special characters",
|
|
expression: `service = "auth-service-v2"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "auth-service-v2",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "boolean operators precedence",
|
|
expression: `service == "auth" && env == "prod" || critical == "true"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "payment",
|
|
"env": "staging",
|
|
"critical": "true",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "boolean operators precedence",
|
|
expression: `service = "auth" AND env = "prod" OR critical = "true"`,
|
|
labelSet: model.LabelSet{
|
|
"service": "payment",
|
|
"env": "staging",
|
|
"critical": "true",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "nonexistent key OR check",
|
|
expression: `threshold.name = 'warning' OR ruleId = 'rule1'`,
|
|
labelSet: model.LabelSet{
|
|
"threshold.name": "warning",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "nonexistent key && check",
|
|
expression: `threshold.name = 'warning' && nonexistent = 'auth'`,
|
|
labelSet: model.LabelSet{
|
|
"threshold.name": "warning",
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := provider.evaluateExpr(context.Background(), tt.expression, tt.labelSet)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.expected, result, "Expression: %s", tt.expression)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProvider_DeleteRoute(t *testing.T) {
|
|
ctx := context.Background()
|
|
providerSettings := createTestProviderSettings()
|
|
config := nfmanager.Config{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
orgID string
|
|
routeID string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid parameters",
|
|
orgID: "test-org-123",
|
|
routeID: "route-uuid-456",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty routeID",
|
|
orgID: "test-org-123",
|
|
routeID: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "valid orgID with valid routeID",
|
|
orgID: "another-org",
|
|
routeID: "another-route-id",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
routeStore := nfroutingstoretest.NewMockSQLRouteStore()
|
|
provider, err := New(ctx, providerSettings, config, routeStore)
|
|
require.NoError(t, err)
|
|
|
|
if !tt.wantErr {
|
|
routeStore.ExpectDelete(tt.orgID, tt.routeID)
|
|
}
|
|
|
|
err = provider.DeleteRoutePolicy(ctx, tt.orgID, tt.routeID)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, routeStore.ExpectationsWereMet())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProvider_CreateRoute(t *testing.T) {
|
|
ctx := context.Background()
|
|
providerSettings := createTestProviderSettings()
|
|
config := nfmanager.Config{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
orgID string
|
|
route *alertmanagertypes.RoutePolicy
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid route",
|
|
orgID: "test-org-123",
|
|
route: &alertmanagertypes.RoutePolicy{
|
|
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
|
Expression: `service == "auth"`,
|
|
ExpressionKind: alertmanagertypes.PolicyBasedExpression,
|
|
Name: "auth-service-route",
|
|
Description: "Route for auth service alerts",
|
|
Enabled: true,
|
|
OrgID: "test-org-123",
|
|
Channels: []string{"slack-channel"},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid route qb format",
|
|
orgID: "test-org-123",
|
|
route: &alertmanagertypes.RoutePolicy{
|
|
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
|
Expression: `service = "auth"`,
|
|
ExpressionKind: alertmanagertypes.PolicyBasedExpression,
|
|
Name: "auth-service-route",
|
|
Description: "Route for auth service alerts",
|
|
Enabled: true,
|
|
OrgID: "test-org-123",
|
|
Channels: []string{"slack-channel"},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "nil route",
|
|
orgID: "test-org-123",
|
|
route: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid route - missing expression",
|
|
orgID: "test-org-123",
|
|
route: &alertmanagertypes.RoutePolicy{
|
|
Expression: "", // empty expression
|
|
ExpressionKind: alertmanagertypes.PolicyBasedExpression,
|
|
Name: "invalid-route",
|
|
OrgID: "test-org-123",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid route - missing name",
|
|
orgID: "test-org-123",
|
|
route: &alertmanagertypes.RoutePolicy{
|
|
Expression: `service == "auth"`,
|
|
ExpressionKind: alertmanagertypes.PolicyBasedExpression,
|
|
Name: "", // empty name
|
|
OrgID: "test-org-123",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid route - missing name",
|
|
orgID: "test-org-123",
|
|
route: &alertmanagertypes.RoutePolicy{
|
|
Expression: `service = "auth"`,
|
|
ExpressionKind: alertmanagertypes.PolicyBasedExpression,
|
|
Name: "", // empty name
|
|
OrgID: "test-org-123",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
routeStore := nfroutingstoretest.NewMockSQLRouteStore()
|
|
provider, err := New(ctx, providerSettings, config, routeStore)
|
|
require.NoError(t, err)
|
|
|
|
if !tt.wantErr && tt.route != nil {
|
|
routeStore.ExpectCreate(tt.route)
|
|
}
|
|
|
|
err = provider.CreateRoutePolicy(ctx, tt.orgID, tt.route)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, routeStore.ExpectationsWereMet())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestProvider_CreateRoutes(t *testing.T) {
|
|
ctx := context.Background()
|
|
providerSettings := createTestProviderSettings()
|
|
config := nfmanager.Config{}
|
|
|
|
routeStore := nfroutingstoretest.NewMockSQLRouteStore()
|
|
provider, err := New(ctx, providerSettings, config, routeStore)
|
|
require.NoError(t, err)
|
|
|
|
validRoute1 := &alertmanagertypes.RoutePolicy{
|
|
Expression: `service == "auth"`,
|
|
ExpressionKind: alertmanagertypes.PolicyBasedExpression,
|
|
Name: "auth-route",
|
|
Description: "Auth service route",
|
|
Enabled: true,
|
|
OrgID: "test-org",
|
|
Channels: []string{"slack-auth"},
|
|
}
|
|
|
|
validRoute2 := &alertmanagertypes.RoutePolicy{
|
|
Expression: `service == "payment"`,
|
|
ExpressionKind: alertmanagertypes.PolicyBasedExpression,
|
|
Name: "payment-route",
|
|
Description: "Payment service route",
|
|
Enabled: true,
|
|
OrgID: "test-org",
|
|
Channels: []string{"slack-payment"},
|
|
}
|
|
|
|
invalidRoute := &alertmanagertypes.RoutePolicy{
|
|
Expression: "", // empty expression - invalid
|
|
ExpressionKind: alertmanagertypes.PolicyBasedExpression,
|
|
Name: "invalid-route",
|
|
OrgID: "test-org",
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
orgID string
|
|
routes []*alertmanagertypes.RoutePolicy
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid routes",
|
|
orgID: "test-org",
|
|
routes: []*alertmanagertypes.RoutePolicy{validRoute1, validRoute2},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty routes list",
|
|
orgID: "test-org",
|
|
routes: []*alertmanagertypes.RoutePolicy{},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nil routes list",
|
|
orgID: "test-org",
|
|
routes: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "routes with nil route",
|
|
orgID: "test-org",
|
|
routes: []*alertmanagertypes.RoutePolicy{validRoute1, nil},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "routes with invalid route",
|
|
orgID: "test-org",
|
|
routes: []*alertmanagertypes.RoutePolicy{validRoute1, invalidRoute},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "single valid route",
|
|
orgID: "test-org",
|
|
routes: []*alertmanagertypes.RoutePolicy{validRoute1},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if !tt.wantErr && len(tt.routes) > 0 {
|
|
routeStore.ExpectCreateBatch(tt.routes)
|
|
}
|
|
|
|
err := provider.CreateRoutePolicies(ctx, tt.orgID, tt.routes)
|
|
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, routeStore.ExpectationsWereMet())
|
|
}
|
|
})
|
|
}
|
|
}
|