| 
									
										
										
										
											2024-04-16 21:56:50 +08:00
										 |  |  | package models | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"testing" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-16 20:36:03 +08:00
										 |  |  | func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-04-16 21:56:50 +08:00
										 |  |  | 	tests := []struct { | 
					
						
							|  |  |  | 		name         string | 
					
						
							|  |  |  | 		query        string | 
					
						
							|  |  |  | 		adhocFilters []ScopeFilter | 
					
						
							|  |  |  | 		scopeFilters []ScopeFilter | 
					
						
							|  |  |  | 		expected     string | 
					
						
							|  |  |  | 		expectErr    bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:      "No filters with no existing filter", | 
					
						
							|  |  |  | 			query:     `http_requests_total`, | 
					
						
							|  |  |  | 			expected:  `http_requests_total`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:      "No filters with existing filter", | 
					
						
							|  |  |  | 			query:     `http_requests_total{job="prometheus"}`, | 
					
						
							|  |  |  | 			expected:  `http_requests_total{job="prometheus"}`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:  "Adhoc filter with existing filter", | 
					
						
							|  |  |  | 			query: `http_requests_total{job="prometheus"}`, | 
					
						
							|  |  |  | 			adhocFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "method", Value: "get", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expected:  `http_requests_total{job="prometheus",method="get"}`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:  "Adhoc filter with no existing filter", | 
					
						
							|  |  |  | 			query: `http_requests_total`, | 
					
						
							|  |  |  | 			adhocFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "method", Value: "get", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 				{Key: "job", Value: "prometheus", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expected:  `http_requests_total{job="prometheus",method="get"}`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:  "Scope filter", | 
					
						
							|  |  |  | 			query: `http_requests_total{job="prometheus"}`, | 
					
						
							|  |  |  | 			scopeFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "status", Value: "200", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expected:  `http_requests_total{job="prometheus",status="200"}`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:  "Adhoc and Scope filter no existing filter", | 
					
						
							|  |  |  | 			query: `http_requests_total`, | 
					
						
							|  |  |  | 			scopeFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "status", Value: "200", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			adhocFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "job", Value: "prometheus", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expected:  `http_requests_total{job="prometheus",status="200"}`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2024-09-09 21:56:43 +08:00
										 |  |  | 			name:  "Adhoc and Scope filter conflict - adhoc wins (if not oneOf or notOneOf)", | 
					
						
							| 
									
										
										
										
											2024-04-16 21:56:50 +08:00
										 |  |  | 			query: `http_requests_total{job="prometheus"}`, | 
					
						
							|  |  |  | 			scopeFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "status", Value: "404", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			adhocFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "status", Value: "200", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expected:  `http_requests_total{job="prometheus",status="200"}`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:  "Adhoc filters with more complex expression", | 
					
						
							|  |  |  | 			query: `capacity_bytes{job="prometheus"} + available_bytes{job="grafana"} / 1024`, | 
					
						
							|  |  |  | 			adhocFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "job", Value: "alloy", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expected:  `capacity_bytes{job="alloy"} + available_bytes{job="alloy"} / 1024`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-09-09 21:56:43 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			name:  "OneOf Operator is combined into a single regex filter", | 
					
						
							|  |  |  | 			query: `http_requests_total{job="prometheus"}`, | 
					
						
							|  |  |  | 			scopeFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "status", Values: []string{"404", "400"}, Operator: FilterOperatorOneOf}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expected:  `http_requests_total{job="prometheus",status=~"404|400"}`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-04-16 21:56:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tt := range tests { | 
					
						
							|  |  |  | 		t.Run(tt.name, func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-05-16 20:36:03 +08:00
										 |  |  | 			expr, err := ApplyFiltersAndGroupBy(tt.query, tt.scopeFilters, tt.adhocFilters, nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if tt.expectErr { | 
					
						
							|  |  |  | 				require.Error(t, err) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-09-09 21:56:43 +08:00
										 |  |  | 				require.Equal(t, tt.expected, expr) | 
					
						
							| 
									
										
										
										
											2024-05-16 20:36:03 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestApplyQueryFiltersAndGroupBy_GroupBy(t *testing.T) { | 
					
						
							|  |  |  | 	tests := []struct { | 
					
						
							|  |  |  | 		name      string | 
					
						
							|  |  |  | 		query     string | 
					
						
							|  |  |  | 		groupBy   []string | 
					
						
							|  |  |  | 		expected  string | 
					
						
							|  |  |  | 		expectErr bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:      "GroupBy with no aggregate expression", | 
					
						
							|  |  |  | 			groupBy:   []string{"job"}, | 
					
						
							|  |  |  | 			query:     `http_requests_total`, | 
					
						
							|  |  |  | 			expected:  `http_requests_total`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:      "No GroupBy with aggregate expression", | 
					
						
							|  |  |  | 			query:     `sum by () (http_requests_total)`, | 
					
						
							|  |  |  | 			expected:  `sum(http_requests_total)`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:      "GroupBy with aggregate expression with no existing group by", | 
					
						
							|  |  |  | 			groupBy:   []string{"job"}, | 
					
						
							|  |  |  | 			query:     `sum(http_requests_total)`, | 
					
						
							|  |  |  | 			expected:  `sum by (job) (http_requests_total)`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:      "GroupBy with aggregate expression with existing group by", | 
					
						
							|  |  |  | 			groupBy:   []string{"status"}, | 
					
						
							|  |  |  | 			query:     `sum by (job) (http_requests_total)`, | 
					
						
							|  |  |  | 			expected:  `sum by (job, status) (http_requests_total)`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:      "GroupBy with aggregate expression with existing group by (already exists)", | 
					
						
							|  |  |  | 			groupBy:   []string{"job"}, | 
					
						
							|  |  |  | 			query:     `sum by (job) (http_requests_total)`, | 
					
						
							|  |  |  | 			expected:  `sum by (job) (http_requests_total)`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tt := range tests { | 
					
						
							|  |  |  | 		t.Run(tt.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			expr, err := ApplyFiltersAndGroupBy(tt.query, nil, nil, tt.groupBy) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if tt.expectErr { | 
					
						
							|  |  |  | 				require.Error(t, err) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 				require.Equal(t, expr, tt.expected) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestApplyQueryFiltersAndGroupBy(t *testing.T) { | 
					
						
							|  |  |  | 	tests := []struct { | 
					
						
							|  |  |  | 		name         string | 
					
						
							|  |  |  | 		query        string | 
					
						
							|  |  |  | 		adhocFilters []ScopeFilter | 
					
						
							|  |  |  | 		scopeFilters []ScopeFilter | 
					
						
							|  |  |  | 		groupby      []string | 
					
						
							|  |  |  | 		expected     string | 
					
						
							|  |  |  | 		expectErr    bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:  "Adhoc filters with more complex expression", | 
					
						
							|  |  |  | 			query: `sum(capacity_bytes{job="prometheus"} + available_bytes{job="grafana"}) / 1024`, | 
					
						
							|  |  |  | 			adhocFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "job", Value: "alloy", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			scopeFilters: []ScopeFilter{ | 
					
						
							|  |  |  | 				{Key: "vol", Value: "/", Operator: FilterOperatorEquals}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			groupby:   []string{"job"}, | 
					
						
							|  |  |  | 			expected:  `sum by (job) (capacity_bytes{job="alloy",vol="/"} + available_bytes{job="alloy",vol="/"}) / 1024`, | 
					
						
							|  |  |  | 			expectErr: false, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tt := range tests { | 
					
						
							|  |  |  | 		t.Run(tt.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			expr, err := ApplyFiltersAndGroupBy(tt.query, tt.scopeFilters, tt.adhocFilters, tt.groupby) | 
					
						
							| 
									
										
										
										
											2024-04-16 21:56:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if tt.expectErr { | 
					
						
							|  |  |  | 				require.Error(t, err) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 				require.Equal(t, expr, tt.expected) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |