Skip to content

Commit

Permalink
refactor(mlablocatev2): use interfaces, remove unused fields, add docs
Browse files Browse the repository at this point in the history
This is a very light refactoring of the mlablocatev2 package where we do
the following things:

1. use interfaces rather than depending on other pkgs where possible

2. add a missing test to the test suite

3. write more comprehensive docs (including todo-next comments)
  • Loading branch information
bassosimone committed Jun 15, 2021
1 parent 2613579 commit b9792d6
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 29 deletions.
2 changes: 1 addition & 1 deletion internal/engine/internal/mlablocate/mlablocate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/apex/log"
)

func TestWithoutProxy(t *testing.T) {
func TestSuccess(t *testing.T) {
client := NewClient(
http.DefaultClient,
log.Log,
Expand Down
69 changes: 55 additions & 14 deletions internal/engine/internal/mlablocatev2/mlablocatev2.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Package mlablocatev2 implements m-lab locate services API v2.
// Package mlablocatev2 implements m-lab locate services API v2. This
// API currently only allows you to get servers for ndt7. Use the
// mlablocate package for all other m-lab tools.
package mlablocatev2

import (
Expand All @@ -10,7 +12,6 @@ import (
"net/url"
"regexp"

"github.com/ooni/probe-cli/v3/internal/engine/model"
"github.com/ooni/probe-cli/v3/internal/iox"
)

Expand All @@ -27,17 +28,40 @@ var (
ErrEmptyResponse = errors.New("mlablocatev2: empty response")
)

// Client is a client for v2 of the locate services.
// Logger is the logger expected by this package.
type Logger interface {
// Debugf formats and emits a debug message.
Debugf(format string, v ...interface{})
}

// HTTPClient is anything that looks like an http.Client.
type HTTPClient interface {
// Do behaves like http.Client.Do.
Do(req *http.Request) (*http.Response, error)
}

// Client is a client for v2 of the locate services. Please use the
// NewClient factory to construct a new instance of client, otherwise
// you MUST fill all the fields marked as MANDATORY.
type Client struct {
HTTPClient *http.Client
Hostname string
Logger model.Logger
Scheme string
UserAgent string
// HTTPClient is the MANDATORY http client to use
HTTPClient HTTPClient

// Hostname is the MANDATORY hostname of the mlablocate API.
Hostname string

// Logger is the MANDATORY logger to use.
Logger Logger

// Scheme is the MANDATORY scheme to use (http or https).
Scheme string

// UserAgent is the MANDATORY user-agent to use.
UserAgent string
}

// NewClient creates a client for v2 of the locate services.
func NewClient(httpClient *http.Client, logger model.Logger, userAgent string) Client {
func NewClient(httpClient HTTPClient, logger Logger, userAgent string) Client {
return Client{
HTTPClient: httpClient,
Hostname: "locate.measurementlab.net",
Expand All @@ -49,8 +73,8 @@ func NewClient(httpClient *http.Client, logger model.Logger, userAgent string) C

// entryRecord describes one of the boxes returned by v2 of
// the locate service. It gives you the FQDN of the specific
// box along with URLs for each experiment phase. Use the
// URLs directly because they contain access tokens.
// box along with URLs for each experiment phase. You MUST
// use the URLs directly because they contain access tokens.
type entryRecord struct {
Machine string `json:"machine"`
URLs map[string]string `json:"urls"`
Expand Down Expand Up @@ -83,6 +107,8 @@ type resultRecord struct {
// query performs a locate.measurementlab.net query
// using v2 of the locate protocol.
func (c Client) query(ctx context.Context, path string) (resultRecord, error) {
// TODO(bassosimone): this code should probably be
// refactored to use the httpx package.
URL := &url.URL{
Scheme: c.Scheme,
Host: c.Hostname,
Expand Down Expand Up @@ -116,10 +142,22 @@ func (c Client) query(ctx context.Context, path string) (resultRecord, error) {

// NDT7Result is the result of a v2 locate services query for ndt7.
type NDT7Result struct {
Hostname string
Site string
// Hostname is an informative field containing the hostname
// to which you're connected. Because there are access tokens,
// you cannot use this field directly.
Hostname string

// Site is an informative field containing the site
// to which the server belongs to.
Site string

// WSSDownloadURL is the WebSocket URL to be used for
// performing a download over HTTPS. Note that the URL
// typically includes the required access token.
WSSDownloadURL string
WSSUploadURL string

// WSSUploadURL is like WSSDownloadURL but for the upload.
WSSUploadURL string
}

// QueryNDT7 performs a v2 locate services query for ndt7.
Expand All @@ -137,6 +175,9 @@ func (c Client) QueryNDT7(ctx context.Context) ([]NDT7Result, error) {
if r.WSSDownloadURL == "" || r.WSSUploadURL == "" {
continue
}
// Implementation note: we extract the hostname from the
// download URL, under the assumption that the download and
// the upload URLs have the same hostname.
url, err := url.Parse(r.WSSDownloadURL)
if err != nil {
continue
Expand Down
47 changes: 33 additions & 14 deletions internal/engine/internal/mlablocatev2/mlablocatev2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import (
)

func TestSuccess(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
// this test is ~0.5 s, so we can always run it
client := NewClient(http.DefaultClient, log.Log, "miniooni/0.1.0-dev")
result, err := client.QueryNDT7(context.Background())
if err != nil {
Expand All @@ -26,7 +24,7 @@ func TestSuccess(t *testing.T) {
}
for _, entry := range result {
if entry.Hostname == "" {
t.Fatal("expected non empty Machine here")
t.Fatal("expected non empty Hostname here")
}
if entry.Site == "" {
t.Fatal("expected non=-empty Site here")
Expand All @@ -47,9 +45,7 @@ func TestSuccess(t *testing.T) {
}

func Test404Response(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
// this test is ~0.5 s, so we can always run it
client := NewClient(http.DefaultClient, log.Log, "miniooni/0.1.0-dev")
result, err := client.query(context.Background(), "nonexistent")
if !errors.Is(err, ErrRequestFailed) {
Expand All @@ -68,7 +64,7 @@ func TestNewRequestFailure(t *testing.T) {
t.Fatal("not the error we expected")
}
if result.Results != nil {
t.Fatal("expected empty fqdn")
t.Fatal("expected nil results")
}
}

Expand All @@ -83,7 +79,7 @@ func TestHTTPClientDoFailure(t *testing.T) {
t.Fatal("not the error we expected")
}
if result.Results != nil {
t.Fatal("expected empty fqdn")
t.Fatal("expected nil results")
}
}

Expand All @@ -105,7 +101,7 @@ func TestCannotReadBody(t *testing.T) {
t.Fatal("not the error we expected")
}
if result.Results != nil {
t.Fatal("expected empty fqdn")
t.Fatal("expected nil results")
}
}

Expand All @@ -127,7 +123,7 @@ func TestInvalidJSON(t *testing.T) {
t.Fatal("not the error we expected")
}
if result.Results != nil {
t.Fatal("expected empty fqdn")
t.Fatal("expected nil results")
}
}

Expand All @@ -149,7 +145,7 @@ func TestEmptyResponse(t *testing.T) {
t.Fatal("not the error we expected")
}
if result != nil {
t.Fatal("expected empty fqdn")
t.Fatal("expected nil results")
}
}

Expand All @@ -168,7 +164,7 @@ func TestNDT7QueryFails(t *testing.T) {
t.Fatal("not the error we expected")
}
if result != nil {
t.Fatal("expected empty fqdn")
t.Fatal("expected nil results")
}
}

Expand All @@ -191,7 +187,30 @@ func TestNDT7InvalidURLs(t *testing.T) {
t.Fatal("not the error we expected")
}
if result != nil {
t.Fatal("expected empty fqdn")
t.Fatal("expected nil results")
}
}

func TestNDT7EmptyURLs(t *testing.T) {
client := NewClient(http.DefaultClient, log.Log, "miniooni/0.1.0-dev")
client.HTTPClient = &http.Client{
Transport: FakeTransport{
Resp: &http.Response{
StatusCode: 200,
Body: FakeBody{
Data: []byte(
`{"results":[{"machine":"mlab3-mil04.mlab-oti.measurement-lab.org","urls":{"wss:///ndt/v7/download":"","wss:///ndt/v7/upload":""}}]}`),
Err: io.EOF,
},
},
},
}
result, err := client.QueryNDT7(context.Background())
if !errors.Is(err, ErrEmptyResponse) {
t.Fatal("not the error we expected")
}
if result != nil {
t.Fatal("expected nil results")
}
}

Expand Down

0 comments on commit b9792d6

Please sign in to comment.