1package alerting
2
3import (
4	"context"
5	"testing"
6
7	"github.com/grafana/grafana/pkg/services/validations"
8	"github.com/grafana/grafana/pkg/tsdb/legacydata"
9
10	"github.com/stretchr/testify/require"
11)
12
13type conditionStub struct {
14	firing   bool
15	operator string
16	matches  []*EvalMatch
17	noData   bool
18}
19
20func (c *conditionStub) Eval(context *EvalContext, reqHandler legacydata.RequestHandler) (*ConditionResult, error) {
21	return &ConditionResult{Firing: c.firing, EvalMatches: c.matches, Operator: c.operator, NoDataFound: c.noData}, nil
22}
23
24func TestAlertingEvaluationHandler(t *testing.T) {
25	handler := NewEvalHandler(nil)
26
27	t.Run("Show return triggered with single passing condition", func(t *testing.T) {
28		context := NewEvalContext(context.TODO(), &Rule{
29			Conditions: []Condition{&conditionStub{
30				firing: true,
31			}},
32		}, &validations.OSSPluginRequestValidator{})
33
34		handler.Eval(context)
35		require.Equal(t, true, context.Firing)
36		require.Equal(t, "true = true", context.ConditionEvals)
37	})
38
39	t.Run("Show return triggered with single passing condition2", func(t *testing.T) {
40		context := NewEvalContext(context.TODO(), &Rule{
41			Conditions: []Condition{&conditionStub{firing: true, operator: "and"}},
42		}, &validations.OSSPluginRequestValidator{})
43
44		handler.Eval(context)
45		require.Equal(t, true, context.Firing)
46		require.Equal(t, "true = true", context.ConditionEvals)
47	})
48
49	t.Run("Show return false with not passing asdf", func(t *testing.T) {
50		context := NewEvalContext(context.TODO(), &Rule{
51			Conditions: []Condition{
52				&conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}},
53				&conditionStub{firing: false, operator: "and"},
54			},
55		}, &validations.OSSPluginRequestValidator{})
56
57		handler.Eval(context)
58		require.Equal(t, false, context.Firing)
59		require.Equal(t, "[true AND false] = false", context.ConditionEvals)
60	})
61
62	t.Run("Show return true if any of the condition is passing with OR operator", func(t *testing.T) {
63		context := NewEvalContext(context.TODO(), &Rule{
64			Conditions: []Condition{
65				&conditionStub{firing: true, operator: "and"},
66				&conditionStub{firing: false, operator: "or"},
67			},
68		}, &validations.OSSPluginRequestValidator{})
69
70		handler.Eval(context)
71		require.Equal(t, true, context.Firing)
72		require.Equal(t, "[true OR false] = true", context.ConditionEvals)
73	})
74
75	t.Run("Show return false if any of the condition is failing with AND operator", func(t *testing.T) {
76		context := NewEvalContext(context.TODO(), &Rule{
77			Conditions: []Condition{
78				&conditionStub{firing: true, operator: "and"},
79				&conditionStub{firing: false, operator: "and"},
80			},
81		}, &validations.OSSPluginRequestValidator{})
82
83		handler.Eval(context)
84		require.Equal(t, false, context.Firing)
85		require.Equal(t, "[true AND false] = false", context.ConditionEvals)
86	})
87
88	t.Run("Show return true if one condition is failing with nested OR operator", func(t *testing.T) {
89		context := NewEvalContext(context.TODO(), &Rule{
90			Conditions: []Condition{
91				&conditionStub{firing: true, operator: "and"},
92				&conditionStub{firing: true, operator: "and"},
93				&conditionStub{firing: false, operator: "or"},
94			},
95		}, &validations.OSSPluginRequestValidator{})
96
97		handler.Eval(context)
98		require.Equal(t, true, context.Firing)
99		require.Equal(t, "[[true AND true] OR false] = true", context.ConditionEvals)
100	})
101
102	t.Run("Show return false if one condition is passing with nested OR operator", func(t *testing.T) {
103		context := NewEvalContext(context.TODO(), &Rule{
104			Conditions: []Condition{
105				&conditionStub{firing: true, operator: "and"},
106				&conditionStub{firing: false, operator: "and"},
107				&conditionStub{firing: false, operator: "or"},
108			},
109		}, &validations.OSSPluginRequestValidator{})
110
111		handler.Eval(context)
112		require.Equal(t, false, context.Firing)
113		require.Equal(t, "[[true AND false] OR false] = false", context.ConditionEvals)
114	})
115
116	t.Run("Show return false if a condition is failing with nested AND operator", func(t *testing.T) {
117		context := NewEvalContext(context.TODO(), &Rule{
118			Conditions: []Condition{
119				&conditionStub{firing: true, operator: "and"},
120				&conditionStub{firing: false, operator: "and"},
121				&conditionStub{firing: true, operator: "and"},
122			},
123		}, &validations.OSSPluginRequestValidator{})
124
125		handler.Eval(context)
126		require.Equal(t, false, context.Firing)
127		require.Equal(t, "[[true AND false] AND true] = false", context.ConditionEvals)
128	})
129
130	t.Run("Show return true if a condition is passing with nested OR operator", func(t *testing.T) {
131		context := NewEvalContext(context.TODO(), &Rule{
132			Conditions: []Condition{
133				&conditionStub{firing: true, operator: "and"},
134				&conditionStub{firing: false, operator: "or"},
135				&conditionStub{firing: true, operator: "or"},
136			},
137		}, &validations.OSSPluginRequestValidator{})
138
139		handler.Eval(context)
140		require.Equal(t, true, context.Firing)
141		require.Equal(t, "[[true OR false] OR true] = true", context.ConditionEvals)
142	})
143
144	t.Run("Should return false if no condition is firing using OR operator", func(t *testing.T) {
145		context := NewEvalContext(context.TODO(), &Rule{
146			Conditions: []Condition{
147				&conditionStub{firing: false, operator: "or"},
148				&conditionStub{firing: false, operator: "or"},
149				&conditionStub{firing: false, operator: "or"},
150			},
151		}, &validations.OSSPluginRequestValidator{})
152
153		handler.Eval(context)
154		require.Equal(t, false, context.Firing)
155		require.Equal(t, "[[false OR false] OR false] = false", context.ConditionEvals)
156	})
157
158	// FIXME: What should the actual test case name be here?
159	t.Run("Should not return NoDataFound if all conditions have data and using OR", func(t *testing.T) {
160		context := NewEvalContext(context.TODO(), &Rule{
161			Conditions: []Condition{
162				&conditionStub{operator: "or", noData: false},
163				&conditionStub{operator: "or", noData: false},
164				&conditionStub{operator: "or", noData: false},
165			},
166		}, &validations.OSSPluginRequestValidator{})
167
168		handler.Eval(context)
169		require.False(t, context.NoDataFound)
170	})
171
172	t.Run("Should return NoDataFound if one condition has no data", func(t *testing.T) {
173		context := NewEvalContext(context.TODO(), &Rule{
174			Conditions: []Condition{
175				&conditionStub{operator: "and", noData: true},
176			},
177		}, &validations.OSSPluginRequestValidator{})
178
179		handler.Eval(context)
180		require.Equal(t, false, context.Firing)
181		require.True(t, context.NoDataFound)
182	})
183
184	t.Run("Should return no data if at least one condition has no data and using AND", func(t *testing.T) {
185		context := NewEvalContext(context.TODO(), &Rule{
186			Conditions: []Condition{
187				&conditionStub{operator: "and", noData: true},
188				&conditionStub{operator: "and", noData: false},
189			},
190		}, &validations.OSSPluginRequestValidator{})
191
192		handler.Eval(context)
193		require.True(t, context.NoDataFound)
194	})
195
196	t.Run("Should return no data if at least one condition has no data and using OR", func(t *testing.T) {
197		context := NewEvalContext(context.TODO(), &Rule{
198			Conditions: []Condition{
199				&conditionStub{operator: "or", noData: true},
200				&conditionStub{operator: "or", noData: false},
201			},
202		}, &validations.OSSPluginRequestValidator{})
203
204		handler.Eval(context)
205		require.True(t, context.NoDataFound)
206	})
207}
208