package archival import ( "errors" "io" "net/http" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" ) // traceTime generates a time that is based off a fixed beginning // in time, so we can easily compare times. func traceTime(d int64) time.Time { t := time.Date(2021, 01, 13, 14, 21, 59, 0, time.UTC) return t.Add(time.Duration(d) * time.Millisecond) } // deltaSinceTraceTime computes the delta since the original // trace time expressed in floating point seconds. func deltaSinceTraceTime(d int64) float64 { return (time.Duration(d) * time.Millisecond).Seconds() } // failureFromString converts a string to a failure. func failureFromString(failure string) *string { return &failure } func TestTraceNewArchivalTCPConnectResultList(t *testing.T) { type fields struct { DNSLookupHTTPS []*DNSLookupEvent DNSLookupHost []*DNSLookupEvent DNSRoundTrip []*DNSRoundTripEvent HTTPRoundTrip []*HTTPRoundTripEvent Network []*NetworkEvent QUICHandshake []*QUICTLSHandshakeEvent TLSHandshake []*QUICTLSHandshakeEvent } type args struct { begin time.Time } tests := []struct { name string fields fields args args wantOut []model.ArchivalTCPConnectResult }{{ name: "with empty trace", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: nil, }, { name: "we ignore I/O operations", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{{ Count: 1024, Failure: nil, Finished: traceTime(2), Network: "tcp", Operation: netxlite.WriteOperation, RemoteAddr: "8.8.8.8:443", Started: traceTime(1), }, { Count: 4096, Failure: nil, Finished: traceTime(4), Network: "tcp", Operation: netxlite.ReadOperation, RemoteAddr: "8.8.8.8:443", Started: traceTime(3), }}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: nil, }, { name: "we ignore UDP connect", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{{ Count: 0, Failure: nil, Finished: traceTime(2), Network: "udp", Operation: netxlite.ConnectOperation, RemoteAddr: "8.8.8.8:53", Started: traceTime(1), }}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: nil, }, { name: "with TCP connect success", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{{ Count: 0, Failure: nil, Finished: traceTime(2), Network: "tcp", Operation: netxlite.ConnectOperation, RemoteAddr: "8.8.8.8:443", Started: traceTime(1), }}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalTCPConnectResult{{ IP: "8.8.8.8", Port: 443, Status: model.ArchivalTCPConnectStatus{ Blocked: nil, Failure: nil, Success: true, }, T: deltaSinceTraceTime(2), }}, }, { name: "with TCP connect failure", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{{ Count: 0, Failure: netxlite.NewTopLevelGenericErrWrapper(netxlite.ECONNREFUSED), Finished: traceTime(2), Network: "tcp", Operation: netxlite.ConnectOperation, RemoteAddr: "8.8.8.8:443", Started: traceTime(1), }}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalTCPConnectResult{{ IP: "8.8.8.8", Port: 443, Status: model.ArchivalTCPConnectStatus{ Blocked: nil, Failure: failureFromString(netxlite.FailureConnectionRefused), Success: false, }, T: deltaSinceTraceTime(2), }}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tr := &Trace{ DNSLookupHTTPS: tt.fields.DNSLookupHTTPS, DNSLookupHost: tt.fields.DNSLookupHost, DNSRoundTrip: tt.fields.DNSRoundTrip, HTTPRoundTrip: tt.fields.HTTPRoundTrip, Network: tt.fields.Network, QUICHandshake: tt.fields.QUICHandshake, TLSHandshake: tt.fields.TLSHandshake, } gotOut := tr.NewArchivalTCPConnectResultList(tt.args.begin) if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" { t.Fatal(diff) } }) } } func TestTraceNewArchivalHTTPRequestResultList(t *testing.T) { type fields struct { DNSLookupHTTPS []*DNSLookupEvent DNSLookupHost []*DNSLookupEvent DNSRoundTrip []*DNSRoundTripEvent HTTPRoundTrip []*HTTPRoundTripEvent Network []*NetworkEvent QUICHandshake []*QUICTLSHandshakeEvent TLSHandshake []*QUICTLSHandshakeEvent } type args struct { begin time.Time } tests := []struct { name string fields fields args args wantOut []model.ArchivalHTTPRequestResult }{{ name: "with empty trace", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: nil, }, { name: "with failure", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{{ Failure: netxlite.NewTopLevelGenericErrWrapper(netxlite.ECONNRESET), Finished: traceTime(2), Method: "GET", RequestHeaders: http.Header{ "Accept": {"*/*"}, "X-Cookie": {"A", "B", "C"}, }, ResponseBody: nil, ResponseBodyIsTruncated: false, ResponseBodyLength: 0, ResponseHeaders: nil, Started: traceTime(1), StatusCode: 0, Transport: "tcp", URL: "http://x.org/", }}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalHTTPRequestResult{{ Failure: failureFromString(netxlite.FailureConnectionReset), Request: model.ArchivalHTTPRequest{ Body: model.ArchivalMaybeBinaryData{}, BodyIsTruncated: false, HeadersList: []model.ArchivalHTTPHeader{{ Key: "Accept", Value: model.ArchivalMaybeBinaryData{ Value: "*/*", }, }, { Key: "X-Cookie", Value: model.ArchivalMaybeBinaryData{ Value: "A", }, }, { Key: "X-Cookie", Value: model.ArchivalMaybeBinaryData{ Value: "B", }, }, { Key: "X-Cookie", Value: model.ArchivalMaybeBinaryData{ Value: "C", }, }}, Headers: map[string]model.ArchivalMaybeBinaryData{ "Accept": {Value: "*/*"}, "X-Cookie": {Value: "A"}, }, Method: "GET", Tor: model.ArchivalHTTPTor{}, Transport: "tcp", URL: "http://x.org/", }, Response: model.ArchivalHTTPResponse{ Body: model.ArchivalMaybeBinaryData{}, BodyIsTruncated: false, Code: 0, HeadersList: nil, Headers: nil, Locations: nil, }, T: deltaSinceTraceTime(2), }}, }, { name: "with success", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{{ Failure: nil, Finished: traceTime(2), Method: "GET", RequestHeaders: http.Header{ "Accept": {"*/*"}, "X-Cookie": {"A", "B", "C"}, }, ResponseBody: []byte("0xdeadbeef"), ResponseBodyIsTruncated: true, ResponseBodyLength: 10, ResponseHeaders: http.Header{ "Server": {"antani/1.0"}, "X-Cookie-Reply": {"C", "D", "F"}, "Location": {"https://x.org/", "https://x.org/robots.txt"}, }, Started: traceTime(1), StatusCode: 302, Transport: "tcp", URL: "http://x.org/", }}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalHTTPRequestResult{{ Failure: nil, Request: model.ArchivalHTTPRequest{ Body: model.ArchivalMaybeBinaryData{}, BodyIsTruncated: false, HeadersList: []model.ArchivalHTTPHeader{{ Key: "Accept", Value: model.ArchivalMaybeBinaryData{ Value: "*/*", }, }, { Key: "X-Cookie", Value: model.ArchivalMaybeBinaryData{ Value: "A", }, }, { Key: "X-Cookie", Value: model.ArchivalMaybeBinaryData{ Value: "B", }, }, { Key: "X-Cookie", Value: model.ArchivalMaybeBinaryData{ Value: "C", }, }}, Headers: map[string]model.ArchivalMaybeBinaryData{ "Accept": {Value: "*/*"}, "X-Cookie": {Value: "A"}, }, Method: "GET", Tor: model.ArchivalHTTPTor{}, Transport: "tcp", URL: "http://x.org/", }, Response: model.ArchivalHTTPResponse{ Body: model.ArchivalMaybeBinaryData{ Value: "0xdeadbeef", }, BodyIsTruncated: true, Code: 302, HeadersList: []model.ArchivalHTTPHeader{{ Key: "Location", Value: model.ArchivalMaybeBinaryData{ Value: "https://x.org/", }, }, { Key: "Location", Value: model.ArchivalMaybeBinaryData{ Value: "https://x.org/robots.txt", }, }, { Key: "Server", Value: model.ArchivalMaybeBinaryData{ Value: "antani/1.0", }, }, { Key: "X-Cookie-Reply", Value: model.ArchivalMaybeBinaryData{ Value: "C", }, }, { Key: "X-Cookie-Reply", Value: model.ArchivalMaybeBinaryData{ Value: "D", }, }, { Key: "X-Cookie-Reply", Value: model.ArchivalMaybeBinaryData{ Value: "F", }, }}, Headers: map[string]model.ArchivalMaybeBinaryData{ "Server": {Value: "antani/1.0"}, "X-Cookie-Reply": {Value: "C"}, "Location": {Value: "https://x.org/"}, }, Locations: []string{ "https://x.org/", "https://x.org/robots.txt", }, }, T: deltaSinceTraceTime(2), }}, }, { name: "The result is sorted by the value of T", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{{ Failure: nil, Finished: traceTime(3), Method: "", RequestHeaders: map[string][]string{}, ResponseBody: []byte{}, ResponseBodyIsTruncated: false, ResponseBodyLength: 0, ResponseHeaders: map[string][]string{}, Started: time.Time{}, StatusCode: 0, Transport: "", URL: "", }, { Failure: nil, Finished: traceTime(2), Method: "", RequestHeaders: map[string][]string{}, ResponseBody: []byte{}, ResponseBodyIsTruncated: false, ResponseBodyLength: 0, ResponseHeaders: map[string][]string{}, Started: time.Time{}, StatusCode: 0, Transport: "", URL: "", }, { Failure: nil, Finished: traceTime(5), Method: "", RequestHeaders: map[string][]string{}, ResponseBody: []byte{}, ResponseBodyIsTruncated: false, ResponseBodyLength: 0, ResponseHeaders: map[string][]string{}, Started: time.Time{}, StatusCode: 0, Transport: "", URL: "", }}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalHTTPRequestResult{{ Failure: nil, Request: model.ArchivalHTTPRequest{}, Response: model.ArchivalHTTPResponse{}, T: deltaSinceTraceTime(5), }, { Failure: nil, Request: model.ArchivalHTTPRequest{}, Response: model.ArchivalHTTPResponse{}, T: deltaSinceTraceTime(3), }, { Failure: nil, Request: model.ArchivalHTTPRequest{}, Response: model.ArchivalHTTPResponse{}, T: deltaSinceTraceTime(2), }}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tr := &Trace{ DNSLookupHTTPS: tt.fields.DNSLookupHTTPS, DNSLookupHost: tt.fields.DNSLookupHost, DNSRoundTrip: tt.fields.DNSRoundTrip, HTTPRoundTrip: tt.fields.HTTPRoundTrip, Network: tt.fields.Network, QUICHandshake: tt.fields.QUICHandshake, TLSHandshake: tt.fields.TLSHandshake, } gotOut := tr.NewArchivalHTTPRequestResultList(tt.args.begin) if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" { t.Fatal(diff) } }) } } func TestTraceNewArchivalDNSLookupResultList(t *testing.T) { type fields struct { DNSLookupHTTPS []*DNSLookupEvent DNSLookupHost []*DNSLookupEvent DNSRoundTrip []*DNSRoundTripEvent HTTPRoundTrip []*HTTPRoundTripEvent Network []*NetworkEvent QUICHandshake []*QUICTLSHandshakeEvent TLSHandshake []*QUICTLSHandshakeEvent } type args struct { begin time.Time } tests := []struct { name string fields fields args args wantOut []model.ArchivalDNSLookupResult }{{ name: "with empty trace", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: nil, }, { name: "with NXDOMAIN failure", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{{ ALPNs: nil, Addresses: nil, Domain: "example.com", Failure: netxlite.NewTopLevelGenericErrWrapper(errors.New(netxlite.DNSNoSuchHostSuffix)), Finished: traceTime(2), LookupType: "", // not processed ResolverAddress: "8.8.8.8:53", ResolverNetwork: "udp", Started: traceTime(1), }}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalDNSLookupResult{{ Answers: nil, Engine: "udp", Failure: failureFromString(netxlite.FailureDNSNXDOMAINError), Hostname: "example.com", QueryType: "A", ResolverHostname: nil, ResolverPort: nil, ResolverAddress: "8.8.8.8:53", T: deltaSinceTraceTime(2), }, { Answers: nil, Engine: "udp", Failure: failureFromString(netxlite.FailureDNSNXDOMAINError), Hostname: "example.com", QueryType: "AAAA", ResolverHostname: nil, ResolverPort: nil, ResolverAddress: "8.8.8.8:53", T: deltaSinceTraceTime(2), }}, }, { name: "with success for A and AAAA", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{{ ALPNs: nil, Addresses: []string{ "8.8.8.8", "8.8.4.4", "2001:4860:4860::8844", }, Domain: "dns.google", Failure: nil, Finished: traceTime(2), LookupType: "", // not processed ResolverAddress: "8.8.8.8:53", ResolverNetwork: "udp", Started: traceTime(1), }}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalDNSLookupResult{{ Answers: []model.ArchivalDNSAnswer{{ ASN: 15169, ASOrgName: "Google LLC", AnswerType: "A", Hostname: "", IPv4: "8.8.8.8", IPv6: "", TTL: nil, }, { ASN: 15169, ASOrgName: "Google LLC", AnswerType: "A", Hostname: "", IPv4: "8.8.4.4", IPv6: "", TTL: nil, }}, Engine: "udp", Failure: nil, Hostname: "dns.google", QueryType: "A", ResolverHostname: nil, ResolverPort: nil, ResolverAddress: "8.8.8.8:53", T: deltaSinceTraceTime(2), }, { Answers: []model.ArchivalDNSAnswer{{ ASN: 15169, ASOrgName: "Google LLC", AnswerType: "AAAA", Hostname: "", IPv4: "", IPv6: "2001:4860:4860::8844", TTL: nil, }}, Engine: "udp", Failure: nil, Hostname: "dns.google", QueryType: "AAAA", ResolverHostname: nil, ResolverPort: nil, ResolverAddress: "8.8.8.8:53", T: deltaSinceTraceTime(2), }}, }, { name: "when a domain has no AAAA addresses", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{{ ALPNs: nil, Addresses: []string{ "8.8.8.8", "8.8.4.4", }, Domain: "dns.google", Failure: nil, Finished: traceTime(2), LookupType: "", // not processed ResolverAddress: "8.8.8.8:53", ResolverNetwork: "udp", Started: traceTime(1), }}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalDNSLookupResult{{ Answers: []model.ArchivalDNSAnswer{{ ASN: 15169, ASOrgName: "Google LLC", AnswerType: "A", Hostname: "", IPv4: "8.8.8.8", IPv6: "", TTL: nil, }, { ASN: 15169, ASOrgName: "Google LLC", AnswerType: "A", Hostname: "", IPv4: "8.8.4.4", IPv6: "", TTL: nil, }}, Engine: "udp", Failure: nil, Hostname: "dns.google", QueryType: "A", ResolverHostname: nil, ResolverPort: nil, ResolverAddress: "8.8.8.8:53", T: deltaSinceTraceTime(2), }}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tr := &Trace{ DNSLookupHTTPS: tt.fields.DNSLookupHTTPS, DNSLookupHost: tt.fields.DNSLookupHost, DNSRoundTrip: tt.fields.DNSRoundTrip, HTTPRoundTrip: tt.fields.HTTPRoundTrip, Network: tt.fields.Network, QUICHandshake: tt.fields.QUICHandshake, TLSHandshake: tt.fields.TLSHandshake, } gotOut := tr.NewArchivalDNSLookupResultList(tt.args.begin) if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" { t.Fatal(diff) } }) } } func TestTraceNewArchivalNetworkEventList(t *testing.T) { type fields struct { DNSLookupHTTPS []*DNSLookupEvent DNSLookupHost []*DNSLookupEvent DNSRoundTrip []*DNSRoundTripEvent HTTPRoundTrip []*HTTPRoundTripEvent Network []*NetworkEvent QUICHandshake []*QUICTLSHandshakeEvent TLSHandshake []*QUICTLSHandshakeEvent } type args struct { begin time.Time } tests := []struct { name string fields fields args args wantOut []model.ArchivalNetworkEvent }{{ name: "with empty trace", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: nil, }, { name: "we fill all the fields we should be filling", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{{ Count: 1234, Failure: netxlite.NewTopLevelGenericErrWrapper(io.EOF), Finished: traceTime(2), Network: "tcp", Operation: netxlite.ReadOperation, RemoteAddr: "8.8.8.8:443", Started: traceTime(1), }}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalNetworkEvent{{ Address: "8.8.8.8:443", Failure: failureFromString(netxlite.FailureEOFError), NumBytes: 1234, Operation: netxlite.ReadOperation, Proto: "tcp", T: deltaSinceTraceTime(2), Tags: nil, }}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tr := &Trace{ DNSLookupHTTPS: tt.fields.DNSLookupHTTPS, DNSLookupHost: tt.fields.DNSLookupHost, DNSRoundTrip: tt.fields.DNSRoundTrip, HTTPRoundTrip: tt.fields.HTTPRoundTrip, Network: tt.fields.Network, QUICHandshake: tt.fields.QUICHandshake, TLSHandshake: tt.fields.TLSHandshake, } gotOut := tr.NewArchivalNetworkEventList(tt.args.begin) if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" { t.Fatal(diff) } }) } } func TestTraceNewArchivalTLSHandshakeResultList(t *testing.T) { type fields struct { DNSLookupHTTPS []*DNSLookupEvent DNSLookupHost []*DNSLookupEvent DNSRoundTrip []*DNSRoundTripEvent HTTPRoundTrip []*HTTPRoundTripEvent Network []*NetworkEvent QUICHandshake []*QUICTLSHandshakeEvent TLSHandshake []*QUICTLSHandshakeEvent } type args struct { begin time.Time } tests := []struct { name string fields fields args args wantOut []model.ArchivalTLSOrQUICHandshakeResult }{{ name: "with empty trace", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{}, }, args: args{ begin: traceTime(0), }, wantOut: nil, }, { name: "we fill all the fields we should be filling", fields: fields{ DNSLookupHTTPS: []*DNSLookupEvent{}, DNSLookupHost: []*DNSLookupEvent{}, DNSRoundTrip: []*DNSRoundTripEvent{}, HTTPRoundTrip: []*HTTPRoundTripEvent{}, Network: []*NetworkEvent{}, QUICHandshake: []*QUICTLSHandshakeEvent{}, TLSHandshake: []*QUICTLSHandshakeEvent{{ ALPN: []string{"h2", "http/1.1"}, CipherSuite: "TLS_AES_128_GCM_SHA256", Failure: netxlite.NewTopLevelGenericErrWrapper(io.EOF), Finished: traceTime(2), NegotiatedProto: "h2", Network: "tcp", PeerCerts: [][]byte{ []byte("deadbeef"), []byte("xox"), }, RemoteAddr: "8.8.8.8:443", SNI: "dns.google", SkipVerify: true, Started: traceTime(1), TLSVersion: "TLSv1.3", }}, }, args: args{ begin: traceTime(0), }, wantOut: []model.ArchivalTLSOrQUICHandshakeResult{{ CipherSuite: "TLS_AES_128_GCM_SHA256", Failure: failureFromString(netxlite.FailureEOFError), NegotiatedProtocol: "h2", NoTLSVerify: true, PeerCertificates: []model.ArchivalMaybeBinaryData{{ Value: "deadbeef", }, { Value: "xox", }}, ServerName: "dns.google", T: deltaSinceTraceTime(2), Tags: nil, TLSVersion: "TLSv1.3", }}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tr := &Trace{ DNSLookupHTTPS: tt.fields.DNSLookupHTTPS, DNSLookupHost: tt.fields.DNSLookupHost, DNSRoundTrip: tt.fields.DNSRoundTrip, HTTPRoundTrip: tt.fields.HTTPRoundTrip, Network: tt.fields.Network, QUICHandshake: tt.fields.QUICHandshake, TLSHandshake: tt.fields.TLSHandshake, } gotOut := tr.NewArchivalTLSHandshakeResultList(tt.args.begin) if diff := cmp.Diff(tt.wantOut, gotOut); diff != "" { t.Fatal(diff) } }) } }