Skip to content

chore(probeservices): better tests for psiphon and tor #1568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
75ef7fd
refactor: consolidate httpx and httpapi
bassosimone Apr 22, 2024
f9210ec
refactor to make testing the whole package easier
bassosimone Apr 23, 2024
587290c
Merge branch 'master' into issue/2700
bassosimone Apr 23, 2024
af394c2
Merge branch 'master' into issue/2700
bassosimone Apr 23, 2024
c6f2f5a
Merge branch 'master' into issue/2700
bassosimone Apr 23, 2024
68c9779
Merge branch 'issue/2700' of github.com:ooni/probe-cli into issue/2700
bassosimone Apr 23, 2024
57e29da
Merge branch 'master' into issue/2700
bassosimone Apr 23, 2024
5c953f0
x
bassosimone Apr 23, 2024
e03e810
x
bassosimone Apr 23, 2024
a6046fd
x
bassosimone Apr 23, 2024
341fcf2
x
bassosimone Apr 23, 2024
8c34524
x
bassosimone Apr 23, 2024
4b464ff
try to entirely remove httpx usages
bassosimone Apr 23, 2024
6d57184
fix: make sure there is nil safety
bassosimone Apr 23, 2024
9c2a226
oxford comma: yes/no?
bassosimone Apr 23, 2024
1123b4e
x
bassosimone Apr 23, 2024
d421d24
fix: unit test needs to be adapted
bassosimone Apr 24, 2024
67e0a10
chore: improve testing for cloudflare IP lookup
bassosimone Apr 24, 2024
a69d981
chore: improve the ubuntu IP lookup tests
bassosimone Apr 24, 2024
cd25c56
Merge branch 'master' into issue/2700
bassosimone Apr 24, 2024
642ae5c
x
bassosimone Apr 24, 2024
548e6bc
doc: document oonirun/v2_test.go tests
bassosimone Apr 24, 2024
40db0e5
Merge branch 'master' into issue/2700
bassosimone Apr 24, 2024
4cf3566
start improving probeservices tests
bassosimone Apr 24, 2024
e736e42
Merge branch 'master' into issue/2700
bassosimone Apr 26, 2024
e8471c4
x
bassosimone Apr 26, 2024
aa1c836
Merge branch 'master' into issue/2700
bassosimone Apr 26, 2024
08e81a9
x
bassosimone Apr 26, 2024
fa74b48
x
bassosimone Apr 26, 2024
a7e748f
x
bassosimone Apr 26, 2024
87146cc
x
bassosimone Apr 26, 2024
f0c3590
x
bassosimone Apr 26, 2024
dd05d33
Apply suggestions from code review
bassosimone Apr 26, 2024
f30aded
x
bassosimone Apr 26, 2024
c389989
Merge branch 'issue/2718c' of github.com:ooni/probe-cli into issue/2718c
bassosimone Apr 26, 2024
754914e
x
bassosimone Apr 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 187 additions & 33 deletions internal/probeservices/psiphon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,198 @@ import (
"context"
"encoding/json"
"errors"
"net/http"
"net/url"
"testing"
"time"

"github.com/ooni/probe-cli/v3/internal/mocks"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/runtimex"
"github.com/ooni/probe-cli/v3/internal/testingx"
)

func TestFetchPsiphonConfig(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}

