// Copyright (c) 2017 Arista Networks, Inc. // Use of this source code is governed by the Apache License 2.0 // that can be found in the COPYING file. package gnmi import ( "bytes" "io/ioutil" "os" "testing" "github.com/aristanetworks/goarista/test" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes/any" pb "github.com/openconfig/gnmi/proto/gnmi" ) func TestNewSetRequest(t *testing.T) { pathFoo := &pb.Path{ Element: []string{"foo"}, Elem: []*pb.PathElem{{Name: "foo"}}, } pathCli := &pb.Path{ Origin: "cli", } pathP4 := &pb.Path{ Origin: "p4_config", } p4FileContent := "p4_config test" p4TestFile, err := ioutil.TempFile("", "p4TestFile") if err != nil { t.Errorf("cannot create test file for p4_config") } p4Filename := p4TestFile.Name() defer os.Remove(p4Filename) if _, err := p4TestFile.WriteString(p4FileContent); err != nil { t.Errorf("cannot write test file for p4_config") } p4TestFile.Close() testCases := map[string]struct { setOps []*Operation exp pb.SetRequest }{ "delete": { setOps: []*Operation{{Type: "delete", Path: []string{"foo"}}}, exp: pb.SetRequest{Delete: []*pb.Path{pathFoo}}, }, "update": { setOps: []*Operation{{Type: "update", Path: []string{"foo"}, Val: "true"}}, exp: pb.SetRequest{ Update: []*pb.Update{{ Path: pathFoo, Val: &pb.TypedValue{ Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("true")}}, }}, }, }, "replace": { setOps: []*Operation{{Type: "replace", Path: []string{"foo"}, Val: "true"}}, exp: pb.SetRequest{ Replace: []*pb.Update{{ Path: pathFoo, Val: &pb.TypedValue{ Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte("true")}}, }}, }, }, "cli-replace": { setOps: []*Operation{{Type: "replace", Origin: "cli", Val: "hostname foo\nip routing"}}, exp: pb.SetRequest{ Replace: []*pb.Update{{ Path: pathCli, Val: &pb.TypedValue{ Value: &pb.TypedValue_AsciiVal{AsciiVal: "hostname foo\nip routing"}}, }}, }, }, "p4_config": { setOps: []*Operation{{Type: "replace", Origin: "p4_config", Val: p4Filename}}, exp: pb.SetRequest{ Replace: []*pb.Update{{ Path: pathP4, Val: &pb.TypedValue{ Value: &pb.TypedValue_ProtoBytes{ProtoBytes: []byte(p4FileContent)}}, }}, }, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { got, err := newSetRequest(tc.setOps) if err != nil { t.Fatal(err) } if diff := test.Diff(tc.exp, *got); diff != "" { t.Errorf("unexpected diff: %s", diff) } }) } } func TestStrUpdateVal(t *testing.T) { anyBytes, err := proto.Marshal(&pb.ModelData{Name: "foobar"}) if err != nil { t.Fatal(err) } anyMessage := &any.Any{TypeUrl: "gnmi/ModelData", Value: anyBytes} anyString := proto.CompactTextString(anyMessage) for name, tc := range map[string]struct { update *pb.Update exp string }{ "JSON Value": { update: &pb.Update{ Value: &pb.Value{ Value: []byte(`{"foo":"bar"}`), Type: pb.Encoding_JSON}}, exp: `{ "foo": "bar" }`, }, "JSON_IETF Value": { update: &pb.Update{ Value: &pb.Value{ Value: []byte(`{"foo":"bar"}`), Type: pb.Encoding_JSON_IETF}}, exp: `{ "foo": "bar" }`, }, "BYTES Value": { update: &pb.Update{ Value: &pb.Value{ Value: []byte{0xde, 0xad}, Type: pb.Encoding_BYTES}}, exp: "3q0=", }, "PROTO Value": { update: &pb.Update{ Value: &pb.Value{ Value: []byte{0xde, 0xad}, Type: pb.Encoding_PROTO}}, exp: "3q0=", }, "ASCII Value": { update: &pb.Update{ Value: &pb.Value{ Value: []byte("foobar"), Type: pb.Encoding_ASCII}}, exp: "foobar", }, "INVALID Value": { update: &pb.Update{ Value: &pb.Value{ Value: []byte("foobar"), Type: pb.Encoding(42)}}, exp: "foobar", }, "StringVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_StringVal{StringVal: "foobar"}}}, exp: "foobar", }, "IntVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_IntVal{IntVal: -42}}}, exp: "-42", }, "UintVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_UintVal{UintVal: 42}}}, exp: "42", }, "BoolVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_BoolVal{BoolVal: true}}}, exp: "true", }, "BytesVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_BytesVal{BytesVal: []byte{0xde, 0xad}}}}, exp: "3q0=", }, "FloatVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_FloatVal{FloatVal: 3.14}}}, exp: "3.14", }, "DecimalVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_DecimalVal{ DecimalVal: &pb.Decimal64{Digits: 314, Precision: 2}, }}}, exp: "3.14", }, "LeafListVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_LeaflistVal{ LeaflistVal: &pb.ScalarArray{Element: []*pb.TypedValue{ &pb.TypedValue{Value: &pb.TypedValue_BoolVal{BoolVal: true}}, &pb.TypedValue{Value: &pb.TypedValue_AsciiVal{AsciiVal: "foobar"}}, }}, }}}, exp: "[true, foobar]", }, "AnyVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_AnyVal{AnyVal: anyMessage}}}, exp: anyString, }, "JsonVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_JsonVal{JsonVal: []byte(`{"foo":"bar"}`)}}}, exp: `{ "foo": "bar" }`, }, "JsonIetfVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: []byte(`{"foo":"bar"}`)}}}, exp: `{ "foo": "bar" }`, }, "AsciiVal": { update: &pb.Update{Val: &pb.TypedValue{ Value: &pb.TypedValue_AsciiVal{AsciiVal: "foobar"}}}, exp: "foobar", }, } { t.Run(name, func(t *testing.T) { got := StrUpdateVal(tc.update) if got != tc.exp { t.Errorf("Expected: %q Got: %q", tc.exp, got) } }) } } func TestExtractJSON(t *testing.T) { jsonFile, err := ioutil.TempFile("", "extractJSON") if err != nil { t.Fatal(err) } defer os.Remove(jsonFile.Name()) if _, err := jsonFile.Write([]byte(`"jsonFile"`)); err != nil { jsonFile.Close() t.Fatal(err) } if err := jsonFile.Close(); err != nil { t.Fatal(err) } for val, exp := range map[string][]byte{ jsonFile.Name(): []byte(`"jsonFile"`), "foobar": []byte(`"foobar"`), `"foobar"`: []byte(`"foobar"`), "Val: true": []byte(`"Val: true"`), "host42": []byte(`"host42"`), "42": []byte("42"), "-123.43": []byte("-123.43"), "0xFFFF": []byte("0xFFFF"), // Int larger than can fit in 32 bits should be quoted "0x8000000000": []byte(`"0x8000000000"`), "-0x8000000000": []byte(`"-0x8000000000"`), "true": []byte("true"), "false": []byte("false"), "null": []byte("null"), "{true: 42}": []byte("{true: 42}"), "[]": []byte("[]"), } { t.Run(val, func(t *testing.T) { got := extractJSON(val) if !bytes.Equal(exp, got) { t.Errorf("Unexpected diff. Expected: %q Got: %q", exp, got) } }) } }