github.com/yandex/pandora@v0.5.32/components/providers/http/decoders/jsonline_test.go (about) 1 package decoders 2 3 import ( 4 "bytes" 5 "context" 6 "net/http" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 "github.com/yandex/pandora/components/providers/http/config" 14 "github.com/yandex/pandora/components/providers/http/decoders/ammo" 15 "github.com/yandex/pandora/lib/pointer" 16 ) 17 18 const ( 19 jsonlineDecoderInput = `{"host": "ya.net", "method": "GET", "uri": "/?sleep=100", "tag": "sleep1", "headers": {"User-agent": "Tank", "Connection": "close"}} 20 {"host": "ya.net", "method": "POST", "uri": "/?sleep=200", "tag": "sleep2", "headers": {"User-agent": "Tank", "Connection": "close"}, "body": "body_data"} 21 {"host": "ya.net", "method": "PUT", "uri": "/", "tag": "sleep3", "headers": {"User-agent": "Tank", "Connection": "close"}, "body": "body_data"} 22 23 24 ` 25 26 jsonlineDecoderMultiInput = ` 27 28 { 29 "host": "ya.net", 30 "method": "GET", 31 "uri": "/?sleep=100", 32 "tag": "sleep1", 33 "headers": { 34 "User-agent": "Tank", 35 "Connection": "close" 36 } 37 } 38 { 39 "host": "ya.net", 40 "method": "POST", 41 "uri": "/?sleep=200", 42 "tag": "sleep2", 43 "headers": { 44 "User-agent": "Tank", 45 "Connection": "close" 46 }, 47 "body": "body_data" 48 } 49 50 { 51 "host": "ya.net", 52 "method": "PUT", 53 "uri": "/", 54 "tag": "sleep3", 55 "headers": { 56 "User-agent": "Tank", 57 "Connection": "close" 58 }, 59 "body": "body_data" 60 } 61 62 ` 63 64 jsonlineDecoderArrayInput = ` 65 66 [ 67 { 68 "host": "ya.net", 69 "method": "GET", 70 "uri": "/?sleep=100", 71 "tag": "sleep1", 72 "headers": { 73 "User-agent": "Tank", 74 "Connection": "close" 75 } 76 }, 77 { 78 "host": "ya.net", 79 "method": "POST", 80 "uri": "/?sleep=200", 81 "tag": "sleep2", 82 "headers": { 83 "User-agent": "Tank", 84 "Connection": "close" 85 }, 86 "body": "body_data" 87 }, 88 { 89 "host": "ya.net", 90 "method": "PUT", 91 "uri": "/", 92 "tag": "sleep3", 93 "headers": { 94 "User-agent": "Tank", 95 "Connection": "close" 96 }, 97 "body": "body_data" 98 } 99 ] 100 101 102 ` 103 ) 104 105 func getJsonlineAmmoWants(t *testing.T) []DecodedAmmo { 106 var mustNewAmmo = func(t *testing.T, method string, url string, body []byte, header http.Header, tag string) *ammo.Ammo { 107 a := ammo.Ammo{} 108 err := a.Setup(method, url, body, header, tag) 109 require.NoError(t, err) 110 return &a 111 } 112 return []DecodedAmmo{ 113 mustNewAmmo(t, 114 "GET", 115 "http://ya.net/?sleep=100", 116 nil, 117 http.Header{"Connection": []string{"close"}, "Content-Type": []string{"application/json"}, "User-Agent": []string{"Tank"}}, 118 "sleep1", 119 ), 120 mustNewAmmo(t, 121 "POST", 122 "http://ya.net/?sleep=200", 123 []byte("body_data"), 124 http.Header{"Connection": []string{"close"}, "Content-Type": []string{"application/json"}, "User-Agent": []string{"Tank"}}, 125 "sleep2", 126 ), 127 mustNewAmmo(t, 128 "PUT", 129 "http://ya.net/", 130 []byte("body_data"), 131 http.Header{"Connection": []string{"close"}, "Content-Type": []string{"application/json"}, "User-Agent": []string{"Tank"}}, 132 "sleep3", 133 ), 134 } 135 } 136 137 func Test_jsonlineDecoder_Scan(t *testing.T) { 138 cases := []struct { 139 name string 140 input string 141 wants []DecodedAmmo 142 }{ 143 { 144 name: "default", 145 input: jsonlineDecoderInput, 146 wants: getJsonlineAmmoWants(t), 147 }, 148 { 149 name: "multiline json", 150 input: jsonlineDecoderMultiInput, 151 wants: getJsonlineAmmoWants(t), 152 }, 153 { 154 name: "array json", 155 input: jsonlineDecoderArrayInput, 156 wants: getJsonlineAmmoWants(t), 157 }, 158 } 159 for _, tt := range cases { 160 t.Run(tt.name, func(t *testing.T) { 161 decoder, err := newJsonlineDecoder(strings.NewReader(tt.input), config.Config{ 162 Limit: 6, 163 }, http.Header{"Content-Type": []string{"application/json"}}) 164 require.NoError(t, err) 165 166 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 167 defer cancel() 168 169 for j := 0; j < 2; j++ { 170 for i, want := range tt.wants { 171 ammo, err := decoder.Scan(ctx) 172 assert.NoError(t, err, "iteration %d-%d", j, i) 173 assert.Equal(t, want, ammo, "iteration %d-%d", j, i) 174 } 175 } 176 177 _, err = decoder.Scan(ctx) 178 assert.Equal(t, ErrAmmoLimit, err) 179 assert.Equal(t, uint(len(tt.wants)*2), decoder.ammoNum) 180 if tt.name == "array json" { 181 assert.Equal(t, uint(2), decoder.passNum) 182 } else { 183 assert.Equal(t, uint(1), decoder.passNum) 184 } 185 }) 186 } 187 } 188 189 func Test_jsonlineDecoder_Scan_PassesOnce(t *testing.T) { 190 cases := []struct { 191 name string 192 input string 193 wants []DecodedAmmo 194 }{ 195 { 196 name: "default", 197 input: jsonlineDecoderInput, 198 wants: getJsonlineAmmoWants(t), 199 }, 200 { 201 name: "multiline json", 202 input: jsonlineDecoderMultiInput, 203 wants: getJsonlineAmmoWants(t), 204 }, 205 { 206 name: "array json", 207 input: jsonlineDecoderArrayInput, 208 wants: getJsonlineAmmoWants(t), 209 }, 210 } 211 for _, tt := range cases { 212 t.Run(tt.name, func(t *testing.T) { 213 decoder, err := newJsonlineDecoder(strings.NewReader(tt.input), config.Config{ 214 Passes: 1, 215 }, http.Header{"Content-Type": []string{"application/json"}}) 216 require.NoError(t, err) 217 218 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 219 defer cancel() 220 221 for i, want := range tt.wants { 222 ammo, err := decoder.Scan(ctx) 223 assert.NoError(t, err, "iteration %d", i) 224 assert.Equal(t, want, ammo, "iteration %d", i) 225 } 226 227 _, err = decoder.Scan(ctx) 228 assert.Equal(t, ErrPassLimit, err) 229 assert.Equal(t, uint(len(tt.wants)), decoder.ammoNum) 230 assert.Equal(t, uint(1), decoder.passNum) 231 }) 232 } 233 } 234 235 func Test_jsonlineDecoder_LoadAmmo(t *testing.T) { 236 decoder, err := newJsonlineDecoder(strings.NewReader(jsonlineDecoderInput), config.Config{ 237 Limit: 7, 238 }, http.Header{"Content-Type": []string{"application/json"}}) 239 require.NoError(t, err) 240 241 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 242 defer cancel() 243 244 wants := getJsonlineAmmoWants(t) 245 246 ammos, err := decoder.LoadAmmo(ctx) 247 assert.NoError(t, err) 248 assert.Equal(t, wants, ammos) 249 assert.Equal(t, decoder.config.Limit, uint(7)) 250 assert.Equal(t, decoder.config.Passes, uint(0)) 251 } 252 253 func TestIsArray(t *testing.T) { 254 tests := []struct { 255 name string 256 input string 257 want bool 258 wantErr *string 259 }{ 260 { 261 name: "empty json array", 262 input: " [] ", 263 want: true, 264 wantErr: nil, 265 }, 266 { 267 name: "non-empty json array", 268 input: ` [{"name": "Alice"}, {"name": "Bob"}] `, 269 want: true, 270 wantErr: nil, 271 }, 272 { 273 name: "empty json object", 274 input: ` { } `, 275 want: false, 276 wantErr: nil, 277 }, 278 { 279 name: "non-empty json object", 280 input: ` {"name": "Alice", "age": 30} `, 281 want: false, 282 wantErr: nil, 283 }, 284 { 285 name: "invalid json", 286 input: `{"name": "Alice",}`, 287 want: false, 288 wantErr: nil, 289 }, 290 { 291 name: "invalid json", 292 input: ` s{"name": "Alice",}`, 293 want: false, 294 wantErr: pointer.ToString("invalid character 's' looking for beginning of value"), 295 }, 296 } 297 298 for _, tt := range tests { 299 t.Run(tt.name, func(t *testing.T) { 300 r := bytes.NewReader([]byte(tt.input)) 301 result, err := isArray(r) 302 assert.Equal(t, tt.want, result) 303 if tt.wantErr == nil { 304 assert.NoError(t, err) 305 } else { 306 assert.Equal(t, *tt.wantErr, err.Error()) 307 } 308 }) 309 } 310 } 311 312 func Benchmark_jsonlineDecoderScan_line(b *testing.B) { 313 decoder, err := newJsonlineDecoder( 314 strings.NewReader(jsonlineDecoderInput), config.Config{}, 315 http.Header{"Content-Type": []string{"application/json"}}, 316 ) 317 require.NoError(b, err) 318 319 ctx := context.Background() 320 b.ResetTimer() 321 for i := 0; i < b.N; i++ { 322 a, err := decoder.Scan(ctx) 323 require.NoError(b, err) 324 _, err = a.BuildRequest() 325 require.NoError(b, err) 326 } 327 } 328 329 func Benchmark_jsonlineDecoderScan_multi(b *testing.B) { 330 decoder, err := newJsonlineDecoder( 331 strings.NewReader(jsonlineDecoderMultiInput), config.Config{}, 332 http.Header{"Content-Type": []string{"application/json"}}, 333 ) 334 require.NoError(b, err) 335 336 ctx := context.Background() 337 b.ResetTimer() 338 for i := 0; i < b.N; i++ { 339 a, err := decoder.Scan(ctx) 340 require.NoError(b, err) 341 _, err = a.BuildRequest() 342 require.NoError(b, err) 343 } 344 }