2021-11-09 14:42:48 -07:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2023-02-02 17:15:17 -08:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
|
|
2021-10-25 10:36:58 -07:00
|
|
|
package patchstruct
|
|
|
|
|
|
|
|
|
|
import (
|
2021-11-09 14:42:48 -07:00
|
|
|
"strings"
|
2021-10-25 10:36:58 -07:00
|
|
|
"testing"
|
|
|
|
|
|
2021-11-09 14:42:48 -07:00
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
|
|
|
"github.com/hashicorp/boundary/internal/host"
|
2021-10-25 10:36:58 -07:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type testCase struct {
|
|
|
|
|
name string
|
2022-10-25 18:29:38 -05:00
|
|
|
dst map[string]any
|
|
|
|
|
src map[string]any
|
|
|
|
|
expected map[string]any
|
2021-10-25 10:36:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var testCases = []testCase{
|
|
|
|
|
{
|
|
|
|
|
name: "merge",
|
2022-10-25 18:29:38 -05:00
|
|
|
dst: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
src: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"baz": "qux",
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
expected: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
"baz": "qux",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "overwrite",
|
2022-10-25 18:29:38 -05:00
|
|
|
dst: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
src: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "baz",
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
expected: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "baz",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "delete",
|
2022-10-25 18:29:38 -05:00
|
|
|
dst: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
"baz": "qux",
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
src: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"baz": nil,
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
expected: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "recursive",
|
2022-10-25 18:29:38 -05:00
|
|
|
dst: map[string]any{
|
|
|
|
|
"nested": map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"a": "b",
|
|
|
|
|
},
|
|
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
src: map[string]any{
|
|
|
|
|
"nested": map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"c": "d",
|
|
|
|
|
},
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
expected: map[string]any{
|
|
|
|
|
"nested": map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"a": "b",
|
|
|
|
|
"c": "d",
|
|
|
|
|
},
|
|
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
|
|
|
|
},
|
2021-11-09 14:42:48 -07:00
|
|
|
{
|
|
|
|
|
name: "nested with nil",
|
2021-11-11 11:28:08 -05:00
|
|
|
dst: nil,
|
2022-10-25 18:29:38 -05:00
|
|
|
src: map[string]any{
|
2021-11-09 14:42:48 -07:00
|
|
|
"a": "b",
|
2022-10-25 18:29:38 -05:00
|
|
|
"nested": map[string]any{
|
2021-11-09 14:42:48 -07:00
|
|
|
"c": "d",
|
|
|
|
|
"e": nil,
|
|
|
|
|
},
|
|
|
|
|
"f": nil,
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
expected: map[string]any{
|
2021-11-09 14:42:48 -07:00
|
|
|
"a": "b",
|
2022-10-25 18:29:38 -05:00
|
|
|
"nested": map[string]any{
|
2021-11-09 14:42:48 -07:00
|
|
|
"c": "d",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2021-10-25 10:36:58 -07:00
|
|
|
{
|
|
|
|
|
name: "overwrite with map in src",
|
2022-10-25 18:29:38 -05:00
|
|
|
dst: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
src: map[string]any{
|
|
|
|
|
"foo": map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"a": "b",
|
|
|
|
|
},
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
expected: map[string]any{
|
|
|
|
|
"foo": map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"a": "b",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil src",
|
2022-10-25 18:29:38 -05:00
|
|
|
dst: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
2021-11-26 13:58:19 -08:00
|
|
|
src: nil,
|
|
|
|
|
expected: nil,
|
2021-10-25 10:36:58 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil dst",
|
|
|
|
|
dst: nil,
|
2022-10-25 18:29:38 -05:00
|
|
|
src: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
expected: map[string]any{
|
2021-10-25 10:36:58 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
|
|
|
|
},
|
2021-11-09 14:42:48 -07:00
|
|
|
{
|
|
|
|
|
name: "nil dst with src nil value",
|
|
|
|
|
dst: nil,
|
2022-10-25 18:29:38 -05:00
|
|
|
src: map[string]any{
|
2021-11-09 14:42:48 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
"fiz": nil,
|
|
|
|
|
},
|
2022-10-25 18:29:38 -05:00
|
|
|
expected: map[string]any{
|
2021-11-09 14:42:48 -07:00
|
|
|
"foo": "bar",
|
|
|
|
|
},
|
|
|
|
|
},
|
2021-10-25 10:36:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchStruct(t *testing.T) {
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
|
tc := tc
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
dst, src := mustStruct(tc.dst), mustStruct(tc.src)
|
|
|
|
|
dstOrig, srcOrig := mustStruct(tc.dst), mustStruct(tc.src)
|
|
|
|
|
if tc.dst == nil {
|
|
|
|
|
dst = nil
|
|
|
|
|
dstOrig = nil
|
|
|
|
|
}
|
|
|
|
|
if tc.src == nil {
|
|
|
|
|
src = nil
|
|
|
|
|
srcOrig = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actual := PatchStruct(dst, src)
|
2021-11-09 14:42:48 -07:00
|
|
|
require.Empty(cmp.Diff(mustStruct(tc.expected), actual,
|
|
|
|
|
cmpopts.IgnoreUnexported(structpb.Struct{}, structpb.Value{}),
|
2022-10-25 18:29:38 -05:00
|
|
|
cmpopts.SortSlices(func(x, y any) bool {
|
2021-11-09 14:42:48 -07:00
|
|
|
return x.(*host.IpAddress).Address < y.(*host.IpAddress).Address
|
|
|
|
|
})))
|
2021-10-25 10:36:58 -07:00
|
|
|
require.Equal(dstOrig, dst)
|
|
|
|
|
require.Equal(srcOrig, src)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchBytes(t *testing.T) {
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
|
tc := tc
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
dst, src := mustMarshal(tc.dst), mustMarshal(tc.src)
|
|
|
|
|
|
|
|
|
|
actual, err := PatchBytes(dst, src)
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
requireEqualEncoded(t, mustMarshal(tc.expected), actual)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchBytesErr(t *testing.T) {
|
|
|
|
|
t.Run("src", func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
_, err := PatchBytes(nil, []byte("foo"))
|
2021-11-09 14:42:48 -07:00
|
|
|
require.ErrorIs(err, proto.Error)
|
|
|
|
|
require.True(strings.HasPrefix(err.Error(), "error reading source data: "))
|
2021-10-25 10:36:58 -07:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 18:29:38 -05:00
|
|
|
func mustStruct(in map[string]any) *structpb.Struct {
|
2021-10-25 10:36:58 -07:00
|
|
|
out, err := structpb.NewStruct(in)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 18:29:38 -05:00
|
|
|
func mustMarshal(in map[string]any) []byte {
|
2021-10-25 10:36:58 -07:00
|
|
|
b, err := proto.Marshal(mustStruct(in))
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func requireEqualEncoded(t *testing.T, expected, actual []byte) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
|
|
|
|
expectedpb, actualpb := new(structpb.Struct), new(structpb.Struct)
|
|
|
|
|
|
|
|
|
|
err := proto.Unmarshal(expected, expectedpb)
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
|
|
err = proto.Unmarshal(actual, actualpb)
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
|
|
require.Equal(expectedpb, actualpb)
|
|
|
|
|
}
|