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 }