refactor(mlablocatev2): use interfaces, add missing tests, add docs (#384)
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)
This commit is contained in:
parent
2613579768
commit
d84cf5b69f
|
@ -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,
|
||||
|
|
|
@ -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 (
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
// HTTPClient is the MANDATORY http client to use
|
||||
HTTPClient HTTPClient
|
||||
|
||||
// Hostname is the MANDATORY hostname of the mlablocate API.
|
||||
Hostname string
|
||||
Logger model.Logger
|
||||
|
||||
// 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",
|
||||
|
@ -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"`
|
||||
|
@ -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,
|
||||
|
@ -116,9 +142,21 @@ 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 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 is like WSSDownloadURL but for the upload.
|
||||
WSSUploadURL string
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user