83440cf110
The legacy part for now is internal/errorsx. It will stay there until I figure out whether it also needs some extra bug fixing. The good part is now in internal/netxlite/errorsx and contains all the logic for mapping errors. We need to further improve upon this logic by writing more thorough integration tests for QUIC. We also need to copy the various dialer, conn, etc adapters that set errors. We will put them inside netxlite and we will generate errors in a way that is less crazy with respect to the major operation. (The idea is to always wrap, given that now we measure in an incremental way and we don't measure every operation together.) Part of https://github.com/ooni/probe/issues/1591
1086 lines
26 KiB
Go
1086 lines
26 KiB
Go
package oonidatamodel
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/modelx"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/legacy/oonitemplates"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
|
)
|
|
|
|
func TestNewTCPConnectListEmpty(t *testing.T) {
|
|
out := NewTCPConnectList(oonitemplates.Results{})
|
|
if len(out) != 0 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
}
|
|
|
|
func TestNewTCPConnectListSuccess(t *testing.T) {
|
|
out := NewTCPConnectList(oonitemplates.Results{
|
|
Connects: []*modelx.ConnectEvent{
|
|
{
|
|
RemoteAddress: "8.8.8.8:53",
|
|
},
|
|
{
|
|
RemoteAddress: "8.8.4.4:853",
|
|
},
|
|
},
|
|
})
|
|
if len(out) != 2 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
if out[0].IP != "8.8.8.8" {
|
|
t.Fatal("unexpected out[0].IP")
|
|
}
|
|
if out[0].Port != 53 {
|
|
t.Fatal("unexpected out[0].Port")
|
|
}
|
|
if out[0].Status.Failure != nil {
|
|
t.Fatal("unexpected out[0].Failure")
|
|
}
|
|
if out[0].Status.Success != true {
|
|
t.Fatal("unexpected out[0].Success")
|
|
}
|
|
if out[1].IP != "8.8.4.4" {
|
|
t.Fatal("unexpected out[1].IP")
|
|
}
|
|
if out[1].Port != 853 {
|
|
t.Fatal("unexpected out[1].Port")
|
|
}
|
|
if out[1].Status.Failure != nil {
|
|
t.Fatal("unexpected out[0].Failure")
|
|
}
|
|
if out[1].Status.Success != true {
|
|
t.Fatal("unexpected out[0].Success")
|
|
}
|
|
}
|
|
|
|
func TestNewTCPConnectListFailure(t *testing.T) {
|
|
out := NewTCPConnectList(oonitemplates.Results{
|
|
Connects: []*modelx.ConnectEvent{
|
|
{
|
|
RemoteAddress: "8.8.8.8:53",
|
|
Error: errors.New(errorsx.FailureConnectionReset),
|
|
},
|
|
},
|
|
})
|
|
if len(out) != 1 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
if out[0].IP != "8.8.8.8" {
|
|
t.Fatal("unexpected out[0].IP")
|
|
}
|
|
if out[0].Port != 53 {
|
|
t.Fatal("unexpected out[0].Port")
|
|
}
|
|
if *out[0].Status.Failure != errorsx.FailureConnectionReset {
|
|
t.Fatal("unexpected out[0].Failure")
|
|
}
|
|
if out[0].Status.Success != false {
|
|
t.Fatal("unexpected out[0].Success")
|
|
}
|
|
}
|
|
|
|
func TestNewTCPConnectListInvalidInput(t *testing.T) {
|
|
out := NewTCPConnectList(oonitemplates.Results{
|
|
Connects: []*modelx.ConnectEvent{
|
|
{
|
|
RemoteAddress: "8.8.8.8",
|
|
Error: errors.New(errorsx.FailureConnectionReset),
|
|
},
|
|
},
|
|
})
|
|
if len(out) != 1 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
if out[0].IP != "" {
|
|
t.Fatal("unexpected out[0].IP")
|
|
}
|
|
if out[0].Port != 0 {
|
|
t.Fatal("unexpected out[0].Port")
|
|
}
|
|
if *out[0].Status.Failure != errorsx.FailureConnectionReset {
|
|
t.Fatal("unexpected out[0].Failure")
|
|
}
|
|
if out[0].Status.Success != false {
|
|
t.Fatal("unexpected out[0].Success")
|
|
}
|
|
}
|
|
|
|
func TestNewRequestsListEmptyList(t *testing.T) {
|
|
out := NewRequestList(oonitemplates.Results{})
|
|
if len(out) != 0 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
}
|
|
|
|
func TestNewRequestsListGood(t *testing.T) {
|
|
out := NewRequestList(oonitemplates.Results{
|
|
HTTPRequests: []*modelx.HTTPRoundTripDoneEvent{
|
|
// need two requests to test that order is inverted
|
|
{
|
|
RequestBodySnap: []byte("abcdefx"),
|
|
RequestHeaders: http.Header{
|
|
"Content-Type": []string{
|
|
"text/plain",
|
|
"foobar",
|
|
},
|
|
"Content-Length": []string{
|
|
"17",
|
|
},
|
|
},
|
|
RequestMethod: "GET",
|
|
RequestURL: "http://x.org/",
|
|
ResponseBodySnap: []byte("abcdef"),
|
|
ResponseHeaders: http.Header{
|
|
"Content-Type": []string{
|
|
"application/json",
|
|
"foobaz",
|
|
},
|
|
"Server": []string{
|
|
"antani",
|
|
},
|
|
"Content-Length": []string{
|
|
"14",
|
|
},
|
|
},
|
|
ResponseStatusCode: 451,
|
|
MaxBodySnapSize: 10,
|
|
},
|
|
{
|
|
Error: errors.New("antani"),
|
|
},
|
|
},
|
|
})
|
|
if len(out) != 2 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
|
|
if *out[0].Failure != "antani" {
|
|
t.Fatal("unexpected out[0].Failure")
|
|
}
|
|
if out[0].Request.Body.Value != "" {
|
|
t.Fatal("unexpected out[0].Request.Body.Value")
|
|
}
|
|
if len(out[0].Request.Headers) != 0 {
|
|
t.Fatal("unexpected out[0].Request.Headers")
|
|
}
|
|
if out[0].Request.Method != "" {
|
|
t.Fatal("unexpected out[0].Request.Method")
|
|
}
|
|
if out[0].Request.URL != "" {
|
|
t.Fatal("unexpected out[0].Request.URL")
|
|
}
|
|
if out[0].Request.BodyIsTruncated != false {
|
|
t.Fatal("unexpected out[0].Request.BodyIsTruncated")
|
|
}
|
|
if out[0].Response.Body.Value != "" {
|
|
t.Fatal("unexpected out[0].Response.Body.Value")
|
|
}
|
|
if out[0].Response.Code != 0 {
|
|
t.Fatal("unexpected out[0].Response.Code")
|
|
}
|
|
if len(out[0].Response.Headers) != 0 {
|
|
t.Fatal("unexpected out[0].Response.Headers")
|
|
}
|
|
if out[0].Response.BodyIsTruncated != false {
|
|
t.Fatal("unexpected out[0].Response.BodyIsTruncated")
|
|
}
|
|
|
|
if out[1].Failure != nil {
|
|
t.Fatal("unexpected out[1].Failure")
|
|
}
|
|
if out[1].Request.Body.Value != "abcdefx" {
|
|
t.Fatal("unexpected out[1].Request.Body.Value")
|
|
}
|
|
if len(out[1].Request.Headers) != 2 {
|
|
t.Fatal("unexpected out[1].Request.Headers")
|
|
}
|
|
if out[1].Request.Headers["Content-Type"].Value != "text/plain" {
|
|
t.Fatal("unexpected out[1].Request.Headers Content-Type value")
|
|
}
|
|
if out[1].Request.Headers["Content-Length"].Value != "17" {
|
|
t.Fatal("unexpected out[1].Request.Headers Content-Length value")
|
|
}
|
|
var (
|
|
requestHasTextPlain bool
|
|
requestHasFoobar bool
|
|
requestHasContentLength bool
|
|
requestHasOther int64
|
|
)
|
|
for _, header := range out[1].Request.HeadersList {
|
|
if header.Key == "Content-Type" {
|
|
if header.Value.Value == "text/plain" {
|
|
requestHasTextPlain = true
|
|
} else if header.Value.Value == "foobar" {
|
|
requestHasFoobar = true
|
|
} else {
|
|
requestHasOther++
|
|
}
|
|
} else if header.Key == "Content-Length" {
|
|
if header.Value.Value == "17" {
|
|
requestHasContentLength = true
|
|
} else {
|
|
requestHasOther++
|
|
}
|
|
} else {
|
|
requestHasOther++
|
|
}
|
|
}
|
|
if !requestHasTextPlain {
|
|
t.Fatal("missing text/plain for request")
|
|
}
|
|
if !requestHasFoobar {
|
|
t.Fatal("missing foobar for request")
|
|
}
|
|
if !requestHasContentLength {
|
|
t.Fatal("missing content_length for request")
|
|
}
|
|
if requestHasOther != 0 {
|
|
t.Fatal("seen something unexpected")
|
|
}
|
|
if out[1].Request.Method != "GET" {
|
|
t.Fatal("unexpected out[1].Request.Method")
|
|
}
|
|
if out[1].Request.URL != "http://x.org/" {
|
|
t.Fatal("unexpected out[1].Request.URL")
|
|
}
|
|
if out[1].Request.BodyIsTruncated != false {
|
|
t.Fatal("unexpected out[1].Request.BodyIsTruncated")
|
|
}
|
|
|
|
if out[1].Response.Body.Value != "abcdef" {
|
|
t.Fatal("unexpected out[1].Response.Body.Value")
|
|
}
|
|
if out[1].Response.Code != 451 {
|
|
t.Fatal("unexpected out[1].Response.Code")
|
|
}
|
|
if len(out[1].Response.Headers) != 3 {
|
|
t.Fatal("unexpected out[1].Response.Headers")
|
|
}
|
|
if out[1].Response.Headers["Content-Type"].Value != "application/json" {
|
|
t.Fatal("unexpected out[1].Response.Headers Content-Type value")
|
|
}
|
|
if out[1].Response.Headers["Server"].Value != "antani" {
|
|
t.Fatal("unexpected out[1].Response.Headers Server value")
|
|
}
|
|
if out[1].Response.Headers["Content-Length"].Value != "14" {
|
|
t.Fatal("unexpected out[1].Response.Headers Content-Length value")
|
|
}
|
|
var (
|
|
responseHasApplicationJSON bool
|
|
responseHasFoobaz bool
|
|
responseHasServer bool
|
|
responseHasContentLength bool
|
|
responseHasOther int64
|
|
)
|
|
for _, header := range out[1].Response.HeadersList {
|
|
if header.Key == "Content-Type" {
|
|
if header.Value.Value == "application/json" {
|
|
responseHasApplicationJSON = true
|
|
} else if header.Value.Value == "foobaz" {
|
|
responseHasFoobaz = true
|
|
} else {
|
|
responseHasOther++
|
|
}
|
|
} else if header.Key == "Content-Length" {
|
|
if header.Value.Value == "14" {
|
|
responseHasContentLength = true
|
|
} else {
|
|
responseHasOther++
|
|
}
|
|
} else if header.Key == "Server" {
|
|
if header.Value.Value == "antani" {
|
|
responseHasServer = true
|
|
} else {
|
|
responseHasOther++
|
|
}
|
|
} else {
|
|
responseHasOther++
|
|
}
|
|
}
|
|
if !responseHasApplicationJSON {
|
|
t.Fatal("missing application/json for response")
|
|
}
|
|
if !responseHasFoobaz {
|
|
t.Fatal("missing foobaz for response")
|
|
}
|
|
if !responseHasContentLength {
|
|
t.Fatal("missing content_length for response")
|
|
}
|
|
if !responseHasServer {
|
|
t.Fatal("missing server for response")
|
|
}
|
|
if responseHasOther != 0 {
|
|
t.Fatal("seen something unexpected")
|
|
}
|
|
if out[1].Response.BodyIsTruncated != false {
|
|
t.Fatal("unexpected out[1].Response.BodyIsTruncated")
|
|
}
|
|
}
|
|
|
|
func TestNewRequestsSnaps(t *testing.T) {
|
|
out := NewRequestList(oonitemplates.Results{
|
|
HTTPRequests: []*modelx.HTTPRoundTripDoneEvent{
|
|
{
|
|
RequestBodySnap: []byte("abcd"),
|
|
MaxBodySnapSize: 4,
|
|
ResponseBodySnap: []byte("defg"),
|
|
},
|
|
},
|
|
})
|
|
if len(out) != 1 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
if out[0].Request.BodyIsTruncated != true {
|
|
t.Fatal("wrong out[0].Request.BodyIsTruncated")
|
|
}
|
|
if out[0].Response.BodyIsTruncated != true {
|
|
t.Fatal("wrong out[0].Response.BodyIsTruncated")
|
|
}
|
|
}
|
|
|
|
func TestMarshalUnmarshalHTTPBodyString(t *testing.T) {
|
|
mbv := HTTPBody{
|
|
Value: "1234",
|
|
}
|
|
data, err := json.Marshal(mbv)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(data, []byte(`"1234"`)) {
|
|
t.Fatal("result is unexpected")
|
|
}
|
|
var newbody HTTPBody
|
|
if err := json.Unmarshal(data, &newbody); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if newbody.Value != mbv.Value {
|
|
t.Fatal("string value mistmatch")
|
|
}
|
|
}
|
|
|
|
var binaryInput = []uint8{
|
|
0x57, 0xe5, 0x79, 0xfb, 0xa6, 0xbb, 0x0d, 0xbc, 0xce, 0xbd, 0xa7, 0xa0,
|
|
0xba, 0xa4, 0x78, 0x78, 0x12, 0x59, 0xee, 0x68, 0x39, 0xa4, 0x07, 0x98,
|
|
0xc5, 0x3e, 0xbc, 0x55, 0xcb, 0xfe, 0x34, 0x3c, 0x7e, 0x1b, 0x5a, 0xb3,
|
|
0x22, 0x9d, 0xc1, 0x2d, 0x6e, 0xca, 0x5b, 0xf1, 0x10, 0x25, 0x47, 0x1e,
|
|
0x44, 0xe2, 0x2d, 0x60, 0x08, 0xea, 0xb0, 0x0a, 0xcc, 0x05, 0x48, 0xa0,
|
|
0xf5, 0x78, 0x38, 0xf0, 0xdb, 0x3f, 0x9d, 0x9f, 0x25, 0x6f, 0x89, 0x00,
|
|
0x96, 0x93, 0xaf, 0x43, 0xac, 0x4d, 0xc9, 0xac, 0x13, 0xdb, 0x22, 0xbe,
|
|
0x7a, 0x7d, 0xd9, 0x24, 0xa2, 0x52, 0x69, 0xd8, 0x89, 0xc1, 0xd1, 0x57,
|
|
0xaa, 0x04, 0x2b, 0xa2, 0xd8, 0xb1, 0x19, 0xf6, 0xd5, 0x11, 0x39, 0xbb,
|
|
0x80, 0xcf, 0x86, 0xf9, 0x5f, 0x9d, 0x8c, 0xab, 0xf5, 0xc5, 0x74, 0x24,
|
|
0x3a, 0xa2, 0xd4, 0x40, 0x4e, 0xd7, 0x10, 0x1f,
|
|
}
|
|
|
|
func TestMarshalUnmarshalHTTPBodyBinary(t *testing.T) {
|
|
mbv := HTTPBody{
|
|
Value: string(binaryInput),
|
|
}
|
|
data, err := json.Marshal(mbv)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(data, []byte(`{"data":"V+V5+6a7DbzOvaeguqR4eBJZ7mg5pAeYxT68Vcv+NDx+G1qzIp3BLW7KW/EQJUceROItYAjqsArMBUig9Xg48Ns/nZ8lb4kAlpOvQ6xNyawT2yK+en3ZJKJSadiJwdFXqgQrotixGfbVETm7gM+G+V+djKv1xXQkOqLUQE7XEB8=","format":"base64"}`)) {
|
|
t.Fatal("result is unexpected")
|
|
}
|
|
var newbody HTTPBody
|
|
if err := json.Unmarshal(data, &newbody); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if newbody.Value != mbv.Value {
|
|
t.Fatal("string value mistmatch")
|
|
}
|
|
}
|
|
|
|
func TestMaybeBinaryValueUnmarshalJSON(t *testing.T) {
|
|
t.Run("when the code is not a map or string", func(t *testing.T) {
|
|
var (
|
|
mbv MaybeBinaryValue
|
|
input = []byte("[1, 2, 3, 4]")
|
|
)
|
|
if err := json.Unmarshal(input, &mbv); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the format field is missing", func(t *testing.T) {
|
|
var (
|
|
mbv MaybeBinaryValue
|
|
input = []byte("{}")
|
|
)
|
|
if err := json.Unmarshal(input, &mbv); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the format field is invalid", func(t *testing.T) {
|
|
var (
|
|
mbv MaybeBinaryValue
|
|
input = []byte(`{"format":"antani"}`)
|
|
)
|
|
if err := json.Unmarshal(input, &mbv); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the data field is missing", func(t *testing.T) {
|
|
var (
|
|
mbv MaybeBinaryValue
|
|
input = []byte(`{"format":"base64"}`)
|
|
)
|
|
if err := json.Unmarshal(input, &mbv); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the data field is not base64", func(t *testing.T) {
|
|
var (
|
|
mbv MaybeBinaryValue
|
|
input = []byte(`{"format":"base64","data":"antani"}`)
|
|
)
|
|
if err := json.Unmarshal(input, &mbv); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMarshalUnmarshalHTTPHeaderString(t *testing.T) {
|
|
mbh := HTTPHeadersList{
|
|
HTTPHeader{
|
|
Key: "Content-Type",
|
|
Value: MaybeBinaryValue{
|
|
Value: "application/json",
|
|
},
|
|
},
|
|
HTTPHeader{
|
|
Key: "Content-Type",
|
|
Value: MaybeBinaryValue{
|
|
Value: "antani",
|
|
},
|
|
},
|
|
HTTPHeader{
|
|
Key: "Content-Length",
|
|
Value: MaybeBinaryValue{
|
|
Value: "17",
|
|
},
|
|
},
|
|
}
|
|
data, err := json.Marshal(mbh)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expected := []byte(
|
|
`[["Content-Type","application/json"],["Content-Type","antani"],["Content-Length","17"]]`,
|
|
)
|
|
if !bytes.Equal(data, expected) {
|
|
t.Fatal("result is unexpected")
|
|
}
|
|
var newlist HTTPHeadersList
|
|
if err := json.Unmarshal(data, &newlist); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(mbh, newlist) {
|
|
t.Fatal("result mismatch")
|
|
}
|
|
}
|
|
|
|
func TestMarshalUnmarshalHTTPHeaderBinary(t *testing.T) {
|
|
mbh := HTTPHeadersList{
|
|
HTTPHeader{
|
|
Key: "Content-Type",
|
|
Value: MaybeBinaryValue{
|
|
Value: "application/json",
|
|
},
|
|
},
|
|
HTTPHeader{
|
|
Key: "Content-Type",
|
|
Value: MaybeBinaryValue{
|
|
Value: string(binaryInput),
|
|
},
|
|
},
|
|
HTTPHeader{
|
|
Key: "Content-Length",
|
|
Value: MaybeBinaryValue{
|
|
Value: "17",
|
|
},
|
|
},
|
|
}
|
|
data, err := json.Marshal(mbh)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expected := []byte(
|
|
`[["Content-Type","application/json"],["Content-Type",{"data":"V+V5+6a7DbzOvaeguqR4eBJZ7mg5pAeYxT68Vcv+NDx+G1qzIp3BLW7KW/EQJUceROItYAjqsArMBUig9Xg48Ns/nZ8lb4kAlpOvQ6xNyawT2yK+en3ZJKJSadiJwdFXqgQrotixGfbVETm7gM+G+V+djKv1xXQkOqLUQE7XEB8=","format":"base64"}],["Content-Length","17"]]`,
|
|
)
|
|
if !bytes.Equal(data, expected) {
|
|
t.Fatal("result is unexpected")
|
|
}
|
|
var newlist HTTPHeadersList
|
|
if err := json.Unmarshal(data, &newlist); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(mbh, newlist) {
|
|
t.Fatal("result mismatch")
|
|
}
|
|
}
|
|
|
|
func TestHTTPHeaderUnmarshalJSON(t *testing.T) {
|
|
t.Run("when the code is not a list", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`{"foo":1}`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the pair length is not two", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte("[1,2,3]")
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the first element is not a string", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`[1, "antani"]`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the second element is not map[string]interface{}", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`["antani", ["base64", "foo"]]`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the format field is missing", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`["antani", {}]`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the format field is not a string", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`["antani", {"format":1}]`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the format field is invalid", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`["antani", {"format":"antani"}]`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the data field is missing", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`["antani", {"format":"base64"}]`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the data field is not a string", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`["antani", {"format":"base64","data":10}]`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the data field is not base64", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`["antani", {"format":"base64","data":"antani"}]`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
t.Run("when the data field is not base64", func(t *testing.T) {
|
|
var (
|
|
hh HTTPHeader
|
|
input = []byte(`["antani", {"format":"base64","data":"antani"}]`)
|
|
)
|
|
if err := json.Unmarshal(input, &hh); err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNewDNSQueriesListEmpty(t *testing.T) {
|
|
out := NewDNSQueriesList(oonitemplates.Results{})
|
|
if len(out) != 0 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
}
|
|
|
|
func TestNewDNSQueriesListSuccess(t *testing.T) {
|
|
out := NewDNSQueriesList(oonitemplates.Results{
|
|
Resolves: []*modelx.ResolveDoneEvent{
|
|
{
|
|
Addresses: []string{
|
|
"8.8.4.4", "2001:4860:4860::8888",
|
|
},
|
|
Hostname: "dns.google",
|
|
TransportNetwork: "system",
|
|
},
|
|
{
|
|
Error: errors.New(errorsx.FailureDNSNXDOMAINError),
|
|
Hostname: "dns.googlex",
|
|
TransportNetwork: "system",
|
|
},
|
|
},
|
|
})
|
|
if len(out) != 4 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
var (
|
|
foundDNSGoogleA bool
|
|
foundDNSGoogleAAAA bool
|
|
foundErrorA bool
|
|
foundErrorAAAA bool
|
|
foundOther bool
|
|
)
|
|
for _, e := range out {
|
|
switch e.Hostname {
|
|
case "dns.google":
|
|
switch e.QueryType {
|
|
case "A":
|
|
foundDNSGoogleA = true
|
|
if err := dnscheckgood(e); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
case "AAAA":
|
|
foundDNSGoogleAAAA = true
|
|
if err := dnscheckgood(e); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
default:
|
|
foundOther = true
|
|
}
|
|
case "dns.googlex":
|
|
switch e.QueryType {
|
|
case "A":
|
|
foundErrorA = true
|
|
if err := dnscheckbad(e); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
case "AAAA":
|
|
foundErrorAAAA = true
|
|
if err := dnscheckbad(e); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
default:
|
|
foundOther = true
|
|
}
|
|
default:
|
|
foundOther = true
|
|
}
|
|
}
|
|
if foundDNSGoogleA == false {
|
|
t.Fatal("missing A for dns.google")
|
|
}
|
|
if foundDNSGoogleAAAA == false {
|
|
t.Fatal("missing AAAA for dns.google")
|
|
}
|
|
if foundErrorA == false {
|
|
t.Fatal("missing A for invalid domain")
|
|
}
|
|
if foundErrorAAAA == false {
|
|
t.Fatal("missing AAAA for invalid domain")
|
|
}
|
|
if foundOther == true {
|
|
t.Fatal("seen something unexpected")
|
|
}
|
|
}
|
|
|
|
func dnscheckgood(e DNSQueryEntry) error {
|
|
if len(e.Answers) != 1 {
|
|
return errors.New("unexpected number of answers")
|
|
}
|
|
if e.Engine != "system" {
|
|
return errors.New("invalid engine")
|
|
}
|
|
if e.Failure != nil {
|
|
return errors.New("invalid failure")
|
|
}
|
|
if e.Hostname != "dns.google" {
|
|
return errors.New("invalid hostname")
|
|
}
|
|
switch e.QueryType {
|
|
case "A", "AAAA":
|
|
default:
|
|
return errors.New("invalid query type")
|
|
}
|
|
if e.Answers[0].AnswerType != e.QueryType {
|
|
return errors.New("AnswerType mismatch")
|
|
}
|
|
switch e.QueryType {
|
|
case "A":
|
|
if e.Answers[0].IPv4 != "8.8.4.4" {
|
|
return errors.New("unexpected IPv4 entry")
|
|
}
|
|
case "AAAA":
|
|
if e.Answers[0].IPv6 != "2001:4860:4860::8888" {
|
|
return errors.New("unexpected IPv6 entry")
|
|
}
|
|
}
|
|
if e.ResolverHostname != nil {
|
|
return errors.New("invalid resolver hostname")
|
|
}
|
|
if e.ResolverPort != nil {
|
|
return errors.New("invalid resolver port")
|
|
}
|
|
if e.ResolverAddress != "" {
|
|
return errors.New("invalid resolver address")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func dnscheckbad(e DNSQueryEntry) error {
|
|
if len(e.Answers) != 0 {
|
|
return errors.New("unexpected number of answers")
|
|
}
|
|
if e.Engine != "system" {
|
|
return errors.New("invalid engine")
|
|
}
|
|
if *e.Failure != errorsx.FailureDNSNXDOMAINError {
|
|
return errors.New("invalid failure")
|
|
}
|
|
if e.Hostname != "dns.googlex" {
|
|
return errors.New("invalid hostname")
|
|
}
|
|
switch e.QueryType {
|
|
case "A", "AAAA":
|
|
default:
|
|
return errors.New("invalid query type")
|
|
}
|
|
if e.ResolverHostname != nil {
|
|
return errors.New("invalid resolver hostname")
|
|
}
|
|
if e.ResolverPort != nil {
|
|
return errors.New("invalid resolver port")
|
|
}
|
|
if e.ResolverAddress != "" {
|
|
return errors.New("invalid resolver address")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestDNSQueryTypeIPOfType(t *testing.T) {
|
|
qtype := dnsQueryType("ANTANI")
|
|
if qtype.ipoftype("8.8.8.8") == true {
|
|
t.Fatal("ipoftype misbehaving")
|
|
}
|
|
}
|
|
|
|
func TestNewNetworkEventsListEmpty(t *testing.T) {
|
|
out := NewNetworkEventsList(oonitemplates.Results{})
|
|
if len(out) != 0 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
}
|
|
|
|
func TestNewNetworkEventsListNoSuitableEvents(t *testing.T) {
|
|
out := NewNetworkEventsList(oonitemplates.Results{
|
|
NetworkEvents: []*modelx.Measurement{
|
|
{},
|
|
{},
|
|
{},
|
|
},
|
|
})
|
|
if len(out) != 0 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
}
|
|
|
|
func TestNewNetworkEventsListGood(t *testing.T) {
|
|
out := NewNetworkEventsList(oonitemplates.Results{
|
|
NetworkEvents: []*modelx.Measurement{
|
|
{
|
|
Connect: &modelx.ConnectEvent{
|
|
DurationSinceBeginning: 10 * time.Millisecond,
|
|
RemoteAddress: "1.1.1.1:443",
|
|
},
|
|
},
|
|
{
|
|
Read: &modelx.ReadEvent{
|
|
DurationSinceBeginning: 20 * time.Millisecond,
|
|
NumBytes: 1789,
|
|
},
|
|
},
|
|
{
|
|
Write: &modelx.WriteEvent{
|
|
DurationSinceBeginning: 30 * time.Millisecond,
|
|
NumBytes: 17714,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if len(out) != 3 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
|
|
if out[0].Address != "1.1.1.1:443" {
|
|
t.Fatal("wrong out[0].Address")
|
|
}
|
|
if out[0].Failure != nil {
|
|
t.Fatal("wrong out[0].Failure")
|
|
}
|
|
if out[0].NumBytes != 0 {
|
|
t.Fatal("wrong out[0].NumBytes")
|
|
}
|
|
if out[0].Operation != errorsx.ConnectOperation {
|
|
t.Fatal("wrong out[0].Operation")
|
|
}
|
|
if !floatEquals(out[0].T, 0.010) {
|
|
t.Fatal("wrong out[0].T")
|
|
}
|
|
|
|
if out[1].Address != "" {
|
|
t.Fatal("wrong out[1].Address")
|
|
}
|
|
if out[1].Failure != nil {
|
|
t.Fatal("wrong out[1].Failure")
|
|
}
|
|
if out[1].NumBytes != 1789 {
|
|
t.Fatal("wrong out[1].NumBytes")
|
|
}
|
|
if out[1].Operation != errorsx.ReadOperation {
|
|
t.Fatal("wrong out[1].Operation")
|
|
}
|
|
if !floatEquals(out[1].T, 0.020) {
|
|
t.Fatal("wrong out[1].T")
|
|
}
|
|
|
|
if out[2].Address != "" {
|
|
t.Fatal("wrong out[2].Address")
|
|
}
|
|
if out[2].Failure != nil {
|
|
t.Fatal("wrong out[2].Failure")
|
|
}
|
|
if out[2].NumBytes != 17714 {
|
|
t.Fatal("wrong out[2].NumBytes")
|
|
}
|
|
if out[2].Operation != errorsx.WriteOperation {
|
|
t.Fatal("wrong out[2].Operation")
|
|
}
|
|
if !floatEquals(out[2].T, 0.030) {
|
|
t.Fatal("wrong out[2].T")
|
|
}
|
|
}
|
|
|
|
func TestNewNetworkEventsListGoodUDPAndErrors(t *testing.T) {
|
|
out := NewNetworkEventsList(oonitemplates.Results{
|
|
NetworkEvents: []*modelx.Measurement{
|
|
{
|
|
Connect: &modelx.ConnectEvent{
|
|
DurationSinceBeginning: 10 * time.Millisecond,
|
|
Error: errors.New("mocked error"),
|
|
RemoteAddress: "1.1.1.1:443",
|
|
},
|
|
},
|
|
{
|
|
Read: &modelx.ReadEvent{
|
|
DurationSinceBeginning: 20 * time.Millisecond,
|
|
Error: errors.New("mocked error"),
|
|
NumBytes: 1789,
|
|
},
|
|
},
|
|
{
|
|
Write: &modelx.WriteEvent{
|
|
DurationSinceBeginning: 30 * time.Millisecond,
|
|
Error: errors.New("mocked error"),
|
|
NumBytes: 17714,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if len(out) != 3 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
|
|
if out[0].Address != "1.1.1.1:443" {
|
|
t.Fatal("wrong out[0].Address")
|
|
}
|
|
if *out[0].Failure != "mocked error" {
|
|
t.Fatal("wrong out[0].Failure")
|
|
}
|
|
if out[0].NumBytes != 0 {
|
|
t.Fatal("wrong out[0].NumBytes")
|
|
}
|
|
if out[0].Operation != errorsx.ConnectOperation {
|
|
t.Fatal("wrong out[0].Operation")
|
|
}
|
|
if !floatEquals(out[0].T, 0.010) {
|
|
t.Fatal("wrong out[0].T")
|
|
}
|
|
|
|
if out[1].Address != "" {
|
|
t.Fatal("wrong out[1].Address")
|
|
}
|
|
if *out[1].Failure != "mocked error" {
|
|
t.Fatal("wrong out[1].Failure")
|
|
}
|
|
if out[1].NumBytes != 1789 {
|
|
t.Fatal("wrong out[1].NumBytes")
|
|
}
|
|
if out[1].Operation != errorsx.ReadOperation {
|
|
t.Fatal("wrong out[1].Operation")
|
|
}
|
|
if !floatEquals(out[1].T, 0.020) {
|
|
t.Fatal("wrong out[1].T")
|
|
}
|
|
|
|
if out[2].Address != "" {
|
|
t.Fatal("wrong out[2].Address")
|
|
}
|
|
if *out[2].Failure != "mocked error" {
|
|
t.Fatal("wrong out[2].Failure")
|
|
}
|
|
if out[2].NumBytes != 17714 {
|
|
t.Fatal("wrong out[2].NumBytes")
|
|
}
|
|
if out[2].Operation != errorsx.WriteOperation {
|
|
t.Fatal("wrong out[2].Operation")
|
|
}
|
|
if !floatEquals(out[2].T, 0.030) {
|
|
t.Fatal("wrong out[2].T")
|
|
}
|
|
}
|
|
|
|
func floatEquals(a, b float64) bool {
|
|
const c = 1e-03
|
|
return (a-b) < c && (b-a) < c
|
|
}
|
|
|
|
func TestNewTLSHandshakesListEmpty(t *testing.T) {
|
|
out := NewTLSHandshakesList(oonitemplates.Results{})
|
|
if len(out) != 0 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
}
|
|
|
|
func TestNewTLSHandshakesListSuccess(t *testing.T) {
|
|
out := NewTLSHandshakesList(oonitemplates.Results{
|
|
TLSHandshakes: []*modelx.TLSHandshakeDoneEvent{
|
|
{},
|
|
{
|
|
Error: errors.New("mocked error"),
|
|
},
|
|
{
|
|
ConnectionState: modelx.TLSConnectionState{
|
|
CipherSuite: tls.TLS_AES_128_GCM_SHA256,
|
|
NegotiatedProtocol: "h2",
|
|
PeerCertificates: []modelx.X509Certificate{
|
|
{
|
|
Data: []byte("deadbeef"),
|
|
},
|
|
{
|
|
Data: []byte("abad1dea"),
|
|
},
|
|
},
|
|
Version: tls.VersionTLS11,
|
|
},
|
|
DurationSinceBeginning: 10 * time.Millisecond,
|
|
},
|
|
},
|
|
})
|
|
if len(out) != 3 {
|
|
t.Fatal("unexpected output length")
|
|
}
|
|
|
|
if out[0].CipherSuite != "" {
|
|
t.Fatal("invalid out[0].CipherSuite")
|
|
}
|
|
if out[0].Failure != nil {
|
|
t.Fatal("invalid out[0].Failure")
|
|
}
|
|
if out[0].NegotiatedProtocol != "" {
|
|
t.Fatal("invalid out[0].NegotiatedProtocol")
|
|
}
|
|
if len(out[0].PeerCertificates) != 0 {
|
|
t.Fatal("invalid out[0].PeerCertificates")
|
|
}
|
|
if !floatEquals(out[0].T, 0) {
|
|
t.Fatal("invalid out[0].T")
|
|
}
|
|
if out[0].TLSVersion != "" {
|
|
t.Fatal("invalid out[0].TLSVersion")
|
|
}
|
|
|
|
if out[1].CipherSuite != "" {
|
|
t.Fatal("invalid out[1].CipherSuite")
|
|
}
|
|
if *out[1].Failure != "mocked error" {
|
|
t.Fatal("invalid out[1].Failure")
|
|
}
|
|
if out[1].NegotiatedProtocol != "" {
|
|
t.Fatal("invalid out[1].NegotiatedProtocol")
|
|
}
|
|
if len(out[1].PeerCertificates) != 0 {
|
|
t.Fatal("invalid out[1].PeerCertificates")
|
|
}
|
|
if !floatEquals(out[1].T, 0) {
|
|
t.Fatal("invalid out[1].T")
|
|
}
|
|
if out[1].TLSVersion != "" {
|
|
t.Fatal("invalid out[1].TLSVersion")
|
|
}
|
|
|
|
if out[2].CipherSuite != "TLS_AES_128_GCM_SHA256" {
|
|
t.Fatal("invalid out[2].CipherSuite")
|
|
}
|
|
if out[2].Failure != nil {
|
|
t.Fatal("invalid out[2].Failure")
|
|
}
|
|
if out[2].NegotiatedProtocol != "h2" {
|
|
t.Fatal("invalid out[2].NegotiatedProtocol")
|
|
}
|
|
if len(out[2].PeerCertificates) != 2 {
|
|
t.Fatal("invalid out[2].PeerCertificates")
|
|
}
|
|
if !floatEquals(out[2].T, 0.010) {
|
|
t.Fatal("invalid out[2].T")
|
|
}
|
|
if out[2].TLSVersion != "TLSv1.1" {
|
|
t.Fatal("invalid out[2].TLSVersion")
|
|
}
|
|
|
|
for idx, mbv := range out[2].PeerCertificates {
|
|
if idx == 0 && mbv.Value != "deadbeef" {
|
|
t.Fatal("invalid first certificate")
|
|
}
|
|
if idx == 1 && mbv.Value != "abad1dea" {
|
|
t.Fatal("invalid second certificate")
|
|
}
|
|
if idx < 0 || idx > 1 {
|
|
t.Fatal("invalid index")
|
|
}
|
|
}
|
|
}
|