go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/web/negotiate_content_type_test.go (about)

     1  /*
     2  
     3  Copyright (c) 2023 - Present. Will Charczuk. All rights reserved.
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository.
     5  
     6  */
     7  
     8  package web
     9  
    10  import (
    11  	"fmt"
    12  	"net/http"
    13  	"testing"
    14  
    15  	"go.charczuk.com/sdk/assert"
    16  )
    17  
    18  func Test_NegotiateContentType(t *testing.T) {
    19  	testCases := [...]struct {
    20  		s         string
    21  		available []string
    22  		expect    string
    23  		expectErr error
    24  	}{
    25  		/* if no accept header, use first accepted result */
    26  		{"", []string{"x/y", "foo/bar", "moo/loo"}, "x/y", nil},
    27  
    28  		/* examples from rfc2616 */
    29  		{"audio/*; q=0.2, audio/basic", []string{"audio/mp3", "audio/ogg-vorbis"}, "audio/mp3", nil},
    30  		{"text/plain; q=0.5, text/html,text/x-dvi; q=0.8, text/x-c", []string{"text/html", "text/plain"}, "text/html", nil},
    31  		{"text/*, text/html, text/html;level=1, */*", []string{"text/html", "text/plain"}, "text/html", nil},
    32  		{"text/*, text/html, text/plain;level=1, */*", []string{"text/html", "text/plain"}, "text/html", nil},
    33  		{"text/*, text/html, text/plain;level=1, */*", []string{"text/x-c", "text/html", "text/plain"}, "text/html", nil},
    34  
    35  		/* test how q=0 interacts */
    36  		{"text/html, */*;q=0", []string{"x/y"}, "", nil},
    37  		{"text/html, */*;q=0", []string{"text/html"}, "text/html", nil},
    38  
    39  		/* test wildcard matching */
    40  		{"text/html, */*", []string{"x/y"}, "x/y", nil},
    41  		{"text/html, text/*", []string{"text/plain"}, "text/plain", nil},
    42  
    43  		/* test Not Acceptable results */
    44  		{"text/html, image/png; q=0.4", []string{"foo/bar"}, "", nil},
    45  
    46  		/* test matching order */
    47  		{"text/html, image/png", []string{"text/html", "image/png"}, "text/html", nil},
    48  		{"text/html, image/png", []string{"image/png", "text/html"}, "image/png", nil},
    49  		{"text/html, image/png; q=0.4", []string{"image/png"}, "image/png", nil},
    50  		{"text/html, image/png; q=0.4", []string{"text/html"}, "text/html", nil},
    51  		{"text/html, image/png; q=0.4", []string{"image/png", "text/html"}, "text/html", nil},
    52  		{"text/html, image/png; q=0.4", []string{"text/html", "image/png"}, "text/html", nil},
    53  		{"text/html;q=0.4, image/png", []string{"image/png"}, "image/png", nil},
    54  		{"text/html;q=0.4, image/png", []string{"text/html"}, "text/html", nil},
    55  		{"text/html;q=0.4, image/png", []string{"image/png", "text/html"}, "image/png", nil},
    56  		{"text/html;q=0.4, image/png", []string{"text/html", "image/png"}, "image/png", nil},
    57  		{"image/png, image/*;q=0.4", []string{"image/jpg", "image/png"}, "image/png", nil},
    58  		{"image/png, image/*;q=0.4", []string{"image/jpg"}, "image/jpg", nil},
    59  		{"image/png, image/*;q=0.4", []string{"image/jpg", "image/gif"}, "image/jpg", nil},
    60  		{"image/png, image/*", []string{"image/jpg", "image/gif"}, "image/jpg", nil},
    61  		{"image/png, image/*", []string{"image/gif", "image/jpg"}, "image/gif", nil},
    62  		{"image/png, image/*", []string{"image/gif", "image/png"}, "image/png", nil},
    63  		{"image/png, image/*", []string{"image/png", "image/gif"}, "image/png", nil},
    64  
    65  		/* test parsing errors */
    66  		{",", nil, "", errNegotiateContentTypeAvailableEmpty},
    67  		{",", []string{"text/html"}, "", errParseAcceptInvalidTag},
    68  		{"foo*/bar", []string{"text/html"}, "", errParseAcceptInvalidTag},
    69  		{"foo/bar", []string{"text/"}, "", errNegotiateContentTypeInvalidAvailableContentType},
    70  		{"foo/bar", []string{"/html"}, "", errNegotiateContentTypeInvalidAvailableContentType},
    71  	}
    72  
    73  	for index, tt := range testCases {
    74  		r := &http.Request{Header: http.Header{"Accept": {tt.s}}}
    75  		actual, err := NegotiateContentType(r, tt.available...)
    76  		if tt.expectErr != nil {
    77  			assert.ItsNotNil(t, err, fmt.Sprintf("tc index: %d, input: %s", index, tt.s))
    78  			assert.ItsEqual(t, tt.expectErr.Error(), err.Error(), fmt.Sprintf("tc index: %d, input: %s", index, tt.s))
    79  		} else {
    80  			assert.ItsNil(t, err, fmt.Sprintf("tc index: %d, input: %s", index, tt.s))
    81  			assert.ItsEqual(t, tt.expect, actual, fmt.Sprintf("tc index: %d, input: %s", index, tt.s))
    82  		}
    83  	}
    84  }
    85  
    86  func Test_parseAccept(t *testing.T) {
    87  	testCases := [...]struct {
    88  		Input     string
    89  		Expect    []acceptMediaType
    90  		ExpectErr error
    91  	}{
    92  		/* empty case */
    93  		{"", nil, nil},
    94  
    95  		/* error cases */
    96  		{",", nil, errParseAcceptInvalidTag},
    97  		{"/", nil, fmt.Errorf("mime: no media type")},
    98  		{"foo/", nil, fmt.Errorf("mime: expected token after slash")},
    99  		{"/bar", nil, fmt.Errorf("mime: no media type")},
   100  		{"foo//", nil, fmt.Errorf("mime: expected token after slash")},
   101  		{"foo//*", nil, fmt.Errorf("mime: expected token after slash")},
   102  		{"foo/bar/*", nil, fmt.Errorf("mime: unexpected content after media subtype")},
   103  		{"*foo/bar", nil, errParseAcceptInvalidTag},
   104  		{"foo/*bar", nil, errParseAcceptInvalidTag},
   105  		{"foo abcd/bar", nil, fmt.Errorf("mime: expected slash after first token")},
   106  		{"foo/bar abcd", nil, fmt.Errorf("mime: unexpected content after media subtype")},
   107  		{"foo/bar;q=foo", nil, fmt.Errorf("strconv.ParseFloat: parsing \"foo\": invalid syntax")},
   108  		{"foo/bar;level=foo", nil, fmt.Errorf("strconv.Atoi: parsing \"foo\": invalid syntax")},
   109  
   110  		/* valid cases */
   111  		{"text/html, image/png", []acceptMediaType{
   112  			{RawMediaType: "text/html", Type: "text", Subtype: "html", Q: 1.0, RawParameters: map[string]string{}},
   113  			{RawMediaType: "image/png", Type: "image", Subtype: "png", Q: 1.0, RawParameters: map[string]string{}},
   114  		}, nil},
   115  		{"text/html,image/png", []acceptMediaType{
   116  			{RawMediaType: "text/html", Type: "text", Subtype: "html", Q: 1.0, RawParameters: map[string]string{}},
   117  			{RawMediaType: "image/png", Type: "image", Subtype: "png", Q: 1.0, RawParameters: map[string]string{}},
   118  		}, nil},
   119  		{"text/html, image/*", []acceptMediaType{
   120  			{RawMediaType: "text/html", Type: "text", Subtype: "html", Q: 1.0, RawParameters: map[string]string{}},
   121  			{RawMediaType: "image/*", Type: "image", Subtype: "*", Q: 1.0, RawParameters: map[string]string{}},
   122  		}, nil},
   123  		{"*/*, image/*", []acceptMediaType{
   124  			{RawMediaType: "*/*", Type: "*", Subtype: "*", Q: 1.0, RawParameters: map[string]string{}},
   125  			{RawMediaType: "image/*", Type: "image", Subtype: "*", Q: 1.0, RawParameters: map[string]string{}},
   126  		}, nil},
   127  		{"image/png, text/html", []acceptMediaType{
   128  			{RawMediaType: "image/png", Type: "image", Subtype: "png", Q: 1.0, RawParameters: map[string]string{}},
   129  			{RawMediaType: "text/html", Type: "text", Subtype: "html", Q: 1.0, RawParameters: map[string]string{}},
   130  		}, nil},
   131  		{"text/html, image/png; q=0.5", []acceptMediaType{
   132  			{RawMediaType: "text/html", Type: "text", Subtype: "html", Q: 1.0, RawParameters: map[string]string{}},
   133  			{RawMediaType: "image/png", Type: "image", Subtype: "png", Q: 0.5, RawParameters: map[string]string{"q": "0.5"}},
   134  		}, nil},
   135  		{"text/html; q=0.5; level=1, image/png; q=0.5", []acceptMediaType{
   136  			{RawMediaType: "text/html", Type: "text", Subtype: "html", Q: 0.5, Level: 1, RawParameters: map[string]string{"q": "0.5", "level": "1"}},
   137  			{RawMediaType: "image/png", Type: "image", Subtype: "png", Q: 0.5, RawParameters: map[string]string{"q": "0.5"}},
   138  		}, nil},
   139  	}
   140  
   141  	for _, tc := range testCases {
   142  		tags, err := parseAccept(tc.Input)
   143  		if tc.ExpectErr != nil {
   144  			assert.ItsNotNil(t, err)
   145  			assert.ItsEqual(t, tc.ExpectErr.Error(), err.Error())
   146  		} else {
   147  			assert.ItsNil(t, err)
   148  			assert.ItsEqual(t, tc.Expect, tags)
   149  		}
   150  	}
   151  }
   152  
   153  func Test_splitOn(t *testing.T) {
   154  	testCases := [...]struct {
   155  		Input          string
   156  		Split          rune
   157  		ExpectedBefore string
   158  		ExpectedAfter  string
   159  	}{
   160  		{"", 0, "", ""},
   161  		{"foo", rune('/'), "foo", ""},
   162  		{"foo/", rune('/'), "foo", ""},
   163  		{"foo/bar", rune('/'), "foo", "bar"},
   164  		{"foo|bar", rune('/'), "foo|bar", ""},
   165  		{"/bar", rune('/'), "", "bar"},
   166  		{"//bar", rune('/'), "", "/bar"},
   167  		{"/", rune('/'), "", ""},
   168  		{"/bar", rune('/'), "", "bar"},
   169  		{"foo/bar/baz", rune('/'), "foo", "bar/baz"},
   170  		{"foo/bar//", rune('/'), "foo", "bar//"},
   171  	}
   172  
   173  	for _, tc := range testCases {
   174  		before, after := splitOn(tc.Input, tc.Split)
   175  		assert.ItsEqual(t, tc.ExpectedBefore, before, fmt.Sprintf("invalid before: %s", tc.Input))
   176  		assert.ItsEqual(t, tc.ExpectedAfter, after, fmt.Sprintf("invalid after: %s", tc.Input))
   177  	}
   178  }