traefik/pkg/config/parser/flat_encode_test.go
Ludovic Fernandez 8d7eccad5d New static configuration loading system.
Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
2019-06-17 11:48:05 +02:00

1251 lines
28 KiB
Go

package parser
import (
"reflect"
"testing"
"time"
"github.com/containous/traefik/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEncodeToFlat(t *testing.T) {
testCases := []struct {
desc string
element interface{}
node *Node
opts *FlatOpts
expected []Flat
}{
{
desc: "string field",
element: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
FieldName: "Field",
Description: "field description",
Value: "test",
Kind: reflect.String,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "test",
}},
},
{
desc: "int field",
element: &struct {
Field int `description:"field description"`
}{
Field: 6,
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "6",
Kind: reflect.Int,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "6",
}},
},
{
desc: "bool field",
element: &struct {
Field bool `description:"field description"`
}{
Field: true,
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "true",
Kind: reflect.Bool,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "true",
}},
},
{
desc: "string pointer field",
element: &struct {
Field *string `description:"field description"`
}{
Field: func(v string) *string { return &v }("test"),
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "test",
Kind: reflect.Ptr,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "test",
}},
},
{
desc: "string pointer field, custom option",
element: &struct {
Field *string `description:"field description"`
}{
Field: func(v string) *string { return &v }("test"),
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "test",
Kind: reflect.Ptr,
Tag: `description:"field description"`,
},
},
},
opts: &FlatOpts{
Case: "upper",
Separator: "_",
SkipRoot: false,
},
expected: []Flat{{
Name: "TRAEFIK_FIELD",
Description: "field description",
Default: "test",
}},
},
{
desc: "int pointer field",
element: &struct {
Field *int `description:"field description"`
}{
Field: func(v int) *int { return &v }(6),
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "6",
Kind: reflect.Ptr,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "6",
}},
},
{
desc: "bool pointer field",
element: &struct {
Field *bool `description:"field description"`
}{
Field: func(v bool) *bool { return &v }(true),
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "true",
Kind: reflect.Ptr,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "true",
}},
},
{
desc: "slice of string field, no initial value",
element: &struct {
Field []string `description:"field description"`
}{},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Kind: reflect.Slice,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "",
}},
},
{
desc: "slice of string field, with initial value",
element: &struct {
Field []string `description:"field description"`
}{
Field: []string{"foo", "bar"},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "foo, bar",
Kind: reflect.Slice,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "foo, bar",
}},
},
{
desc: "slice of int field, no initial value",
element: &struct {
Field []int `description:"field description"`
}{},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Kind: reflect.Slice,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "",
}},
},
{
desc: "slice of int field, with initial value",
element: &struct {
Field []int `description:"field description"`
}{
Field: []int{6, 3},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "6, 3",
Kind: reflect.Slice,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "6, 3",
}},
},
{
desc: "map string field",
element: &struct {
Field map[string]string `description:"field description"`
}{
Field: map[string]string{
MapNamePlaceholder: "",
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Kind: reflect.Map,
Tag: `description:"field description"`,
Children: []*Node{
{
Name: "\u003cname\u003e",
FieldName: "\u003cname\u003e",
Kind: reflect.String,
},
},
},
},
},
expected: []Flat{{
Name: "field.<name>",
Description: "field description",
Default: "",
}},
},
{
desc: "struct pointer field",
element: &struct {
Foo *struct {
Field string `description:"field description"`
} `description:"foo description"`
}{
Foo: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Ptr,
Tag: `description:"foo description"`,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "test",
Kind: reflect.String,
Tag: `description:"field description"`,
},
},
},
},
},
expected: []Flat{
{
Name: "foo.field",
Description: "field description",
Default: "test",
},
},
},
{
desc: "struct pointer field, hide field",
element: &struct {
Foo *struct {
Field string `description:"-"`
} `description:"foo description"`
}{
Foo: &struct {
Field string `description:"-"`
}{
Field: "test",
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Ptr,
Tag: `description:"foo description"`,
Children: []*Node{
{
Name: "Field",
Description: "-",
FieldName: "Field",
Value: "test",
Kind: reflect.String,
Tag: `description:"-"`,
},
},
},
},
},
expected: nil,
},
{
desc: "struct pointer field, allow empty",
element: &struct {
Foo *struct {
Field string `description:"field description"`
} `description:"foo description" label:"allowEmpty"`
}{
Foo: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Ptr,
Tag: `description:"foo description" label:"allowEmpty"`,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "test",
Kind: reflect.String,
Tag: `description:"field description"`,
},
},
},
},
},
expected: []Flat{
{
Name: "foo",
Description: "foo description",
Default: "false",
},
{
Name: "foo.field",
Description: "field description",
Default: "test",
},
},
},
{
desc: "struct pointer field level 2",
element: &struct {
Foo *struct {
Fii *struct {
Field string `description:"field description"`
} `description:"fii description"`
} `description:"foo description"`
}{
Foo: &struct {
Fii *struct {
Field string `description:"field description"`
} `description:"fii description"`
}{
Fii: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Ptr,
Tag: `description:"foo description"`,
Children: []*Node{
{
Name: "Fii",
Description: "fii description",
FieldName: "Fii",
Kind: reflect.Ptr,
Tag: `description:"fii description"`,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "test",
Kind: reflect.String,
Tag: `description:"field description"`,
},
},
},
},
},
},
},
expected: []Flat{
{
Name: "foo.fii.field",
Description: "field description",
Default: "test",
},
},
},
{
desc: "struct pointer field level 2, allow empty",
element: &struct {
Foo *struct {
Fii *struct {
Field string `description:"field description"`
} `description:"fii description" label:"allowEmpty"`
} `description:"foo description" label:"allowEmpty"`
}{
Foo: &struct {
Fii *struct {
Field string `description:"field description"`
} `description:"fii description" label:"allowEmpty"`
}{
Fii: &struct {
Field string `description:"field description"`
}{
Field: "test",
},
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Ptr,
Tag: `description:"foo description" label:"allowEmpty"`,
Children: []*Node{
{
Name: "Fii",
Description: "fii description",
FieldName: "Fii",
Kind: reflect.Ptr,
Tag: `description:"fii description" label:"allowEmpty"`,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "test",
Kind: reflect.String,
Tag: `description:"field description"`,
},
},
},
},
},
},
},
expected: []Flat{
{
Name: "foo",
Description: "foo description",
Default: "false",
},
{
Name: "foo.fii",
Description: "fii description",
Default: "false",
},
{
Name: "foo.fii.field",
Description: "field description",
Default: "test",
},
},
},
{
desc: "map string field level 2",
element: &struct {
Foo *struct {
Fii map[string]string `description:"fii description"`
} `description:"foo description"`
}{
Foo: &struct {
Fii map[string]string `description:"fii description"`
}{
Fii: map[string]string{
MapNamePlaceholder: "",
},
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Ptr,
Tag: `description:"foo description"`,
Children: []*Node{
{
Name: "Fii",
Description: "fii description",
FieldName: "Fii",
Kind: reflect.Map,
Tag: `description:"fii description"`,
Children: []*Node{
{
Name: "\u003cname\u003e",
FieldName: "\u003cname\u003e",
Kind: reflect.String,
},
},
},
},
},
},
},
expected: []Flat{
{
Name: "foo.fii.<name>",
Description: "fii description",
Default: "",
},
},
},
{
desc: "map string pointer field level 2",
element: &struct {
Foo *struct {
Fii map[string]*string `description:"fii description"`
} `description:"foo description"`
}{
Foo: &struct {
Fii map[string]*string `description:"fii description"`
}{
Fii: map[string]*string{
MapNamePlaceholder: func(v string) *string { return &v }(""),
},
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Ptr,
Tag: `description:"foo description"`,
Children: []*Node{
{
Name: "Fii",
Description: "fii description",
FieldName: "Fii",
Kind: reflect.Map,
Tag: `description:"fii description"`,
Children: []*Node{
{
Name: "\u003cname\u003e",
FieldName: "\u003cname\u003e",
Kind: reflect.Ptr,
},
},
},
},
},
},
},
expected: []Flat{
{
Name: "foo.fii.<name>",
Description: "fii description",
Default: "",
},
},
},
{
desc: "map struct level 1",
element: &struct {
Foo map[string]struct {
Field string `description:"field description"`
Yo int `description:"yo description"`
} `description:"foo description"`
}{},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Map,
Tag: `description:"foo description"`,
Children: []*Node{
{
Name: "\u003cname\u003e",
FieldName: "\u003cname\u003e",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Kind: reflect.String,
Tag: `description:"field description"`,
},
{
Name: "Yo",
Description: "yo description",
FieldName: "Yo",
Value: "0",
Kind: reflect.Int,
Tag: `description:"yo description"`,
},
},
},
},
},
},
},
expected: []Flat{
{
Name: "foo.<name>",
Description: "foo description",
Default: "false",
},
{
Name: "foo.<name>.field",
Description: "field description",
Default: "",
},
{
Name: "foo.<name>.yo",
Description: "yo description",
Default: "0",
},
},
},
{
desc: "map struct pointer level 1",
element: &struct {
Foo map[string]*struct {
Field string `description:"field description"`
Yo string `description:"yo description"`
} `description:"foo description"`
}{},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Map,
Tag: `description:"foo description"`,
Children: []*Node{
{
Name: "\u003cname\u003e",
FieldName: "\u003cname\u003e",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Kind: reflect.String,
Tag: `description:"field description"`,
},
{
Name: "Yo",
Description: "yo description",
FieldName: "Yo",
Kind: reflect.String,
Tag: `description:"yo description"`,
},
},
},
},
},
},
},
expected: []Flat{
{
Name: "foo.<name>",
Description: "foo description",
Default: "false",
},
{
Name: "foo.<name>.field",
Description: "field description",
Default: "",
},
{
Name: "foo.<name>.yo",
Description: "yo description",
Default: "",
},
},
},
{
desc: "time duration field",
element: &struct {
Field time.Duration `description:"field description"`
}{
Field: 1 * time.Second,
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "1000000000",
Kind: reflect.Int64,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "1s",
}},
},
{
desc: "time duration field map",
element: &struct {
Foo map[string]*struct {
Field time.Duration `description:"field description"`
} `description:"foo description"`
}{
Foo: map[string]*struct {
Field time.Duration `description:"field description"`
}{
"<name>": {
Field: 0,
},
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Map,
Tag: `description:"foo description"`,
Children: []*Node{
{
Name: "\u003cname\u003e",
FieldName: "\u003cname\u003e",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "0",
Kind: reflect.Int64,
Tag: `description:"field description"`,
},
},
},
},
},
},
},
expected: []Flat{
{
Name: "foo.<name>",
Description: "foo description",
Default: "false",
},
{
Name: "foo.<name>.field",
Description: "field description",
Default: "0s",
},
},
},
{
desc: "time duration field map 2",
element: &struct {
Foo map[string]*struct {
Fii *struct {
Field time.Duration `description:"field description"`
}
} `description:"foo description"`
}{
Foo: map[string]*struct {
Fii *struct {
Field time.Duration `description:"field description"`
}
}{
"<name>": {
Fii: &struct {
Field time.Duration `description:"field description"`
}{
Field: 0,
},
},
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
Description: "foo description",
FieldName: "Foo",
Kind: reflect.Map,
Tag: `description:"foo description"`,
Children: []*Node{
{
Name: "\u003cname\u003e",
FieldName: "\u003cname\u003e",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Fii",
FieldName: "Fii",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "0",
Kind: reflect.Int64,
Tag: `description:"field description"`,
},
},
},
},
},
},
},
},
},
expected: []Flat{
{
Name: "foo.<name>",
Description: "foo description",
Default: "false",
},
{
Name: "foo.<name>.fii.field",
Description: "field description",
Default: "0s",
},
},
},
{
desc: "time duration field 2",
element: &struct {
Foo *struct {
Field time.Duration `description:"field description"`
}
}{
Foo: &struct {
Field time.Duration `description:"field description"`
}{
Field: 1 * time.Second,
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
FieldName: "Foo",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "1000000000",
Kind: reflect.Int64,
Tag: `description:"field description"`,
},
},
},
},
},
expected: []Flat{{
Name: "foo.field",
Description: "field description",
Default: "1s",
}},
},
{
desc: "time duration field 3",
element: &struct {
Foo *struct {
Fii *struct {
Field time.Duration `description:"field description"`
}
}
}{
Foo: &struct {
Fii *struct {
Field time.Duration `description:"field description"`
}
}{
Fii: &struct {
Field time.Duration `description:"field description"`
}{
Field: 1 * time.Second,
},
},
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Foo",
FieldName: "Foo",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Fii",
FieldName: "Fii",
Kind: reflect.Ptr,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "1000000000",
Kind: reflect.Int64,
Tag: `description:"field description"`,
},
},
},
},
},
},
},
expected: []Flat{{
Name: "foo.fii.field",
Description: "field description",
Default: "1s",
}},
},
{
desc: "time duration field",
element: &struct {
Field types.Duration `description:"field description"`
}{
Field: types.Duration(180 * time.Second),
},
node: &Node{
Name: "traefik",
FieldName: "",
Kind: reflect.Struct,
Children: []*Node{
{
Name: "Field",
Description: "field description",
FieldName: "Field",
Value: "180000000000",
Kind: reflect.Int64,
Tag: `description:"field description"`,
},
},
},
expected: []Flat{{
Name: "field",
Description: "field description",
Default: "180",
}},
},
{
desc: "slice of struct",
element: &struct {
Foo *struct {
Fii []struct {
Field1 string `description:"field1 description"`
Field2 int `description:"field2 description"`
} `description:"fii description"`
} `description:"foo description"`
}{
Foo: &struct {
Fii []struct {
Field1 string `description:"field1 description"`
Field2 int `description:"field2 description"`
} `description:"fii description"`
}{
Fii: []struct {
Field1 string `description:"field1 description"`
Field2 int `description:"field2 description"`
}{
{
Field1: "",
Field2: 0,
},
},
},
},
node: &Node{
Name: "traefik",
Kind: reflect.Struct,
Children: []*Node{
{Name: "Foo", Kind: reflect.Ptr, Description: "foo description", Children: []*Node{
{Name: "Fii", Kind: reflect.Slice, Description: "fii description", Children: []*Node{
{Name: "[0]", Kind: reflect.Struct, Children: []*Node{
{Name: "Field1", Value: "", Kind: reflect.String, Description: "field1 description"},
{Name: "Field2", Value: "0", Kind: reflect.Int, Description: "field2 description"},
}},
}},
}},
},
},
expected: []Flat{
{
Name: "foo.fii",
Description: "fii description",
Default: "",
},
{
Name: "foo.fii[0].field1",
Description: "field1 description",
Default: "",
},
{
Name: "foo.fii[0].field2",
Description: "field2 description",
Default: "0",
},
},
},
// Skipped: because realistically not needed in Traefik for now.
// {
// desc: "map of map field level 2",
// element: &struct {
// Foo *struct {
// Fii map[string]map[string]string `description:"fii description"`
// } `description:"foo description"`
// }{
// Foo: &struct {
// Fii map[string]map[string]string `description:"fii description"`
// }{
// Fii: map[string]map[string]string{
// MapNamePlaceholder: {
// MapNamePlaceholder: "test",
// },
// },
// },
// },
// expected: `XXX`,
// },
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var opts FlatOpts
if test.opts == nil {
opts = FlatOpts{Separator: ".", SkipRoot: true}
} else {
opts = *test.opts
}
entries, err := EncodeToFlat(test.element, test.node, opts)
require.NoError(t, err)
assert.Equal(t, test.expected, entries)
})
}
}