github.com/grafana/pyroscope@v1.18.0/pkg/featureflags/client_capability_test.go (about)

     1  package featureflags
     2  
     3  import (
     4  	"net/http"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  )
     9  
    10  func Test_parseClientCapabilities(t *testing.T) {
    11  	tests := []struct {
    12  		Name         string
    13  		Header       http.Header
    14  		Want         ClientCapabilities
    15  		WantError    bool
    16  		ErrorMessage string
    17  	}{
    18  		{
    19  			Name:   "empty header returns default capabilities",
    20  			Header: http.Header{},
    21  			Want:   ClientCapabilities{AllowUtf8LabelNames: false},
    22  		},
    23  		{
    24  			Name: "no Accept header returns default capabilities",
    25  			Header: http.Header{
    26  				"Content-Type": []string{"application/json"},
    27  			},
    28  			Want: ClientCapabilities{AllowUtf8LabelNames: false},
    29  		},
    30  		{
    31  			Name: "empty Accept header value returns default capabilities",
    32  			Header: http.Header{
    33  				"Accept": []string{""},
    34  			},
    35  			Want: ClientCapabilities{AllowUtf8LabelNames: false},
    36  		},
    37  		{
    38  			Name: "simple Accept header without capabilities",
    39  			Header: http.Header{
    40  				"Accept": []string{"application/json"},
    41  			},
    42  			Want: ClientCapabilities{AllowUtf8LabelNames: false},
    43  		},
    44  		{
    45  			Name: "Accept header with utf8 label names capability true",
    46  			Header: http.Header{
    47  				"Accept": []string{"*/*; allow-utf8-labelnames=true"},
    48  			},
    49  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
    50  		},
    51  		{
    52  			Name: "Accept header with utf8 label names capability false",
    53  			Header: http.Header{
    54  				"Accept": []string{"*/*; allow-utf8-labelnames=false"},
    55  			},
    56  			Want: ClientCapabilities{AllowUtf8LabelNames: false},
    57  		},
    58  		{
    59  			Name: "Accept header with utf8 label names capability invalid value",
    60  			Header: http.Header{
    61  				"Accept": []string{"*/*; allow-utf8-labelnames=invalid"},
    62  			},
    63  			Want: ClientCapabilities{AllowUtf8LabelNames: false},
    64  		},
    65  		{
    66  			Name: "Accept header with unknown capability",
    67  			Header: http.Header{
    68  				"Accept": []string{"*/*; unknown-capability=true"},
    69  			},
    70  			Want: ClientCapabilities{AllowUtf8LabelNames: false},
    71  		},
    72  		{
    73  			Name: "Accept header with multiple capabilities",
    74  			Header: http.Header{
    75  				"Accept": []string{"*/*; allow-utf8-labelnames=true; unknown-capability=false"},
    76  			},
    77  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
    78  		},
    79  		{
    80  			Name: "multiple Accept header values",
    81  			Header: http.Header{
    82  				"Accept": []string{"application/json", "*/*; allow-utf8-labelnames=true"},
    83  			},
    84  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
    85  		},
    86  		{
    87  			Name: "multiple Accept header values with different capabilities",
    88  			Header: http.Header{
    89  				"Accept": []string{
    90  					"application/json; allow-utf8-labelnames=false",
    91  					"*/*; allow-utf8-labelnames=true",
    92  				},
    93  			},
    94  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
    95  		},
    96  		{
    97  			Name: "Accept header with quality values",
    98  			Header: http.Header{
    99  				"Accept": []string{"text/html; q=0.9; allow-utf8-labelnames=true"},
   100  			},
   101  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
   102  		},
   103  		{
   104  			Name: "complex Accept header",
   105  			Header: http.Header{
   106  				"Accept": []string{
   107  					"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8;allow-utf8-labelnames=true",
   108  				},
   109  			},
   110  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
   111  		},
   112  		{
   113  			Name: "multiple Accept header entries",
   114  			Header: http.Header{
   115  				"Accept": []string{
   116  					"application/json",
   117  					"text/plain; allow-utf8-labelnames=true",
   118  					"*/*; q=0.1",
   119  				},
   120  			},
   121  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
   122  		},
   123  		{
   124  			Name: "invalid mime type in Accept header",
   125  			Header: http.Header{
   126  				"Accept": []string{"invalid/mime/type/format"},
   127  			},
   128  			Want:         ClientCapabilities{},
   129  			WantError:    true,
   130  			ErrorMessage: "mime: unexpected content after media subtype",
   131  		},
   132  		{
   133  			Name: "Accept header with invalid syntax",
   134  			Header: http.Header{
   135  				"Accept": []string{"text/html; invalid-parameter-syntax"},
   136  			},
   137  			Want:         ClientCapabilities{},
   138  			WantError:    true,
   139  			ErrorMessage: "mime: invalid media parameter",
   140  		},
   141  		{
   142  			Name: "mixed valid and invalid Accept header values",
   143  			Header: http.Header{
   144  				"Accept": []string{
   145  					"application/json",
   146  					"invalid/mime/type/format",
   147  				},
   148  			},
   149  			Want:         ClientCapabilities{},
   150  			WantError:    true,
   151  			ErrorMessage: "mime: unexpected content after media subtype",
   152  		},
   153  		{
   154  			// Parameter names are case-insensitive in mime.ParseMediaType
   155  			Name: "case sensitivity test for capability name",
   156  			Header: http.Header{
   157  				"Accept": []string{"*/*; Allow-Utf8-Labelnames=true"},
   158  			},
   159  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
   160  		},
   161  		{
   162  			Name: "whitespace handling in Accept header",
   163  			Header: http.Header{
   164  				"Accept": []string{" application/json ; allow-utf8-labelnames=true "},
   165  			},
   166  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
   167  		},
   168  	}
   169  
   170  	for _, tt := range tests {
   171  		t.Run(tt.Name, func(t *testing.T) {
   172  			t.Parallel()
   173  
   174  			got, err := parseClientCapabilities(tt.Header)
   175  
   176  			if tt.WantError {
   177  				require.Error(t, err)
   178  				if tt.ErrorMessage != "" {
   179  					require.Contains(t, err.Error(), tt.ErrorMessage)
   180  				}
   181  				return
   182  			}
   183  
   184  			require.NoError(t, err)
   185  			require.Equal(t, tt.Want, got)
   186  		})
   187  	}
   188  }
   189  
   190  func Test_parseClientCapabilities_MultipleCapabilities(t *testing.T) {
   191  	// This test specifically checks that when the same capability appears
   192  	// multiple times with different values, the last "true" value wins
   193  	tests := []struct {
   194  		Name   string
   195  		Header http.Header
   196  		Want   ClientCapabilities
   197  	}{
   198  		{
   199  			Name: "capability appears multiple times - last true wins",
   200  			Header: http.Header{
   201  				"Accept": []string{
   202  					"application/json; allow-utf8-labelnames=false",
   203  					"text/plain; allow-utf8-labelnames=true",
   204  				},
   205  			},
   206  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
   207  		},
   208  		{
   209  			Name: "capability appears multiple times - last false loses to earlier true",
   210  			Header: http.Header{
   211  				"Accept": []string{
   212  					"application/json; allow-utf8-labelnames=true",
   213  					"text/plain; allow-utf8-labelnames=false",
   214  				},
   215  			},
   216  			Want: ClientCapabilities{AllowUtf8LabelNames: true},
   217  		},
   218  		{
   219  			Name: "capability appears multiple times - all false",
   220  			Header: http.Header{
   221  				"Accept": []string{
   222  					"application/json; allow-utf8-labelnames=false",
   223  					"text/plain; allow-utf8-labelnames=false",
   224  				},
   225  			},
   226  			Want: ClientCapabilities{AllowUtf8LabelNames: false},
   227  		},
   228  	}
   229  
   230  	for _, tt := range tests {
   231  		t.Run(tt.Name, func(t *testing.T) {
   232  			t.Parallel()
   233  
   234  			got, err := parseClientCapabilities(tt.Header)
   235  			require.NoError(t, err)
   236  			require.Equal(t, tt.Want, got)
   237  		})
   238  	}
   239  }