stRedundantError   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 50
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

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