SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
5.3 KB · 148 lines go
Raw Blame History
1package http23import (4	"context"5	"crypto/tls"6	"crypto/x509"7	"errors"8	"io"9	"net"10	"net/http"11	"net/http/httptest"12	"net/url"13	"os"14	"strings"15	"syscall"16	"testing"1718	"internetpressure.io/probe-agent/internal/protocol"19)2021type timeoutErr struct{}2223func (timeoutErr) Error() string   { return "i/o timeout" }24func (timeoutErr) Timeout() bool   { return true }25func (timeoutErr) Temporary() bool { return true }2627func TestClassify(t *testing.T) {28	wrap := func(e error) error { return &url.Error{Op: "Get", URL: "https://x/", Err: e} }29	cases := []struct {30		name string31		err  error32		st   stage33		want string34	}{35		{"dns", wrap(&net.DNSError{Err: "no such host", IsNotFound: true}), stageDNS, protocol.ErrDNSFail},36		{"dns timeout", wrap(&net.DNSError{Err: "timeout", IsTimeout: true}), stageDNS, protocol.ErrDNSFail},37		{"refused", wrap(&net.OpError{Op: "dial", Err: &os.SyscallError{Syscall: "connect", Err: syscall.ECONNREFUSED}}), stageConnect, protocol.ErrTCPRefused},38		{"connect timeout", wrap(timeoutErr{}), stageConnect, protocol.ErrTCPTimeout},39		{"tls timeout", wrap(timeoutErr{}), stageTLS, protocol.ErrTLSFail},40		{"http timeout", wrap(context.DeadlineExceeded), stageResponse, protocol.ErrHTTPTimeout},41		{"client timeout string", wrap(errors.New("net/http: request canceled (Client.Timeout exceeded while awaiting headers)")), stageResponse, protocol.ErrHTTPTimeout},42		{"reset early", wrap(syscall.ECONNRESET), stageConnect, protocol.ErrTCPReset},43		{"reset late", wrap(syscall.ECONNRESET), stageResponse, protocol.ErrReset},44		{"cert unknown authority", wrap(x509.UnknownAuthorityError{}), stageTLS, protocol.ErrTLSCert},45		{"cert hostname", wrap(x509.HostnameError{Host: "x"}), stageTLS, protocol.ErrTLSCert},46		{"cert verification", wrap(&tls.CertificateVerificationError{Err: errors.New("expired")}), stageTLS, protocol.ErrTLSCert},47		{"tls record", wrap(tls.RecordHeaderError{Msg: "first record does not look like a TLS handshake"}), stageTLS, protocol.ErrTLSFail},48		{"tls alert", wrap(tls.AlertError(40)), stageTLS, protocol.ErrTLSFail},49		{"eof during tls", wrap(io.EOF), stageTLS, protocol.ErrTLSFail},50		{"eof during response", wrap(io.ErrUnexpectedEOF), stageResponse, protocol.ErrReset},51		{"unknown", wrap(errors.New("something odd")), stageResponse, protocol.ErrOther},52		{"nil", nil, stageResponse, ""},53	}54	for _, c := range cases {55		if got := classify(c.err, c.st); got != c.want {56			t.Errorf("%s: got %q want %q", c.name, got, c.want)57		}58	}59}6061// End-to-end against a local TLS server: status codes, no-redirect, body cap and cert error mapping.62func TestRunAgainstLocalServer(t *testing.T) {63	big := strings.Repeat("x", 1<<20)64	srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {65		if !strings.HasPrefix(r.UserAgent(), "InternetPressureProbe/") {66			t.Errorf("unexpected UA %q", r.UserAgent())67		}68		switch r.URL.Path {69		case "/redirect":70			http.Redirect(w, r, "/elsewhere", http.StatusFound)71		case "/boom":72			w.WriteHeader(503)73		case "/missing":74			w.WriteHeader(404)75		default:76			w.WriteHeader(200)77			io.WriteString(w, big)78		}79	}))80	srv.EnableHTTP2 = true81	srv.StartTLS()82	defer srv.Close()8384	u, _ := url.Parse(srv.URL)85	host, port, _ := net.SplitHostPort(u.Host)86	c := &Checker{UserAgent: "InternetPressureProbe/test (+https://www.internetpressure.io/probes)"}8788	// 1. Untrusted certificate → tls_cert, not ok.89	target := protocol.Target{TargetID: "local", Hostname: host, URL: srv.URL + "/", Port: 443}90	m := c.Run(context.Background(), target)91	if m.OK || m.Error != protocol.ErrTLSCert {92		t.Fatalf("expected tls_cert, got ok=%v error=%q", m.OK, m.Error)93	}94	if m.TCPMs == nil || m.TotalMs == nil {95		t.Fatalf("tcp/total should be measured even when TLS fails: %+v", m)96	}9798	// Trust the test CA for the remaining cases by swapping the run function's TLS root via a helper.99	pool := x509.NewCertPool()100	pool.AddCert(srv.Certificate())101	run := func(path string) protocol.Measurement {102		return runWithRoots(c, protocol.Target{TargetID: "local", Hostname: host, URL: srv.URL + path, Port: atoi(port)}, pool)103	}104105	m = run("/")106	if !m.OK || m.Error != "" || m.HTTPStatus == nil || *m.HTTPStatus != 200 {107		t.Fatalf("200: %+v", m)108	}109	if m.HTTPProto != "HTTP/2.0" {110		t.Errorf("expected HTTP/2.0, got %q", m.HTTPProto)111	}112	if m.TLSVersion != "TLS1.3" {113		t.Errorf("expected TLS1.3, got %q", m.TLSVersion)114	}115	if m.TCPMs == nil || m.TLSMs == nil || m.TTFBMs == nil || m.TotalMs == nil || m.ResolvedIP != host {116		t.Errorf("timings incomplete: %+v", m)117	}118	if *m.TotalMs > 5000 {119		t.Errorf("body cap not applied, total=%v ms", *m.TotalMs)120	}121	if m = run("/redirect"); !m.OK || *m.HTTPStatus != 302 {122		t.Errorf("redirect must not be followed: %+v", m)123	}124	if m = run("/boom"); m.OK || m.Error != protocol.ErrHTTP5xx || *m.HTTPStatus != 503 {125		t.Errorf("5xx: %+v", m)126	}127	if m = run("/missing"); !m.OK || m.Error != protocol.ErrHTTP4xx {128		t.Errorf("4xx should be ok with http_4xx code: %+v", m)129	}130131	// Connection refused.132	srv2 := httptest.NewServer(http.NotFoundHandler())133	refusedURL := srv2.URL134	srv2.Close()135	m = c.Run(context.Background(), protocol.Target{TargetID: "dead", Hostname: host, URL: refusedURL + "/"})136	if m.OK || m.Error != protocol.ErrTCPRefused {137		t.Errorf("refused: %+v", m)138	}139}140141func atoi(s string) int {142	n := 0143	for _, r := range s {144		n = n*10 + int(r-'0')145	}146	return n147}148