Passed
Push — master ( 6ba115...bb6332 )
by Stefano
01:37
created

scan_test.TestCanCancelScanUsingContext   B

Complexity

Conditions 7

Size

Total Lines 66
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 42
nop 1
dl 0
loc 66
rs 7.472
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
package scan_test
2
3
import (
4
	"context"
5
	"net/http"
6
	"testing"
7
	"time"
8
9
	"github.com/stefanoj3/dirstalk/pkg/common/test"
10
	"github.com/stefanoj3/dirstalk/pkg/scan"
11
	"github.com/stefanoj3/dirstalk/pkg/scan/client"
12
	"github.com/stefanoj3/dirstalk/pkg/scan/filter"
13
	"github.com/stefanoj3/dirstalk/pkg/scan/producer"
14
	"github.com/stretchr/testify/assert"
15
)
16
17
func TestScanningWithEmptyProducerWillProduceNoResults(t *testing.T) {
18
	logger, _ := test.NewLogger()
19
20
	prod := producer.NewDictionaryProducer([]string{}, []string{}, 1)
21
	c := &http.Client{Timeout: time.Microsecond}
22
	sut := scan.NewScanner(
23
		c,
24
		prod,
25
		producer.NewReProducer(prod),
26
		filter.NewHTTPStatusResultFilter([]int{http.StatusNotFound}),
27
		logger,
28
	)
29
30
	results := sut.Scan(context.Background(), test.MustParseURL(t, "http://localhost/"), 10)
31
32
	for r := range results {
33
		t.Fatalf("No results expected, got %s", r.Target.Path)
34
	}
35
}
36
37
func TestScannerWillLogAnErrorWithInvalidDictionary(t *testing.T) {
38
	logger, loggerBuffer := test.NewLogger()
39
40
	prod := producer.NewDictionaryProducer(
41
		[]string{"\n"},
42
		[]string{"/home"},
43
		1,
44
	)
45
46
	testServer, serverAssertion := test.NewServerWithAssertion(
47
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
48
	)
49
	defer testServer.Close()
50
51
	c, err := client.NewClientFromConfig(
52
		1000,
53
		nil,
54
		"",
55
		false,
56
		nil,
57
		nil,
58
		true,
59
		test.MustParseURL(t, testServer.URL),
60
	)
61
	assert.NoError(t, err)
62
63
	sut := scan.NewScanner(
64
		c,
65
		prod,
66
		producer.NewReProducer(prod),
67
		filter.NewHTTPStatusResultFilter([]int{http.StatusNotFound}),
68
		logger,
69
	)
70
71
	results := sut.Scan(context.Background(), test.MustParseURL(t, testServer.URL), 10)
72
73
	for r := range results {
74
		t.Fatalf("No results expected, got %s", r.Target.Path)
75
	}
76
77
	assert.Contains(t, loggerBuffer.String(), "failed to build request")
78
	assert.Contains(t, loggerBuffer.String(), "invalid method")
79
	assert.Equal(t, 0, serverAssertion.Len())
80
}
81
82
func TestScannerWillNotRedirectIfStatusCodeIsInvalid(t *testing.T) {
83
	logger, loggerBuffer := test.NewLogger()
84
85
	prod := producer.NewDictionaryProducer(
86
		[]string{http.MethodGet},
87
		[]string{"/home"},
88
		3,
89
	)
90
91
	testServer, serverAssertion := test.NewServerWithAssertion(
92
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
93
			w.Header().Add("location", "/potato")
94
			if r.URL.Path == "/home" {
95
				w.WriteHeader(http.StatusOK)
96
				return
97
			}
98
99
			w.WriteHeader(http.StatusNotFound)
100
		}),
101
	)
102
	defer testServer.Close()
103
104
	c, err := client.NewClientFromConfig(
105
		1000,
106
		nil,
107
		"",
108
		false,
109
		nil,
110
		nil,
111
		true,
112
		test.MustParseURL(t, testServer.URL),
113
	)
