chore: merge probe-engine into probe-cli (#201)

This is how I did it:

1. `git clone https://github.com/ooni/probe-engine internal/engine`

2. ```
(cd internal/engine && git describe --tags)
v0.23.0
```

3. `nvim go.mod` (merging `go.mod` with `internal/engine/go.mod`

4. `rm -rf internal/.git internal/engine/go.{mod,sum}`

5. `git add internal/engine`

6. `find . -type f -name \*.go -exec sed -i 's@/ooni/probe-engine@/ooni/probe-cli/v3/internal/engine@g' {} \;`

7. `go build ./...` (passes)

8. `go test -race ./...` (temporary failure on RiseupVPN)

9. `go mod tidy`

10. this commit message

Once this piece of work is done, we can build a new version of `ooniprobe` that
is using `internal/engine` directly. We need to do more work to ensure all the
other functionality in `probe-engine` (e.g. making mobile packages) are still WAI.

Part of https://github.com/ooni/probe/issues/1335
This commit is contained in:
Simone Basso
2021-02-02 12:05:47 +01:00
committed by GitHub
parent b1ce300c8d
commit d57c78bc71
535 changed files with 66182 additions and 23 deletions
@@ -0,0 +1,237 @@
// Package whatsapp contains the WhatsApp network experiment.
//
// See https://github.com/ooni/spec/blob/master/nettests/ts-018-whatsapp.md.
package whatsapp
import (
"context"
"errors"
"fmt"
"math/rand"
"net/url"
"regexp"
"time"
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
"github.com/ooni/probe-cli/v3/internal/engine/internal/httpfailure"
"github.com/ooni/probe-cli/v3/internal/engine/internal/runtimex"
"github.com/ooni/probe-cli/v3/internal/engine/model"
)
const (
// RegistrationServiceURL is the URL used by WhatsApp registration service
RegistrationServiceURL = "https://v.whatsapp.net/v2/register"
// WebHTTPURL is WhatsApp web's HTTP URL
WebHTTPURL = "http://web.whatsapp.com/"
// WebHTTPSURL is WhatsApp web's HTTPS URL
WebHTTPSURL = "https://web.whatsapp.com/"
testName = "whatsapp"
testVersion = "0.9.0"
)
var endpointPattern = regexp.MustCompile(`^tcpconnect://e[0-9]{1,2}\.whatsapp\.net:[0-9]{3,5}$`)
// Config contains the experiment config.
type Config struct{}
// TestKeys contains the experiment results
type TestKeys struct {
urlgetter.TestKeys
RegistrationServerFailure *string `json:"registration_server_failure"`
RegistrationServerStatus string `json:"registration_server_status"`
WhatsappEndpointsBlocked []string `json:"whatsapp_endpoints_blocked"`
WhatsappEndpointsDNSInconsistent []string `json:"whatsapp_endpoints_dns_inconsistent"`
WhatsappEndpointsStatus string `json:"whatsapp_endpoints_status"`
WhatsappWebFailure *string `json:"whatsapp_web_failure"`
WhatsappWebStatus string `json:"whatsapp_web_status"`
WhatsappEndpointsCount map[string]int `json:"-"`
WhatsappHTTPFailure *string `json:"-"`
WhatsappHTTPSFailure *string `json:"-"`
}
// NewTestKeys returns a new instance of the test keys.
func NewTestKeys() *TestKeys {
failure := "unknown_failure"
return &TestKeys{
RegistrationServerFailure: &failure,
RegistrationServerStatus: "blocked",
WhatsappEndpointsBlocked: []string{},
WhatsappEndpointsDNSInconsistent: []string{},
WhatsappEndpointsStatus: "blocked",
WhatsappWebFailure: &failure,
WhatsappWebStatus: "blocked",
WhatsappEndpointsCount: make(map[string]int),
WhatsappHTTPFailure: &failure,
WhatsappHTTPSFailure: &failure,
}
}
// Update updates the TestKeys using the given MultiOutput result.
func (tk *TestKeys) Update(v urlgetter.MultiOutput) {
// Update the easy to update entries first
tk.NetworkEvents = append(tk.NetworkEvents, v.TestKeys.NetworkEvents...)
tk.Queries = append(tk.Queries, v.TestKeys.Queries...)
tk.Requests = append(tk.Requests, v.TestKeys.Requests...)
tk.TCPConnect = append(tk.TCPConnect, v.TestKeys.TCPConnect...)
tk.TLSHandshakes = append(tk.TLSHandshakes, v.TestKeys.TLSHandshakes...)
// Set the status of WhatsApp endpoints
if endpointPattern.MatchString(v.Input.Target) {
if v.TestKeys.Failure != nil {
parsed, err := url.Parse(v.Input.Target)
runtimex.PanicOnError(err, "url.Parse should not fail here")
hostname := parsed.Hostname()
tk.WhatsappEndpointsCount[hostname]++
if tk.WhatsappEndpointsCount[hostname] >= 2 {
tk.WhatsappEndpointsBlocked = append(tk.WhatsappEndpointsBlocked, hostname)
}
return
}
tk.WhatsappEndpointsStatus = "ok"
return
}
// Set the status of the registration service
if v.Input.Target == RegistrationServiceURL {
tk.RegistrationServerFailure = v.TestKeys.Failure
if v.TestKeys.Failure == nil {
tk.RegistrationServerStatus = "ok"
}
return
}
// Track result of accessing the web interface.
switch v.Input.Target {
case WebHTTPSURL:
tk.WhatsappHTTPSFailure = v.TestKeys.Failure
case WebHTTPURL:
failure := v.TestKeys.Failure
if failure != nil {
// nothing to do here
} else if v.TestKeys.HTTPResponseStatus != 302 {
failure = &httpfailure.UnexpectedStatusCode
} else if len(v.TestKeys.HTTPResponseLocations) != 1 {
failure = &httpfailure.UnexpectedRedirectURL
} else if v.TestKeys.HTTPResponseLocations[0] != WebHTTPSURL {
failure = &httpfailure.UnexpectedRedirectURL
}
tk.WhatsappHTTPFailure = failure
}
}
// ComputeWebStatus sets the web status fields.
func (tk *TestKeys) ComputeWebStatus() {
if tk.WhatsappHTTPFailure == nil && tk.WhatsappHTTPSFailure == nil {
tk.WhatsappWebFailure = nil
tk.WhatsappWebStatus = "ok"
return
}
tk.WhatsappWebStatus = "blocked" // must be here because of unit tests
if tk.WhatsappHTTPSFailure != nil {
tk.WhatsappWebFailure = tk.WhatsappHTTPSFailure
return
}
tk.WhatsappWebFailure = tk.WhatsappHTTPFailure
}
// Measurer performs the measurement
type Measurer struct {
// Config contains the experiment settings. If empty we
// will be using default settings.
Config Config
// Getter is an optional getter to be used for testing.
Getter urlgetter.MultiGetter
}
// ExperimentName implements ExperimentMeasurer.ExperimentName
func (m Measurer) ExperimentName() string {
return testName
}
// ExperimentVersion implements ExperimentMeasurer.ExperimentVersion
func (m Measurer) ExperimentVersion() string {
return testVersion
}
// Run implements ExperimentMeasurer.Run
func (m Measurer) Run(
ctx context.Context, sess model.ExperimentSession,
measurement *model.Measurement, callbacks model.ExperimentCallbacks,
) error {
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
urlgetter.RegisterExtensions(measurement)
// generate all the inputs
var inputs []urlgetter.MultiInput
for idx := 1; idx <= 16; idx++ {
for _, port := range []string{"443", "5222"} {
inputs = append(inputs, urlgetter.MultiInput{
Target: fmt.Sprintf("tcpconnect://e%d.whatsapp.net:%s", idx, port),
})
}
}
inputs = append(inputs, urlgetter.MultiInput{
Config: urlgetter.Config{FailOnHTTPError: true},
Target: RegistrationServiceURL,
})
inputs = append(inputs, urlgetter.MultiInput{
// We consider this check successful if we can establish a TLS
// connection and we don't see any socket/TLS errors. Hence, we
// don't care about the HTTP response code.
Target: WebHTTPSURL,
})
inputs = append(inputs, urlgetter.MultiInput{
// We consider this check successful if we get a valid redirect
// for the HTTPS web interface. No need to follow redirects.
Config: urlgetter.Config{NoFollowRedirects: true},
Target: WebHTTPURL,
})
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
rnd.Shuffle(len(inputs), func(i, j int) {
inputs[i], inputs[j] = inputs[j], inputs[i]
})
// measure in parallel
multi := urlgetter.Multi{Begin: time.Now(), Getter: m.Getter, Session: sess}
testkeys := NewTestKeys()
testkeys.Agent = "redirect"
measurement.TestKeys = testkeys
for entry := range multi.Collect(ctx, inputs, "whatsapp", callbacks) {
testkeys.Update(entry)
}
testkeys.ComputeWebStatus()
return nil
}
// NewExperimentMeasurer creates a new ExperimentMeasurer.
func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
return Measurer{Config: config}
}
// SummaryKeys contains summary keys for this experiment.
//
// Note that this structure is part of the ABI contract with probe-cli
// therefore we should be careful when changing it.
type SummaryKeys struct {
RegistrationServerBlocking bool `json:"registration_server_blocking"`
WebBlocking bool `json:"whatsapp_web_blocking"`
EndpointsBlocking bool `json:"whatsapp_endpoints_blocking"`
IsAnomaly bool `json:"-"`
}
// GetSummaryKeys implements model.ExperimentMeasurer.GetSummaryKeys.
func (m Measurer) GetSummaryKeys(measurement *model.Measurement) (interface{}, error) {
sk := SummaryKeys{IsAnomaly: false}
tk, ok := measurement.TestKeys.(*TestKeys)
if !ok {
return sk, errors.New("invalid test keys type")
}
blocking := func(value string) bool {
return value == "blocked"
}
sk.RegistrationServerBlocking = blocking(tk.RegistrationServerStatus)
sk.WebBlocking = blocking(tk.WhatsappWebStatus)
sk.EndpointsBlocking = blocking(tk.WhatsappEndpointsStatus)
sk.IsAnomaly = (sk.RegistrationServerBlocking || sk.WebBlocking || sk.EndpointsBlocking)
return sk, nil
}
@@ -0,0 +1,675 @@
package whatsapp_test
import (
"context"
"fmt"
"io"
"regexp"
"testing"
"github.com/apex/log"
"github.com/google/go-cmp/cmp"
"github.com/ooni/probe-cli/v3/internal/engine/atomicx"
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
"github.com/ooni/probe-cli/v3/internal/engine/experiment/whatsapp"
"github.com/ooni/probe-cli/v3/internal/engine/internal/httpfailure"
"github.com/ooni/probe-cli/v3/internal/engine/internal/mockable"
"github.com/ooni/probe-cli/v3/internal/engine/model"
)
func TestNewExperimentMeasurer(t *testing.T) {
measurer := whatsapp.NewExperimentMeasurer(whatsapp.Config{})
if measurer.ExperimentName() != "whatsapp" {
t.Fatal("unexpected name")
}
if measurer.ExperimentVersion() != "0.9.0" {
t.Fatal("unexpected version")
}
}
func TestSuccess(t *testing.T) {
measurer := whatsapp.NewExperimentMeasurer(whatsapp.Config{})
ctx := context.Background()
sess := &mockable.Session{MockableLogger: log.Log}
measurement := new(model.Measurement)
callbacks := model.NewPrinterCallbacks(log.Log)
err := measurer.Run(ctx, sess, measurement, callbacks)
if err != nil {
t.Fatal(err)
}
tk := measurement.TestKeys.(*whatsapp.TestKeys)
if tk.RegistrationServerFailure != nil {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "ok" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 0 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "ok" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if tk.WhatsappWebFailure != nil {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "ok" {
t.Fatal("invalid WhatsappWebStatus")
}
}
func TestFailureAllEndpoints(t *testing.T) {
measurer := whatsapp.NewExperimentMeasurer(whatsapp.Config{})
ctx, cancel := context.WithCancel(context.Background())
cancel() // fail immediately
sess := &mockable.Session{MockableLogger: log.Log}
measurement := new(model.Measurement)
callbacks := model.NewPrinterCallbacks(log.Log)
err := measurer.Run(ctx, sess, measurement, callbacks)
if err != nil {
t.Fatal(err)
}
tk := measurement.TestKeys.(*whatsapp.TestKeys)
if *tk.RegistrationServerFailure != "interrupted" {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "blocked" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 16 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
pattern := regexp.MustCompile("^e[0-9]{1,2}.whatsapp.net$")
for i := 0; i < len(tk.WhatsappEndpointsBlocked); i++ {
if !pattern.MatchString(tk.WhatsappEndpointsBlocked[i]) {
t.Fatalf("invalid WhatsappEndpointsBlocked[%d]", i)
}
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "blocked" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if *tk.WhatsappWebFailure != "interrupted" {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "blocked" {
t.Fatal("invalid WhatsappWebStatus")
}
sk, err := measurer.GetSummaryKeys(measurement)
if err != nil {
t.Fatal(err)
}
if _, ok := sk.(whatsapp.SummaryKeys); !ok {
t.Fatal("invalid type for summary keys")
}
}
func TestTestKeysComputeWebStatus(t *testing.T) {
errorString := io.EOF.Error()
secondErrorString := context.Canceled.Error()
type fields struct {
TestKeys urlgetter.TestKeys
RegistrationServerFailure *string
RegistrationServerStatus string
WhatsappEndpointsBlocked []string
WhatsappEndpointsDNSInconsistent []string
WhatsappEndpointsStatus string
WhatsappWebStatus string
WhatsappWebFailure *string
WhatsappHTTPFailure *string
WhatsappHTTPSFailure *string
}
tests := []struct {
name string
fields fields
failure *string
status string
}{{
name: "with success",
failure: nil,
status: "ok",
}, {
name: "with HTTP failure",
fields: fields{
WhatsappHTTPFailure: &errorString,
},
failure: &errorString,
status: "blocked",
}, {
name: "with HTTPS failure",
fields: fields{
WhatsappHTTPSFailure: &errorString,
},
failure: &errorString,
status: "blocked",
}, {
name: "with both HTTP and HTTPS failure",
fields: fields{
WhatsappHTTPFailure: &errorString,
WhatsappHTTPSFailure: &secondErrorString,
},
failure: &secondErrorString,
status: "blocked",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tk := &whatsapp.TestKeys{
TestKeys: tt.fields.TestKeys,
RegistrationServerFailure: tt.fields.RegistrationServerFailure,
RegistrationServerStatus: tt.fields.RegistrationServerStatus,
WhatsappEndpointsBlocked: tt.fields.WhatsappEndpointsBlocked,
WhatsappEndpointsDNSInconsistent: tt.fields.WhatsappEndpointsDNSInconsistent,
WhatsappEndpointsStatus: tt.fields.WhatsappEndpointsStatus,
WhatsappWebStatus: tt.fields.WhatsappWebStatus,
WhatsappWebFailure: tt.fields.WhatsappWebFailure,
WhatsappHTTPFailure: tt.fields.WhatsappHTTPFailure,
WhatsappHTTPSFailure: tt.fields.WhatsappHTTPSFailure,
}
tk.ComputeWebStatus()
diff := cmp.Diff(tk.WhatsappWebFailure, tt.failure)
if diff != "" {
t.Fatal(diff)
}
diff = cmp.Diff(tk.WhatsappWebStatus, tt.status)
if diff != "" {
t.Fatal(diff)
}
})
}
}
func TestTestKeysMixedEndpointsFailure(t *testing.T) {
failure := io.EOF.Error()
tk := whatsapp.NewTestKeys()
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:443"},
TestKeys: urlgetter.TestKeys{Failure: &failure},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:5222"},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.RegistrationServiceURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPSURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPURL},
TestKeys: urlgetter.TestKeys{
HTTPResponseStatus: 302,
HTTPResponseLocations: []string{whatsapp.WebHTTPSURL},
},
})
tk.ComputeWebStatus()
if tk.RegistrationServerFailure != nil {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "ok" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 0 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "ok" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if tk.WhatsappWebFailure != nil {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "ok" {
t.Fatal("invalid WhatsappWebStatus")
}
}
func TestTestKeysOnlyEndpointsFailure(t *testing.T) {
failure := io.EOF.Error()
tk := whatsapp.NewTestKeys()
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:443"},
TestKeys: urlgetter.TestKeys{Failure: &failure},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:5222"},
TestKeys: urlgetter.TestKeys{Failure: &failure},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.RegistrationServiceURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPSURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPURL},
TestKeys: urlgetter.TestKeys{
HTTPResponseStatus: 302,
HTTPResponseLocations: []string{whatsapp.WebHTTPSURL},
},
})
tk.ComputeWebStatus()
if tk.RegistrationServerFailure != nil {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "ok" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 1 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "blocked" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if tk.WhatsappWebFailure != nil {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "ok" {
t.Fatal("invalid WhatsappWebStatus")
}
}
func TestTestKeysOnlyRegistrationServerFailure(t *testing.T) {
failure := io.EOF.Error()
tk := whatsapp.NewTestKeys()
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:443"},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.RegistrationServiceURL},
TestKeys: urlgetter.TestKeys{Failure: &failure},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPSURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPURL},
TestKeys: urlgetter.TestKeys{
HTTPResponseStatus: 302,
HTTPResponseLocations: []string{whatsapp.WebHTTPSURL},
},
})
tk.ComputeWebStatus()
if *tk.RegistrationServerFailure != failure {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "blocked" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 0 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "ok" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if tk.WhatsappWebFailure != nil {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "ok" {
t.Fatal("invalid WhatsappWebStatus")
}
}
func TestTestKeysOnlyWebHTTPSFailure(t *testing.T) {
failure := io.EOF.Error()
tk := whatsapp.NewTestKeys()
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:443"},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.RegistrationServiceURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPSURL},
TestKeys: urlgetter.TestKeys{Failure: &failure},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPURL},
TestKeys: urlgetter.TestKeys{
HTTPResponseStatus: 302,
HTTPResponseLocations: []string{whatsapp.WebHTTPSURL},
},
})
tk.ComputeWebStatus()
if tk.RegistrationServerFailure != nil {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "ok" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 0 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "ok" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if *tk.WhatsappWebFailure != failure {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "blocked" {
t.Fatal("invalid WhatsappWebStatus")
}
}
func TestTestKeysOnlyWebHTTPFailureNo302(t *testing.T) {
tk := whatsapp.NewTestKeys()
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:443"},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.RegistrationServiceURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPSURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPURL},
TestKeys: urlgetter.TestKeys{
HTTPResponseStatus: 400,
},
})
tk.ComputeWebStatus()
if tk.RegistrationServerFailure != nil {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "ok" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 0 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "ok" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if *tk.WhatsappWebFailure != httpfailure.UnexpectedStatusCode {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "blocked" {
t.Fatal("invalid WhatsappWebStatus")
}
}
func TestTestKeysOnlyWebHTTPFailureNoLocations(t *testing.T) {
tk := whatsapp.NewTestKeys()
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:443"},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.RegistrationServiceURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPSURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPURL},
TestKeys: urlgetter.TestKeys{
HTTPResponseStatus: 302,
HTTPResponseLocations: nil,
},
})
tk.ComputeWebStatus()
if tk.RegistrationServerFailure != nil {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "ok" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 0 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "ok" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if *tk.WhatsappWebFailure != httpfailure.UnexpectedRedirectURL {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "blocked" {
t.Fatal("invalid WhatsappWebStatus")
}
}
func TestTestKeysOnlyWebHTTPFailureNotExpectedURL(t *testing.T) {
tk := whatsapp.NewTestKeys()
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:443"},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.RegistrationServiceURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPSURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPURL},
TestKeys: urlgetter.TestKeys{
HTTPResponseStatus: 302,
HTTPResponseLocations: []string{"https://x.org/"},
},
})
tk.ComputeWebStatus()
if tk.RegistrationServerFailure != nil {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "ok" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 0 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "ok" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if *tk.WhatsappWebFailure != httpfailure.UnexpectedRedirectURL {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "blocked" {
t.Fatal("invalid WhatsappWebStatus")
}
}
func TestTestKeysOnlyWebHTTPFailureTooManyURLs(t *testing.T) {
tk := whatsapp.NewTestKeys()
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: "tcpconnect://e7.whatsapp.net:443"},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.RegistrationServiceURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPSURL},
TestKeys: urlgetter.TestKeys{},
})
tk.Update(urlgetter.MultiOutput{
Input: urlgetter.MultiInput{Target: whatsapp.WebHTTPURL},
TestKeys: urlgetter.TestKeys{
HTTPResponseStatus: 302,
HTTPResponseLocations: []string{whatsapp.WebHTTPSURL, "https://x.org/"},
},
})
tk.ComputeWebStatus()
if tk.RegistrationServerFailure != nil {
t.Fatal("invalid RegistrationServerFailure")
}
if tk.RegistrationServerStatus != "ok" {
t.Fatal("invalid RegistrationServerStatus")
}
if len(tk.WhatsappEndpointsBlocked) != 0 {
t.Fatal("invalid WhatsappEndpointsBlocked")
}
if len(tk.WhatsappEndpointsDNSInconsistent) != 0 {
t.Fatal("invalid WhatsappEndpointsDNSInconsistent")
}
if tk.WhatsappEndpointsStatus != "ok" {
t.Fatal("invalid WhatsappEndpointsStatus")
}
if *tk.WhatsappWebFailure != httpfailure.UnexpectedRedirectURL {
t.Fatal("invalid WhatsappWebFailure")
}
if tk.WhatsappWebStatus != "blocked" {
t.Fatal("invalid WhatsappWebStatus")
}
}
func TestWeConfigureWebChecksCorrectly(t *testing.T) {
called := atomicx.NewInt64()
emptyConfig := urlgetter.Config{}
configWithFailOnHTTPError := urlgetter.Config{FailOnHTTPError: true}
configWithNoFollowRedirects := urlgetter.Config{NoFollowRedirects: true}
measurer := whatsapp.Measurer{
Config: whatsapp.Config{},
Getter: func(ctx context.Context, g urlgetter.Getter) (urlgetter.TestKeys, error) {
switch g.Target {
case whatsapp.WebHTTPSURL:
called.Add(1)
if diff := cmp.Diff(g.Config, emptyConfig); diff != "" {
panic(diff)
}
case whatsapp.WebHTTPURL:
called.Add(2)
if diff := cmp.Diff(g.Config, configWithNoFollowRedirects); diff != "" {
panic(diff)
}
case whatsapp.RegistrationServiceURL:
called.Add(4)
if diff := cmp.Diff(g.Config, configWithFailOnHTTPError); diff != "" {
panic(diff)
}
default:
called.Add(8)
if diff := cmp.Diff(g.Config, emptyConfig); diff != "" {
panic(diff)
}
}
return urlgetter.DefaultMultiGetter(ctx, g)
},
}
ctx := context.Background()
sess := &mockable.Session{
MockableLogger: log.Log,
}
measurement := new(model.Measurement)
callbacks := model.NewPrinterCallbacks(log.Log)
if err := measurer.Run(ctx, sess, measurement, callbacks); err != nil {
t.Fatal(err)
}
if called.Load() != 263 {
t.Fatal("not called the expected number of times")
}
}
func TestSummaryKeysInvalidType(t *testing.T) {
measurement := new(model.Measurement)
m := &whatsapp.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 whatsapp.TestKeys
RegistrationServerBlocking bool
WebBlocking bool
EndpointsBlocking bool
isAnomaly bool
}{{
tk: whatsapp.TestKeys{},
RegistrationServerBlocking: false,
WebBlocking: false,
EndpointsBlocking: false,
isAnomaly: false,
}, {
tk: whatsapp.TestKeys{
RegistrationServerStatus: "blocked",
},
RegistrationServerBlocking: true,
WebBlocking: false,
EndpointsBlocking: false,
isAnomaly: true,
}, {
tk: whatsapp.TestKeys{
WhatsappWebStatus: "blocked",
},
RegistrationServerBlocking: false,
WebBlocking: true,
EndpointsBlocking: false,
isAnomaly: true,
}, {
tk: whatsapp.TestKeys{
WhatsappEndpointsStatus: "blocked",
},
RegistrationServerBlocking: false,
WebBlocking: false,
EndpointsBlocking: true,
isAnomaly: true,
}}
for idx, tt := range tests {
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
m := &whatsapp.Measurer{}
measurement := &model.Measurement{TestKeys: &tt.tk}
got, err := m.GetSummaryKeys(measurement)
if err != nil {
t.Fatal(err)
return
}
sk := got.(whatsapp.SummaryKeys)
if sk.IsAnomaly != tt.isAnomaly {
t.Fatal("unexpected isAnomaly value")
}
if sk.RegistrationServerBlocking != tt.RegistrationServerBlocking {
t.Fatal("unexpected registrationServerBlocking value")
}
if sk.WebBlocking != tt.WebBlocking {
t.Fatal("unexpected webBlocking value")
}
if sk.EndpointsBlocking != tt.EndpointsBlocking {
t.Fatal("unexpected endpointsBlocking value")
}
})
}
}