diff --git a/validator/node/BUILD.bazel b/validator/node/BUILD.bazel index e8db9be686..2ed76e8114 100644 --- a/validator/node/BUILD.bazel +++ b/validator/node/BUILD.bazel @@ -4,6 +4,7 @@ go_test( name = "go_default_test", size = "small", srcs = ["node_test.go"], + data = glob(["testdata/**"]), embed = [":go_default_library"], deps = [ "//cmd/validator/flags:go_default_library", diff --git a/validator/node/node.go b/validator/node/node.go index 0569540cff..ccaf785c14 100644 --- a/validator/node/node.go +++ b/validator/node/node.go @@ -5,7 +5,10 @@ package node import ( "context" + "encoding/json" "fmt" + "io" + "io/ioutil" "net/http" "net/url" "os" @@ -645,3 +648,65 @@ func clearDB(ctx context.Context, dataDir string, force bool) error { return nil } + +func unmarshalFromURL(ctx context.Context, from string, to interface{}) error { + u, err := url.ParseRequestURI(from) + if err != nil { + return err + } + if u.Scheme == "" || u.Host == "" { + return fmt.Errorf("invalid URL: %s", from) + } + req, reqerr := http.NewRequestWithContext(ctx, http.MethodGet, from, nil) + if reqerr != nil { + return errors.Wrap(reqerr, "failed to create http request") + } + req.Header.Set("Content-Type", "application/json") + resp, resperr := http.DefaultClient.Do(req) + if resperr != nil { + return errors.Wrap(resperr, "failed to send http request") + } + defer func(Body io.ReadCloser) { + err = Body.Close() + if err != nil { + log.WithError(err).Error("failed to close response body") + } + }(resp.Body) + if resp.StatusCode != http.StatusOK { + return errors.Errorf("http request to %v failed with status code %d", from, resp.StatusCode) + } + if decodeerr := json.NewDecoder(resp.Body).Decode(&to); decodeerr != nil { + return errors.Wrap(decodeerr, "failed to decode http response") + } + return nil +} + +func unmarshalFromFile(ctx context.Context, from string, to interface{}) error { + if ctx == nil { + return errors.New("node: nil context passed to unmarshalFromFile") + } + cleanpath := filepath.Clean(from) + fileExtension := filepath.Ext(cleanpath) + if fileExtension != ".json" { + return errors.Errorf("unsupported file extension %s , (ex. '.json')", fileExtension) + } + jsonFile, jsonerr := os.Open(cleanpath) + if jsonerr != nil { + return errors.Wrap(jsonerr, "failed to open json file") + } + // defer the closing of our jsonFile so that we can parse it later on + defer func(jsonFile *os.File) { + err := jsonFile.Close() + if err != nil { + log.WithError(err).Error("failed to close json file") + } + }(jsonFile) + byteValue, readerror := ioutil.ReadAll(jsonFile) + if readerror != nil { + return errors.Wrap(readerror, "failed to read json file") + } + if unmarshalerr := json.Unmarshal(byteValue, &to); unmarshalerr != nil { + return errors.Wrap(unmarshalerr, "failed to unmarshal json file") + } + return nil +} diff --git a/validator/node/node_test.go b/validator/node/node_test.go index 852358a2a8..0115a55d36 100644 --- a/validator/node/node_test.go +++ b/validator/node/node_test.go @@ -5,6 +5,8 @@ import ( "flag" "fmt" "io/ioutil" + "net/http" + "net/http/httptest" "os" "path/filepath" "testing" @@ -184,3 +186,125 @@ func newWeb3SignerCli(t *testing.T, baseUrl string, publicKeysOrURL string) *cli require.NoError(t, set.Set(flags.Web3SignerPublicValidatorKeysFlag.Name, publicKeysOrURL)) return cli.NewContext(&app, set, nil) } + +type test struct { + Foo string `json:"foo"` + Bar int `json:"bar"` +} + +func TestUnmarshalFromFile(t *testing.T) { + ctx := context.Background() + type args struct { + File string + To interface{} + } + tests := []struct { + name string + args args + want interface{} + urlResponse string + wantErr bool + }{ + { + name: "Happy Path File", + args: args{ + File: "./testdata/test-unmarshal-good.json", + To: &test{}, + }, + want: &test{ + Foo: "foo", + Bar: 1, + }, + wantErr: false, + }, + { + name: "Bad File Path, not json", + args: args{ + File: "./jsontools.go", + To: &test{}, + }, + want: &test{}, + wantErr: true, + }, + { + name: "Bad File Path", + args: args{ + File: "./testdata/test-unmarshal-bad.json", + To: &test{}, + }, + want: &test{}, + wantErr: true, + }, + { + name: "Bad File Path, not found", + args: args{ + File: "./test-notfound.json", + To: &test{}, + }, + want: &test{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := unmarshalFromFile(ctx, tt.args.File, tt.args.To); (err != nil) != tt.wantErr { + t.Errorf(" error = %v, wantErr %v", err, tt.wantErr) + return + } + require.DeepEqual(t, tt.want, tt.args.To) + }) + } +} + +func TestUnmarshalFromURL(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Header().Set("Content-Type", "application/json") + _, err := fmt.Fprintf(w, `{ "foo": "foo", "bar": 1}`) + require.NoError(t, err) + })) + defer srv.Close() + ctx := context.Background() + type args struct { + URL string + To interface{} + } + tests := []struct { + name string + args args + want interface{} + urlResponse string + wantErr bool + }{ + { + name: "Happy Path URL", + args: args{ + URL: srv.URL, + To: &test{}, + }, + want: &test{ + Foo: "foo", + Bar: 1, + }, + wantErr: false, + }, + { + name: "Bad URL", + args: args{ + URL: "sadjflksdjflksadjflkdj", + To: &test{}, + }, + want: &test{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := unmarshalFromURL(ctx, tt.args.URL, tt.args.To); (err != nil) != tt.wantErr { + t.Errorf(" error = %v, wantErr %v", err, tt.wantErr) + return + } + require.DeepEqual(t, tt.want, tt.args.To) + }) + } +} diff --git a/validator/node/testdata/test-unmarshal-bad.json b/validator/node/testdata/test-unmarshal-bad.json new file mode 100644 index 0000000000..0b2fffd4fd --- /dev/null +++ b/validator/node/testdata/test-unmarshal-bad.json @@ -0,0 +1,2 @@ +{ + "foo": "bar" diff --git a/validator/node/testdata/test-unmarshal-good.json b/validator/node/testdata/test-unmarshal-good.json new file mode 100644 index 0000000000..c1150b906a --- /dev/null +++ b/validator/node/testdata/test-unmarshal-good.json @@ -0,0 +1,4 @@ +{ + "foo": "foo", + "bar": 1 +} \ No newline at end of file