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
1056 lines
24 KiB
Go
1056 lines
24 KiB
Go
package archival_test
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite/errorsx"
|
|
)
|
|
|
|
func TestNewTCPConnectList(t *testing.T) {
|
|
begin := time.Now()
|
|
type args struct {
|
|
begin time.Time
|
|
events []trace.Event
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want []archival.TCPConnectEntry
|
|
}{{
|
|
name: "empty run",
|
|
args: args{
|
|
begin: begin,
|
|
events: nil,
|
|
},
|
|
want: nil,
|
|
}, {
|
|
name: "realistic run",
|
|
args: args{
|
|
begin: begin,
|
|
events: []trace.Event{{
|
|
Addresses: []string{"8.8.8.8", "8.8.4.4"},
|
|
Hostname: "dns.google.com",
|
|
Name: "resolve_done",
|
|
Time: begin.Add(100 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Duration: 30 * time.Millisecond,
|
|
Name: errorsx.ConnectOperation,
|
|
Proto: "tcp",
|
|
Time: begin.Add(130 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Duration: 55 * time.Millisecond,
|
|
Name: errorsx.ConnectOperation,
|
|
Proto: "udp",
|
|
Time: begin.Add(130 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.4.4:53",
|
|
Duration: 50 * time.Millisecond,
|
|
Err: io.EOF,
|
|
Name: errorsx.ConnectOperation,
|
|
Proto: "tcp",
|
|
Time: begin.Add(180 * time.Millisecond),
|
|
}},
|
|
},
|
|
want: []archival.TCPConnectEntry{{
|
|
IP: "8.8.8.8",
|
|
Port: 853,
|
|
Status: archival.TCPConnectStatus{
|
|
Success: true,
|
|
},
|
|
T: 0.13,
|
|
}, {
|
|
IP: "8.8.4.4",
|
|
Port: 53,
|
|
Status: archival.TCPConnectStatus{
|
|
Failure: archival.NewFailure(io.EOF),
|
|
Success: false,
|
|
},
|
|
T: 0.18,
|
|
}},
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := archival.NewTCPConnectList(tt.args.begin, tt.args.events); !reflect.DeepEqual(got, tt.want) {
|
|
t.Error(cmp.Diff(got, tt.want))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewRequestList(t *testing.T) {
|
|
begin := time.Now()
|
|
type args struct {
|
|
begin time.Time
|
|
events []trace.Event
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want []archival.RequestEntry
|
|
}{{
|
|
name: "empty run",
|
|
args: args{
|
|
begin: begin,
|
|
events: nil,
|
|
},
|
|
want: nil,
|
|
}, {
|
|
name: "realistic run",
|
|
args: args{
|
|
begin: begin,
|
|
events: []trace.Event{{
|
|
Name: "http_transaction_start",
|
|
Time: begin.Add(10 * time.Millisecond),
|
|
}, {
|
|
Name: "http_request_body_snapshot",
|
|
Data: []byte("deadbeef"),
|
|
DataIsTruncated: false,
|
|
}, {
|
|
Name: "http_request_metadata",
|
|
HTTPHeaders: http.Header{
|
|
"User-Agent": []string{"miniooni/0.1.0-dev"},
|
|
},
|
|
HTTPMethod: "POST",
|
|
HTTPURL: "https://www.example.com/submit",
|
|
}, {
|
|
Name: "http_response_metadata",
|
|
HTTPHeaders: http.Header{
|
|
"Server": []string{"miniooni/0.1.0-dev"},
|
|
},
|
|
HTTPStatusCode: 200,
|
|
}, {
|
|
Name: "http_response_body_snapshot",
|
|
Data: []byte("{}"),
|
|
DataIsTruncated: false,
|
|
}, {
|
|
Name: "http_transaction_done",
|
|
}, {
|
|
Name: "http_transaction_start",
|
|
Time: begin.Add(20 * time.Millisecond),
|
|
}, {
|
|
Name: "http_request_metadata",
|
|
HTTPHeaders: http.Header{
|
|
"User-Agent": []string{"miniooni/0.1.0-dev"},
|
|
},
|
|
HTTPMethod: "GET",
|
|
HTTPURL: "https://www.example.com/result",
|
|
}, {
|
|
Name: "http_transaction_done",
|
|
Err: io.EOF,
|
|
}},
|
|
},
|
|
want: []archival.RequestEntry{{
|
|
Failure: archival.NewFailure(io.EOF),
|
|
Request: archival.HTTPRequest{
|
|
HeadersList: []archival.HTTPHeader{{
|
|
Key: "User-Agent",
|
|
Value: archival.MaybeBinaryValue{
|
|
Value: "miniooni/0.1.0-dev",
|
|
},
|
|
}},
|
|
Headers: map[string]archival.MaybeBinaryValue{
|
|
"User-Agent": {Value: "miniooni/0.1.0-dev"},
|
|
},
|
|
Method: "GET",
|
|
URL: "https://www.example.com/result",
|
|
},
|
|
T: 0.02,
|
|
}, {
|
|
Request: archival.HTTPRequest{
|
|
Body: archival.MaybeBinaryValue{
|
|
Value: "deadbeef",
|
|
},
|
|
HeadersList: []archival.HTTPHeader{{
|
|
Key: "User-Agent",
|
|
Value: archival.MaybeBinaryValue{
|
|
Value: "miniooni/0.1.0-dev",
|
|
},
|
|
}},
|
|
Headers: map[string]archival.MaybeBinaryValue{
|
|
"User-Agent": {Value: "miniooni/0.1.0-dev"},
|
|
},
|
|
Method: "POST",
|
|
URL: "https://www.example.com/submit",
|
|
},
|
|
Response: archival.HTTPResponse{
|
|
Body: archival.MaybeBinaryValue{
|
|
Value: "{}",
|
|
},
|
|
Code: 200,
|
|
HeadersList: []archival.HTTPHeader{{
|
|
Key: "Server",
|
|
Value: archival.MaybeBinaryValue{
|
|
Value: "miniooni/0.1.0-dev",
|
|
},
|
|
}},
|
|
Headers: map[string]archival.MaybeBinaryValue{
|
|
"Server": {Value: "miniooni/0.1.0-dev"},
|
|
},
|
|
Locations: nil,
|
|
},
|
|
T: 0.01,
|
|
}},
|
|
}, {
|
|
// for an example of why we need to sort headers, see
|
|
// https://github.com/ooni/probe-cli/v3/internal/engine/pull/751/checks?check_run_id=853562310
|
|
name: "run with redirect and headers to sort",
|
|
args: args{
|
|
begin: begin,
|
|
events: []trace.Event{{
|
|
Name: "http_transaction_start",
|
|
Time: begin.Add(10 * time.Millisecond),
|
|
}, {
|
|
Name: "http_request_metadata",
|
|
HTTPHeaders: http.Header{
|
|
"User-Agent": []string{"miniooni/0.1.0-dev"},
|
|
},
|
|
HTTPMethod: "GET",
|
|
HTTPURL: "https://www.example.com/",
|
|
}, {
|
|
Name: "http_response_metadata",
|
|
HTTPHeaders: http.Header{
|
|
"Server": []string{"miniooni/0.1.0-dev"},
|
|
"Location": []string{"https://x.example.com", "https://y.example.com"},
|
|
},
|
|
HTTPStatusCode: 302,
|
|
}, {
|
|
Name: "http_transaction_done",
|
|
}},
|
|
},
|
|
want: []archival.RequestEntry{{
|
|
Request: archival.HTTPRequest{
|
|
HeadersList: []archival.HTTPHeader{{
|
|
Key: "User-Agent",
|
|
Value: archival.MaybeBinaryValue{
|
|
Value: "miniooni/0.1.0-dev",
|
|
},
|
|
}},
|
|
Headers: map[string]archival.MaybeBinaryValue{
|
|
"User-Agent": {Value: "miniooni/0.1.0-dev"},
|
|
},
|
|
Method: "GET",
|
|
URL: "https://www.example.com/",
|
|
},
|
|
Response: archival.HTTPResponse{
|
|
Code: 302,
|
|
HeadersList: []archival.HTTPHeader{{
|
|
Key: "Location",
|
|
Value: archival.MaybeBinaryValue{
|
|
Value: "https://x.example.com",
|
|
},
|
|
}, {
|
|
Key: "Location",
|
|
Value: archival.MaybeBinaryValue{
|
|
Value: "https://y.example.com",
|
|
},
|
|
}, {
|
|
Key: "Server",
|
|
Value: archival.MaybeBinaryValue{
|
|
Value: "miniooni/0.1.0-dev",
|
|
},
|
|
}},
|
|
Headers: map[string]archival.MaybeBinaryValue{
|
|
"Server": {Value: "miniooni/0.1.0-dev"},
|
|
"Location": {Value: "https://x.example.com"},
|
|
},
|
|
Locations: []string{
|
|
"https://x.example.com", "https://y.example.com",
|
|
},
|
|
},
|
|
T: 0.01,
|
|
}},
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := archival.NewRequestList(tt.args.begin, tt.args.events); !reflect.DeepEqual(got, tt.want) {
|
|
t.Error(cmp.Diff(got, tt.want))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewDNSQueriesList(t *testing.T) {
|
|
begin := time.Now()
|
|
type args struct {
|
|
begin time.Time
|
|
events []trace.Event
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want []archival.DNSQueryEntry
|
|
}{{
|
|
name: "empty run",
|
|
args: args{
|
|
begin: begin,
|
|
events: nil,
|
|
},
|
|
want: nil,
|
|
}, {
|
|
name: "realistic run",
|
|
args: args{
|
|
begin: begin,
|
|
events: []trace.Event{{
|
|
Address: "1.1.1.1:853",
|
|
Addresses: []string{"8.8.8.8", "8.8.4.4"},
|
|
Hostname: "dns.google.com",
|
|
Name: "resolve_done",
|
|
Proto: "dot",
|
|
Time: begin.Add(100 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Duration: 30 * time.Millisecond,
|
|
Name: errorsx.ConnectOperation,
|
|
Proto: "tcp",
|
|
Time: begin.Add(130 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.4.4:53",
|
|
Duration: 50 * time.Millisecond,
|
|
Err: io.EOF,
|
|
Name: errorsx.ConnectOperation,
|
|
Proto: "tcp",
|
|
Time: begin.Add(180 * time.Millisecond),
|
|
}},
|
|
},
|
|
want: []archival.DNSQueryEntry{{
|
|
Answers: []archival.DNSAnswerEntry{{
|
|
ASN: 15169,
|
|
ASOrgName: "Google LLC",
|
|
AnswerType: "A",
|
|
IPv4: "8.8.8.8",
|
|
}, {
|
|
ASN: 15169,
|
|
ASOrgName: "Google LLC",
|
|
AnswerType: "A",
|
|
IPv4: "8.8.4.4",
|
|
}},
|
|
Engine: "dot",
|
|
Hostname: "dns.google.com",
|
|
QueryType: "A",
|
|
ResolverAddress: "1.1.1.1:853",
|
|
T: 0.1,
|
|
}},
|
|
}, {
|
|
name: "run with IPv6 results",
|
|
args: args{
|
|
begin: begin,
|
|
events: []trace.Event{{
|
|
Addresses: []string{"2001:4860:4860::8888"},
|
|
Hostname: "dns.google.com",
|
|
Name: "resolve_done",
|
|
Time: begin.Add(200 * time.Millisecond),
|
|
}},
|
|
},
|
|
want: []archival.DNSQueryEntry{{
|
|
Answers: []archival.DNSAnswerEntry{{
|
|
ASN: 15169,
|
|
ASOrgName: "Google LLC",
|
|
AnswerType: "AAAA",
|
|
IPv6: "2001:4860:4860::8888",
|
|
}},
|
|
Hostname: "dns.google.com",
|
|
QueryType: "AAAA",
|
|
T: 0.2,
|
|
}},
|
|
}, {
|
|
name: "run with errors",
|
|
args: args{
|
|
begin: begin,
|
|
events: []trace.Event{{
|
|
Err: &errorsx.ErrWrapper{Failure: errorsx.FailureDNSNXDOMAINError},
|
|
Hostname: "dns.google.com",
|
|
Name: "resolve_done",
|
|
Time: begin.Add(200 * time.Millisecond),
|
|
}},
|
|
},
|
|
want: []archival.DNSQueryEntry{{
|
|
Answers: nil,
|
|
Failure: archival.NewFailure(
|
|
&errorsx.ErrWrapper{Failure: errorsx.FailureDNSNXDOMAINError}),
|
|
Hostname: "dns.google.com",
|
|
QueryType: "A",
|
|
T: 0.2,
|
|
}, {
|
|
Answers: nil,
|
|
Failure: archival.NewFailure(
|
|
&errorsx.ErrWrapper{Failure: errorsx.FailureDNSNXDOMAINError}),
|
|
Hostname: "dns.google.com",
|
|
QueryType: "AAAA",
|
|
T: 0.2,
|
|
}},
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := archival.NewDNSQueriesList(tt.args.begin, tt.args.events)
|
|
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewNetworkEventsList(t *testing.T) {
|
|
begin := time.Now()
|
|
type args struct {
|
|
begin time.Time
|
|
events []trace.Event
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want []archival.NetworkEvent
|
|
}{{
|
|
name: "empty run",
|
|
args: args{
|
|
begin: begin,
|
|
events: nil,
|
|
},
|
|
want: nil,
|
|
}, {
|
|
name: "realistic run",
|
|
args: args{
|
|
begin: begin,
|
|
events: []trace.Event{{
|
|
Name: errorsx.ConnectOperation,
|
|
Address: "8.8.8.8:853",
|
|
Err: io.EOF,
|
|
Proto: "tcp",
|
|
Time: begin.Add(7 * time.Millisecond),
|
|
}, {
|
|
Name: errorsx.ReadOperation,
|
|
Err: context.Canceled,
|
|
NumBytes: 7117,
|
|
Time: begin.Add(11 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Name: errorsx.ReadFromOperation,
|
|
Err: context.Canceled,
|
|
NumBytes: 7117,
|
|
Time: begin.Add(11 * time.Millisecond),
|
|
}, {
|
|
Name: errorsx.WriteOperation,
|
|
Err: websocket.ErrBadHandshake,
|
|
NumBytes: 4114,
|
|
Time: begin.Add(14 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Name: errorsx.WriteToOperation,
|
|
Err: websocket.ErrBadHandshake,
|
|
NumBytes: 4114,
|
|
Time: begin.Add(14 * time.Millisecond),
|
|
}, {
|
|
Name: errorsx.CloseOperation,
|
|
Err: websocket.ErrReadLimit,
|
|
Time: begin.Add(17 * time.Millisecond),
|
|
}},
|
|
},
|
|
want: []archival.NetworkEvent{{
|
|
Address: "8.8.8.8:853",
|
|
Failure: archival.NewFailure(io.EOF),
|
|
Operation: errorsx.ConnectOperation,
|
|
Proto: "tcp",
|
|
T: 0.007,
|
|
}, {
|
|
Failure: archival.NewFailure(context.Canceled),
|
|
NumBytes: 7117,
|
|
Operation: errorsx.ReadOperation,
|
|
T: 0.011,
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Failure: archival.NewFailure(context.Canceled),
|
|
NumBytes: 7117,
|
|
Operation: errorsx.ReadFromOperation,
|
|
T: 0.011,
|
|
}, {
|
|
Failure: archival.NewFailure(websocket.ErrBadHandshake),
|
|
NumBytes: 4114,
|
|
Operation: errorsx.WriteOperation,
|
|
T: 0.014,
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Failure: archival.NewFailure(websocket.ErrBadHandshake),
|
|
NumBytes: 4114,
|
|
Operation: errorsx.WriteToOperation,
|
|
T: 0.014,
|
|
}, {
|
|
Failure: archival.NewFailure(websocket.ErrReadLimit),
|
|
Operation: errorsx.CloseOperation,
|
|
T: 0.017,
|
|
}},
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := archival.NewNetworkEventsList(tt.args.begin, tt.args.events); !reflect.DeepEqual(got, tt.want) {
|
|
t.Error(cmp.Diff(got, tt.want))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewTLSHandshakesList(t *testing.T) {
|
|
begin := time.Now()
|
|
type args struct {
|
|
begin time.Time
|
|
events []trace.Event
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want []archival.TLSHandshake
|
|
}{{
|
|
name: "empty run",
|
|
args: args{
|
|
begin: begin,
|
|
events: nil,
|
|
},
|
|
want: nil,
|
|
}, {
|
|
name: "realistic run",
|
|
args: args{
|
|
begin: begin,
|
|
events: []trace.Event{{
|
|
Name: errorsx.CloseOperation,
|
|
Err: websocket.ErrReadLimit,
|
|
Time: begin.Add(17 * time.Millisecond),
|
|
}, {
|
|
Name: "tls_handshake_done",
|
|
Err: io.EOF,
|
|
NoTLSVerify: false,
|
|
TLSCipherSuite: "SUITE",
|
|
TLSNegotiatedProto: "h2",
|
|
TLSPeerCerts: []*x509.Certificate{{
|
|
Raw: []byte("deadbeef"),
|
|
}, {
|
|
Raw: []byte("abad1dea"),
|
|
}},
|
|
TLSServerName: "x.org",
|
|
TLSVersion: "TLSv1.3",
|
|
Time: begin.Add(55 * time.Millisecond),
|
|
}},
|
|
},
|
|
want: []archival.TLSHandshake{{
|
|
CipherSuite: "SUITE",
|
|
Failure: archival.NewFailure(io.EOF),
|
|
NegotiatedProtocol: "h2",
|
|
NoTLSVerify: false,
|
|
PeerCertificates: []archival.MaybeBinaryValue{{
|
|
Value: "deadbeef",
|
|
}, {
|
|
Value: "abad1dea",
|
|
}},
|
|
ServerName: "x.org",
|
|
T: 0.055,
|
|
TLSVersion: "TLSv1.3",
|
|
}},
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := archival.NewTLSHandshakesList(tt.args.begin, tt.args.events); !reflect.DeepEqual(got, tt.want) {
|
|
t.Error(cmp.Diff(got, tt.want))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtSpec_AddTo(t *testing.T) {
|
|
m := new(model.Measurement)
|
|
archival.ExtDNS.AddTo(m)
|
|
expected := map[string]int64{"dnst": 0}
|
|
if d := cmp.Diff(m.Extensions, expected); d != "" {
|
|
t.Fatal(d)
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
var encodedBinaryInput = []byte(`{"data":"V+V5+6a7DbzOvaeguqR4eBJZ7mg5pAeYxT68Vcv+NDx+G1qzIp3BLW7KW/EQJUceROItYAjqsArMBUig9Xg48Ns/nZ8lb4kAlpOvQ6xNyawT2yK+en3ZJKJSadiJwdFXqgQrotixGfbVETm7gM+G+V+djKv1xXQkOqLUQE7XEB8=","format":"base64"}`)
|
|
|
|
func TestMaybeBinaryValue_MarshalJSON(t *testing.T) {
|
|
type fields struct {
|
|
Value string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
want []byte
|
|
wantErr bool
|
|
}{{
|
|
name: "with string input",
|
|
fields: fields{
|
|
Value: "antani",
|
|
},
|
|
want: []byte(`"antani"`),
|
|
wantErr: false,
|
|
}, {
|
|
name: "with binary input",
|
|
fields: fields{
|
|
Value: string(binaryInput),
|
|
},
|
|
want: encodedBinaryInput,
|
|
wantErr: false,
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hb := archival.MaybeBinaryValue{
|
|
Value: tt.fields.Value,
|
|
}
|
|
got, err := hb.MarshalJSON()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("MaybeBinaryValue.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Error(cmp.Diff(got, tt.want))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMaybeBinaryValue_UnmarshalJSON(t *testing.T) {
|
|
type fields struct {
|
|
WantValue string
|
|
}
|
|
type args struct {
|
|
d []byte
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr bool
|
|
}{{
|
|
name: "with string input",
|
|
fields: fields{
|
|
WantValue: "xo",
|
|
},
|
|
args: args{d: []byte(`"xo"`)},
|
|
wantErr: false,
|
|
}, {
|
|
name: "with nil input",
|
|
fields: fields{
|
|
WantValue: "",
|
|
},
|
|
args: args{d: nil},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with missing/invalid format",
|
|
fields: fields{
|
|
WantValue: "",
|
|
},
|
|
args: args{d: []byte(`{"format": "foo"}`)},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with missing data",
|
|
fields: fields{
|
|
WantValue: "",
|
|
},
|
|
args: args{d: []byte(`{"format": "base64"}`)},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with invalid base64 data",
|
|
fields: fields{
|
|
WantValue: "",
|
|
},
|
|
args: args{d: []byte(`{"format": "base64", "data": "x"}`)},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with valid base64 data",
|
|
fields: fields{
|
|
WantValue: string(binaryInput),
|
|
},
|
|
args: args{d: encodedBinaryInput},
|
|
wantErr: false,
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hb := &archival.MaybeBinaryValue{}
|
|
if err := hb.UnmarshalJSON(tt.args.d); (err != nil) != tt.wantErr {
|
|
t.Errorf("MaybeBinaryValue.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
if d := cmp.Diff(tt.fields.WantValue, hb.Value); d != "" {
|
|
t.Error(d)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHTTPHeader_MarshalJSON(t *testing.T) {
|
|
type fields struct {
|
|
Key string
|
|
Value archival.MaybeBinaryValue
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
want []byte
|
|
wantErr bool
|
|
}{{
|
|
name: "with string value",
|
|
fields: fields{
|
|
Key: "Content-Type",
|
|
Value: archival.MaybeBinaryValue{
|
|
Value: "text/plain",
|
|
},
|
|
},
|
|
want: []byte(`["Content-Type","text/plain"]`),
|
|
wantErr: false,
|
|
}, {
|
|
name: "with binary value",
|
|
fields: fields{
|
|
Key: "Content-Type",
|
|
Value: archival.MaybeBinaryValue{
|
|
Value: string(binaryInput),
|
|
},
|
|
},
|
|
want: []byte(`["Content-Type",` + string(encodedBinaryInput) + `]`),
|
|
wantErr: false,
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hh := archival.HTTPHeader{
|
|
Key: tt.fields.Key,
|
|
Value: tt.fields.Value,
|
|
}
|
|
got, err := hh.MarshalJSON()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("HTTPHeader.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Error(cmp.Diff(got, tt.want))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHTTPHeader_UnmarshalJSON(t *testing.T) {
|
|
type fields struct {
|
|
WantKey string
|
|
WantValue archival.MaybeBinaryValue
|
|
}
|
|
type args struct {
|
|
d []byte
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr bool
|
|
}{{
|
|
name: "with invalid input",
|
|
fields: fields{
|
|
WantKey: "",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`{}`),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with unexpected number of items",
|
|
fields: fields{
|
|
WantKey: "",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`[]`),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with first item not being a string",
|
|
fields: fields{
|
|
WantKey: "",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`[0,0]`),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with both items being a string",
|
|
fields: fields{
|
|
WantKey: "x",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "y",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`["x","y"]`),
|
|
},
|
|
wantErr: false,
|
|
}, {
|
|
name: "with second item not being a map[string]interface{}",
|
|
fields: fields{
|
|
WantKey: "",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`["x",[]]`),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with missing format key in second item",
|
|
fields: fields{
|
|
WantKey: "",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`["x",{}]`),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with format value not being base64",
|
|
fields: fields{
|
|
WantKey: "",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`["x",{"format":1}]`),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with missing data field",
|
|
fields: fields{
|
|
WantKey: "",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`["x",{"format":"base64"}]`),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with data not being a string",
|
|
fields: fields{
|
|
WantKey: "",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`["x",{"format":"base64","data":1}]`),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with data not being base64",
|
|
fields: fields{
|
|
WantKey: "",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: "",
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`["x",{"format":"base64","data":"xx"}]`),
|
|
},
|
|
wantErr: true,
|
|
}, {
|
|
name: "with correctly encoded base64 data",
|
|
fields: fields{
|
|
WantKey: "x",
|
|
WantValue: archival.MaybeBinaryValue{
|
|
Value: string(binaryInput),
|
|
},
|
|
},
|
|
args: args{
|
|
d: []byte(`["x",` + string(encodedBinaryInput) + `]`),
|
|
},
|
|
wantErr: false,
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hh := &archival.HTTPHeader{}
|
|
if err := hh.UnmarshalJSON(tt.args.d); (err != nil) != tt.wantErr {
|
|
t.Errorf("HTTPHeader.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
expect := &archival.HTTPHeader{
|
|
Key: tt.fields.WantKey,
|
|
Value: tt.fields.WantValue,
|
|
}
|
|
if d := cmp.Diff(hh, expect); d != "" {
|
|
t.Error(d)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewFailure(t *testing.T) {
|
|
type args struct {
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *string
|
|
}{{
|
|
name: "when error is nil",
|
|
args: args{
|
|
err: nil,
|
|
},
|
|
want: nil,
|
|
}, {
|
|
name: "when error is wrapped and failure meaningful",
|
|
args: args{
|
|
err: &errorsx.ErrWrapper{
|
|
Failure: errorsx.FailureConnectionRefused,
|
|
},
|
|
},
|
|
want: func() *string {
|
|
s := errorsx.FailureConnectionRefused
|
|
return &s
|
|
}(),
|
|
}, {
|
|
name: "when error is wrapped and failure is not meaningful",
|
|
args: args{
|
|
err: &errorsx.ErrWrapper{},
|
|
},
|
|
want: func() *string {
|
|
s := "unknown_failure: errWrapper.Failure is empty"
|
|
return &s
|
|
}(),
|
|
}, {
|
|
name: "when error is not wrapped but wrappable",
|
|
args: args{err: io.EOF},
|
|
want: func() *string {
|
|
s := "eof_error"
|
|
return &s
|
|
}(),
|
|
}, {
|
|
name: "when the error is not wrapped and not wrappable",
|
|
args: args{
|
|
err: errors.New("use of closed socket 127.0.0.1:8080->10.0.0.1:22"),
|
|
},
|
|
want: func() *string {
|
|
s := "unknown_failure: use of closed socket [scrubbed]->[scrubbed]"
|
|
return &s
|
|
}(),
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := archival.NewFailure(tt.args.err)
|
|
if tt.want == nil && got == nil {
|
|
return
|
|
}
|
|
if tt.want == nil && got != nil {
|
|
t.Errorf("NewFailure: want %+v, got %s", tt.want, *got)
|
|
return
|
|
}
|
|
if tt.want != nil && got == nil {
|
|
t.Errorf("NewFailure: want %s, got %+v", *tt.want, got)
|
|
return
|
|
}
|
|
if *tt.want != *got {
|
|
t.Errorf("NewFailure: want %s, got %s", *tt.want, *got)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewFailedOperation(t *testing.T) {
|
|
type args struct {
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *string
|
|
}{{
|
|
name: "With no error",
|
|
args: args{
|
|
err: nil, // explicit
|
|
},
|
|
want: nil, // explicit
|
|
}, {
|
|
name: "With wrapped error and non-empty operation",
|
|
args: args{
|
|
err: &errorsx.ErrWrapper{
|
|
Failure: errorsx.FailureConnectionRefused,
|
|
Operation: errorsx.ConnectOperation,
|
|
},
|
|
},
|
|
want: (func() *string {
|
|
s := errorsx.ConnectOperation
|
|
return &s
|
|
})(),
|
|
}, {
|
|
name: "With wrapped error and empty operation",
|
|
args: args{
|
|
err: &errorsx.ErrWrapper{
|
|
Failure: errorsx.FailureConnectionRefused,
|
|
},
|
|
},
|
|
want: (func() *string {
|
|
s := errorsx.UnknownOperation
|
|
return &s
|
|
})(),
|
|
}, {
|
|
name: "With non wrapped error",
|
|
args: args{
|
|
err: io.EOF,
|
|
},
|
|
want: (func() *string {
|
|
s := errorsx.UnknownOperation
|
|
return &s
|
|
})(),
|
|
}}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := archival.NewFailedOperation(tt.args.err)
|
|
if got == nil && tt.want == nil {
|
|
return
|
|
}
|
|
if got == nil && tt.want != nil {
|
|
t.Errorf("NewFailedOperation() = %v, want %v", got, tt.want)
|
|
return
|
|
}
|
|
if got != nil && tt.want == nil {
|
|
t.Errorf("NewFailedOperation() = %v, want %v", got, tt.want)
|
|
return
|
|
}
|
|
if got != nil && tt.want != nil && *got != *tt.want {
|
|
t.Errorf("NewFailedOperation() = %v, want %v", got, tt.want)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
}
|