github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/confr/loader_test.go (about)

     1  package confr
     2  
     3  import (
     4  	"flag"
     5  	"os"
     6  	"reflect"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  )
    13  
    14  type DBConfig struct {
    15  	MySQL string `json:"mysql" toml:"mysql" yaml:"mysql" flag:"mysql"`
    16  	Redis string `json:"redis" toml:"redis" yaml:"redis"`
    17  }
    18  
    19  type MQConfig struct {
    20  	Cluster string `json:"cluster" toml:"cluster" yaml:"cluster" default:"some_default_cluster"`
    21  	Topic   string `json:"topic" toml:"topic" yaml:"topic"`
    22  	Group   string `json:"group" toml:"group" yaml:"group"`
    23  }
    24  
    25  type TestConfig struct {
    26  	Some1    string  `json:"some_1" toml:"some_1" yaml:"some_1" flag:"some1"`
    27  	Some1Ptr *string `json:"some_1_ptr" toml:"some_1_ptr" yaml:"some_1_ptr" flag:"some1_ptr"`
    28  	Some2    int     `json:"some_2" toml:"some_2" yaml:"some_2" flag:"some2"`
    29  	Some2Ptr *int    `json:"some_2_ptr" toml:"some_2_ptr" yaml:"some_2_ptr"`
    30  	Some3    int64   `json:"some_3" toml:"some_3" yaml:"some_3"`
    31  	Some3Ptr *int64  `json:"some_3_ptr" toml:"some_3_ptr" yaml:"some_3_ptr"`
    32  	Some4    int32   `json:"some_4" toml:"some_4" yaml:"some_4"`
    33  	Some4Ptr *int32  `json:"some_4_ptr" toml:"some_4_ptr" yaml:"some_4_ptr"`
    34  
    35  	Some5 []int    `json:"some_5" toml:"some_5" yaml:"some_5"`
    36  	Some6 []int64  `json:"some_6" toml:"some_6" yaml:"some_6"`
    37  	Some7 []int32  `json:"some_7" toml:"some_7" yaml:"some_7"`
    38  	Some8 []string `json:"some_8" toml:"some_8" yaml:"some_8"`
    39  	Some9 []string `json:"some_9" toml:"some_9" yaml:"some_9"`
    40  
    41  	SomeBool     bool  `json:"some_bool" toml:"some_bool" yaml:"some_bool" flag:"some-bool"`
    42  	SomeBoolPtr1 *bool `json:"some_bool_ptr1" toml:"some_bool_ptr1" yaml:"some_bool_ptr1" flag:"some-bool-ptr1"`
    43  	SomeBoolPtr2 *bool `json:"some_bool_ptr2" toml:"some_bool_ptr2" yaml:"some_bool_ptr2" flag:"some-bool-ptr2"`
    44  
    45  	OverrideEnvVar  int64    `json:"override_env_var" toml:"override_env_var" yaml:"override_env_var" env:"OVERRIDE_ENV_VAR"`
    46  	ExplicitEnvVar1 string   `env:"EXPLICIT_ENV_VAR1"`
    47  	ExplicitEnvVar2 *float64 `env:"EXPLICIT_ENV_VAR2"`
    48  
    49  	ImplicitEnvVar1        *string // test with Implicit_Env_Var1
    50  	ImplicitEnvVar2        string  // test with IMPLICIT_ENV_VAR2
    51  	ImplicitEnvVarOverride string  `json:"implicit_env_var_override" toml:"implicit_env_var_override" yaml:"implicit_env_var_override"`
    52  
    53  	SomeRemoteValue1    string    `custom:"some_remote_value_1"`
    54  	SomeRemoteValue1Ptr *string   `custom:"some_remote_value_1_ptr"`
    55  	SomeRemoteValue2    []string  `custom:"some_remote_value_2"`
    56  	SomeRemoteStruct    *MQConfig `custom:"some_remote_struct"`
    57  
    58  	DB    DBConfig  `json:"db" toml:"db" yaml:"db"`
    59  	DBPtr *DBConfig `json:"db_ptr" toml:"db_ptr" yaml:"db_ptr"`
    60  
    61  	DefaultMQ    MQConfig  `json:"default_mq" toml:"default_mq" yaml:"default_mq"`
    62  	DefaultMQPtr *MQConfig `json:"default_mq_ptr" toml:"default_mq_ptr" yaml:"default_mq_ptr"`
    63  
    64  	MQList []*MQConfig `json:"mq_list" toml:"mq_list" yaml:"mq_list"`
    65  
    66  	SomeDuration time.Duration `json:"some_duration" yaml:"some_duration" default:"2s"`
    67  }
    68  
    69  func TestLoad_SingleFile_JSON(t *testing.T) {
    70  	configFiles := []string{
    71  		"./testdata/config.test.json",
    72  	}
    73  	testLoad_SingleFile(t, configFiles...)
    74  }
    75  
    76  func TestLoad_SingleFile_TOML(t *testing.T) {
    77  	configFiles := []string{
    78  		"./testdata/config.test.toml",
    79  	}
    80  	testLoad_SingleFile(t, configFiles...)
    81  }
    82  
    83  func TestLoad_SingleFile_YAML(t *testing.T) {
    84  	configFiles := []string{
    85  		"./testdata/config.test.yml",
    86  	}
    87  	testLoad_SingleFile(t, configFiles...)
    88  }
    89  
    90  func testLoad_SingleFile(t *testing.T, files ...string) {
    91  	cfg := &TestConfig{}
    92  	err := New(&Config{Verbose: true}).loadFiles(cfg, files...)
    93  	assert.Nil(t, err)
    94  	assertSingleFileConfig(t, cfg)
    95  
    96  	assert.Empty(t, cfg.Some4)                   // not set in test config
    97  	assert.Equal(t, []int{5, 6, 7}, cfg.Some5)   // from test config
    98  	assert.Equal(t, []int64{6, 7, 8}, cfg.Some6) // from test config
    99  	assert.Empty(t, cfg.Some7)                   // not set in test config
   100  }
   101  
   102  func assertSingleFileConfig(t *testing.T, cfg *TestConfig, exclude ...string) {
   103  	testFields := []struct {
   104  		Key      string
   105  		Expected any
   106  		Actual   any
   107  	}{
   108  		{"some_1", "some_1", cfg.Some1},
   109  		{"some_1_ptr", "some_1_ptr", *cfg.Some1Ptr},
   110  		{"some_2", 2345, cfg.Some2},
   111  		{"some_2_ptr", 23456, *cfg.Some2Ptr},
   112  		{"some_8", []string{"a", "b", "c"}, cfg.Some8},
   113  		{"mysql_dsn", "mysql_dsn", cfg.DBPtr.MySQL},
   114  		{"redis_dsn", "redis_dsn", cfg.DBPtr.Redis},
   115  		{"override_env_var", int64(12345), cfg.OverrideEnvVar},
   116  	}
   117  	for _, tf := range testFields {
   118  		if !inStrings(exclude, tf.Key) {
   119  			assert.Equal(t, tf.Expected, tf.Actual)
   120  		}
   121  	}
   122  	assert.NotEmpty(t, cfg.MQList)
   123  }
   124  
   125  func inStrings(slice []string, elem string) bool {
   126  	for _, x := range slice {
   127  		if x == elem {
   128  			return true
   129  		}
   130  	}
   131  	return false
   132  }
   133  
   134  func TestLoad_MultipleFiles_JSON(t *testing.T) {
   135  	configFiles := []string{
   136  		"./testdata/config.test.json",
   137  		"./testdata/config.common.json",
   138  	}
   139  	testLoad_MultipleFiles(t, configFiles...)
   140  }
   141  
   142  func TestLoad_MultipleFiles_TOML(t *testing.T) {
   143  	configFiles := []string{
   144  		"./testdata/config.test.toml",
   145  		"./testdata/config.common.toml",
   146  	}
   147  	testLoad_MultipleFiles(t, configFiles...)
   148  }
   149  
   150  func TestLoad_MultipleFiles_YAML(t *testing.T) {
   151  	configFiles := []string{
   152  		"./testdata/config.test.yml",
   153  		"./testdata/config.common.yml",
   154  	}
   155  	testLoad_MultipleFiles(t, configFiles...)
   156  }
   157  
   158  func testLoad_MultipleFiles(t *testing.T, files ...string) {
   159  	cfg := &TestConfig{}
   160  	loader := New(&Config{Verbose: true})
   161  	err := loader.loadFiles(cfg, files...)
   162  	assert.Nil(t, err)
   163  	assertSingleFileConfig(t, cfg)
   164  	assertCommonConfig(t, cfg)
   165  
   166  	assert.Equal(t, 2345, cfg.Some2) // config.test override config.common
   167  }
   168  
   169  func assertCommonConfig(t *testing.T, cfg *TestConfig) {
   170  	assert.Equal(t, int64(-3456), cfg.Some3)
   171  	assert.Equal(t, int64(-34567), *cfg.Some3Ptr)
   172  	assert.Equal(t, "common_mysql_dsn", cfg.DB.MySQL)
   173  	assert.Equal(t, "common_redis_dsn", cfg.DB.Redis)
   174  	assert.Equal(t, "common_default_mq_topic", cfg.DefaultMQ.Topic)
   175  	assert.Empty(t, cfg.DefaultMQ.Cluster) // processDefaults not called
   176  	assert.Empty(t, cfg.DefaultMQ.Group)   // neither set in config.test.yml or config.common.yml
   177  
   178  	assert.Empty(t, cfg.Some4)                      // not set in either test config or common config
   179  	assert.Equal(t, []int{5, 6, 7}, cfg.Some5)      // from test config
   180  	assert.Equal(t, []int64{6, 7, 8}, cfg.Some6)    // from test config
   181  	assert.Equal(t, []int32{-7, -8, -9}, cfg.Some7) // from common config
   182  	assert.Equal(t, "env_var_override", cfg.ImplicitEnvVarOverride)
   183  }
   184  
   185  func TestLoad_DefaultValues_JSON(t *testing.T) {
   186  	configFiles := []string{
   187  		"./testdata/config.test.json",
   188  		"./testdata/config.common.json",
   189  	}
   190  	testLoad_DefaultValues(t, configFiles...)
   191  }
   192  
   193  func TestLoad_DefaultValues_TOML(t *testing.T) {
   194  	configFiles := []string{
   195  		"./testdata/config.test.toml",
   196  		"./testdata/config.common.toml",
   197  	}
   198  	testLoad_DefaultValues(t, configFiles...)
   199  }
   200  
   201  func TestLoad_DefaultValues_YAML(t *testing.T) {
   202  	configFiles := []string{
   203  		"./testdata/config.test.yml",
   204  		"./testdata/config.common.yml",
   205  	}
   206  	testLoad_DefaultValues(t, configFiles...)
   207  }
   208  
   209  func testLoad_DefaultValues(t *testing.T, files ...string) {
   210  	cfg := &TestConfig{}
   211  	loader := New(&Config{Verbose: true})
   212  	err := loader.loadFiles(cfg, files...)
   213  	assert.Nil(t, err)
   214  	err = loader.processDefaults(cfg)
   215  	assert.Nil(t, err)
   216  	assertSingleFileConfig(t, cfg)
   217  
   218  	assert.Empty(t, cfg.DefaultMQPtr) // pointer value not set
   219  	assert.Equal(t, "some_default_cluster", cfg.DefaultMQ.Cluster)
   220  	assert.Equal(t, "some_default_cluster", cfg.MQList[0].Cluster)
   221  	assert.Equal(t, "topic_0", cfg.MQList[0].Topic)
   222  	assert.Equal(t, "group_0", cfg.MQList[0].Group)
   223  	assert.Equal(t, "some_default_cluster", cfg.MQList[1].Cluster)
   224  	assert.Equal(t, "topic_1", cfg.MQList[1].Topic)
   225  	assert.Equal(t, "group_1", cfg.MQList[1].Group)
   226  	assert.Equal(t, "cluster_2", cfg.MQList[2].Cluster)
   227  	assert.Equal(t, "topic_2", cfg.MQList[2].Topic)
   228  	assert.Equal(t, "group_2", cfg.MQList[2].Group)
   229  	assert.Equal(t, 2*time.Second, cfg.SomeDuration)
   230  }
   231  
   232  func TestLoad_CommandLineFlag_JSON(t *testing.T) {
   233  	configFiles := []string{
   234  		"./testdata/config.test.json",
   235  		"./testdata/config.common.json",
   236  	}
   237  	testLoad_CommandLineFlag(t, configFiles...)
   238  }
   239  
   240  func TestLoad_CommandLineFlag_TOML(t *testing.T) {
   241  	configFiles := []string{
   242  		"./testdata/config.test.toml",
   243  		"./testdata/config.common.toml",
   244  	}
   245  	testLoad_CommandLineFlag(t, configFiles...)
   246  }
   247  
   248  func TestLoad_CommandLineFlag_YAML(t *testing.T) {
   249  	configFiles := []string{
   250  		"./testdata/config.test.yml",
   251  		"./testdata/config.common.yml",
   252  	}
   253  	testLoad_CommandLineFlag(t, configFiles...)
   254  }
   255  
   256  var defineFlagsOnce sync.Once
   257  
   258  func testLoad_CommandLineFlag(t *testing.T, files ...string) {
   259  	defineFlagsOnce.Do(func() {
   260  		flag.String("some1", "", "some string value 1")
   261  		flag.String("some1_ptr", "flag value some1_ptr", "some string ptr value 1")
   262  		flag.Int("some2", 0, "some int value 2")
   263  		flag.Bool("some-bool", false, "some bool value")
   264  		flag.Bool("some-bool-ptr1", false, "some bool ptr value 1")
   265  		flag.Bool("some-bool-ptr2", true, "some bool ptr value 2")
   266  	})
   267  
   268  	var err error
   269  	err = flag.Set("some1", "some1 flag.Set called")
   270  	assert.Nil(t, err)
   271  	err = flag.Set("some2", "5432")
   272  	assert.Nil(t, err)
   273  	err = flag.Set("some-bool", "true")
   274  	assert.Nil(t, err)
   275  	err = flag.Set("some-bool-ptr1", "false")
   276  	assert.Nil(t, err)
   277  
   278  	cfg := &TestConfig{}
   279  	loader := New(&Config{
   280  		Verbose: true,
   281  		FlagSet: flag.CommandLine,
   282  	})
   283  	err = loader.loadFiles(cfg, files...)
   284  	assert.Nil(t, err)
   285  	err = loader.processFlags(cfg)
   286  	assert.Nil(t, err)
   287  	assertSingleFileConfig(t, cfg, "some_1", "some_2")
   288  	assertCommonConfig(t, cfg)
   289  
   290  	// flags
   291  
   292  	assert.Equal(t, "some1 flag.Set called", cfg.Some1) // flag override config.test
   293  	assert.Equal(t, 5432, cfg.Some2)                    // flag override config.test and config.common
   294  
   295  	assert.True(t, cfg.SomeBool)       // set to true
   296  	assert.False(t, *cfg.SomeBoolPtr1) // set to false
   297  	assert.True(t, *cfg.SomeBoolPtr2)  // flag default value true
   298  }
   299  
   300  func TestLoad_CustomLoader_JSON(t *testing.T) {
   301  	configFiles := []string{
   302  		"./testdata/config.test.json",
   303  		"./testdata/config.common.json",
   304  	}
   305  	testLoad_CustomLoader(t, configFiles...)
   306  }
   307  
   308  func TestLoad_CustomLoader_TOML(t *testing.T) {
   309  	configFiles := []string{
   310  		"./testdata/config.test.toml",
   311  		"./testdata/config.common.toml",
   312  	}
   313  	testLoad_CustomLoader(t, configFiles...)
   314  }
   315  
   316  func TestLoad_CustomLoader_YAML(t *testing.T) {
   317  	configFiles := []string{
   318  		"./testdata/config.test.yml",
   319  		"./testdata/config.common.yml",
   320  	}
   321  	testLoad_CustomLoader(t, configFiles...)
   322  }
   323  
   324  func testLoad_CustomLoader(t *testing.T, files ...string) {
   325  	cfg := &TestConfig{}
   326  	loader := New(&Config{
   327  		Verbose:      true,
   328  		CustomLoader: testCustomLoader,
   329  	})
   330  	err := loader.loadFiles(cfg, files...)
   331  	assert.Nil(t, err)
   332  	assertSingleFileConfig(t, cfg)
   333  	err = loader.processCustom(cfg)
   334  	assert.Nil(t, err)
   335  
   336  	assert.Equal(t, "remote_value_1", cfg.SomeRemoteValue1)
   337  	assert.Equal(t, "remote_value_1_ptr", *cfg.SomeRemoteValue1Ptr)
   338  	assert.Equal(t, []string{"a", "b", "c"}, cfg.SomeRemoteValue2)
   339  	assert.Equal(t, MQConfig{
   340  		Cluster: "remote_struct_cluster",
   341  		Topic:   "remote_struct_topic",
   342  		Group:   "remote_struct_group",
   343  	}, *cfg.SomeRemoteStruct)
   344  }
   345  
   346  func testCustomLoader(typ reflect.Type, tag string) (any, error) {
   347  	return testCustomLoaderData[tag], nil
   348  }
   349  
   350  var testCustomLoaderData = map[string]any{
   351  	"some_remote_value_1":     "remote_value_1",
   352  	"some_remote_value_1_ptr": "remote_value_1_ptr",
   353  	"some_remote_value_2":     []string{"a", "b", "c"},
   354  	"some_remote_struct": &MQConfig{
   355  		Cluster: "remote_struct_cluster",
   356  		Topic:   "remote_struct_topic",
   357  		Group:   "remote_struct_group",
   358  	},
   359  }
   360  
   361  func TestUnmarshalFunc(t *testing.T) {
   362  	type testStruct struct {
   363  		A, B string
   364  	}
   365  	loader := New(&Config{
   366  		UnmarshalFunc: func(data []byte, v any, disallowUnknownFields bool) error {
   367  			x := v.(*testStruct)
   368  			x.A = "aaa"
   369  			x.B = "bbb"
   370  			return nil
   371  		},
   372  	})
   373  	var dst testStruct
   374  	err := loader.Load(&dst, "./testdata/config.test.yml")
   375  	assert.Nil(t, err)
   376  	assert.Equal(t, "aaa", dst.A)
   377  	assert.Equal(t, "bbb", dst.B)
   378  }
   379  
   380  func Test_getEnvName(t *testing.T) {
   381  	testcases := [][]string{
   382  		{"ManualOverride1", "Manual_Override1"},
   383  		{"DefaultVar", "Default_Var"},
   384  		{"AutoSplitVar", "Auto_Split_Var"},
   385  		{"SomeID", "Some_ID"},
   386  		{"SomeHTMLWord", "Some_HTML_Word"},
   387  		{"Parent2_SomeID", "Parent2_Some_ID"},
   388  		{"Parent2_SomeHTMLWord", "Parent2_Some_HTML_Word"},
   389  	}
   390  	loader := New(&Config{})
   391  	for _, tc := range testcases {
   392  		name := tc[0]
   393  		want1 := "Confr_" + tc[1]
   394  		got1 := loader.getEnvName("", name)
   395  		assert.Equal(t, want1, got1)
   396  
   397  		want2 := "MyApp_" + tc[1]
   398  		got2 := loader.getEnvName("MyApp", name)
   399  		assert.Equal(t, want2, got2)
   400  	}
   401  }
   402  
   403  func TestLoad_ExplicitEnv(t *testing.T) {
   404  	testEnv := [][]string{
   405  		{"OVERRIDE_ENV_VAR", "54321"},
   406  		{"EXPLICIT_ENV_VAR1", "ExplicitEnvVar1"},
   407  	}
   408  	for _, te := range testEnv {
   409  		_ = os.Setenv(te[0], te[1])
   410  	}
   411  	defer func() {
   412  		for _, te := range testEnv {
   413  			_ = os.Unsetenv(te[0])
   414  		}
   415  	}()
   416  
   417  	configFiles := []string{
   418  		"./testdata/config.test.yml",
   419  		"./testdata/config.common.yml",
   420  	}
   421  	cfg := &TestConfig{}
   422  	loader := New(&Config{
   423  		Verbose:      true,
   424  		CustomLoader: testCustomLoader,
   425  	})
   426  	err := loader.loadFiles(cfg, configFiles...)
   427  	assert.Nil(t, err)
   428  	assertSingleFileConfig(t, cfg)
   429  	err = loader.processCustom(cfg)
   430  	assert.Nil(t, err)
   431  	err = loader.processEnv(cfg, "")
   432  	assert.Nil(t, err)
   433  
   434  	assert.Equal(t, int64(54321), cfg.OverrideEnvVar)
   435  	assert.Equal(t, "ExplicitEnvVar1", cfg.ExplicitEnvVar1)
   436  	assert.Nil(t, cfg.ExplicitEnvVar2)
   437  
   438  	assert.Empty(t, cfg.ImplicitEnvVar1)
   439  	assert.Empty(t, cfg.ImplicitEnvVar2)
   440  	assert.Equal(t, "env_var_override", cfg.ImplicitEnvVarOverride) // from config.common
   441  }
   442  
   443  func TestLoad_ImplicitEnv(t *testing.T) {
   444  	testEnv := [][]string{
   445  		{"Confr_Implicit_Env_Var1", "implicit env var1"},
   446  		{"CONFR_IMPLICIT_ENV_VAR2", "implicit env var2"},
   447  		{"Confr_Implicit_Env_Var_Override", "implicit env var override"},
   448  	}
   449  	for _, te := range testEnv {
   450  		_ = os.Setenv(te[0], te[1])
   451  	}
   452  	defer func() {
   453  		for _, te := range testEnv {
   454  			_ = os.Unsetenv(te[0])
   455  		}
   456  	}()
   457  
   458  	configFiles := []string{
   459  		"./testdata/config.test.yml",
   460  		"./testdata/config.common.yml",
   461  	}
   462  	cfg := &TestConfig{}
   463  	loader := New(&Config{
   464  		Verbose:           true,
   465  		EnableImplicitEnv: true,
   466  		CustomLoader:      testCustomLoader,
   467  	})
   468  	err := loader.loadFiles(cfg, configFiles...)
   469  	assert.Nil(t, err)
   470  	assertSingleFileConfig(t, cfg)
   471  	err = loader.processCustom(cfg)
   472  	assert.Nil(t, err)
   473  	err = loader.processEnv(cfg, "")
   474  	assert.Nil(t, err)
   475  
   476  	assert.Equal(t, "implicit env var1", *cfg.ImplicitEnvVar1)
   477  	assert.Equal(t, "implicit env var2", cfg.ImplicitEnvVar2)
   478  	assert.Equal(t, "implicit env var override", cfg.ImplicitEnvVarOverride)
   479  }
   480  
   481  func TestLoad_AllowUnknownFields_JSON(t *testing.T) {
   482  	configFiles := []string{"./testdata/config.unknown_fields.json"}
   483  	testLoad_AllowUnknownFields(t, configFiles...)
   484  }
   485  
   486  func TestLoad_AllowUnknownFields_TOML(t *testing.T) {
   487  	configFiles := []string{"./testdata/config.unknown_fields.toml"}
   488  	testLoad_AllowUnknownFields(t, configFiles...)
   489  }
   490  
   491  func TestLoad_AllowUnknownFields_YAML(t *testing.T) {
   492  	configFiles := []string{"./testdata/config.unknown_fields.yml"}
   493  	testLoad_AllowUnknownFields(t, configFiles...)
   494  }
   495  
   496  func testLoad_AllowUnknownFields(t *testing.T, files ...string) {
   497  	cfg := &TestConfig{}
   498  	loader := New(&Config{})
   499  	err := loader.Load(cfg, files...)
   500  	assert.Nil(t, err)
   501  }
   502  
   503  func TestLoad_DisallowUnknownFields_JSON(t *testing.T) {
   504  	configFiles := []string{"./testdata/config.unknown_fields.json"}
   505  	testLoad_DisallowUnknownFields(t, configFiles...)
   506  }
   507  
   508  func TestLoad_DisallowUnknownFields_TOML(t *testing.T) {
   509  	configFiles := []string{"./testdata/config.unknown_fields.toml"}
   510  	testLoad_DisallowUnknownFields(t, configFiles...)
   511  }
   512  
   513  func TestLoad_DisallowUnknownFields_YAML(t *testing.T) {
   514  	configFiles := []string{"./testdata/config.unknown_fields.yml"}
   515  	testLoad_DisallowUnknownFields(t, configFiles...)
   516  }
   517  
   518  func testLoad_DisallowUnknownFields(t *testing.T, files ...string) {
   519  	cfg := &TestConfig{}
   520  	loader := New(&Config{DisallowUnknownFields: true})
   521  	err := loader.Load(cfg, files...)
   522  	assert.NotNil(t, err)
   523  	assert.Contains(t, err.Error(), "unknown_field")
   524  }