Skip to content
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

refactor(probeservices): httpx -> httpclientx #1576

Merged
merged 48 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 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
dac7b8f
x
bassosimone Apr 26, 2024
04b0071
Merge branch 'master' into issue/2700
bassosimone Apr 26, 2024
79d1fee
Merge branch 'master' into issue/2700
bassosimone Apr 29, 2024
88b399d
Merge branch 'master' into issue/2700
bassosimone Apr 29, 2024
de23e7d
x
bassosimone Apr 29, 2024
9d87673
Merge branch 'master' into issue/2700
bassosimone Apr 29, 2024
a436f1e
x
bassosimone Apr 29, 2024
08f8ca9
Merge branch 'master' into issue/2700
bassosimone Apr 29, 2024
25140f3
x
bassosimone Apr 29, 2024
1bbe0b7
chore: write tests for oonicollector.go
bassosimone Apr 30, 2024
6707d61
Merge branch 'master' into issue/2700
bassosimone Apr 30, 2024
4ddd507
Merge branch 'master' into issue/2700
bassosimone Apr 30, 2024
c453ee2
x
bassosimone Apr 30, 2024
6f5da49
x
bassosimone Apr 30, 2024
61d9610
x
bassosimone Apr 30, 2024
493dc66
x
bassosimone Apr 30, 2024
0ebe7de
[ci skip]
bassosimone Apr 30, 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
25 changes: 20 additions & 5 deletions internal/probeservices/bouncer.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
package probeservices

//
// bouncer.go - GET /api/v1/test-helpers
//

import (
"context"

"github.com/ooni/probe-cli/v3/internal/httpclientx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/urlx"
)

// GetTestHelpers is like GetCollectors but for test helpers.
func (c Client) GetTestHelpers(
ctx context.Context) (output map[string][]model.OOAPIService, err error) {
err = c.APIClientTemplate.WithBodyLogging().Build().GetJSON(ctx, "/api/v1/test-helpers", &output)
return
// GetTestHelpers queries the /api/v1/test-helpers API.
func (c *Client) GetTestHelpers(ctx context.Context) (map[string][]model.OOAPIService, error) {
// construct the URL to use
URL, err := urlx.ResolveReference(c.BaseURL, "/api/v1/test-helpers", "")
if err != nil {
return nil, err
}

// get the response
return httpclientx.GetJSON[map[string][]model.OOAPIService](ctx, URL, &httpclientx.Config{
Client: c.HTTPClient,
Logger: c.Logger,
UserAgent: c.UserAgent,
})
}
21 changes: 21 additions & 0 deletions internal/probeservices/bouncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,25 @@ func TestGetTestHelpers(t *testing.T) {
t.Fatal("expected result lenght to be zero")
}
})

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"

// issue the GET request
testhelpers, err := client.GetTestHelpers(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 to see a zero-length / nil map
if len(testhelpers) != 0 {
t.Fatal("expected result lenght to be zero")
}
})
}
49 changes: 41 additions & 8 deletions internal/probeservices/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"reflect"
"sync"

"github.com/ooni/probe-cli/v3/internal/httpclientx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/urlx"
)

