github.com/yandex/pandora@v0.5.32/components/guns/http_scenario/gun_test.go (about)

     1  package httpscenario
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  	phttp "github.com/yandex/pandora/components/guns/http"
    15  	"github.com/yandex/pandora/core"
    16  	"github.com/yandex/pandora/core/aggregator/netsample"
    17  	"go.uber.org/zap"
    18  )
    19  
    20  func TestBaseGun_shoot(t *testing.T) {
    21  	type fields struct {
    22  		DebugLog       bool
    23  		Config         phttp.GunConfig
    24  		Connect        func(ctx context.Context) error
    25  		OnClose        func() error
    26  		Aggregator     netsample.Aggregator
    27  		AnswLog        *zap.Logger
    28  		GunDeps        core.GunDeps
    29  		scheme         string
    30  		hostname       string
    31  		targetResolved string
    32  		client         phttp.Client
    33  	}
    34  
    35  	tests := []struct {
    36  		name            string
    37  		templateVars    map[string]any
    38  		wantTempateVars map[string]any
    39  		ammoMock        *Scenario
    40  		clientMock      func(t *testing.T, m *MockClient)
    41  		fields          fields
    42  		wantErr         assert.ErrorAssertionFunc
    43  	}{
    44  		{
    45  			name:         "default",
    46  			templateVars: map[string]any{"source": map[string]any{"users": []map[string]any{{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}}}},
    47  			wantTempateVars: map[string]any{
    48  				"request": map[string]any{
    49  					"step 1": map[string]any{"postprocessor": map[string]any{}},
    50  					"step 2": map[string]any{"postprocessor": map[string]any{}},
    51  				},
    52  				"source": map[string]any{"users": []map[string]any{{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}}},
    53  			},
    54  			ammoMock: &Scenario{
    55  				Requests: []Request{
    56  					{
    57  						Name:      "step 1",
    58  						URI:       "http://localhost:8080",
    59  						Method:    "GET",
    60  						Headers:   map[string]string{"Content-Type": "application/json"},
    61  						Tag:       "tag1",
    62  						Templater: &MockTemplater{err: nil, applyCalls: 1, expectedArgs: [][2]string{{"testAmmo", "step 1"}}},
    63  					},
    64  					{
    65  						Name:      "step 2",
    66  						URI:       "http://localhost:8080",
    67  						Method:    "GET",
    68  						Headers:   map[string]string{"Content-Type": "application/json"},
    69  						Tag:       "tag2",
    70  						Templater: &MockTemplater{err: nil, applyCalls: 1, expectedArgs: [][2]string{{"testAmmo", "step 2"}}},
    71  					},
    72  				},
    73  				ID:             2,
    74  				Name:           "testAmmo",
    75  				MinWaitingTime: 0,
    76  			},
    77  			clientMock: func(t *testing.T, client *MockClient) {
    78  				body := io.NopCloser(strings.NewReader("test response body"))
    79  				resp := &http.Response{Body: body}
    80  				client.On("Do", mock.Anything).Return(resp, nil) //TODO: check response after template
    81  			},
    82  			wantErr: assert.NoError,
    83  		},
    84  		{
    85  			name:         "check preprocessor",
    86  			templateVars: map[string]any{"source": map[string]any{"users": []map[string]any{{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}}}},
    87  			wantTempateVars: map[string]any{
    88  				"request": map[string]any{
    89  					"step 3": map[string]any{
    90  						"preprocessor": map[string]any{
    91  							"preprocessor_var": "preprocessor_test",
    92  						},
    93  						"postprocessor": map[string]any{},
    94  					},
    95  				},
    96  				"source": map[string]any{"users": []map[string]any{{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}}},
    97  			},
    98  			ammoMock: &Scenario{
    99  				Requests: []Request{
   100  					{
   101  						Name:      "step 3",
   102  						Tag:       "tag3",
   103  						URI:       "http://localhost:8080",
   104  						Method:    "GET",
   105  						Headers:   map[string]string{"Content-Type": "application/json"},
   106  						Templater: &MockTemplater{err: nil, applyCalls: 1, expectedArgs: [][2]string{{"testAmmo", "step 3"}}},
   107  						Preprocessor: &mockPreprocessor{
   108  							t:                  t,
   109  							processExpectCalls: 1,
   110  							processArgsReturns: []mockPreprocessorArgsReturns{{
   111  								templateVars: map[string]any{"request": map[string]any{"step 3": map[string]any{}}, "source": map[string]any{"users": []map[string]any{{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}}}},
   112  								returnVars:   map[string]any{"preprocessor_var": "preprocessor_test"},
   113  								returnErr:    nil,
   114  							}},
   115  						},
   116  					}},
   117  				Name: "testAmmo",
   118  			},
   119  			clientMock: func(t *testing.T, client *MockClient) {
   120  				body := io.NopCloser(strings.NewReader("test response body"))
   121  				resp := &http.Response{Body: body}
   122  				client.On("Do", mock.Anything).Return(resp, nil) //TODO: check response after template
   123  			},
   124  			wantErr: assert.NoError,
   125  		},
   126  		{
   127  			name:         "check postprocessor",
   128  			templateVars: map[string]any{"source": map[string]any{"users": []map[string]any{{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}}}},
   129  			wantTempateVars: map[string]any{
   130  				"request": map[string]any{
   131  					"step 4": map[string]any{
   132  						"postprocessor": map[string]any{
   133  							"token":         "body_token",
   134  							"Conteant-Type": "application/json",
   135  						},
   136  					},
   137  				},
   138  				"source": map[string]any{"users": []map[string]any{{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}}},
   139  			},
   140  			ammoMock: &Scenario{
   141  				Requests: []Request{
   142  					{
   143  						Name:      "step 4",
   144  						Tag:       "tag4",
   145  						URI:       "http://localhost:8080",
   146  						Method:    "GET",
   147  						Headers:   map[string]string{"Content-Type": "application/json"},
   148  						Templater: &MockTemplater{err: nil, applyCalls: 1, expectedArgs: [][2]string{{"testAmmo", "step 3"}}},
   149  						Postprocessors: []Postprocessor{
   150  							&mockPostprocessor{
   151  								t:                  t,
   152  								processExpectCalls: 1,
   153  								processArgsReturns: []mockPostprocessorArgsReturns{
   154  									{
   155  										returnVars: map[string]any{"token": "body_token"},
   156  										returnErr:  nil,
   157  									},
   158  								},
   159  							},
   160  							&mockPostprocessor{
   161  								t:                  t,
   162  								processExpectCalls: 1,
   163  								processArgsReturns: []mockPostprocessorArgsReturns{
   164  									{
   165  										returnVars: map[string]any{"Conteant-Type": "application/json"},
   166  										returnErr:  nil,
   167  									},
   168  								},
   169  							},
   170  						},
   171  					}},
   172  				Name: "testAmmo",
   173  			},
   174  			clientMock: func(t *testing.T, client *MockClient) {
   175  				body := io.NopCloser(strings.NewReader("test response body"))
   176  				resp := &http.Response{Body: body}
   177  				client.On("Do", mock.Anything).Return(resp, nil) //TODO: check response after template
   178  			},
   179  			wantErr: assert.NoError,
   180  		},
   181  	}
   182  	for _, tt := range tests {
   183  		t.Run(tt.name, func(t *testing.T) {
   184  
   185  			client := NewMockClient(t)
   186  			tt.clientMock(t, client)
   187  
   188  			aggregator := netsample.NewMockAggregator(t)
   189  			aggregator.On("Report", mock.Anything)
   190  
   191  			g := &ScenarioGun{base: &phttp.BaseGun{Aggregator: aggregator, Client: client}}
   192  			tt.wantErr(t, g.shoot(tt.ammoMock, tt.templateVars), fmt.Sprintf("shoot(%v)", tt.ammoMock))
   193  			require.Equal(t, tt.wantTempateVars, tt.templateVars)
   194  
   195  			for _, req := range tt.ammoMock.Requests {
   196  				if req.Preprocessor != nil {
   197  					req.Preprocessor.(*mockPreprocessor).validateCalls(t, req.Name)
   198  				}
   199  				if req.Templater != nil {
   200  					req.Templater.(*MockTemplater).validateCalls(t, req.Name)
   201  				}
   202  				for _, postprocessor := range req.Postprocessors {
   203  					postprocessor.(*mockPostprocessor).validateCalls(t, req.Name)
   204  				}
   205  			}
   206  		})
   207  	}
   208  }
   209  
   210  var _ Postprocessor = (*mockPostprocessor)(nil)
   211  
   212  type mockPostprocessorArgsReturns struct {
   213  	returnVars map[string]any
   214  	returnErr  error
   215  }
   216  
   217  type mockPostprocessor struct {
   218  	t                  *testing.T
   219  	processExpectCalls int
   220  	processArgsReturns []mockPostprocessorArgsReturns
   221  	i                  int
   222  }
   223  
   224  func (m *mockPostprocessor) Process(resp *http.Response, body io.Reader) (map[string]any, error) {
   225  	m.processExpectCalls--
   226  	require.NotEqual(m.t, 0, len(m.processArgsReturns), "wrong postprocessor.Process calls")
   227  
   228  	i := m.i % len(m.processArgsReturns)
   229  	m.i++
   230  	return m.processArgsReturns[i].returnVars, m.processArgsReturns[i].returnErr
   231  }
   232  
   233  func (m *mockPostprocessor) validateCalls(t *testing.T, stepName string) {
   234  	if m == nil {
   235  		return
   236  	}
   237  	assert.Equalf(t, 0, m.processExpectCalls, "wrong preprocessor.Process calls with step name `%s`", stepName)
   238  }
   239  
   240  var _ Preprocessor = (*mockPreprocessor)(nil)
   241  
   242  type mockPreprocessorArgsReturns struct {
   243  	templateVars map[string]any
   244  	returnVars   map[string]any
   245  	returnErr    error
   246  }
   247  
   248  type mockPreprocessor struct {
   249  	t                  *testing.T
   250  	processExpectCalls int
   251  	processArgsReturns []mockPreprocessorArgsReturns
   252  	invalidArgs        []error
   253  	i                  int
   254  }
   255  
   256  func (m *mockPreprocessor) Process(templateVars map[string]any) (map[string]any, error) {
   257  	m.processExpectCalls--
   258  	if len(m.processArgsReturns) == 0 {
   259  		err := fmt.Errorf("forgot init mockPreprocessor.processArgsReturns; call Process(%+v)", templateVars)
   260  		m.invalidArgs = append(m.invalidArgs, err)
   261  		return nil, err
   262  	}
   263  
   264  	i := m.i % len(m.processArgsReturns)
   265  	m.i++
   266  	args, returnVars, returnErr := m.processArgsReturns[i].templateVars, m.processArgsReturns[i].returnVars, m.processArgsReturns[i].returnErr
   267  
   268  	if !assert.Equalf(m.t, args, templateVars, "unexpected arg templateVars; call#%d Process(%+v)", m.i-1, templateVars) {
   269  		m.invalidArgs = append(m.invalidArgs, fmt.Errorf("unexpected arg templateVars; call Process(%+v)", templateVars))
   270  	}
   271  	return returnVars, returnErr
   272  }
   273  
   274  func (m *mockPreprocessor) validateCalls(t *testing.T, stepName string) {
   275  	if m == nil {
   276  		return
   277  	}
   278  	assert.Equalf(t, 0, m.processExpectCalls, "wrong preprocessor.Process calls with step name `%s`", stepName)
   279  }
   280  
   281  var _ Templater = (*MockTemplater)(nil)
   282  
   283  type MockTemplater struct {
   284  	err          error
   285  	applyCalls   int
   286  	expectedArgs [][2]string
   287  	invalidArgs  []error
   288  	i            int
   289  }
   290  
   291  func (m *MockTemplater) Apply(request *RequestParts, variables map[string]any, scenarioName, stepName string) error {
   292  	if len(m.expectedArgs) == 0 {
   293  		m.invalidArgs = append(m.invalidArgs, fmt.Errorf("forgot init mockTemplate.expectedArgs; call Apply(%+v, %+v, %s, %s)", request, variables, scenarioName, stepName))
   294  	} else {
   295  		i := m.i % len(m.expectedArgs)
   296  		m.i++
   297  		args := m.expectedArgs[i]
   298  		if args[0] != scenarioName {
   299  			m.invalidArgs = append(m.invalidArgs, fmt.Errorf("unexpected arg scenarioName `%s != %s`; call Apply(%+v, %+v, %s, %s)", args[0], scenarioName, request, variables, scenarioName, stepName))
   300  		}
   301  		if args[1] != stepName {
   302  			m.invalidArgs = append(m.invalidArgs, fmt.Errorf("unexpected arg stepName `%s != %s`; call Apply(%+v, %+v, %s, %s)", args[1], stepName, request, variables, scenarioName, stepName))
   303  		}
   304  	}
   305  	m.applyCalls--
   306  	return m.err
   307  }
   308  
   309  func (m *MockTemplater) validateCalls(t *testing.T, stepName string) {
   310  	assert.Equalf(t, 0, m.applyCalls, "wrong template.applyCalls calls with step name `%s`", stepName)
   311  }