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  }