var (
Expand Down Expand Up @@ -58,10 +60,24 @@ func (c Client) OpenReport(ctx context.Context, rt model.OOAPIReportTemplate) (R
if rt.Format != model.OOAPIReportDefaultFormat {
return nil, ErrUnsupportedFormat
}
var cor model.OOAPICollectorOpenResponse
if err := c.APIClientTemplate.WithBodyLogging().Build().PostJSON(ctx, "/report", rt, &cor); err != nil {

URL, err := urlx.ResolveReference(c.BaseURL, "/report", "")
if err != nil {
return nil, err
}

cor, err := httpclientx.PostJSON[model.OOAPIReportTemplate, *model.OOAPICollectorOpenResponse](
ctx, URL, rt, &httpclientx.Config{
Client: c.HTTPClient,
Logger: c.Logger,
UserAgent: c.UserAgent,
},
)

if err != nil {
return nil, err
}

for _, format := range cor.SupportedFormats {
if format == "json" {
return &reportChan{ID: cor.ReportID, client: c, tmpl: rt}, nil
Expand All @@ -83,18 +99,35 @@ func (r reportChan) CanSubmit(m *model.Measurement) bool {
// submitted. Otherwise, we'll set the report ID to the empty
// string, so that you know which measurements weren't submitted.
func (r reportChan) SubmitMeasurement(ctx context.Context, m *model.Measurement) error {
var updateResponse model.OOAPICollectorUpdateResponse
// TODO(bassosimone): do we need to prevent measurement submission
// if the measurement isn't consistent with the orig template?

m.ReportID = r.ID
err := r.client.APIClientTemplate.WithBodyLogging().Build().PostJSON(
ctx, fmt.Sprintf("/report/%s", r.ID), model.OOAPICollectorUpdateRequest{
Format: "json",
Content: m,
}, &updateResponse,

URL, err := urlx.ResolveReference(r.client.BaseURL, fmt.Sprintf("/report/%s", r.ID), "")
if err != nil {
return err
}

apiReq := model.OOAPICollectorUpdateRequest{
Format: "json",
Content: m,
}

updateResponse, err := httpclientx.PostJSON[
model.OOAPICollectorUpdateRequest, *model.OOAPICollectorUpdateResponse](
ctx, URL, apiReq, &httpclientx.Config{
Client: r.client.HTTPClient,
Logger: r.client.Logger,
UserAgent: r.client.UserAgent,
},
)

if err != nil {
m.ReportID = ""
return err
}

// TODO(bassosimone): we should use the session logger here but for now this stopgap
// solution will allow observing the measurement URL for CLI users.
log.Printf("Measurement URL: https://explorer.ooni.org/m/%s", updateResponse.MeasurementUID)
Expand Down
21 changes: 18 additions & 3 deletions internal/probeservices/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package probeservices
import (
"context"

"github.com/ooni/probe-cli/v3/internal/httpclientx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/urlx"
)

// MaybeLogin performs login if necessary
Expand All @@ -17,11 +19,24 @@ func (c Client) MaybeLogin(ctx context.Context) error {
return ErrNotRegistered
}
c.LoginCalls.Add(1)
var auth model.OOAPILoginAuth
if err := c.APIClientTemplate.Build().PostJSON(
ctx, "/api/v1/login", *creds, &auth); err != nil {

URL, err := urlx.ResolveReference(c.BaseURL, "/api/v1/login", "")
if err != nil {
return err
}

auth, err := httpclientx.PostJSON[*model.OOAPILoginCredentials, *model.OOAPILoginAuth](
ctx, URL, creds, &httpclientx.Config{
Client: c.HTTPClient,
Logger: model.DiscardLogger,
UserAgent: c.UserAgent,
},
)

if err != nil {
return err
}

state.Expire = auth.Expire
state.Token = auth.Token
return c.StateFile.Set(state)
Expand Down
26 changes: 17 additions & 9 deletions internal/probeservices/measurementmeta.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package probeservices

//
// measurementmeta.go - GET /api/v1/measurement_meta
//

import (
"context"
"net/url"

"github.com/ooni/probe-cli/v3/internal/httpx"
"github.com/ooni/probe-cli/v3/internal/httpclientx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/urlx"
)

// GetMeasurementMeta returns meta information about a measurement.
func (c Client) GetMeasurementMeta(
ctx context.Context, config model.OOAPIMeasurementMetaConfig) (*model.OOAPIMeasurementMeta, error) {
// construct the query to use
query := url.Values{}
query.Add("report_id", config.ReportID)
if config.Input != "" {
Expand All @@ -19,15 +25,17 @@ func (c Client) GetMeasurementMeta(
if config.Full {
query.Add("full", "true")
}
var response model.OOAPIMeasurementMeta
err := (&httpx.APIClientTemplate{
BaseURL: c.BaseURL,
HTTPClient: c.HTTPClient,
Logger: c.Logger,
UserAgent: c.UserAgent,
}).WithBodyLogging().Build().GetJSONWithQuery(ctx, "/api/v1/measurement_meta", query, &response)

// construct the URL to use
URL, err := urlx.ResolveReference(c.BaseURL, "/api/v1/measurement_meta", query.Encode())
if err != nil {
return nil, err
}
return &response, nil

// get the response
return httpclientx.GetJSON[*model.OOAPIMeasurementMeta](ctx, URL, &httpclientx.Config{
Client: c.HTTPClient,
Logger: c.Logger,
UserAgent: c.UserAgent,
})
}
25 changes: 23 additions & 2 deletions internal/probeservices/psiphon.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,36 @@ package probeservices
import (
"context"
"fmt"

"github.com/ooni/probe-cli/v3/internal/httpclientx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/urlx"
)

// FetchPsiphonConfig fetches psiphon config from authenticated OONI orchestra.
func (c Client) FetchPsiphonConfig(ctx context.Context) ([]byte, error) {
// get credentials and authentication token
_, auth, err := c.GetCredsAndAuth()
if err != nil {
return nil, err
}

// format Authorization header value
s := fmt.Sprintf("Bearer %s", auth.Token)
client := c.APIClientTemplate.BuildWithAuthorization(s)
return client.FetchResource(ctx, "/api/v1/test-list/psiphon-config")

// construct the URL to use
URL, err := urlx.ResolveReference(c.BaseURL, "/api/v1/test-list/psiphon-config", "")
if err != nil {
return nil, err
}

// get response
//
// use a model.DiscardLogger to avoid logging config
return httpclientx.GetRaw(ctx, URL, &httpclientx.Config{
Authorization: s,
Client: c.HTTPClient,
Logger: model.DiscardLogger,
UserAgent: c.UserAgent,
})
}
54 changes: 54 additions & 0 deletions internal/probeservices/psiphon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/ooni/probe-cli/v3/internal/mocks"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/runtimex"
Expand Down Expand Up @@ -198,4 +199,57 @@ func TestFetchPsiphonConfig(t *testing.T) {
t.Fatal("expected zero length data")
}
})

t.Run("is not logging the response body", 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()

// create and use a logger for collecting logs
logger := &testingx.Logger{}
client.Logger = logger

// override the HTTP client so we speak with our 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)
}

// assert that there are no logs
//
// the register, login, and psiphon API should not log their bodies
if diff := cmp.Diff([]string{}, logger.AllLines()); diff != "" {
t.Fatal(diff)
}
})
}
25 changes: 22 additions & 3 deletions internal/probeservices/register.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package probeservices

//
// register.go - POST /api/v1/register
//

import (
"context"

"github.com/ooni/probe-cli/v3/internal/httpclientx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/randx"
"github.com/ooni/probe-cli/v3/internal/urlx"
)

// MaybeRegister registers this client if not already registered
Expand All @@ -24,11 +30,24 @@ func (c Client) MaybeRegister(ctx context.Context, metadata model.OOAPIProbeMeta
OOAPIProbeMetadata: metadata,
Password: pwd,
}
var resp model.OOAPIRegisterResponse
if err := c.APIClientTemplate.Build().PostJSON(
ctx, "/api/v1/register", req, &resp); err != nil {

// construct the URL to use
URL, err := urlx.ResolveReference(c.BaseURL, "/api/v1/register", "")
if err != nil {
return err
}

resp, err := httpclientx.PostJSON[*model.OOAPIRegisterRequest, *model.OOAPIRegisterResponse](
ctx, URL, req, &httpclientx.Config{
Client: c.HTTPClient,
Logger: model.DiscardLogger,
UserAgent: c.UserAgent,
},
)
if err != nil {
return err
}

state.ClientID = resp.ClientID
state.Password = pwd
return c.StateFile.Set(state)
Expand Down
Loading