plugins/testfetchers/uri/uri.go (101 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
package uri
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/facebookincubator/contest/pkg/test"
"github.com/facebookincubator/contest/pkg/xcontext"
"github.com/insomniacslk/xjson"
)
// Name defined the name of the plugin
var (
Name = "URI"
)
var supportedSchemes = []string{
"file",
"https",
"http",
}
// FetchParameters contains the parameters necessary to fetch tests. This
// structure is populated from a JSON blob.
type FetchParameters struct {
TestName string
// URI is the string pointing to where the test definition is stored. At
// the moment only file://, https:// and http:// are supported.
URI *xjson.URL
}
// URI implements contest.TestFetcher interface, returning dummy test fetcher
type URI struct {
}
// ValidateFetchParameters performs sanity checks on the fields of the
// parameters that will be passed to Fetch.
func (tf URI) ValidateFetchParameters(_ xcontext.Context, params []byte) (interface{}, error) {
var fp FetchParameters
if err := json.Unmarshal(params, &fp); err != nil {
return nil, err
}
if fp.TestName == "" {
return nil, fmt.Errorf("test name cannot be empty for fetch parameters")
}
if fp.URI == nil {
return nil, fmt.Errorf("file URI not specified in fetch parameters")
}
scheme := fp.URI.Scheme
if scheme == "" {
// if no scheme is specified, assume "file://"
scheme = "file"
}
scheme = strings.ToLower(scheme)
supported := false
for _, s := range supportedSchemes {
if s == scheme {
supported = true
}
}
if !supported {
return nil, fmt.Errorf("unsupported scheme %s", scheme)
}
if scheme == "file" {
if fp.URI.Host != "" && fp.URI.Host != "localhost" {
return nil, fmt.Errorf("invalid host in URI: '%s'. Only 'localhost' or empty string are supported for scheme %s", fp.URI.Host, scheme)
}
}
return fp, nil
}
// Fetch returns the information necessary to build a Test object. The returned
// values are:
// * Name of the test
// * list of step definitions
// * an error if any
func (tf *URI) Fetch(ctx xcontext.Context, params interface{}) (string, []*test.TestStepDescriptor, error) {
fetchParams, ok := params.(FetchParameters)
if !ok {
return "", nil, fmt.Errorf("Fetch expects uri.FetchParameters object")
}
ctx.Debugf("Fetching tests with params %+v", fetchParams)
scheme := strings.ToLower(strings.ToLower(fetchParams.URI.Scheme))
var (
buf []byte
err error
)
switch scheme {
case "", "file":
// naively assume that it's OK to read the whole file in memory.
buf, err = ioutil.ReadFile(fetchParams.URI.Path)
if err != nil {
return "", nil, err
}
case "http", "https":
resp, err := http.Get(fetchParams.URI.String())
if err != nil {
return "", nil, err
}
buf, err = ioutil.ReadAll(resp.Body)
if err != nil {
return "", nil, err
}
default:
return "", nil, fmt.Errorf("unsupported scheme '%s'", scheme)
}
type doc struct {
Steps []*test.TestStepDescriptor
}
var d doc
if err := json.Unmarshal(buf, &d); err != nil {
return "", nil, fmt.Errorf("cannot decode JSON test description: %v", err)
}
// TODO do something with the Report object (or factor it out from the step
// definition)
return fetchParams.TestName, d.Steps, nil
}
// New initializes the TestFetcher object
func New() test.TestFetcher {
return &URI{}
}
// Load returns the name and factory which are needed to register the
// TestFetcher.
func Load() (string, test.TestFetcherFactory) {
return Name, New
}