Skip to content

Commit c7f5832

Browse files
committed
Use net/http/httptest to emulate server behavior
1 parent 7508603 commit c7f5832

File tree

4 files changed

+100
-51
lines changed

4 files changed

+100
-51
lines changed

cmd/verify.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package cmd
22

33
import (
4-
"github.com/bmuschko/link-verifier/http"
54
"github.com/bmuschko/link-verifier/verify"
65
"github.com/spf13/cobra"
76
)
87

98
func doVerifyCmd(cmd *cobra.Command, args []string) {
10-
http.SetTimeout(parsedOptions.Timeout)
119
files := verify.Resolve(parsedOptions.RootDirs, parsedOptions.IncludePatterns)
12-
verify.Process(files, parsedOptions.IgnoreStatusCodes, parsedOptions.Fail)
10+
verify.Process(files, parsedOptions.Timeout, parsedOptions.IgnoreStatusCodes, parsedOptions.Fail)
1311
}

http/http.go

+18-7
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,33 @@ import (
66
"time"
77
)
88

9-
var client = &http.Client{}
9+
type HTTP struct {
10+
client *http.Client
11+
}
1012

1113
// SetTimeout sets timeout for HTTP requests in seconds.
12-
func SetTimeout(timeout int) {
13-
client.Timeout = time.Duration(int(time.Second) * timeout)
14+
func (h *HTTP) SetTimeout(timeout int) {
15+
h.client.Timeout = time.Duration(int(time.Second) * timeout)
16+
}
17+
18+
// GetTimeout gets timeout for HTTP requests as duration.
19+
func (h *HTTP) GetTimeout() time.Duration {
20+
return h.client.Timeout
1421
}
1522

1623
// Get emits a HTTP HEAD request for a given URL. Captures the status code, status and outcome of the call.
1724
// Returns with information about the response.
18-
func Head(link string) HttpResponse {
25+
func (h *HTTP) Head(link string) HttpResponse {
1926
return sendRequest(link, func(url *url.URL) (resp *http.Response, err error) {
20-
return client.Head(url.String())
27+
return h.client.Head(url.String())
2128
})
2229
}
2330

2431
// Get emits a HTTP GET request for a given URL. Captures the status code, status and outcome of the call.
2532
// Returns with information about the response.
26-
func Get(link string) HttpResponse {
33+
func (h *HTTP) Get(link string) HttpResponse {
2734
return sendRequest(link, func(url *url.URL) (resp *http.Response, err error) {
28-
return client.Get(url.String())
35+
return h.client.Get(url.String())
2936
})
3037
}
3138

@@ -64,3 +71,7 @@ type HttpResponse struct {
6471
Status string
6572
Error error
6673
}
74+
75+
func NewHTTP() *HTTP {
76+
return &HTTP{client: &http.Client{}}
77+
}

http/http_test.go

+73-36
Original file line numberDiff line numberDiff line change
@@ -3,84 +3,121 @@ package http_test
33
import (
44
. "github.com/bmuschko/link-verifier/http"
55
. "github.com/stretchr/testify/assert"
6+
"net/http"
7+
"net/http/httptest"
68
"testing"
9+
"time"
710
)
811

912
const (
10-
validUrl = "http://www.google.com/"
11-
incompleteUrl = "https://www.googleapis.com/urlshortener/v1/url"
12-
unknownUrl = "http://www.unknown1x.com/"
13-
invalidUrl = "123://www.invalid.com/"
13+
invalidUrl = "123://www.invalid.com/"
1414
)
1515

1616
func TestSetTimeout(t *testing.T) {
17-
SetTimeout(20)
17+
h := NewHTTP()
18+
h.SetTimeout(20)
19+
20+
Equal(t, h.GetTimeout(), time.Duration(int(time.Second)*20))
1821
}
1922

2023
func TestHeadValidUrl(t *testing.T) {
21-
result := Head(validUrl)
24+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
25+
w.WriteHeader(http.StatusOK)
26+
}))
27+
defer server.Close()
28+
29+
h := NewHTTP()
30+
result := h.Head(server.URL)
2231

23-
Equal(t, validUrl, result.Url)
32+
Equal(t, server.URL, result.Url)
2433
True(t, result.Success)
2534
Nil(t, result.Error)
26-
Equal(t, 200, result.StatusCode)
35+
Equal(t, http.StatusOK, result.StatusCode)
2736
}
2837

29-
func TestHeadUrlForBadRequest(t *testing.T) {
30-
t.Skip("Needs to emulate an HTTP server to reproducible results")
31-
result := Head(incompleteUrl)
38+
func TestGetValidUrl(t *testing.T) {
39+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40+
w.WriteHeader(http.StatusOK)
41+
}))
42+
defer server.Close()
3243

33-
Equal(t, incompleteUrl, result.Url)
34-
False(t, result.Success)
44+
h := NewHTTP()
45+
result := h.Get(server.URL)
46+
47+
Equal(t, server.URL, result.Url)
48+
True(t, result.Success)
3549
Nil(t, result.Error)
36-
Equal(t, 400, result.StatusCode)
50+
Equal(t, http.StatusOK, result.StatusCode)
3751
}
3852

39-
func TestHeadNonExistentUrl(t *testing.T) {
40-
result := Head(unknownUrl)
53+
func TestHeadUrlForBadRequest(t *testing.T) {
54+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55+
w.WriteHeader(http.StatusBadRequest)
56+
}))
57+
defer server.Close()
58+
59+
h := NewHTTP()
60+
result := h.Head(server.URL)
4161

