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
837 lines
20 KiB
Go
837 lines
20 KiB
Go
package tor
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/legacy/oonidatamodel"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/legacy/oonitemplates"
|
|
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
"github.com/ooni/probe-cli/v3/internal/scrubber"
|
|
)
|
|
|
|
func TestNewExperimentMeasurer(t *testing.T) {
|
|
measurer := NewExperimentMeasurer(Config{})
|
|
if measurer.ExperimentName() != "tor" {
|
|
t.Fatal("unexpected name")
|
|
}
|
|
if measurer.ExperimentVersion() != "0.3.0" {
|
|
t.Fatal("unexpected version")
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureFetchTorTargetsError(t *testing.T) {
|
|
measurer := NewMeasurer(Config{})
|
|
expected := errors.New("mocked error")
|
|
measurer.fetchTorTargets = func(ctx context.Context, sess model.ExperimentSession, cc string) (map[string]model.OOAPITorTarget, error) {
|
|
return nil, expected
|
|
}
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureFetchTorTargetsEmptyList(t *testing.T) {
|
|
measurer := NewMeasurer(Config{})
|
|
measurer.fetchTorTargets = func(ctx context.Context, sess model.ExperimentSession, cc string) (map[string]model.OOAPITorTarget, error) {
|
|
return nil, nil
|
|
}
|
|
measurement := new(model.Measurement)
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tk := measurement.TestKeys.(*TestKeys)
|
|
if len(tk.Targets) != 0 {
|
|
t.Fatal("expected no targets here")
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureGoodWithMockedOrchestra(t *testing.T) {
|
|
// This test mocks orchestra to return a nil list of targets, so the code runs
|
|
// but we don't perform any actual network actions.
|
|
measurer := NewMeasurer(Config{})
|
|
measurer.fetchTorTargets = func(ctx context.Context, sess model.ExperimentSession, cc string) (map[string]model.OOAPITorTarget, error) {
|
|
return nil, nil
|
|
}
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureGood(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skip test in short mode")
|
|
}
|
|
measurer := NewMeasurer(Config{})
|
|
sess := newsession()
|
|
measurement := new(model.Measurement)
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
sess,
|
|
measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sk, err := measurer.GetSummaryKeys(measurement)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := sk.(SummaryKeys); !ok {
|
|
t.Fatal("invalid type for summary keys")
|
|
}
|
|
}
|
|
|
|
var staticPrivateTestingTargetEndpoint = "192.95.36.142:443"
|
|
|
|
var staticPrivateTestingTarget = model.OOAPITorTarget{
|
|
Address: staticPrivateTestingTargetEndpoint,
|
|
Params: map[string][]string{
|
|
"cert": {
|
|
"qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ",
|
|
},
|
|
"iat-mode": {"1"},
|
|
},
|
|
Protocol: "obfs4",
|
|
Source: "bridgedb",
|
|
}
|
|
|
|
func TestMeasurerMeasureSanitiseOutput(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skip test in short mode")
|
|
}
|
|
measurer := NewMeasurer(Config{})
|
|
sess := newsession()
|
|
key := "xyz-xyz-xyz-theCh2ju-ahG4chei-Ai2eka0a"
|
|
sess.MockableFetchTorTargetsResult = map[string]model.OOAPITorTarget{
|
|
key: staticPrivateTestingTarget,
|
|
}
|
|
measurement := new(model.Measurement)
|
|
err := measurer.Run(
|
|
context.Background(),
|
|
sess,
|
|
measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, err := json.Marshal(measurement)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tk := measurement.TestKeys.(*TestKeys)
|
|
entry := tk.Targets[key]
|
|
if entry.Failure != nil {
|
|
t.Fatal("measurement failed unexpectedly")
|
|
}
|
|
if !bytes.Contains(data, []byte(key)) {
|
|
t.Fatal("cannot find expected key")
|
|
}
|
|
if bytes.Contains(data, []byte(staticPrivateTestingTargetEndpoint)) {
|
|
t.Fatal("endpoint found in serialized measurement")
|
|
}
|
|
if !bytes.Contains(data, []byte("[scrubbed]")) {
|
|
t.Fatal("[scrubbed] not found in serialized measurement")
|
|
}
|
|
}
|
|
|
|
var staticTestingTargets = []model.OOAPITorTarget{
|
|
{
|
|
Address: "192.95.36.142:443",
|
|
Params: map[string][]string{
|
|
"cert": {
|
|
"qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ",
|
|
},
|
|
"iat-mode": {"1"},
|
|
},
|
|
Protocol: "obfs4",
|
|
},
|
|
{
|
|
Address: "66.111.2.131:9030",
|
|
Protocol: "dir_port",
|
|
},
|
|
{
|
|
Address: "66.111.2.131:9001",
|
|
Protocol: "or_port",
|
|
},
|
|
{
|
|
Address: "1.1.1.1:80",
|
|
Protocol: "tcp",
|
|
},
|
|
}
|
|
|
|
func TestMeasurerMeasureTargetsNoInput(t *testing.T) {
|
|
var measurement model.Measurement
|
|
measurer := new(Measurer)
|
|
measurer.measureTargets(
|
|
context.Background(),
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
&measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
nil,
|
|
)
|
|
if len(measurement.TestKeys.(*TestKeys).Targets) != 0 {
|
|
t.Fatal("expected no measurements here")
|
|
}
|
|
}
|
|
|
|
func TestMeasurerMeasureTargetsCanceledContext(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // so we don't actually do anything
|
|
var measurement model.Measurement
|
|
measurer := new(Measurer)
|
|
measurer.measureTargets(
|
|
ctx,
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
&measurement,
|
|
model.NewPrinterCallbacks(log.Log),
|
|
map[string]model.OOAPITorTarget{
|
|
"xx": staticTestingTargets[0],
|
|
},
|
|
)
|
|
targets := measurement.TestKeys.(*TestKeys).Targets
|
|
if len(targets) != 1 {
|
|
t.Fatal("expected single measurements here")
|
|
}
|
|
if _, found := targets["xx"]; !found {
|
|
t.Fatal("the target we expected is missing")
|
|
}
|
|
tgt := targets["xx"]
|
|
if *tgt.Failure != "interrupted" {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
}
|
|
|
|
func wrapTestingTarget(tt model.OOAPITorTarget) keytarget {
|
|
return keytarget{
|
|
key: "xx", // using an super simple key; should work anyway
|
|
target: tt,
|
|
}
|
|
}
|
|
|
|
func TestResultsCollectorMeasureSingleTargetGood(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
rc.flexibleConnect = func(context.Context, keytarget) (oonitemplates.Results, error) {
|
|
return oonitemplates.Results{}, nil
|
|
}
|
|
rc.measureSingleTarget(
|
|
context.Background(), wrapTestingTarget(staticTestingTargets[0]),
|
|
len(staticTestingTargets),
|
|
)
|
|
if len(rc.targetresults) != 1 {
|
|
t.Fatal("wrong number of entries")
|
|
}
|
|
// Implementation note: here we won't bother with checking that
|
|
// oonidatamodel works correctly because we already test that.
|
|
if rc.targetresults["xx"].Agent != "redirect" {
|
|
t.Fatal("agent is invalid")
|
|
}
|
|
if rc.targetresults["xx"].Failure != nil {
|
|
t.Fatal("failure is invalid")
|
|
}
|
|
if rc.targetresults["xx"].TargetAddress != staticTestingTargets[0].Address {
|
|
t.Fatal("target address is invalid")
|
|
}
|
|
if rc.targetresults["xx"].TargetProtocol != staticTestingTargets[0].Protocol {
|
|
t.Fatal("target protocol is invalid")
|
|
}
|
|
}
|
|
|
|
func TestResultsCollectorMeasureSingleTargetWithFailure(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
rc.flexibleConnect = func(context.Context, keytarget) (oonitemplates.Results, error) {
|
|
return oonitemplates.Results{}, errors.New("mocked error")
|
|
}
|
|
rc.measureSingleTarget(
|
|
context.Background(), keytarget{
|
|
key: "xx", // using an super simple key; should work anyway
|
|
target: staticTestingTargets[0],
|
|
},
|
|
len(staticTestingTargets),
|
|
)
|
|
if len(rc.targetresults) != 1 {
|
|
t.Fatal("wrong number of entries")
|
|
}
|
|
// Implementation note: here we won't bother with checking that
|
|
// oonidatamodel works correctly because we already test that.
|
|
if rc.targetresults["xx"].Agent != "redirect" {
|
|
t.Fatal("agent is invalid")
|
|
}
|
|
if *rc.targetresults["xx"].Failure != "mocked error" {
|
|
t.Fatal("failure is invalid")
|
|
}
|
|
if rc.targetresults["xx"].TargetAddress != staticTestingTargets[0].Address {
|
|
t.Fatal("target address is invalid")
|
|
}
|
|
if rc.targetresults["xx"].TargetProtocol != staticTestingTargets[0].Protocol {
|
|
t.Fatal("target protocol is invalid")
|
|
}
|
|
}
|
|
|
|
func TestDefautFlexibleConnectDirPort(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
tk, err := rc.defaultFlexibleConnect(ctx, wrapTestingTarget(staticTestingTargets[1]))
|
|
if err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
if !strings.HasSuffix(err.Error(), "interrupted") {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tk.HTTPRequests == nil {
|
|
t.Fatal("expected HTTP data here")
|
|
}
|
|
}
|
|
|
|
func TestDefautFlexibleConnectOrPort(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
tk, err := rc.defaultFlexibleConnect(ctx, wrapTestingTarget(staticTestingTargets[2]))
|
|
if err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
if err.Error() != "interrupted" {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tk.Connects == nil {
|
|
t.Fatal("expected connects data here")
|
|
}
|
|
if tk.NetworkEvents == nil {
|
|
t.Fatal("expected network events data here")
|
|
}
|
|
}
|
|
|
|
func TestDefautFlexibleConnectOBFS4(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
tk, err := rc.defaultFlexibleConnect(ctx, wrapTestingTarget(staticTestingTargets[0]))
|
|
if err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
if err.Error() != "interrupted" {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
if tk.Connects == nil {
|
|
t.Fatal("expected connects data here")
|
|
}
|
|
if tk.NetworkEvents == nil {
|
|
t.Fatal("expected network events data here")
|
|
}
|
|
}
|
|
|
|
func TestDefautFlexibleConnectDefault(t *testing.T) {
|
|
rc := newResultsCollector(
|
|
&mockable.Session{
|
|
MockableLogger: log.Log,
|
|
},
|
|
new(model.Measurement),
|
|
model.NewPrinterCallbacks(log.Log),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
tk, err := rc.defaultFlexibleConnect(ctx, wrapTestingTarget(staticTestingTargets[3]))
|
|
if err == nil {
|
|
t.Fatal("expected an error here")
|
|
}
|
|
if err.Error() != "interrupted" {
|
|
t.Fatalf("not the error we expected: %+v", err)
|
|
}
|
|
if tk.Connects == nil {
|
|
t.Fatalf("expected connects data here, found: %+v", tk.Connects)
|
|
}
|
|
}
|
|
|
|
func TestErrString(t *testing.T) {
|
|
if errString(nil) != "success" {
|
|
t.Fatal("not working with nil")
|
|
}
|
|
if errString(errors.New("antani")) != "antani" {
|
|
t.Fatal("not working with error")
|
|
}
|
|
}
|
|
|
|
func TestSummary(t *testing.T) {
|
|
t.Run("without any piece of data", func(t *testing.T) {
|
|
tr := new(TargetResults)
|
|
tr.fillSummary()
|
|
if len(tr.Summary) != 0 {
|
|
t.Fatal("summary must be empty")
|
|
}
|
|
})
|
|
|
|
t.Run("with a TCP connect and nothing else", func(t *testing.T) {
|
|
tr := new(TargetResults)
|
|
failure := "mocked_error"
|
|
tr.TCPConnect = append(tr.TCPConnect, oonidatamodel.TCPConnectEntry{
|
|
Status: oonidatamodel.TCPConnectStatus{
|
|
Success: true,
|
|
Failure: &failure,
|
|
},
|
|
})
|
|
tr.fillSummary()
|
|
if len(tr.Summary) != 1 {
|
|
t.Fatal("cannot find expected entry")
|
|
}
|
|
if *tr.Summary[netxlite.ConnectOperation].Failure != failure {
|
|
t.Fatal("invalid failure")
|
|
}
|
|
})
|
|
|
|
t.Run("for OBFS4", func(t *testing.T) {
|
|
tr := new(TargetResults)
|
|
tr.TCPConnect = append(tr.TCPConnect, oonidatamodel.TCPConnectEntry{
|
|
Status: oonidatamodel.TCPConnectStatus{
|
|
Success: true,
|
|
},
|
|
})
|
|
failure := "mocked_error"
|
|
tr.TargetProtocol = "obfs4"
|
|
tr.Failure = &failure
|
|
tr.fillSummary()
|
|
if len(tr.Summary) != 2 {
|
|
t.Fatal("cannot find expected entry")
|
|
}
|
|
if tr.Summary[netxlite.ConnectOperation].Failure != nil {
|
|
t.Fatal("invalid failure")
|
|
}
|
|
if *tr.Summary["handshake"].Failure != failure {
|
|
t.Fatal("invalid failure")
|
|
}
|
|
})
|
|
|
|
t.Run("for or_port/or_port_dirauth", func(t *testing.T) {
|
|
doit := func(targetProtocol string, handshake *oonidatamodel.TLSHandshake) {
|
|
tr := new(TargetResults)
|
|
tr.TCPConnect = append(tr.TCPConnect, oonidatamodel.TCPConnectEntry{
|
|
Status: oonidatamodel.TCPConnectStatus{
|
|
Success: true,
|
|
},
|
|
})
|
|
tr.TargetProtocol = targetProtocol
|
|
if handshake != nil {
|
|
tr.TLSHandshakes = append(tr.TLSHandshakes, *handshake)
|
|
}
|
|
tr.fillSummary()
|
|
if len(tr.Summary) < 1 {
|
|
t.Fatal("cannot find expected entry")
|
|
}
|
|
if tr.Summary[netxlite.ConnectOperation].Failure != nil {
|
|
t.Fatal("invalid failure")
|
|
}
|
|
if handshake == nil {
|
|
if len(tr.Summary) != 1 {
|
|
t.Fatal("unexpected summary length")
|
|
}
|
|
return
|
|
}
|
|
if len(tr.Summary) != 2 {
|
|
t.Fatal("unexpected summary length")
|
|
}
|
|
if tr.Summary["handshake"].Failure != handshake.Failure {
|
|
t.Fatal("the failure value is unexpected")
|
|
}
|
|
}
|
|
doit("or_port_dirauth", nil)
|
|
doit("or_port", nil)
|
|
doit("or_port", &oonidatamodel.TLSHandshake{
|
|
Failure: (func() *string {
|
|
s := io.EOF.Error()
|
|
return &s
|
|
})(),
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestFillToplevelKeys(t *testing.T) {
|
|
var tr TargetResults
|
|
tr.TargetProtocol = "or_port"
|
|
tk := new(TestKeys)
|
|
tk.Targets = make(map[string]TargetResults)
|
|
tk.Targets["xxx"] = tr
|
|
tk.fillToplevelKeys()
|
|
if tk.ORPortTotal != 1 {
|
|
t.Fatal("unexpected ORPortTotal value")
|
|
}
|
|
}
|
|
|
|
func newsession() *mockable.Session {
|
|
return &mockable.Session{
|
|
MockableLogger: log.Log,
|
|
MockableHTTPClient: http.DefaultClient,
|
|
}
|
|
}
|
|
|
|
var referenceTargetResult = []byte(`{
|
|
"agent": "redirect",
|
|
"failure": null,
|
|
"network_events": [
|
|
{
|
|
"address": "85.31.186.98:443",
|
|
"conn_id": 19,
|
|
"dial_id": 21,
|
|
"failure": null,
|
|
"operation": "connect",
|
|
"proto": "tcp",
|
|
"t": 8.639313
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1915,
|
|
"operation": "write",
|
|
"proto": "tcp",
|
|
"t": 8.639686
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1440,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.691708
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1440,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.691912
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1383,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.69234
|
|
}
|
|
],
|
|
"queries": null,
|
|
"requests": null,
|
|
"summary": {
|
|
"connect": {
|
|
"failure": null
|
|
}
|
|
},
|
|
"target_address": "85.31.186.98:443",
|
|
"target_protocol": "obfs4",
|
|
"tcp_connect": [
|
|
{
|
|
"conn_id": 19,
|
|
"dial_id": 21,
|
|
"ip": "85.31.186.98",
|
|
"port": 443,
|
|
"status": {
|
|
"failure": null,
|
|
"success": true
|
|
},
|
|
"t": 8.639313
|
|
}
|
|
],
|
|
"tls_handshakes": null
|
|
}`)
|
|
|
|
var scrubbedTargetResult = []byte(`{
|
|
"agent": "redirect",
|
|
"failure": null,
|
|
"network_events": [
|
|
{
|
|
"address": "[scrubbed]",
|
|
"conn_id": 19,
|
|
"dial_id": 21,
|
|
"failure": null,
|
|
"operation": "connect",
|
|
"proto": "tcp",
|
|
"t": 8.639313
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1915,
|
|
"operation": "write",
|
|
"proto": "tcp",
|
|
"t": 8.639686
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1440,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.691708
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1440,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.691912
|
|
},
|
|
{
|
|
"conn_id": 19,
|
|
"failure": null,
|
|
"num_bytes": 1383,
|
|
"operation": "read",
|
|
"proto": "tcp",
|
|
"t": 8.69234
|
|
}
|
|
],
|
|
"queries": null,
|
|
"requests": null,
|
|
"summary": {
|
|
"connect": {
|
|
"failure": null
|
|
}
|
|
},
|
|
"target_address": "[scrubbed]",
|
|
"target_protocol": "obfs4",
|
|
"tcp_connect": [
|
|
{
|
|
"conn_id": 19,
|
|
"dial_id": 21,
|
|
"ip": "[scrubbed]",
|
|
"port": 443,
|
|
"status": {
|
|
"failure": null,
|
|
"success": true
|
|
},
|
|
"t": 8.639313
|
|
}
|
|
],
|
|
"tls_handshakes": null
|
|
}`)
|
|
|
|
func TestMaybeSanitize(t *testing.T) {
|
|
var input TargetResults
|
|
if err := json.Unmarshal(referenceTargetResult, &input); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Run("nothing to do", func(t *testing.T) {
|
|
out := maybeSanitize(input, keytarget{target: model.OOAPITorTarget{Source: ""}})
|
|
diff := cmp.Diff(input, out)
|
|
if diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
t.Run("scrubbing to do", func(t *testing.T) {
|
|
var expected TargetResults
|
|
if err := json.Unmarshal(scrubbedTargetResult, &expected); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out := maybeSanitize(input, keytarget{target: model.OOAPITorTarget{
|
|
Address: "85.31.186.98:443",
|
|
Source: "bridgedb",
|
|
}})
|
|
diff := cmp.Diff(expected, out)
|
|
if diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMaybeScrubbingLogger(t *testing.T) {
|
|
var input model.Logger = log.Log
|
|
|
|
t.Run("for when we don't need to save", func(t *testing.T) {
|
|
kt := keytarget{target: model.OOAPITorTarget{
|
|
Source: "",
|
|
}}
|
|
out := maybeScrubbingLogger(input, kt)
|
|
if out != input {
|
|
t.Fatal("not the output we expected")
|
|
}
|
|
if _, ok := out.(*scrubber.Logger); ok {
|
|
t.Fatal("not the output type we expected")
|
|
}
|
|
})
|
|
|
|
t.Run("for when we need to save", func(t *testing.T) {
|
|
kt := keytarget{target: model.OOAPITorTarget{
|
|
Source: "bridgedb",
|
|
}}
|
|
out := maybeScrubbingLogger(input, kt)
|
|
if out == input {
|
|
t.Fatal("not the output value we expected")
|
|
}
|
|
if _, ok := out.(*scrubber.Logger); !ok {
|
|
t.Fatal("not the output type we expected")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSummaryKeysInvalidType(t *testing.T) {
|
|
measurement := new(model.Measurement)
|
|
m := &Measurer{}
|
|
_, err := m.GetSummaryKeys(measurement)
|
|
if err.Error() != "invalid test keys type" {
|
|
t.Fatal("not the error we expected")
|
|
}
|
|
}
|
|
|
|
func TestSummaryKeysWorksAsIntended(t *testing.T) {
|
|
tests := []struct {
|
|
tk TestKeys
|
|
isAnomaly bool
|
|
}{{
|
|
tk: TestKeys{},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{DirPortAccessible: 1, DirPortTotal: 3},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{DirPortAccessible: 0, DirPortTotal: 3},
|
|
isAnomaly: true,
|
|
}, {
|
|
tk: TestKeys{OBFS4Accessible: 1, OBFS4Total: 3},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{OBFS4Accessible: 0, OBFS4Total: 3},
|
|
isAnomaly: true,
|
|
}, {
|
|
tk: TestKeys{ORPortDirauthAccessible: 1, ORPortDirauthTotal: 3},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{ORPortDirauthAccessible: 0, ORPortDirauthTotal: 3},
|
|
isAnomaly: true,
|
|
}, {
|
|
tk: TestKeys{ORPortAccessible: 1, ORPortTotal: 3},
|
|
isAnomaly: false,
|
|
}, {
|
|
tk: TestKeys{ORPortAccessible: 0, ORPortTotal: 3},
|
|
isAnomaly: true,
|
|
}}
|
|
for idx, tt := range tests {
|
|
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
|
|
m := &Measurer{}
|
|
measurement := &model.Measurement{TestKeys: &tt.tk}
|
|
got, err := m.GetSummaryKeys(measurement)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
return
|
|
}
|
|
sk := got.(SummaryKeys)
|
|
if sk.IsAnomaly != tt.isAnomaly {
|
|
t.Fatal("unexpected isAnomaly value")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTargetResultsFillSummaryDirPort(t *testing.T) {
|
|
tr := &TargetResults{
|
|
TargetProtocol: "dir_port",
|
|
TCPConnect: oonidatamodel.TCPConnectList{{
|
|
IP: "1.2.3.4",
|
|
Port: 443,
|
|
Status: oonidatamodel.TCPConnectStatus{
|
|
Failure: nil,
|
|
},
|
|
}},
|
|
}
|
|
tr.fillSummary()
|
|
if tr.DirPortCount != 1 {
|
|
t.Fatal("unexpected dirPortCount")
|
|
}
|
|
}
|
|
|
|
func TestTestKeysFillToplevelKeysCoverMissingFields(t *testing.T) {
|
|
failureString := "eof_error"
|
|
tk := &TestKeys{
|
|
Targets: map[string]TargetResults{
|
|
"foobar": {Failure: &failureString, TargetProtocol: "dir_port"},
|
|
"baz": {TargetProtocol: "dir_port"},
|
|
"jafar": {Failure: &failureString, TargetProtocol: "or_port_dirauth"},
|
|
"jasmine": {TargetProtocol: "or_port_dirauth"},
|
|
},
|
|
}
|
|
tk.fillToplevelKeys()
|
|
if tk.DirPortTotal != 2 {
|
|
t.Fatal("unexpected DirPortTotal")
|
|
}
|
|
if tk.DirPortAccessible != 1 {
|
|
t.Fatal("unexpected DirPortAccessible")
|
|
}
|
|
if tk.ORPortDirauthTotal != 2 {
|
|
t.Fatal("unexpected ORPortDirauthTotal")
|
|
}
|
|
if tk.ORPortDirauthAccessible != 1 {
|
|
t.Fatal("unexpected ORPortDirauthAccessible")
|
|
}
|
|
}
|