1
0

errors_test.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. package fetch
  2. import (
  3. "context"
  4. "crypto/x509"
  5. "errors"
  6. "net"
  7. "net/http"
  8. "net/url"
  9. "os"
  10. "strings"
  11. "syscall"
  12. "testing"
  13. )
  14. type timeoutErr struct{}
  15. func (timeoutErr) Error() string { return "i/o timeout" }
  16. func (timeoutErr) Timeout() bool { return true }
  17. func TestExplain(t *testing.T) {
  18. wrap := func(inner error) error { return &url.Error{Op: "Get", URL: "https://news.example/story", Err: inner} }
  19. tests := []struct {
  20. name string
  21. err error
  22. want string
  23. is error // must still match with errors.Is, when set
  24. }{
  25. {"DNS", wrap(&net.DNSError{Name: "news.example", Err: "no such host", IsNotFound: true}),
  26. "can't find the server news.example; check the address and your internet connection", nil},
  27. {"refused", wrap(&net.OpError{Op: "dial", Err: os.NewSyscallError("connect", syscall.ECONNREFUSED)}),
  28. "news.example refused the connection", syscall.ECONNREFUSED},
  29. {"certificate", wrap(x509.UnknownAuthorityError{}),
  30. "news.example's security certificate isn't valid, so the connection isn't safe", nil},
  31. {"timeout", wrap(timeoutErr{}), "news.example took too long to respond", nil},
  32. {"unknown", wrap(errors.New("something odd")), `Get "https://news.example/story": something odd`, nil},
  33. }
  34. for _, tt := range tests {
  35. t.Run(tt.name, func(t *testing.T) {
  36. got := Explain(tt.err)
  37. if got.Error() != tt.want {
  38. t.Errorf("Explain = %q, want %q", got, tt.want)
  39. }
  40. if tt.is != nil && !errors.Is(got, tt.is) {
  41. t.Errorf("explained error no longer matches %v", tt.is)
  42. }
  43. })
  44. }
  45. if Explain(nil) != nil {
  46. t.Error("Explain(nil) should be nil")
  47. }
  48. }
  49. func TestFetchRefusedConnection(t *testing.T) {
  50. // A port that was just closed refuses connections.
  51. ln, err := net.Listen("tcp", "127.0.0.1:0")
  52. if err != nil {
  53. t.Fatal(err)
  54. }
  55. addr := ln.Addr().String()
  56. ln.Close()
  57. f := testFetcher()
  58. f.Retries = 0
  59. _, err = f.Fetch(context.Background(), mustParse(t, "http://"+addr+"/page"))
  60. if err == nil || !strings.Contains(err.Error(), "refused the connection") {
  61. t.Errorf("error = %v, want a refused-connection message", err)
  62. }
  63. }
  64. func TestStatusErrorHints(t *testing.T) {
  65. tests := map[int]string{
  66. http.StatusForbidden: "returned 403 Forbidden; the site may need a login or may block automated downloads",
  67. http.StatusNotFound: "returned 404 Not Found; check the address",
  68. http.StatusTooManyRequests: "returned 429 Too Many Requests; the site is limiting requests, so try again later",
  69. http.StatusBadGateway: "returned 502 Bad Gateway",
  70. }
  71. for code, want := range tests {
  72. got := (&StatusError{URL: "https://news.example/story", Code: code}).Error()
  73. if got != "https://news.example/story "+want {
  74. t.Errorf("%d: %q", code, got)
  75. }
  76. }
  77. }