114
	assert.NoError(t, err)
115
116
	sut := scan.NewScanner(
117
		c,
118
		prod,
119
		producer.NewReProducer(prod),
120
		filter.NewHTTPStatusResultFilter([]int{http.StatusNotFound}),
121
		logger,
122
	)
123
124
	results := make([]scan.Result, 0, 2)
125
	resultsChannel := sut.Scan(context.Background(), test.MustParseURL(t, testServer.URL), 10)
126
127
	for r := range resultsChannel {
128
		results = append(results, r)
129
	}
130
131
	expectedsResults := []scan.Result{
132
		{
133
			Target:     scan.Target{Path: "/home", Method: http.MethodGet, Depth: 3},
134
			StatusCode: http.StatusOK,
135
			URL:        *test.MustParseURL(t, testServer.URL+"/home"),
136
		},
137
	}
138
139
	assert.Equal(t, expectedsResults, results)
140
141
	assert.Contains(t, loggerBuffer.String(), "/home")
142
	assert.Contains(t, loggerBuffer.String(), "/home/home")
143
	assert.Equal(t, 2, serverAssertion.Len())
144
}
145
146
func TestScannerWillChangeMethodForRedirect(t *testing.T) {
147
	logger, loggerBuffer := test.NewLogger()
148
149
	prod := producer.NewDictionaryProducer(
150
		[]string{http.MethodPatch},
151
		[]string{"/home"},
152
		3,
153
	)
154
155
	testServer, serverAssertion := test.NewServerWithAssertion(
156
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
157
			if r.URL.Path == "/home" {
158
				http.Redirect(w, r, "/potato", http.StatusMovedPermanently)
159
				return
160
			}
161
162
			if r.URL.Path == "/potato" {
163
				w.WriteHeader(http.StatusCreated)
164
				return
165
			}
166
167
			w.WriteHeader(http.StatusNotFound)
168
		}),
169
	)
170
	defer testServer.Close()
171
172
	c, err := client.NewClientFromConfig(
173
		1000,
174
		nil,
175
		"",
176
		false,
177
		nil,
178
		nil,
179
		true,
180
		test.MustParseURL(t, testServer.URL),
181
	)
182
	assert.NoError(t, err)
183
184
	sut := scan.NewScanner(
185
		c,
186
		prod,
187
		producer.NewReProducer(prod),
188
		filter.NewHTTPStatusResultFilter([]int{http.StatusNotFound}),
189
		logger,
190
	)
191
192
	results := make([]scan.Result, 0, 3)
193
	resultsChannel := sut.Scan(context.Background(), test.MustParseURL(t, testServer.URL), 1)
194
195
	for r := range resultsChannel {
196
		results = append(results, r)
197
	}
198
199
	expectedResults := []scan.Result{
200
		{
201
			Target:     scan.Target{Path: "/home", Method: http.MethodPatch, Depth: 3},
202
			StatusCode: http.StatusMovedPermanently,
203
			URL:        *test.MustParseURL(t, testServer.URL+"/home"),
204
		},
205
		{
206
			Target:     scan.Target{Path: "/potato", Method: http.MethodGet, Depth: 2},
207
			StatusCode: http.StatusCreated,
208
			URL:        *test.MustParseURL(t, testServer.URL+"/potato"),
209
		},
210
	}
211
212
	assert.Equal(t, expectedResults, results)
213
214
	assert.NotContains(t, loggerBuffer.String(), "error")
215
	assert.Equal(t, 4, serverAssertion.Len())
216
}
217
218
func TestScannerWhenOutOfDepthWillNotFollowRedirect(t *testing.T) {
219
	logger, loggerBuffer := test.NewLogger()
220
221
	prod := producer.NewDictionaryProducer(
222
		[]string{http.MethodPatch},
223
		[]string{"/home"},
224
		0,
225
	)
226
227
	testServer, serverAssertion := test.NewServerWithAssertion(
228
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
229
			if r.URL.Path == "/home" {
230
				http.Redirect(w, r, "/potato", http.StatusMovedPermanently)
231
				return
232
			}
233
234
			w.WriteHeader(http.StatusNotFound)
235
		}),