clnt := newclient()
if err := clnt.MaybeRegister(context.Background(), MetadataFixture()); err != nil {
t.Fatal(err)
}
if err := clnt.MaybeLogin(context.Background()); err != nil {
t.Fatal(err)
}
data, err := clnt.FetchPsiphonConfig(context.Background())
if err != nil {
t.Fatal(err)
}
var config interface{}
if err := json.Unmarshal(data, &config); err != nil {
t.Fatal(err)
}
}
// psiphonflow is the flow with which we invoke the psiphon API
psiphonflow := func(t *testing.T, client *Client) ([]byte, error) {
// we need to make sure we're registered and logged in
if err := client.MaybeRegister(context.Background(), MetadataFixture()); err != nil {
t.Fatal(err)
}
if err := client.MaybeLogin(context.Background()); err != nil {
t.Fatal(err)
}

func TestFetchPsiphonConfigNotRegistered(t *testing.T) {
clnt := newclient()
state := State{
// Explicitly empty so the test is more clear
}
if err := clnt.StateFile.Set(state); err != nil {
t.Fatal(err)
}
data, err := clnt.FetchPsiphonConfig(context.Background())
if !errors.Is(err, ErrNotRegistered) {
t.Fatal("expected an error here")
}
if data != nil {
t.Fatal("expected nil data here")
// then we can try to fetch the config
return client.FetchPsiphonConfig(context.Background())
}

// First, let's check whether we can get a response from the real OONI backend.
t.Run("is working as intended with the real backend", func(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}

clnt := newclient()

// run the psiphon flow
data, err := psiphonflow(t, clnt)

// we do not expect an error here
if err != nil {
t.Fatal(err)
}

// the config is bytes but we want to make sure we can parse it
var config interface{}
if err := json.Unmarshal(data, &config); err != nil {
t.Fatal(err)
}
})

// Now let's construct a test server that returns a valid response and try
// to communicate with such a test server successfully and with errors

t.Run("is working as intended with a local test server", func(t *testing.T) {
// create state for emulating the OONI backend
state := &testingx.OONIBackendWithLoginFlow{}

// make sure we return something that is JSON parseable
state.SetPsiphonConfig([]byte(`{}`))

// expose the state via HTTP
srv := testingx.MustNewHTTPServer(state.NewMux())
defer srv.Close()

// create a probeservices client
client := newclient()

// override the HTTP client so we speak with out local server rather than the true backend
client.HTTPClient = &mocks.HTTPClient{
MockDo: func(req *http.Request) (*http.Response, error) {
URL := runtimex.Try1(url.Parse(srv.URL))
req.URL.Scheme = URL.Scheme
req.URL.Host = URL.Host
return http.DefaultClient.Do(req)
},
MockCloseIdleConnections: func() {
http.DefaultClient.CloseIdleConnections()
},
}

// then we can try to fetch the config
data, err := psiphonflow(t, client)

// we do not expect an error here
if err != nil {
t.Fatal(err)
}

// the config is bytes but we want to make sure we can parse it
var config interface{}
if err := json.Unmarshal(data, &config); err != nil {
t.Fatal(err)
}
})

t.Run("reports an error when the connection is reset", func(t *testing.T) {
// create quick and dirty server to serve the response
srv := testingx.MustNewHTTPServer(testingx.HTTPHandlerReset())
defer srv.Close()

// create a probeservices client
client := newclient()

// override the HTTP client
client.HTTPClient = &mocks.HTTPClient{
MockDo: func(req *http.Request) (*http.Response, error) {
URL := runtimex.Try1(url.Parse(srv.URL))
req.URL.Scheme = URL.Scheme
req.URL.Host = URL.Host
return http.DefaultClient.Do(req)
},
MockCloseIdleConnections: func() {
http.DefaultClient.CloseIdleConnections()
},
}

// we need to convince the client that we're logged in first otherwise it will
// refuse to send a request to the server and we won't be testing networking
runtimex.Try0(client.StateFile.Set(State{
ClientID: "ttt-uuu-iii",
Expire: time.Now().Add(30 * time.Hour),
Password: "xxx-xxx-xxx",
Token: "abc-yyy-zzz",
}))

// issue the call directly: no register or login otherwise we're testing
// the register or login implementation LOL
data, err := client.FetchPsiphonConfig(context.Background())

// we do expect an error
if !errors.Is(err, netxlite.ECONNRESET) {
t.Fatal("unexpected error", err)
}

// we expect to see zero-length data
if len(data) != 0 {
t.Fatal("expected result lenght to be zero")
}
})

t.Run("when we're not registered", func(t *testing.T) {
clnt := newclient()

// With explicitly empty state so it's pretty obvioust there's no state
state := State{}

// force the state to be empty
if err := clnt.StateFile.Set(state); err != nil {
t.Fatal(err)
}

// attempt to fetch the config
data, err := clnt.FetchPsiphonConfig(context.Background())

// ensure that the error says we're not registered
if !errors.Is(err, ErrNotRegistered) {
t.Fatal("expected an error here")
}

// obviously the data should be empty as well
if len(data) != 0 {
t.Fatal("expected nil data here")
}
})

t.Run("correctly handles the case where the URL is unparseable", func(t *testing.T) {
// create a probeservices client
client := newclient()

// override the URL to be unparseable
client.BaseURL = "\t\t\t"

// we need to convince the client that we're logged in first otherwise it will
// refuse to send a request to the server and we won't be testing networking
runtimex.Try0(client.StateFile.Set(State{
ClientID: "ttt-uuu-iii",
Expire: time.Now().Add(30 * time.Hour),
Password: "xxx-xxx-xxx",
Token: "abc-yyy-zzz",
}))

// issue the API call proper
data, err := client.FetchPsiphonConfig(context.Background())

// we do expect an error
if err == nil || err.Error() != `parse "\t\t\t": net/url: invalid control character in URL` {
t.Fatal("unexpected error", err)
}

// we expect data to be zero length
if len(data) != 0 {
t.Fatal("expected zero length data")
}
})
}
Loading
Loading