273b70bacc
## Checklist - [x] I have read the [contribution guidelines](https://github.com/ooni/probe-cli/blob/master/CONTRIBUTING.md) - [x] reference issue for this pull request: https://github.com/ooni/probe/issues/1885 - [x] related ooni/spec pull request: N/A Location of the issue tracker: https://github.com/ooni/probe ## Description This PR contains a set of changes to move important interfaces and data types into the `./internal/model` package. The criteria for including an interface or data type in here is roughly that the type should be important and used by several packages. We are especially interested to move more interfaces here to increase modularity. An additional side effect is that, by reading this package, one should be able to understand more quickly how different parts of the codebase interact with each other. This is what I want to move in `internal/model`: - [x] most important interfaces from `internal/netxlite` - [x] everything that was previously part of `internal/engine/model` - [x] mocks from `internal/netxlite/mocks` should also be moved in here as a subpackage
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/netx/archival"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/netx/trace"
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
)
|
|
|
|
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: netxlite.ConnectOperation,
|
|
Proto: "tcp",
|
|
Time: begin.Add(130 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Duration: 55 * time.Millisecond,
|
|
Name: netxlite.ConnectOperation,
|
|
Proto: "udp",
|
|
Time: begin.Add(130 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.4.4:53",
|
|
Duration: 50 * time.Millisecond,
|
|
Err: io.EOF,
|
|
Name: netxlite.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-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: netxlite.ConnectOperation,
|
|
Proto: "tcp",
|
|
Time: begin.Add(130 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.4.4:53",
|
|
Duration: 50 * time.Millisecond,
|
|
Err: io.EOF,
|
|
Name: netxlite.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: &netxlite.ErrWrapper{Failure: netxlite.FailureDNSNXDOMAINError},
|
|
Hostname: "dns.google.com",
|
|
Name: "resolve_done",
|
|
Time: begin.Add(200 * time.Millisecond),
|
|
}},
|
|
},
|
|
want: []archival.DNSQueryEntry{{
|
|
Answers: nil,
|
|
Failure: archival.NewFailure(
|
|
&netxlite.ErrWrapper{Failure: netxlite.FailureDNSNXDOMAINError}),
|
|
Hostname: "dns.google.com",
|
|
QueryType: "A",
|
|
T: 0.2,
|
|
}, {
|
|
Answers: nil,
|
|
Failure: archival.NewFailure(
|
|
&netxlite.ErrWrapper{Failure: netxlite.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: netxlite.ConnectOperation,
|
|
Address: "8.8.8.8:853",
|
|
Err: io.EOF,
|
|
Proto: "tcp",
|
|
Time: begin.Add(7 * time.Millisecond),
|
|
}, {
|
|
Name: netxlite.ReadOperation,
|
|
Err: context.Canceled,
|
|
NumBytes: 7117,
|
|
Time: begin.Add(11 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Name: netxlite.ReadFromOperation,
|
|
Err: context.Canceled,
|
|
NumBytes: 7117,
|
|
Time: begin.Add(11 * time.Millisecond),
|
|
}, {
|
|
Name: netxlite.WriteOperation,
|
|
Err: websocket.ErrBadHandshake,
|
|
NumBytes: 4114,
|
|
Time: begin.Add(14 * time.Millisecond),
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Name: netxlite.WriteToOperation,
|
|
Err: websocket.ErrBadHandshake,
|
|
NumBytes: 4114,
|
|
Time: begin.Add(14 * time.Millisecond),
|
|
}, {
|
|
Name: netxlite.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: netxlite.ConnectOperation,
|
|
Proto: "tcp",
|
|
T: 0.007,
|
|
}, {
|
|
Failure: archival.NewFailure(context.Canceled),
|
|
NumBytes: 7117,
|
|
Operation: netxlite.ReadOperation,
|
|
T: 0.011,
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Failure: archival.NewFailure(context.Canceled),
|
|
NumBytes: 7117,
|
|
Operation: netxlite.ReadFromOperation,
|
|
T: 0.011,
|
|
}, {
|
|
Failure: archival.NewFailure(websocket.ErrBadHandshake),
|
|
NumBytes: 4114,
|
|
Operation: netxlite.WriteOperation,
|
|
T: 0.014,
|
|
}, {
|
|
Address: "8.8.8.8:853",
|
|
Failure: archival.NewFailure(websocket.ErrBadHandshake),
|
|
NumBytes: 4114,
|
|
Operation: netxlite.WriteToOperation,
|
|
T: 0.014,
|
|
}, {
|
|
Failure: archival.NewFailure(websocket.ErrReadLimit),
|
|
Operation: netxlite.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: netxlite.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: &netxlite.ErrWrapper{
|
|
Failure: netxlite.FailureConnectionRefused,
|
|
},
|
|
},
|
|
want: func() *string {
|
|
s := netxlite.FailureConnectionRefused
|
|
return &s
|
|
}(),
|
|
}, {
|
|
name: "when error is wrapped and failure is not meaningful",
|
|
args: args{
|
|
err: &netxlite.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: &netxlite.ErrWrapper{
|
|
Failure: netxlite.FailureConnectionRefused,
|
|
Operation: netxlite.ConnectOperation,
|
|
},
|
|
},
|
|
want: (func() *string {
|
|
s := netxlite.ConnectOperation
|
|
return &s
|
|
})(),
|
|
}, {
|
|
name: "With wrapped error and empty operation",
|
|
args: args{
|
|
err: &netxlite.ErrWrapper{
|
|
Failure: netxlite.FailureConnectionRefused,
|
|
},
|
|
},
|
|
want: (func() *string {
|
|
s := netxlite.UnknownOperation
|
|
return &s
|
|
})(),
|
|
}, {
|
|
name: "With non wrapped error",
|
|
args: args{
|
|
err: io.EOF,
|
|
},
|
|
want: (func() *string {
|
|
s := netxlite.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
|
|
}
|
|
})
|
|
}
|
|
}
|