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  }