package httpfetcher import ( "context" "errors" "net" "os" "syscall" "testing" "time" ) // TestFetchFailedBeforeAnswer checks the error of a fetch whose connection // fails: // - refused: ErrUpstreamRequestFailed. // - timed out: ErrUpstreamTimeout, even for a timeout error that does not // match context.DeadlineExceeded, such as the TLS handshake timeout. // - cancelled by its caller: context.Canceled, not ErrUpstreamRequestFailed. func TestFetchFailedBeforeAnswer(t *testing.T) { t.Parallel() tests := []struct { name string dial func(cancel context.CancelFunc) error wantErr error wantNotErr error }{ { name: "connection refused", dial: func(context.CancelFunc) error { return syscall.ECONNREFUSED }, wantErr: ErrUpstreamRequestFailed, wantNotErr: context.Canceled, }, { name: "timed out while connecting", dial: func(context.CancelFunc) error { return os.ErrDeadlineExceeded }, wantErr: ErrUpstreamTimeout, wantNotErr: ErrUpstreamRequestFailed, }, { name: "cancelled while connecting", dial: func(cancel context.CancelFunc) error { cancel() return context.Canceled }, wantErr: context.Canceled, wantNotErr: ErrUpstreamRequestFailed, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(testContext(t)) defer cancel() f := New(&Config{ Timeout: 30 * time.Second, MaxResponseSize: 50 << 20, AllowHTTP: true, MaxConnectionsPerHost: 20, MaxConnections: 64, DialContext: func(context.Context, string, string) (net.Conn, error) { return nil, tt.dial(cancel) }, }) _, err := f.Fetch(ctx, upstreamSource("/image"), "", "") t.Logf("Fetch() error: %v", err) if !errors.Is(err, tt.wantErr) { t.Errorf("Fetch() error = %v, want %v", err, tt.wantErr) } if errors.Is(err, tt.wantNotErr) { t.Errorf("Fetch() error = %v, must not be %v", err, tt.wantNotErr) } }) } }