github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/services/github_metrics_test.go (about) 1 package services 2 3 import ( 4 "io" 5 "net/http" 6 "net/http/httptest" 7 "sort" 8 "strconv" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/prometheus/client_golang/prometheus" 14 "github.com/prometheus/client_golang/prometheus/promhttp" 15 "github.com/stretchr/testify/assert" 16 ) 17 18 type Metric struct { 19 name string 20 labels []string 21 value string 22 } 23 24 var ( 25 endpointLabel = "endpoint=\"/api/test\"" 26 URL = "/api/test" 27 appsetNamespaceLabel = "appset_namespace=\"test-ns\"" 28 appsetNamespace = "test-ns" 29 appsetName = "test-appset" 30 appsetNameLabel = "appset_name=\"test-appset\"" 31 resourceLabel = "resource=\"core\"" 32 33 rateLimitMetrics = []Metric{ 34 { 35 name: githubAPIRateLimitRemainingMetricName, 36 labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel}, 37 value: "42", 38 }, 39 { 40 name: githubAPIRateLimitLimitMetricName, 41 labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel}, 42 value: "100", 43 }, 44 { 45 name: githubAPIRateLimitUsedMetricName, 46 labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel}, 47 value: "58", 48 }, 49 { 50 name: githubAPIRateLimitResetMetricName, 51 labels: []string{endpointLabel, appsetNamespaceLabel, appsetNameLabel, resourceLabel}, 52 value: "1", 53 }, 54 } 55 successRequestMetrics = Metric{ 56 name: githubAPIRequestTotalMetricName, 57 labels: []string{"method=\"GET\"", endpointLabel, "status=\"201\"", appsetNamespaceLabel, appsetNameLabel}, 58 value: "1", 59 } 60 failureRequestMetrics = Metric{ 61 name: githubAPIRequestTotalMetricName, 62 labels: []string{"method=\"GET\"", endpointLabel, "status=\"0\"", appsetNamespaceLabel, appsetNameLabel}, 63 value: "1", 64 } 65 ) 66 67 func TestGitHubMetrics_CollectorApproach_Success(t *testing.T) { 68 metrics := NewGitHubMetrics() 69 reg := prometheus.NewRegistry() 70 reg.MustRegister( 71 metrics.RequestTotal, 72 metrics.RequestDuration, 73 metrics.RateLimitRemaining, 74 metrics.RateLimitLimit, 75 metrics.RateLimitReset, 76 metrics.RateLimitUsed, 77 ) 78 79 // Setup a fake HTTP server 80 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 81 w.Header().Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Unix()+1, 10)) 82 w.Header().Set("X-RateLimit-Remaining", "42") 83 w.Header().Set("X-RateLimit-Limit", "100") 84 w.Header().Set("X-RateLimit-Used", "58") 85 w.Header().Set("X-RateLimit-Resource", "core") 86 w.WriteHeader(http.StatusCreated) 87 _, _ = w.Write([]byte("ok")) 88 })) 89 defer ts.Close() 90 91 metricsCtx := &MetricsContext{AppSetNamespace: appsetNamespace, AppSetName: appsetName} 92 client := &http.Client{ 93 Transport: NewGitHubMetricsTransport( 94 http.DefaultTransport, 95 metricsCtx, 96 metrics, 97 ), 98 } 99 100 req, _ := http.NewRequest(http.MethodGet, ts.URL+URL, http.NoBody) 101 resp, err := client.Do(req) 102 if err != nil { 103 t.Fatalf("unexpected error: %v", err) 104 } 105 resp.Body.Close() 106 107 // Expose and scrape metrics 108 handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) 109 server := httptest.NewServer(handler) 110 defer server.Close() 111 112 resp, err = http.Get(server.URL) 113 if err != nil { 114 t.Fatalf("failed to scrape metrics: %v", err) 115 } 116 defer resp.Body.Close() 117 body, _ := io.ReadAll(resp.Body) 118 metricsOutput := string(body) 119 120 sort.Strings(successRequestMetrics.labels) 121 assert.Contains(t, metricsOutput, successRequestMetrics.name+"{"+strings.Join(successRequestMetrics.labels, ",")+"} "+successRequestMetrics.value) 122 123 for _, metric := range rateLimitMetrics { 124 sort.Strings(metric.labels) 125 assert.Contains(t, metricsOutput, metric.name+"{"+strings.Join(metric.labels, ",")+"} "+metric.value) 126 } 127 } 128 129 type RoundTripperFunc func(*http.Request) (*http.Response, error) 130 131 func (f RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } 132 133 func TestGitHubMetrics_CollectorApproach_NoRateLimitMetricsOnNilResponse(t *testing.T) { 134 metrics := NewGitHubMetrics() 135 reg := prometheus.NewRegistry() 136 reg.MustRegister( 137 metrics.RequestTotal, 138 metrics.RequestDuration, 139 metrics.RateLimitRemaining, 140 metrics.RateLimitLimit, 141 metrics.RateLimitReset, 142 metrics.RateLimitUsed, 143 ) 144 145 client := &http.Client{ 146 Transport: &GitHubMetricsTransport{ 147 transport: RoundTripperFunc(func(*http.Request) (*http.Response, error) { 148 return nil, http.ErrServerClosed 149 }), 150 metricsContext: &MetricsContext{AppSetNamespace: appsetNamespace, AppSetName: appsetName}, 151 metrics: metrics, 152 }, 153 } 154 155 req, _ := http.NewRequest(http.MethodGet, URL, http.NoBody) 156 _, _ = client.Do(req) 157 158 handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) 159 server := httptest.NewServer(handler) 160 defer server.Close() 161 162 resp, err := http.Get(server.URL) 163 if err != nil { 164 t.Fatalf("failed to scrape metrics: %v", err) 165 } 166 defer resp.Body.Close() 167 body, _ := io.ReadAll(resp.Body) 168 metricsOutput := string(body) 169 170 // Verify request metric exists with status "0" 171 sort.Strings(failureRequestMetrics.labels) 172 assert.Contains(t, metricsOutput, failureRequestMetrics.name+"{"+strings.Join(failureRequestMetrics.labels, ",")+"} "+failureRequestMetrics.value) 173 174 // Verify rate limit metrics don't exist 175 for _, metric := range rateLimitMetrics { 176 sort.Strings(metric.labels) 177 assert.NotContains(t, metricsOutput, metric.name+"{"+strings.Join(metric.labels, ",")+"} "+metric.value) 178 } 179 } 180 181 func TestNewGitHubMetricsClient(t *testing.T) { 182 // Test cases 183 testCases := []struct { 184 name string 185 metricsCtx *MetricsContext 186 }{ 187 { 188 name: "with metrics context", 189 metricsCtx: &MetricsContext{ 190 AppSetNamespace: appsetNamespace, 191 AppSetName: appsetName, 192 }, 193 }, 194 { 195 name: "with nil metrics context", 196 metricsCtx: nil, 197 }, 198 } 199 200 for _, tc := range testCases { 201 t.Run(tc.name, func(t *testing.T) { 202 // Create client 203 client := NewGitHubMetricsClient(tc.metricsCtx) 204 205 // Assert client is not nil 206 assert.NotNil(t, client) 207 208 // Assert transport is properly configured 209 transport, ok := client.Transport.(*GitHubMetricsTransport) 210 assert.True(t, ok, "Transport should be GitHubMetricsTransport") 211 212 // Verify transport configuration 213 assert.Equal(t, tc.metricsCtx, transport.metricsContext) 214 assert.NotNil(t, transport.metrics, "Metrics should not be nil") 215 assert.Equal(t, http.DefaultTransport, transport.transport, "Base transport should be http.DefaultTransport") 216 217 // Verify metrics are global metrics 218 assert.Equal(t, globalGitHubMetrics, transport.metrics, "Should use global metrics") 219 }) 220 } 221 }