Improve checker/fixer cohesion and harden workflows

This commit is contained in:
Julien Bisconti
2026-02-28 01:31:37 +01:00
parent e6e23bf1cf
commit c14a071c8d
12 changed files with 279 additions and 85 deletions

View File

@@ -25,6 +25,15 @@ type LinkResult struct {
Error string
}
func shouldFallbackToGET(statusCode int) bool {
switch statusCode {
case http.StatusBadRequest, http.StatusForbidden, http.StatusMethodNotAllowed, http.StatusNotImplemented:
return true
default:
return false
}
}
// CheckLink checks a single URL. Uses HEAD first, falls back to GET.
func CheckLink(url string, client *http.Client) LinkResult {
result := LinkResult{URL: url}
@@ -32,14 +41,6 @@ func CheckLink(url string, client *http.Client) LinkResult {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
// Try HEAD first
req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
if err != nil {
result.Error = err.Error()
return result
}
req.Header.Set("User-Agent", userAgent)
// Track redirects
var finalURL string
origCheckRedirect := client.CheckRedirect
@@ -52,16 +53,25 @@ func CheckLink(url string, client *http.Client) LinkResult {
}
defer func() { client.CheckRedirect = origCheckRedirect }()
resp, err := client.Do(req)
doRequest := func(method string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, method, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
return client.Do(req)
}
resp, err := doRequest(http.MethodHead)
if err != nil {
// Fallback to GET
req, err2 := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err2 != nil {
resp, err = doRequest(http.MethodGet)
if err != nil {
result.Error = err.Error()
return result
}
req.Header.Set("User-Agent", userAgent)
resp, err = client.Do(req)
} else if shouldFallbackToGET(resp.StatusCode) {
resp.Body.Close()
resp, err = doRequest(http.MethodGet)
if err != nil {
result.Error = err.Error()
return result

View File

@@ -78,3 +78,41 @@ func TestCheckLinks(t *testing.T) {
}
}
}
func TestCheckLinkFallbackToGETOnMethodNotAllowed(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
result := CheckLink(server.URL, &http.Client{})
if !result.OK {
t.Errorf("expected OK after GET fallback, got status %d, error: %s", result.StatusCode, result.Error)
}
if result.StatusCode != http.StatusOK {
t.Errorf("status = %d, want 200", result.StatusCode)
}
}
func TestCheckLinkFallbackToGETOnForbiddenHead(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead {
w.WriteHeader(http.StatusForbidden)
return
}
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
result := CheckLink(server.URL, &http.Client{})
if !result.OK {
t.Errorf("expected OK after GET fallback, got status %d, error: %s", result.StatusCode, result.Error)
}
if result.StatusCode != http.StatusOK {
t.Errorf("status = %d, want 200", result.StatusCode)
}
}