c740be987b
Rather than matching a string, match a type. This is more robust considering future refactorings. We're confident the names did not change in _this_ refactoring because we're still testing the same strings in the tests. Part of https://github.com/ooni/probe/issues/2121
469 lines
10 KiB
Go
469 lines
10 KiB
Go
package tracex
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
)
|
|
|
|
func TestSaverMetadataSuccess(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skip test in short mode")
|
|
}
|
|
saver := &Saver{}
|
|
txp := SaverMetadataHTTPTransport{
|
|
HTTPTransport: netxlite.NewHTTPTransportStdlib(model.DiscardLogger),
|
|
Saver: saver,
|
|
}
|
|
req, err := http.NewRequest("GET", "https://www.google.com", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req.Header.Add("User-Agent", "miniooni/0.1.0-dev")
|
|
resp, err := txp.RoundTrip(req)
|
|
if err != nil {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("expected non nil response here")
|
|
}
|
|
ev := saver.Read()
|
|
if len(ev) != 2 {
|
|
t.Fatal("expected two events")
|
|
}
|
|
//
|
|
if ev[0].Value().HTTPMethod != "GET" {
|
|
t.Fatal("unexpected Method")
|
|
}
|
|
if len(ev[0].Value().HTTPHeaders) <= 0 {
|
|
t.Fatal("unexpected Headers")
|
|
}
|
|
if ev[0].Value().HTTPURL != "https://www.google.com" {
|
|
t.Fatal("unexpected URL")
|
|
}
|
|
if ev[0].Name() != "http_request_metadata" {
|
|
t.Fatal("unexpected Name")
|
|
}
|
|
if !ev[0].Value().Time.Before(time.Now()) {
|
|
t.Fatal("unexpected Time")
|
|
}
|
|
//
|
|
if ev[1].Value().HTTPStatusCode != 200 {
|
|
t.Fatal("unexpected StatusCode")
|
|
}
|
|
if len(ev[1].Value().HTTPHeaders) <= 0 {
|
|
t.Fatal("unexpected Headers")
|
|
}
|
|
if ev[1].Name() != "http_response_metadata" {
|
|
t.Fatal("unexpected Name")
|
|
}
|
|
if !ev[1].Value().Time.After(ev[0].Value().Time) {
|
|
t.Fatal("unexpected Time")
|
|
}
|
|
}
|
|
|
|
func TestSaverMetadataFailure(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
saver := &Saver{}
|
|
txp := SaverMetadataHTTPTransport{
|
|
HTTPTransport: FakeTransport{
|
|
Err: expected,
|
|
},
|
|
Saver: saver,
|
|
}
|
|
req, err := http.NewRequest("GET", "http://www.google.com", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req.Header.Add("User-Agent", "miniooni/0.1.0-dev")
|
|
resp, err := txp.RoundTrip(req)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if resp != nil {
|
|
t.Fatal("expected nil response here")
|
|
}
|
|
ev := saver.Read()
|
|
if len(ev) != 1 {
|
|
t.Fatal("expected one event")
|
|
}
|
|
if ev[0].Value().HTTPMethod != "GET" {
|
|
t.Fatal("unexpected Method")
|
|
}
|
|
if len(ev[0].Value().HTTPHeaders) <= 0 {
|
|
t.Fatal("unexpected Headers")
|
|
}
|
|
if ev[0].Value().HTTPURL != "http://www.google.com" {
|
|
t.Fatal("unexpected URL")
|
|
}
|
|
if ev[0].Name() != "http_request_metadata" {
|
|
t.Fatal("unexpected Name")
|
|
}
|
|
if !ev[0].Value().Time.Before(time.Now()) {
|
|
t.Fatal("unexpected Time")
|
|
}
|
|
}
|
|
|
|
func TestSaverTransactionSuccess(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skip test in short mode")
|
|
}
|
|
saver := &Saver{}
|
|
txp := SaverTransactionHTTPTransport{
|
|
HTTPTransport: netxlite.NewHTTPTransportStdlib(model.DiscardLogger),
|
|
Saver: saver,
|
|
}
|
|
req, err := http.NewRequest("GET", "https://www.google.com", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp, err := txp.RoundTrip(req)
|
|
if err != nil {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if resp == nil {
|
|
t.Fatal("expected non nil response here")
|
|
}
|
|
ev := saver.Read()
|
|
if len(ev) != 2 {
|
|
t.Fatal("expected two events")
|
|
}
|
|
//
|
|
if ev[0].Name() != "http_transaction_start" {
|
|
t.Fatal("unexpected Name")
|
|
}
|
|
if !ev[0].Value().Time.Before(time.Now()) {
|
|
t.Fatal("unexpected Time")
|
|
}
|
|
//
|
|
if ev[1].Value().Err != nil {
|
|
t.Fatal("unexpected Err")
|
|
}
|
|
if ev[1].Name() != "http_transaction_done" {
|
|
t.Fatal("unexpected Name")
|
|
}
|
|
if !ev[1].Value().Time.After(ev[0].Value().Time) {
|
|
t.Fatal("unexpected Time")
|
|
}
|
|
}
|
|
|
|
func TestSaverTransactionFailure(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
saver := &Saver{}
|
|
txp := SaverTransactionHTTPTransport{
|
|
HTTPTransport: FakeTransport{
|
|
Err: expected,
|
|
},
|
|
Saver: saver,
|
|
}
|
|
req, err := http.NewRequest("GET", "http://www.google.com", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp, err := txp.RoundTrip(req)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if resp != nil {
|
|
t.Fatal("expected nil response here")
|
|
}
|
|
ev := saver.Read()
|
|
if len(ev) != 2 {
|
|
t.Fatal("expected two events")
|
|
}
|
|
if ev[0].Name() != "http_transaction_start" {
|
|
t.Fatal("unexpected Name")
|
|
}
|
|
if !ev[0].Value().Time.Before(time.Now()) {
|
|
t.Fatal("unexpected Time")
|
|
}
|
|
if ev[1].Name() != "http_transaction_done" {
|
|
t.Fatal("unexpected Name")
|
|
}
|
|
if !errors.Is(ev[1].Value().Err, expected) {
|
|
t.Fatal("unexpected Err")
|
|
}
|
|
if !ev[1].Value().Time.After(ev[0].Value().Time) {
|
|
t.Fatal("unexpected Time")
|
|
}
|
|
}
|
|
|
|
func TestSaverBodySuccess(t *testing.T) {
|
|
saver := new(Saver)
|
|
txp := SaverBodyHTTPTransport{
|
|
HTTPTransport: FakeTransport{
|
|
Func: func(req *http.Request) (*http.Response, error) {
|
|
data, err := netxlite.ReadAllContext(context.Background(), req.Body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(data) != "deadbeef" {
|
|
t.Fatal("invalid data")
|
|
}
|
|
return &http.Response{
|
|
StatusCode: 501,
|
|
Body: io.NopCloser(strings.NewReader("abad1dea")),
|
|
}, nil
|
|
},
|
|
},
|
|
SnapshotSize: 4,
|
|
Saver: saver,
|
|
}
|
|
body := strings.NewReader("deadbeef")
|
|
req, err := http.NewRequest("POST", "http://x.org/y", body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp, err := txp.RoundTrip(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp.StatusCode != 501 {
|
|
t.Fatal("unexpected status code")
|
|
}
|
|
defer resp.Body.Close()
|
|
data, err := netxlite.ReadAllContext(context.Background(), resp.Body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(data) != "abad1dea" {
|
|
t.Fatal("unexpected body")
|
|
}
|
|
ev := saver.Read()
|
|
if len(ev) != 2 {
|
|
t.Fatal("unexpected number of events")
|
|
}
|
|
if string(ev[0].Value().Data) != "dead" {
|
|
t.Fatal("invalid Data")
|
|
}
|
|
if ev[0].Value().DataIsTruncated != true {
|
|
t.Fatal("invalid DataIsTruncated")
|
|
}
|
|
if ev[0].Name() != "http_request_body_snapshot" {
|
|
t.Fatal("invalid Name")
|
|
}
|
|
if ev[0].Value().Time.After(time.Now()) {
|
|
t.Fatal("invalid Time")
|
|
}
|
|
if string(ev[1].Value().Data) != "abad" {
|
|
t.Fatal("invalid Data")
|
|
}
|
|
if ev[1].Value().DataIsTruncated != true {
|
|
t.Fatal("invalid DataIsTruncated")
|
|
}
|
|
if ev[1].Name() != "http_response_body_snapshot" {
|
|
t.Fatal("invalid Name")
|
|
}
|
|
if ev[1].Value().Time.Before(ev[0].Value().Time) {
|
|
t.Fatal("invalid Time")
|
|
}
|
|
}
|
|
|
|
func TestSaverBodyRequestReadError(t *testing.T) {
|
|
saver := new(Saver)
|
|
txp := SaverBodyHTTPTransport{
|
|
HTTPTransport: FakeTransport{
|
|
Func: func(req *http.Request) (*http.Response, error) {
|
|
panic("should not be called")
|
|
},
|
|
},
|
|
SnapshotSize: 4,
|
|
Saver: saver,
|
|
}
|
|
expected := errors.New("mocked error")
|
|
body := FakeBody{Err: expected}
|
|
req, err := http.NewRequest("POST", "http://x.org/y", body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp, err := txp.RoundTrip(req)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if resp != nil {
|
|
t.Fatal("expected nil response")
|
|
}
|
|
ev := saver.Read()
|
|
if len(ev) != 0 {
|
|
t.Fatal("unexpected number of events")
|
|
}
|
|
}
|
|
|
|
func TestSaverBodyRoundTripError(t *testing.T) {
|
|
saver := new(Saver)
|
|
expected := errors.New("mocked error")
|
|
txp := SaverBodyHTTPTransport{
|
|
HTTPTransport: FakeTransport{
|
|
Err: expected,
|
|
},
|
|
SnapshotSize: 4,
|
|
Saver: saver,
|
|
}
|
|
body := strings.NewReader("deadbeef")
|
|
req, err := http.NewRequest("POST", "http://x.org/y", body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp, err := txp.RoundTrip(req)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if resp != nil {
|
|
t.Fatal("expected nil response")
|
|
}
|
|
ev := saver.Read()
|
|
if len(ev) != 1 {
|
|
t.Fatal("unexpected number of events")
|
|
}
|
|
if string(ev[0].Value().Data) != "dead" {
|
|
t.Fatal("invalid Data")
|
|
}
|
|
if ev[0].Value().DataIsTruncated != true {
|
|
t.Fatal("invalid DataIsTruncated")
|
|
}
|
|
if ev[0].Name() != "http_request_body_snapshot" {
|
|
t.Fatal("invalid Name")
|
|
}
|
|
if ev[0].Value().Time.After(time.Now()) {
|
|
t.Fatal("invalid Time")
|
|
}
|
|
}
|
|
|
|
func TestSaverBodyResponseReadError(t *testing.T) {
|
|
saver := new(Saver)
|
|
expected := errors.New("mocked error")
|
|
txp := SaverBodyHTTPTransport{
|
|
HTTPTransport: FakeTransport{
|
|
Func: func(req *http.Request) (*http.Response, error) {
|
|
return &http.Response{
|
|
StatusCode: 200,
|
|
Body: FakeBody{
|
|
Err: expected,
|
|
},
|
|
}, nil
|
|
},
|
|
},
|
|
SnapshotSize: 4,
|
|
Saver: saver,
|
|
}
|
|
body := strings.NewReader("deadbeef")
|
|
req, err := http.NewRequest("POST", "http://x.org/y", body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resp, err := txp.RoundTrip(req)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if resp != nil {
|
|
t.Fatal("expected nil response")
|
|
}
|
|
ev := saver.Read()
|
|
if len(ev) != 1 {
|
|
t.Fatal("unexpected number of events")
|
|
}
|
|
if string(ev[0].Value().Data) != "dead" {
|
|
t.Fatal("invalid Data")
|
|
}
|
|
if ev[0].Value().DataIsTruncated != true {
|
|
t.Fatal("invalid DataIsTruncated")
|
|
}
|
|
if ev[0].Name() != "http_request_body_snapshot" {
|
|
t.Fatal("invalid Name")
|
|
}
|
|
if ev[0].Value().Time.After(time.Now()) {
|
|
t.Fatal("invalid Time")
|
|
}
|
|
}
|
|
|
|
func TestCloneHeaders(t *testing.T) {
|
|
t.Run("with req.Host set", func(t *testing.T) {
|
|
req := &http.Request{
|
|
Host: "www.example.com",
|
|
URL: &url.URL{
|
|
Host: "www.kernel.org",
|
|
},
|
|
Header: http.Header{},
|
|
}
|
|
header := httpCloneHeaders(req)
|
|
if header.Get("Host") != "www.example.com" {
|
|
t.Fatal("did not set Host header correctly")
|
|
}
|
|
})
|
|
|
|
t.Run("with only req.URL.Host set", func(t *testing.T) {
|
|
req := &http.Request{
|
|
Host: "",
|
|
URL: &url.URL{
|
|
Host: "www.kernel.org",
|
|
},
|
|
Header: http.Header{},
|
|
}
|
|
header := httpCloneHeaders(req)
|
|
if header.Get("Host") != "www.kernel.org" {
|
|
t.Fatal("did not set Host header correctly")
|
|
}
|
|
})
|
|
}
|
|
|
|
type FakeDialer struct {
|
|
Conn net.Conn
|
|
Err error
|
|
}
|
|
|
|
func (d FakeDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
|
time.Sleep(10 * time.Microsecond)
|
|
return d.Conn, d.Err
|
|
}
|
|
|
|
type FakeTransport struct {
|
|
Name string
|
|
Err error
|
|
Func func(*http.Request) (*http.Response, error)
|
|
Resp *http.Response
|
|
}
|
|
|
|
func (txp FakeTransport) Network() string {
|
|
return txp.Name
|
|
}
|
|
|
|
func (txp FakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
time.Sleep(10 * time.Microsecond)
|
|
if txp.Func != nil {
|
|
return txp.Func(req)
|
|
}
|
|
if req.Body != nil {
|
|
netxlite.ReadAllContext(req.Context(), req.Body)
|
|
req.Body.Close()
|
|
}
|
|
if txp.Err != nil {
|
|
return nil, txp.Err
|
|
}
|
|
txp.Resp.Request = req // non thread safe but it doesn't matter
|
|
return txp.Resp, nil
|
|
}
|
|
|
|
func (txp FakeTransport) CloseIdleConnections() {}
|
|
|
|
type FakeBody struct {
|
|
Err error
|
|
}
|
|
|
|
func (fb FakeBody) Read(p []byte) (int, error) {
|
|
time.Sleep(10 * time.Microsecond)
|
|
return 0, fb.Err
|
|
}
|
|
|
|
func (fb FakeBody) Close() error {
|
|
return nil
|
|
}
|