236
	)
237
	defer testServer.Close()
238
239
	c, err := client.NewClientFromConfig(
240
		1000,
241
		nil,
242
		"",
243
		false,
244
		nil,
245
		nil,
246
		true,
247
		test.MustParseURL(t, testServer.URL),
248
	)
249
	assert.NoError(t, err)
250
251
	sut := scan.NewScanner(
252
		c,
253
		prod,
254
		producer.NewReProducer(prod),
255
		filter.NewHTTPStatusResultFilter([]int{http.StatusNotFound}),
256
		logger,
257
	)
258
259
	results := make([]scan.Result, 0, 1)
260
	resultsChannel := sut.Scan(context.Background(), test.MustParseURL(t, testServer.URL), 1)
261
262
	for r := range resultsChannel {
263
		results = append(results, r)
264
	}
265
266
	expectedResults := []scan.Result{
267
		{
268
			Target:     scan.Target{Path: "/home", Method: http.MethodPatch, Depth: 0},
269
			StatusCode: http.StatusMovedPermanently,
270
			URL:        *test.MustParseURL(t, testServer.URL+"/home"),
271
		},
272
	}
273
274
	assert.Equal(t, expectedResults, results)
275
276
	loggerBufferAsString := loggerBuffer.String()
277
	assert.Contains(t, loggerBufferAsString, "/home")
278
	assert.Contains(t, loggerBufferAsString, "depth is 0, not following any redirect")
279
	assert.NotContains(t, loggerBufferAsString, "error")
280
	assert.Equal(t, 1, serverAssertion.Len())
281
}
282
283
func TestScannerWillSkipRedirectWhenLocationHostIsDifferent(t *testing.T) {
284
	logger, loggerBuffer := test.NewLogger()
285
286
	prod := producer.NewDictionaryProducer(
287
		[]string{http.MethodPatch},
288
		[]string{"/home"},
289
		3,
290
	)
291
292
	testServer, serverAssertion := test.NewServerWithAssertion(
293
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
294
			if r.URL.Path == "/home" {
295
				http.Redirect(w, r, "http://gibberish/potato", http.StatusMovedPermanently)
296
				return
297
			}
298
299
			w.WriteHeader(http.StatusNotFound)
300
		}),
301
	)
302
	defer testServer.Close()
303
304
	c, err := client.NewClientFromConfig(
305
		1000,
306
		nil,
307
		"",
308
		false,
309
		nil,
310
		nil,
311
		true,
312
		test.MustParseURL(t, testServer.URL),
313
	)
314
	assert.NoError(t, err)
315
316
	sut := scan.NewScanner(
317
		c,
318
		prod,
319
		producer.NewReProducer(prod),
320
		filter.NewHTTPStatusResultFilter([]int{http.StatusNotFound}),
321
		logger,
322
	)
323
324
	results := make([]scan.Result, 0, 2)
325
	resultsChannel := sut.Scan(context.Background(), test.MustParseURL(t, testServer.URL), 1)
326
327
	for r := range resultsChannel {
328
		results = append(results, r)
329
	}
330
331
	expectedResults := []scan.Result{
332
		{
333
			Target:     scan.Target{Path: "/home", Method: http.MethodPatch, Depth: 3},
334
			StatusCode: http.StatusMovedPermanently,
335
			URL:        *test.MustParseURL(t, testServer.URL+"/home"),
336
		},
337
	}
338
339
	assert.Equal(t, expectedResults, results)
340
341
	loggerBufferAsString := loggerBuffer.String()
342
	assert.Contains(t, loggerBufferAsString, "skipping redirect, pointing to a different host")
343
	assert.NotContains(t, loggerBufferAsString, "error")
