ooni-probe-cli/internal/engine/netx/archival/archival_test.go
Simone Basso 79e8424677
refactor: remove model.ExperimentOrchestraClient (#284)
* ongoing

* while there, make sure we test everything

* reorganize previous commit

* ensure we have reasonable coverage in session

The code in here would be better with unit tests. We have too many
integration tests and the tests overall are too slow. But it's also
true that I should not write a giant diff as part of this PR.
2021-04-02 12:03:18 +02:00

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/errorx"
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
)
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: errorx.ConnectOperation,
Proto: "tcp",
Time: begin.Add(130 * time.Millisecond),
}, {
Address: "8.8.8.8:853",
Duration: 55 * time.Millisecond,
Name: errorx.ConnectOperation,
Proto: "udp",
Time: begin.Add(130 * time.Millisecond),
}, {
Address: "8.8.4.4:53",
Duration: 50 * time.Millisecond,
Err: io.EOF,
Name: errorx.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: errorx.ConnectOperation,
Proto: "tcp",
Time: begin.Add(130 * time.Millisecond),
}, {
Address: "8.8.4.4:53",
Duration: 50 * time.Millisecond,
Err: io.EOF,
Name: errorx.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: &errorx.ErrWrapper{Failure: errorx.FailureDNSNXDOMAINError},
Hostname: "dns.google.com",
Name: "resolve_done",
Time: begin.Add(200 * time.Millisecond),
}},
},
want: []archival.DNSQueryEntry{{
Answers: nil,
Failure: archival.NewFailure(
&errorx.ErrWrapper{Failure: errorx.FailureDNSNXDOMAINError}),
Hostname: "dns.google.com",
QueryType: "A",
T: 0.2,
}, {
Answers: nil,
Failure: archival.NewFailure(
&errorx.ErrWrapper{Failure: errorx.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: errorx.ConnectOperation,
Address: "8.8.8.8:853",
Err: io.EOF,
Proto: "tcp",
Time: begin.Add(7 * time.Millisecond),
}, {
Name: errorx.ReadOperation,
Err: context.Canceled,
NumBytes: 7117,
Time: begin.Add(11 * time.Millisecond),
}, {
Address: "8.8.8.8:853",
Name: errorx.ReadFromOperation,
Err: context.Canceled,
NumBytes: 7117,
Time: begin.Add(11 * time.Millisecond),
}, {
Name: errorx.WriteOperation,
Err: websocket.ErrBadHandshake,
NumBytes: 4114,
Time: begin.Add(14 * time.Millisecond),
}, {
Address: "8.8.8.8:853",
Name: errorx.WriteToOperation,
Err: websocket.ErrBadHandshake,
NumBytes: 4114,
Time: begin.Add(14 * time.Millisecond),
}, {
Name: errorx.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: errorx.ConnectOperation,
Proto: "tcp",
T: 0.007,
}, {
Failure: archival.NewFailure(context.Canceled),
NumBytes: 7117,
Operation: errorx.ReadOperation,
T: 0.011,
}, {
Address: "8.8.8.8:853",
Failure: archival.NewFailure(context.Canceled),
NumBytes: 7117,
Operation: errorx.ReadFromOperation,
T: 0.011,
}, {
Failure: archival.NewFailure(websocket.ErrBadHandshake),
NumBytes: 4114,
Operation: errorx.WriteOperation,
T: 0.014,
}, {
Address: "8.8.8.8:853",
Failure: archival.NewFailure(websocket.ErrBadHandshake),
NumBytes: 4114,
Operation: errorx.WriteToOperation,
T: 0.014,
}, {
Failure: archival.NewFailure(websocket.ErrReadLimit),
Operation: errorx.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: errorx.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: &errorx.ErrWrapper{
Failure: errorx.FailureConnectionRefused,
},
},
want: func() *string {
s := errorx.FailureConnectionRefused
return &s
}(),
}, {
name: "when error is wrapped and failure is not meaningful",
args: args{
err: &errorx.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: &errorx.ErrWrapper{
Failure: errorx.FailureConnectionRefused,
Operation: errorx.ConnectOperation,
},
},
want: (func() *string {
s := errorx.ConnectOperation
return &s
})(),
}, {
name: "With wrapped error and empty operation",
args: args{
err: &errorx.ErrWrapper{
Failure: errorx.FailureConnectionRefused,
},
},
want: (func() *string {
s := errorx.UnknownOperation
return &s
})(),
}, {
name: "With non wrapped error",
args: args{
err: io.EOF,
},
want: (func() *string {
s := errorx.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
}
})
}
}