accept_test.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. package headers_test
  2. import (
  3. "testing"
  4. "github.com/google/go-cmp/cmp"
  5. "github.com/google/go-cmp/cmp/cmpopts"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/require"
  8. "github.com/ydb-platform/ydb/library/go/httputil/headers"
  9. )
  10. // examples for tests taken from https://tools.ietf.org/html/rfc2616#section-14.3
  11. func TestParseAcceptEncoding(t *testing.T) {
  12. testCases := []struct {
  13. name string
  14. input string
  15. expected headers.AcceptableEncodings
  16. expectedErr error
  17. }{
  18. {
  19. "ietf_example_1",
  20. "compress, gzip",
  21. headers.AcceptableEncodings{
  22. {Encoding: headers.ContentEncoding("compress"), Weight: 1.0},
  23. {Encoding: headers.ContentEncoding("gzip"), Weight: 1.0},
  24. },
  25. nil,
  26. },
  27. {
  28. "ietf_example_2",
  29. "",
  30. nil,
  31. nil,
  32. },
  33. {
  34. "ietf_example_3",
  35. "*",
  36. headers.AcceptableEncodings{
  37. {Encoding: headers.ContentEncoding("*"), Weight: 1.0},
  38. },
  39. nil,
  40. },
  41. {
  42. "ietf_example_4",
  43. "compress;q=0.5, gzip;q=1.0",
  44. headers.AcceptableEncodings{
  45. {Encoding: headers.ContentEncoding("gzip"), Weight: 1.0},
  46. {Encoding: headers.ContentEncoding("compress"), Weight: 0.5},
  47. },
  48. nil,
  49. },
  50. {
  51. "ietf_example_5",
  52. "gzip;q=1.0, identity; q=0.5, *;q=0",
  53. headers.AcceptableEncodings{
  54. {Encoding: headers.ContentEncoding("gzip"), Weight: 1.0},
  55. {Encoding: headers.ContentEncoding("identity"), Weight: 0.5},
  56. {Encoding: headers.ContentEncoding("*"), Weight: 0},
  57. },
  58. nil,
  59. },
  60. {
  61. "solomon_headers",
  62. "zstd,lz4,gzip,deflate",
  63. headers.AcceptableEncodings{
  64. {Encoding: headers.ContentEncoding("zstd"), Weight: 1.0},
  65. {Encoding: headers.ContentEncoding("lz4"), Weight: 1.0},
  66. {Encoding: headers.ContentEncoding("gzip"), Weight: 1.0},
  67. {Encoding: headers.ContentEncoding("deflate"), Weight: 1.0},
  68. },
  69. nil,
  70. },
  71. }
  72. for _, tc := range testCases {
  73. t.Run(tc.name, func(t *testing.T) {
  74. acceptableEncodings, err := headers.ParseAcceptEncoding(tc.input)
  75. if tc.expectedErr != nil {
  76. assert.EqualError(t, err, tc.expectedErr.Error())
  77. } else {
  78. assert.NoError(t, err)
  79. }
  80. require.Len(t, acceptableEncodings, len(tc.expected))
  81. opt := cmpopts.IgnoreUnexported(headers.AcceptableEncoding{})
  82. assert.True(t, cmp.Equal(tc.expected, acceptableEncodings, opt), cmp.Diff(tc.expected, acceptableEncodings, opt))
  83. })
  84. }
  85. }
  86. func TestParseAccept(t *testing.T) {
  87. testCases := []struct {
  88. name string
  89. input string
  90. expected headers.AcceptableTypes
  91. expectedErr error
  92. }{
  93. {
  94. "empty_header",
  95. "",
  96. nil,
  97. nil,
  98. },
  99. {
  100. "accept_any",
  101. "*/*",
  102. headers.AcceptableTypes{
  103. {Type: headers.ContentTypeAny, Weight: 1.0},
  104. },
  105. nil,
  106. },
  107. {
  108. "accept_single",
  109. "application/json",
  110. headers.AcceptableTypes{
  111. {Type: headers.TypeApplicationJSON, Weight: 1.0},
  112. },
  113. nil,
  114. },
  115. {
  116. "accept_multiple",
  117. "application/json, application/protobuf",
  118. headers.AcceptableTypes{
  119. {Type: headers.TypeApplicationJSON, Weight: 1.0},
  120. {Type: headers.TypeApplicationProtobuf, Weight: 1.0},
  121. },
  122. nil,
  123. },
  124. {
  125. "accept_multiple_weighted",
  126. "application/json;q=0.8, application/protobuf",
  127. headers.AcceptableTypes{
  128. {Type: headers.TypeApplicationProtobuf, Weight: 1.0},
  129. {Type: headers.TypeApplicationJSON, Weight: 0.8},
  130. },
  131. nil,
  132. },
  133. {
  134. "accept_multiple_weighted_unsorted",
  135. "text/plain;q=0.5, application/protobuf, application/json;q=0.5",
  136. headers.AcceptableTypes{
  137. {Type: headers.TypeApplicationProtobuf, Weight: 1.0},
  138. {Type: headers.TypeTextPlain, Weight: 0.5},
  139. {Type: headers.TypeApplicationJSON, Weight: 0.5},
  140. },
  141. nil,
  142. },
  143. {
  144. "unknown_type",
  145. "custom/type, unknown/my_type;q=0.2",
  146. headers.AcceptableTypes{
  147. {Type: headers.ContentType("custom/type"), Weight: 1.0},
  148. {Type: headers.ContentType("unknown/my_type"), Weight: 0.2},
  149. },
  150. nil,
  151. },
  152. {
  153. "yabro_19.6.0",
  154. "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
  155. headers.AcceptableTypes{
  156. {Type: headers.ContentType("text/html"), Weight: 1.0},
  157. {Type: headers.ContentType("application/xhtml+xml"), Weight: 1.0},
  158. {Type: headers.ContentType("image/webp"), Weight: 1.0},
  159. {Type: headers.ContentType("image/apng"), Weight: 1.0},
  160. {Type: headers.ContentType("application/signed-exchange"), Weight: 1.0, Extension: map[string]string{"v": "b3"}},
  161. {Type: headers.ContentType("application/xml"), Weight: 0.9},
  162. {Type: headers.ContentType("*/*"), Weight: 0.8},
  163. },
  164. nil,
  165. },
  166. {
  167. "chrome_81.0.4044",
  168. "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
  169. headers.AcceptableTypes{
  170. {Type: headers.ContentType("text/html"), Weight: 1.0},
  171. {Type: headers.ContentType("application/xhtml+xml"), Weight: 1.0},
  172. {Type: headers.ContentType("image/webp"), Weight: 1.0},
  173. {Type: headers.ContentType("image/apng"), Weight: 1.0},
  174. {Type: headers.ContentType("application/xml"), Weight: 0.9},
  175. {Type: headers.ContentType("application/signed-exchange"), Weight: 0.9, Extension: map[string]string{"v": "b3"}},
  176. {Type: headers.ContentType("*/*"), Weight: 0.8},
  177. },
  178. nil,
  179. },
  180. {
  181. "firefox_77.0b3",
  182. "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
  183. headers.AcceptableTypes{
  184. {Type: headers.ContentType("text/html"), Weight: 1.0},
  185. {Type: headers.ContentType("application/xhtml+xml"), Weight: 1.0},
  186. {Type: headers.ContentType("image/webp"), Weight: 1.0},
  187. {Type: headers.ContentType("application/xml"), Weight: 0.9},
  188. {Type: headers.ContentType("*/*"), Weight: 0.8},
  189. },
  190. nil,
  191. },
  192. {
  193. "sort_by_most_specific",
  194. "text/*, text/html, */*, text/html;level=1",
  195. headers.AcceptableTypes{
  196. {Type: headers.ContentType("text/html"), Weight: 1.0, Extension: map[string]string{"level": "1"}},
  197. {Type: headers.ContentType("text/html"), Weight: 1.0},
  198. {Type: headers.ContentType("text/*"), Weight: 1.0},
  199. {Type: headers.ContentType("*/*"), Weight: 1.0},
  200. },
  201. nil,
  202. },
  203. }
  204. for _, tc := range testCases {
  205. t.Run(tc.name, func(t *testing.T) {
  206. at, err := headers.ParseAccept(tc.input)
  207. if tc.expectedErr != nil {
  208. assert.EqualError(t, err, tc.expectedErr.Error())
  209. } else {
  210. assert.NoError(t, err)
  211. }
  212. require.Len(t, at, len(tc.expected))
  213. opt := cmpopts.IgnoreUnexported(headers.AcceptableType{})
  214. assert.True(t, cmp.Equal(tc.expected, at, opt), cmp.Diff(tc.expected, at, opt))
  215. })
  216. }
  217. }
  218. func TestAcceptableTypesString(t *testing.T) {
  219. testCases := []struct {
  220. name string
  221. types headers.AcceptableTypes
  222. expected string
  223. }{
  224. {
  225. "empty",
  226. headers.AcceptableTypes{},
  227. "",
  228. },
  229. {
  230. "single",
  231. headers.AcceptableTypes{
  232. {Type: headers.TypeApplicationJSON},
  233. },
  234. "application/json",
  235. },
  236. {
  237. "single_weighted",
  238. headers.AcceptableTypes{
  239. {Type: headers.TypeApplicationJSON, Weight: 0.8},
  240. },
  241. "application/json;q=0.8",
  242. },
  243. {
  244. "multiple",
  245. headers.AcceptableTypes{
  246. {Type: headers.TypeApplicationJSON},
  247. {Type: headers.TypeApplicationProtobuf},
  248. },
  249. "application/json, application/protobuf",
  250. },
  251. {
  252. "multiple_weighted",
  253. headers.AcceptableTypes{
  254. {Type: headers.TypeApplicationProtobuf},
  255. {Type: headers.TypeApplicationJSON, Weight: 0.8},
  256. },
  257. "application/protobuf, application/json;q=0.8",
  258. },
  259. {
  260. "multiple_weighted_with_extension",
  261. headers.AcceptableTypes{
  262. {Type: headers.TypeApplicationProtobuf},
  263. {Type: headers.TypeApplicationJSON, Weight: 0.8},
  264. {Type: headers.TypeApplicationXML, Weight: 0.5, Extension: map[string]string{"label": "1"}},
  265. },
  266. "application/protobuf, application/json;q=0.8, application/xml;q=0.5;label=1",
  267. },
  268. }
  269. for _, tc := range testCases {
  270. t.Run(tc.name, func(t *testing.T) {
  271. assert.Equal(t, tc.expected, tc.types.String())
  272. })
  273. }
  274. }
  275. func BenchmarkParseAccept(b *testing.B) {
  276. benchCases := []string{
  277. "",
  278. "*/*",
  279. "application/json",
  280. "application/json, application/protobuf",
  281. "application/json;q=0.8, application/protobuf",
  282. "text/plain;q=0.5, application/protobuf, application/json;q=0.5",
  283. "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
  284. "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
  285. "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
  286. "text/*, text/html, */*, text/html;level=1",
  287. }
  288. b.ReportAllocs()
  289. b.ResetTimer()
  290. for i := 0; i < b.N; i++ {
  291. _, _ = headers.ParseAccept(benchCases[i%len(benchCases)])
  292. }
  293. }