42-
Equal(t, unknownUrl, result.Url)
62+
Equal(t, server.URL, result.Url)
4363
False(t, result.Success)
44-
NotNil(t, result.Error)
64+
Nil(t, result.Error)
65+
Equal(t, http.StatusBadRequest, result.StatusCode)
4566
}
4667

47-
func TestHeadInvalidUrl(t *testing.T) {
48-
result := Head(invalidUrl)
68+
func TestGetUrlForBadRequest(t *testing.T) {
69+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
70+
w.WriteHeader(http.StatusBadRequest)
71+
}))
72+
defer server.Close()
4973

50-
Equal(t, invalidUrl, result.Url)
74+
h := NewHTTP()
75+
result := h.Get(server.URL)
76+
77+
Equal(t, server.URL, result.Url)
5178
False(t, result.Success)
52-
NotNil(t, result.Error)
79+
Nil(t, result.Error)
80+
Equal(t, http.StatusBadRequest, result.StatusCode)
5381
}
5482

55-
func TestGetValidUrl(t *testing.T) {
56-
result := Get(validUrl)
83+
func TestHeadNotFoundUrl(t *testing.T) {
84+
server := httptest.NewServer(http.NotFoundHandler())
85+
defer server.Close()
5786

58-
Equal(t, validUrl, result.Url)
59-
True(t, result.Success)
87+
h := NewHTTP()
88+
result := h.Head(server.URL)
89+
90+
Equal(t, server.URL, result.Url)
91+
False(t, result.Success)
6092
Nil(t, result.Error)
61-
Equal(t, 200, result.StatusCode)
93+
Equal(t, http.StatusNotFound, result.StatusCode)
6294
}
6395

64-
func TestGetUrlForBadRequest(t *testing.T) {
65-
t.Skip("Needs to emulate an HTTP server to reproducible results")
66-
result := Get(incompleteUrl)
96+
func TestGetNotFoundUrl(t *testing.T) {
97+
server := httptest.NewServer(http.NotFoundHandler())
98+
defer server.Close()
6799

68-
Equal(t, incompleteUrl, result.Url)
100+
h := NewHTTP()
101+
result := h.Get(server.URL)
102+
103+
Equal(t, server.URL, result.Url)
69104
False(t, result.Success)
70105
Nil(t, result.Error)
71-
Equal(t, 400, result.StatusCode)
106+
Equal(t, http.StatusNotFound, result.StatusCode)
72107
}
73108

74-
func TestGetNonExistentUrl(t *testing.T) {
75-
result := Get(unknownUrl)
109+
func TestHeadInvalidUrl(t *testing.T) {
110+
h := NewHTTP()
111+
result := h.Head(invalidUrl)
76112

77-
Equal(t, unknownUrl, result.Url)
113+
Equal(t, invalidUrl, result.Url)
78114
False(t, result.Success)
79115
NotNil(t, result.Error)
80116
}
81117

82118
func TestGetInvalidUrl(t *testing.T) {
83-
result := Get(invalidUrl)
119+
h := NewHTTP()
120+
result := h.Get(invalidUrl)
84121

85122
Equal(t, invalidUrl, result.Url)
86123
False(t, result.Success)

verify/verify.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ func Resolve(rootDirs []string, includePatterns []string) []string {
3535
// Process processes text-based files by verifying each parsed links by emitting a HTTP call.
3636
// Prints out a summary of successful and failed links.
3737
// By default fails the program if at least one link could not be resolved.
38-
func Process(files []string, ignoreStatusCodes []int, fail bool) {
38+
func Process(files []string, timeout int, ignoreStatusCodes []int, fail bool) {
3939
aggregateSummary := []stat.Summary{}
4040

4141
for _, textBasedFile := range files {
4242
fmt.Println()
4343
fmt.Println("-> Verifying file:", textBasedFile)
4444
content := file.ReadFile(textBasedFile)
45-
summary := parseLinks(content, ignoreStatusCodes)
45+
summary := parseLinks(content, timeout, ignoreStatusCodes)
4646
aggregateSummary = append(aggregateSummary, summary)
4747
}
4848

@@ -62,7 +62,7 @@ func Process(files []string, ignoreStatusCodes []int, fail bool) {
6262
}
6363
}
6464

65-
func parseLinks(content string, ignoreStatusCodes []int) stat.Summary {
65+
func parseLinks(content string, timeout int, ignoreStatusCodes []int) stat.Summary {
6666
links := text.ParseLinks(content)
6767
summary := stat.Summary{Successful: 0, Failed: 0}
6868

@@ -73,7 +73,7 @@ func parseLinks(content string, ignoreStatusCodes []int) stat.Summary {
7373
ch := make(chan string)
7474

7575
for _, link := range links {
76-
go validateLink(link, ignoreStatusCodes, &summary, ch)
76+
go validateLink(link, timeout, ignoreStatusCodes, &summary, ch)
7777
}
7878

7979
for range links {
@@ -83,7 +83,10 @@ func parseLinks(content string, ignoreStatusCodes []int) stat.Summary {
8383
return summary
8484
}
8585

86-
func validateLink(link string, ignoreStatusCodes []int, summary *stat.Summary, ch chan<- string) {
86+
func validateLink(link string, timeout int, ignoreStatusCodes []int, summary *stat.Summary, ch chan<- string) {
87+
http := http.NewHTTP()
88+
http.SetTimeout(timeout)
89+
8790
// Try HEAD request first
8891
response := http.Head(link)
8992

0 commit comments

Comments
 (0)