344
	assert.Equal(t, 2, serverAssertion.Len())
345
}
346
347
func TestScannerWillIgnoreRequestRedundantError(t *testing.T) {
348
	logger, loggerBuffer := test.NewLogger()
349
350
	prod := producer.NewDictionaryProducer(
351
		[]string{http.MethodGet},
352
		[]string{"/home", "/home"}, // twice the same entry to trick the client into doing the same request twice
353
		3,
354
	)
355
356
	testServer, serverAssertion := test.NewServerWithAssertion(
357
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
358
			w.WriteHeader(http.StatusNotFound)
359
		}),
360
	)
361
	defer testServer.Close()
362
363
	c, err := client.NewClientFromConfig(
364
		1000,
365
		nil,
366
		"",
367
		false,
368
		nil,
369
		nil,
370
		true,
371
		test.MustParseURL(t, testServer.URL),
372
	)
373
	assert.NoError(t, err)
374
375
	sut := scan.NewScanner(
376
		c,
377
		prod,
378
		producer.NewReProducer(prod),
379
		filter.NewHTTPStatusResultFilter([]int{http.StatusNotFound}),
380
		logger,
381
	)
382
383
	results := make([]scan.Result, 0, 1)
384
	resultsChannel := sut.Scan(context.Background(), test.MustParseURL(t, testServer.URL), 1)
385
386
	for r := range resultsChannel {
387
		results = append(results, r)
388
	}
389
390
	assert.Equal(t, 0, len(results))
391
392
	loggerBufferAsString := loggerBuffer.String()
393
	assert.Contains(t, loggerBufferAsString, "/home")
394
	assert.Contains(t, loggerBufferAsString, "/home: this request has been made already")
395
	assert.Equal(t, 1, serverAssertion.Len())
396
}
397
398
func TestCanCancelScanUsingContext(t *testing.T) {
399
	logger, _ := test.NewLogger()
400
401
	prod := producer.NewDictionaryProducer(
402
		[]string{http.MethodGet, http.MethodPatch, http.MethodDelete, http.MethodPost, http.MethodPut},
403
		[]string{"/home", "/index", "/about", "/search", "/jobs", "robots.txt", "/subscription", "/orders"},
404
		200000000,
405
	)
406
407
	testServer, serverAssertion := test.NewServerWithAssertion(
408
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
409
			w.WriteHeader(http.StatusOK)
410
		}),
411
	)
412
	defer testServer.Close()
413
414
	// the depth of the dictionary and the fact that the server returns always a
415
	// http.StatusOK should keep this test running forever in case the cancellation would not work
416
417
	c, err := client.NewClientFromConfig(
418
		1000,
419
		nil,
420
		"",
421
		false,
422
		nil,
423
		nil,
424
		true,
425
		test.MustParseURL(t, testServer.URL),
426
	)
427
	assert.NoError(t, err)
428
429
	sut := scan.NewScanner(
430
		c,
431
		prod,
432
		producer.NewReProducer(prod),
433
		filter.NewHTTPStatusResultFilter([]int{http.StatusNotFound}),
434
		logger,
435
	)
436
437
	ctx := context.Background()
438
	ctx, cancelFunc := context.WithCancel(ctx)
439
440
	resultsChannel := sut.Scan(ctx, test.MustParseURL(t, testServer.URL), 100)
441
442
	go func() {
443
		time.Sleep(50 * time.Millisecond)
444
		cancelFunc()
445
	}()
446
447
	done := make(chan struct{})
448
449
	go func() {
450
		for range resultsChannel {
451
452
		}
453
		done <- struct{}{}
454
	}()
455
456
	select {
457
	case <-done:
458
		t.Log("result channel closed")
459
	case <-time.After(time.Second * 8):
460
		t.Fatalf("the scan should have terminated by now, something is wrong with the context cancellation")
461
	}
462
463
	assert.True(t, serverAssertion.Len() > 1)
464
}
465