github.com/openshift-online/ocm-sdk-go@v0.1.420/configuration/object_test.go (about)

     1  /*
     2  Copyright (c) 2020 Red Hat, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8    http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // This file contains tests for the configuration object.
    18  
    19  package configuration
    20  
    21  import (
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"gopkg.in/yaml.v3"
    27  
    28  	. "github.com/onsi/ginkgo/v2/dsl/core"  // nolint
    29  	. "github.com/onsi/ginkgo/v2/dsl/table" // nolint
    30  	. "github.com/onsi/gomega"              // nolint
    31  )
    32  
    33  var _ = Describe("Object", func() {
    34  	Describe("Load", func() {
    35  		It("Can be loaded from bytes", func() {
    36  			// Load the configuration:
    37  			object, err := New().
    38  				Load([]byte(`mykey: myvalue`)).
    39  				Build()
    40  			Expect(err).ToNot(HaveOccurred())
    41  			Expect(object).ToNot(BeNil())
    42  
    43  			// Populate the configuration:
    44  			var config struct {
    45  				MyKey string `yaml:"mykey"`
    46  			}
    47  			err = object.Populate(&config)
    48  			Expect(err).ToNot(HaveOccurred())
    49  			Expect(config.MyKey).To(Equal("myvalue"))
    50  		})
    51  
    52  		It("Can be loaded from string", func() {
    53  			// Load the configuration:
    54  			object, err := New().
    55  				Load(`mykey: myvalue`).
    56  				Build()
    57  			Expect(err).ToNot(HaveOccurred())
    58  			Expect(object).ToNot(BeNil())
    59  
    60  			// Populate the configuration:
    61  			var config struct {
    62  				MyKey string `yaml:"mykey"`
    63  			}
    64  			err = object.Populate(&config)
    65  			Expect(err).ToNot(HaveOccurred())
    66  			Expect(config.MyKey).To(Equal("myvalue"))
    67  		})
    68  
    69  		It("Can be loaded from reader", func() {
    70  			// Load the configuration:
    71  			object, err := New().
    72  				Load(strings.NewReader(`mykey: myvalue`)).
    73  				Build()
    74  			Expect(err).ToNot(HaveOccurred())
    75  			Expect(object).ToNot(BeNil())
    76  
    77  			// Populate the configuration:
    78  			var config struct {
    79  				MyKey string `yaml:"mykey"`
    80  			}
    81  			err = object.Populate(&config)
    82  			Expect(err).ToNot(HaveOccurred())
    83  			Expect(config.MyKey).To(Equal("myvalue"))
    84  		})
    85  
    86  		It("Can be loaded from file", func() {
    87  			// Create a temporary file containing the configuration:
    88  			tmp, err := os.CreateTemp("", "*.test.yaml")
    89  			Expect(err).ToNot(HaveOccurred())
    90  			defer func() {
    91  				err = os.Remove(tmp.Name())
    92  				Expect(err).ToNot(HaveOccurred())
    93  			}()
    94  			_, err = tmp.Write([]byte(`mykey: myvalue`))
    95  			Expect(err).ToNot(HaveOccurred())
    96  			err = tmp.Close()
    97  			Expect(err).ToNot(HaveOccurred())
    98  
    99  			// Load the configuration:
   100  			object, err := New().
   101  				Load(tmp.Name()).
   102  				Build()
   103  			Expect(err).ToNot(HaveOccurred())
   104  			Expect(object).ToNot(BeNil())
   105  
   106  			// Populate the configuration:
   107  			var config struct {
   108  				MyKey string `yaml:"mykey"`
   109  			}
   110  			err = object.Populate(&config)
   111  			Expect(err).ToNot(HaveOccurred())
   112  			Expect(config.MyKey).To(Equal("myvalue"))
   113  		})
   114  
   115  		It("Can be loaded from struct", func() {
   116  			// Load the configuration:
   117  			type Source struct {
   118  				MyKey string `yaml:"mykey"`
   119  			}
   120  			object, err := New().
   121  				Load(&Source{
   122  					MyKey: "myvalue",
   123  				}).
   124  				Build()
   125  			Expect(err).ToNot(HaveOccurred())
   126  			Expect(object).ToNot(BeNil())
   127  
   128  			// Populate the configuration:
   129  			var config struct {
   130  				MyKey string `yaml:"mykey"`
   131  			}
   132  			err = object.Populate(&config)
   133  			Expect(err).ToNot(HaveOccurred())
   134  			Expect(config.MyKey).To(Equal("myvalue"))
   135  		})
   136  
   137  		It("Can be loaded from map", func() {
   138  			// Load the configuration:
   139  			object, err := New().
   140  				Load(map[string]string{
   141  					"mykey": "myvalue",
   142  				}).
   143  				Build()
   144  			Expect(err).ToNot(HaveOccurred())
   145  			Expect(object).ToNot(BeNil())
   146  
   147  			// Populate the configuration:
   148  			var config struct {
   149  				MyKey string `yaml:"mykey"`
   150  			}
   151  			err = object.Populate(&config)
   152  			Expect(err).ToNot(HaveOccurred())
   153  			Expect(config.MyKey).To(Equal("myvalue"))
   154  		})
   155  
   156  		It("Can be loaded from YAML node", func() {
   157  			// Create the YAML node:
   158  			var node yaml.Node
   159  			err := yaml.Unmarshal([]byte(`mykey: myvalue`), &node)
   160  			Expect(err).ToNot(HaveOccurred())
   161  
   162  			// Load the configuration:
   163  			object, err := New().
   164  				Load(node).
   165  				Build()
   166  			Expect(err).ToNot(HaveOccurred())
   167  			Expect(object).ToNot(BeNil())
   168  
   169  			// Populate the configuration:
   170  			var config struct {
   171  				MyKey string `yaml:"mykey"`
   172  			}
   173  			err = object.Populate(&config)
   174  			Expect(err).ToNot(HaveOccurred())
   175  			Expect(config.MyKey).To(Equal("myvalue"))
   176  		})
   177  
   178  		It("Can be loaded from configuration object", func() {
   179  			// Load the source configuration:
   180  			source, err := New().
   181  				Load(`mykey: myvalue`).
   182  				Build()
   183  			Expect(err).ToNot(HaveOccurred())
   184  			Expect(source).ToNot(BeNil())
   185  
   186  			// Load the configuration:
   187  			object, err := New().
   188  				Load(source).
   189  				Build()
   190  			Expect(err).ToNot(HaveOccurred())
   191  			Expect(object).ToNot(BeNil())
   192  
   193  			// Populate the configuration:
   194  			var config struct {
   195  				MyKey string `yaml:"mykey"`
   196  			}
   197  			err = object.Populate(&config)
   198  			Expect(err).ToNot(HaveOccurred())
   199  			Expect(config.MyKey).To(Equal("myvalue"))
   200  		})
   201  
   202  		It("Can be loaded from directory", func() {
   203  			// Create a temporary directory containing two configuration files:
   204  			tmp, err := os.MkdirTemp("", "*.test.d")
   205  			Expect(err).ToNot(HaveOccurred())
   206  			defer func() {
   207  				err = os.RemoveAll(tmp)
   208  				Expect(err).ToNot(HaveOccurred())
   209  			}()
   210  			first := filepath.Join(tmp, "my.yaml")
   211  			err = os.WriteFile(first, []byte("mykey: myvalue"), 0600)
   212  			Expect(err).ToNot(HaveOccurred())
   213  			second := filepath.Join(tmp, "your.yaml")
   214  			err = os.WriteFile(second, []byte("yourkey: yourvalue"), 0600)
   215  
   216  			// Load the configuration:
   217  			object, err := New().
   218  				Load(tmp).
   219  				Build()
   220  			Expect(err).ToNot(HaveOccurred())
   221  			Expect(object).ToNot(BeNil())
   222  
   223  			// Populate the configuration:
   224  			var config struct {
   225  				MyKey   string `yaml:"mykey"`
   226  				YourKey string `yaml:"yourkey"`
   227  			}
   228  			err = object.Populate(&config)
   229  			Expect(err).ToNot(HaveOccurred())
   230  			Expect(config.MyKey).To(Equal("myvalue"))
   231  			Expect(config.YourKey).To(Equal("yourvalue"))
   232  		})
   233  
   234  		It("Honours order of files in directory", func() {
   235  			// Create a temporary directory containing two configuration files:
   236  			tmp, err := os.MkdirTemp("", "*.test.d")
   237  			Expect(err).ToNot(HaveOccurred())
   238  			defer func() {
   239  				err = os.RemoveAll(tmp)
   240  				Expect(err).ToNot(HaveOccurred())
   241  			}()
   242  			first := filepath.Join(tmp, "0.yaml")
   243  			err = os.WriteFile(first, []byte("mykey: firstvalue"), 0600)
   244  			Expect(err).ToNot(HaveOccurred())
   245  			second := filepath.Join(tmp, "1.yaml")
   246  			err = os.WriteFile(second, []byte("mykey: secondvalue"), 0600)
   247  
   248  			// Load the configuration:
   249  			object, err := New().
   250  				Load(tmp).
   251  				Build()
   252  			Expect(err).ToNot(HaveOccurred())
   253  			Expect(object).ToNot(BeNil())
   254  
   255  			// Populate the configuration:
   256  			var config struct {
   257  				MyKey string `yaml:"mykey"`
   258  			}
   259  			err = object.Populate(&config)
   260  			Expect(err).ToNot(HaveOccurred())
   261  			Expect(config.MyKey).To(Equal("secondvalue"))
   262  		})
   263  
   264  		It("Decodes binary data", func() {
   265  			// Load the configuration:
   266  			object, err := New().
   267  				Load(`mykey: !!binary bXl2YWx1ZQ==`).
   268  				Build()
   269  			Expect(err).ToNot(HaveOccurred())
   270  			Expect(object).ToNot(BeNil())
   271  
   272  			// Populate the configuration:
   273  			var config struct {
   274  				MyKey string `yaml:"mykey"`
   275  			}
   276  			err = object.Populate(&config)
   277  			Expect(err).ToNot(HaveOccurred())
   278  			Expect(config.MyKey).To(Equal("myvalue"))
   279  		})
   280  	})
   281  
   282  	Describe("Merge", func() {
   283  		It("Honours order of sources", func() {
   284  			// Load the configuration:
   285  			object, err := New().
   286  				Load(`mykey: myvalue`).
   287  				Load(`mykey: yourvalue`).
   288  				Build()
   289  			Expect(err).ToNot(HaveOccurred())
   290  			Expect(object).ToNot(BeNil())
   291  
   292  			// Populate the configuration:
   293  			var config struct {
   294  				MyKey string `yaml:"mykey"`
   295  			}
   296  			err = object.Populate(&config)
   297  			Expect(err).ToNot(HaveOccurred())
   298  			Expect(config.MyKey).To(Equal("yourvalue"))
   299  		})
   300  
   301  		It("Adds item to slice", func() {
   302  			// Load the configuration:
   303  			object, err := New().
   304  				Load(`"myslice": [ "firstvalue" ]`).
   305  				Load(`"myslice": [ "secondvalue" ]`).
   306  				Build()
   307  			Expect(err).ToNot(HaveOccurred())
   308  			Expect(object).ToNot(BeNil())
   309  
   310  			// Populate the configuration:
   311  			var config struct {
   312  				MySlice []string `yaml:"myslice"`
   313  			}
   314  			err = object.Populate(&config)
   315  			Expect(err).ToNot(HaveOccurred())
   316  			Expect(config.MySlice).To(ConsistOf("firstvalue", "secondvalue"))
   317  		})
   318  
   319  		It("Adds entry to map", func() {
   320  			// Load the configuration:
   321  			object, err := New().
   322  				Load(`"mymap": { firstkey: firstvalue }`).
   323  				Load(`"mymap": { secondkey: secondvalue }`).
   324  				Build()
   325  			Expect(err).ToNot(HaveOccurred())
   326  			Expect(object).ToNot(BeNil())
   327  
   328  			// Populate the configuration:
   329  			var config struct {
   330  				MyMap map[string]string `yaml:"mymap"`
   331  			}
   332  			err = object.Populate(&config)
   333  			Expect(err).ToNot(HaveOccurred())
   334  			Expect(config.MyMap).To(HaveLen(2))
   335  			Expect(config.MyMap).To(HaveKeyWithValue("firstkey", "firstvalue"))
   336  			Expect(config.MyMap).To(HaveKeyWithValue("secondkey", "secondvalue"))
   337  		})
   338  	})
   339  
   340  	It("Can be used as a struct field", func() {
   341  		var top, sub func(*Object)
   342  
   343  		// This simulates a top level component that uses its own struct to decode the
   344  		// configuration but has no visibility of the struct used by the sub-component.
   345  		top = func(object *Object) {
   346  			// Populate and verify the configuration:
   347  			var config struct {
   348  				TopKey string  `yaml:"topkey"`
   349  				Sub    *Object `yaml:"sub"`
   350  			}
   351  			err := object.Populate(&config)
   352  			Expect(err).ToNot(HaveOccurred())
   353  			Expect(config.TopKey).To(Equal("topvalue"))
   354  			Expect(config.Sub).ToNot(BeNil())
   355  
   356  			// Call the sub-component:
   357  			sub(config.Sub)
   358  		}
   359  
   360  		// This simulates a sub-component that uses its own private struct to decode the
   361  		// configuration.
   362  		sub = func(object *Object) {
   363  			// Populate and verify the configuration:
   364  			var config struct {
   365  				SubKey string `yaml:"subkey"`
   366  			}
   367  			err := object.Populate(&config)
   368  			Expect(err).ToNot(HaveOccurred())
   369  			Expect(config.SubKey).To(Equal("subvalue"))
   370  		}
   371  
   372  		// Load the configuration:
   373  		object, err := New().
   374  			Load(`
   375  				topkey: topvalue
   376  				sub:
   377  				  subkey: subvalue
   378  			`).
   379  			Build()
   380  		Expect(err).ToNot(HaveOccurred())
   381  		Expect(object).ToNot(BeNil())
   382  
   383  		// Call the top level components:
   384  		top(object)
   385  	})
   386  
   387  	Describe("Tags", func() {
   388  		// This type will be used to check values of fields pupulated using the tags in the
   389  		// tests below:
   390  		type Config struct {
   391  			User     string `yaml:"user"`
   392  			Password string `yaml:"password"`
   393  			ID       int    `yaml:"id"`
   394  			Enabled  bool   `yaml:"enabled"`
   395  		}
   396  
   397  		DescribeTable(
   398  			"Successful processing",
   399  			func(source string, variables, files map[string]string, expected Config) {
   400  				var err error
   401  
   402  				// Set the environment variables:
   403  				var names []string
   404  				for name, value := range variables {
   405  					err = os.Setenv(name, value)
   406  					Expect(err).ToNot(HaveOccurred())
   407  					names = append(names, name)
   408  				}
   409  				defer func() {
   410  					for _, name := range names {
   411  						err = os.Unsetenv(name)
   412  						Expect(err).ToNot(HaveOccurred())
   413  					}
   414  				}()
   415  
   416  				// Create a temporary directory to contain the temporary files:
   417  				var tmp string
   418  				tmp, err = os.MkdirTemp("", "*.test")
   419  				Expect(err).ToNot(HaveOccurred())
   420  				defer func() {
   421  					err = os.RemoveAll(tmp)
   422  					Expect(err).ToNot(HaveOccurred())
   423  				}()
   424  
   425  				// Create the files into the temporary directory:
   426  				for name, content := range files {
   427  					path := filepath.Join(tmp, name)
   428  					err = os.WriteFile(path, []byte(content), 0600)
   429  					Expect(err).ToNot(HaveOccurred())
   430  				}
   431  
   432  				// Change into the temporary directory:
   433  				var wd string
   434  				wd, err = os.Getwd()
   435  				Expect(err).ToNot(HaveOccurred())
   436  				err = os.Chdir(tmp)
   437  				Expect(err).ToNot(HaveOccurred())
   438  				defer func() {
   439  					err = os.Chdir(wd)
   440  					Expect(err).ToNot(HaveOccurred())
   441  				}()
   442  
   443  				// Do the check:
   444  				object, err := New().Load(source).Build()
   445  				Expect(err).ToNot(HaveOccurred())
   446  				var config Config
   447  				err = object.Populate(&config)
   448  				Expect(err).ToNot(HaveOccurred())
   449  				Expect(config).To(Equal(expected))
   450  			},
   451  			Entry(
   452  				"Empty",
   453  				"",
   454  				nil,
   455  				nil,
   456  				Config{},
   457  			),
   458  			Entry(
   459  				"One environment variable",
   460  				`
   461  				user: !variable MYUSER
   462  				`,
   463  				map[string]string{
   464  					"MYUSER": "myuser",
   465  				},
   466  				nil,
   467  				Config{
   468  					User: "myuser",
   469  				},
   470  			),
   471  			Entry(
   472  				"Two environment variables",
   473  				`
   474  				user: !variable MYUSER
   475  				password: !variable MYPASSWORD
   476  				`,
   477  				map[string]string{
   478  					"MYUSER":     "myuser",
   479  					"MYPASSWORD": "mypassword",
   480  				},
   481  				nil,
   482  				Config{
   483  					User:     "myuser",
   484  					Password: "mypassword",
   485  				},
   486  			),
   487  			Entry(
   488  				"Environment variable with `var`",
   489  				`
   490  				user: !var MYUSER
   491  				`,
   492  				map[string]string{
   493  					"MYUSER": "myuser",
   494  				},
   495  				nil,
   496  				Config{
   497  					User: "myuser",
   498  				},
   499  			),
   500  			Entry(
   501  				"Environment variable with `v`",
   502  				`
   503  				user: !v MYUSER
   504  				`,
   505  				map[string]string{
   506  					"MYUSER": "myuser",
   507  				},
   508  				nil,
   509  				Config{
   510  					User: "myuser",
   511  				},
   512  			),
   513  			Entry(
   514  				"Environment variable with leading space",
   515  				`
   516  				password: !variable MYPASSWORD
   517  				`,
   518  				map[string]string{
   519  					"MYPASSWORD": " mypassword",
   520  				},
   521  				nil,
   522  				Config{
   523  					Password: " mypassword",
   524  				},
   525  			),
   526  			Entry(
   527  				"Environment variable with trailing space",
   528  				`
   529  				password: !variable MYPASSWORD
   530  				`,
   531  				map[string]string{
   532  					"MYPASSWORD": "mypassword ",
   533  				},
   534  				nil,
   535  				Config{
   536  					Password: "mypassword ",
   537  				},
   538  			),
   539  			Entry(
   540  				"Environment variable with quotes",
   541  				`
   542  				password: !variable MYPASSWORD
   543  				`,
   544  				map[string]string{
   545  					"MYPASSWORD": "my\"pass\"word",
   546  				},
   547  				nil,
   548  				Config{
   549  					Password: "my\"pass\"word",
   550  				},
   551  			),
   552  			Entry(
   553  				"Numeric environment variable",
   554  				`
   555  				id: !variable/integer MYID
   556  				`,
   557  				map[string]string{
   558  					"MYID": "123",
   559  				},
   560  				nil,
   561  				Config{
   562  					ID: 123,
   563  				},
   564  			),
   565  			Entry(
   566  				"Boolean environment variable",
   567  				`
   568  				enabled: !variable/boolean MYENABLED
   569  				`,
   570  				map[string]string{
   571  					"MYENABLED": "true",
   572  				},
   573  				nil,
   574  				Config{
   575  					Enabled: true,
   576  				},
   577  			),
   578  			Entry(
   579  				"One file",
   580  				`
   581  				user: !file myuser.txt
   582  				`,
   583  				nil,
   584  				map[string]string{
   585  					"myuser.txt": "myuser",
   586  				},
   587  				Config{
   588  					User: "myuser",
   589  				},
   590  			),
   591  			Entry(
   592  				"Two files",
   593  				`
   594  				user: !file myuser.txt
   595  				password: !file mypassword.txt
   596  				`,
   597  				nil,
   598  				map[string]string{
   599  					"myuser.txt":     "myuser",
   600  					"mypassword.txt": "mypassword",
   601  				},
   602  				Config{
   603  					User:     "myuser",
   604  					Password: "mypassword",
   605  				},
   606  			),
   607  			Entry(
   608  				"File with `f`",
   609  				`
   610  				user: !f myuser.txt
   611  				`,
   612  				nil,
   613  				map[string]string{
   614  					"myuser.txt": "myuser",
   615  				},
   616  				Config{
   617  					User: "myuser",
   618  				},
   619  			),
   620  			Entry(
   621  				"File with leading space preserved",
   622  				`
   623  				user: !file myuser.txt
   624  				`,
   625  				nil,
   626  				map[string]string{
   627  					"myuser.txt": " myuser",
   628  				},
   629  				Config{
   630  					User: " myuser",
   631  				},
   632  			),
   633  			Entry(
   634  				"File with leading space trimmed",
   635  				`
   636  				user: !file/trim myuser.txt
   637  				`,
   638  				nil,
   639  				map[string]string{
   640  					"myuser.txt": "myuser",
   641  				},
   642  				Config{
   643  					User: "myuser",
   644  				},
   645  			),
   646  			Entry(
   647  				"File with trailing space preserved",
   648  				`
   649  				user: !file myuser.txt
   650  				`,
   651  				nil,
   652  				map[string]string{
   653  					"myuser.txt": "myuser ",
   654  				},
   655  				Config{
   656  					User: "myuser ",
   657  				},
   658  			),
   659  			Entry(
   660  				"File with trailing space trimmed",
   661  				`
   662  				user: !file/trim myuser.txt
   663  				`,
   664  				nil,
   665  				map[string]string{
   666  					"myuser.txt": "myuser ",
   667  				},
   668  				Config{
   669  					User: "myuser",
   670  				},
   671  			),
   672  			Entry(
   673  				"File with trailing line break preserved",
   674  				`
   675  				user: !file myuser.txt
   676  				`,
   677  				nil,
   678  				map[string]string{
   679  					"myuser.txt": "myuser\n",
   680  				},
   681  				Config{
   682  					User: "myuser\n",
   683  				},
   684  			),
   685  			Entry(
   686  				"File with trailing line break trimmed",
   687  				`
   688  				user: !file/trim myuser.txt
   689  				`,
   690  				nil,
   691  				map[string]string{
   692  					"myuser.txt": "myuser\n",
   693  				},
   694  				Config{
   695  					User: "myuser",
   696  				},
   697  			),
   698  			Entry(
   699  				"Variable and file",
   700  				`
   701  				user: !variable MYUSER
   702  				password: !file mypassword.txt
   703  				`,
   704  				map[string]string{
   705  					"MYUSER": "myuser",
   706  				},
   707  				map[string]string{
   708  					"mypassword.txt": "mypassword",
   709  				},
   710  				Config{
   711  					User:     "myuser",
   712  					Password: "mypassword",
   713  				},
   714  			),
   715  			Entry(
   716  				"File with quotes",
   717  				`
   718  				user: myuser
   719  				password: !file mypassword.txt
   720  				`,
   721  				nil,
   722  				map[string]string{
   723  					"mypassword.txt": "my\"pass\"word",
   724  				},
   725  				Config{
   726  					User:     "myuser",
   727  					Password: "my\"pass\"word",
   728  				},
   729  			),
   730  			Entry(
   731  				"Numeric file",
   732  				`
   733  				id: !file/integer myid.txt
   734  				`,
   735  				nil,
   736  				map[string]string{
   737  					"myid.txt": "123",
   738  				},
   739  				Config{
   740  					ID: 123,
   741  				},
   742  			),
   743  			Entry(
   744  				"Boolean file",
   745  				`
   746  				enabled: !file/boolean myenabled.txt
   747  				`,
   748  				nil,
   749  				map[string]string{
   750  					"myenabled.txt": "true",
   751  				},
   752  				Config{
   753  					Enabled: true,
   754  				},
   755  			),
   756  			Entry(
   757  				"Script echo variable",
   758  				`
   759  				user: !script echo -n ${MYUSER}
   760  				`,
   761  				map[string]string{
   762  					"MYUSER": "myuser",
   763  				},
   764  				nil,
   765  				Config{
   766  					User: "myuser",
   767  				},
   768  			),
   769  			Entry(
   770  				"Script cat file",
   771  				`
   772  				user: !script cat myuser.txt
   773  				`,
   774  				nil,
   775  				map[string]string{
   776  					"myuser.txt": "myuser",
   777  				},
   778  				Config{
   779  					User: "myuser",
   780  				},
   781  			),
   782  			Entry(
   783  				"Include string",
   784  				`
   785  				user: !file/yaml myuser.yaml
   786  				`,
   787  				nil,
   788  				map[string]string{
   789  					"myuser.yaml": "myuser",
   790  				},
   791  				Config{
   792  					User: "myuser",
   793  				},
   794  			),
   795  			Entry(
   796  				"Include boolean",
   797  				`
   798  				enabled: !file/yaml myenabled.yaml
   799  				`,
   800  				nil,
   801  				map[string]string{
   802  					"myenabled.yaml": "true",
   803  				},
   804  				Config{
   805  					Enabled: true,
   806  				},
   807  			),
   808  			Entry(
   809  				"Include int",
   810  				`
   811  				id: !file/yaml myid.yaml
   812  				`,
   813  				nil,
   814  				map[string]string{
   815  					"myid.yaml": "123",
   816  				},
   817  				Config{
   818  					ID: 123,
   819  				},
   820  			),
   821  			Entry(
   822  				"Include map",
   823  				`
   824  				!file/yaml mymap.yaml
   825  				`,
   826  				nil,
   827  				map[string]string{
   828  					"mymap.yaml": "{ user: myuser, id: 123 }",
   829  				},
   830  				Config{
   831  					User: "myuser",
   832  					ID:   123,
   833  				},
   834  			),
   835  			Entry(
   836  				"Include chain",
   837  				`
   838  				user: !file/yaml first.yaml
   839  				`,
   840  				nil,
   841  				map[string]string{
   842  					"first.yaml":  "!file/yaml second.yaml",
   843  					"second.yaml": "myuser",
   844  				},
   845  				Config{
   846  					User: "myuser",
   847  				},
   848  			),
   849  			Entry(
   850  				"Include chain and variable",
   851  				`
   852  				user: !file/yaml first.yaml
   853  				`,
   854  				map[string]string{
   855  					"MYUSER": "myuser",
   856  				},
   857  				map[string]string{
   858  					"first.yaml":  "!file/yaml second.yaml",
   859  					"second.yaml": "!variable MYUSER",
   860  				},
   861  				Config{
   862  					User: "myuser",
   863  				},
   864  			),
   865  			Entry(
   866  				"Include chain and file",
   867  				`
   868  				user: !file/yaml first.yaml
   869  				`,
   870  				nil,
   871  				map[string]string{
   872  					"first.yaml":  "!file/yaml second.yaml",
   873  					"second.yaml": "!file myuser.txt",
   874  					"myuser.txt":  "myuser",
   875  				},
   876  				Config{
   877  					User: "myuser",
   878  				},
   879  			),
   880  			Entry(
   881  				"Include chain and script",
   882  				`
   883  				user: !file/yaml first.yaml
   884  				`,
   885  				map[string]string{
   886  					"MYUSER": "myuser",
   887  				},
   888  				map[string]string{
   889  					"first.yaml":  "!file/yaml second.yaml",
   890  					"second.yaml": "!script echo -n ${MYUSER}",
   891  				},
   892  				Config{
   893  					User: "myuser",
   894  				},
   895  			),
   896  			Entry(
   897  				"Parse results of running script",
   898  				`!script/yaml echo user: myuser`,
   899  				nil,
   900  				nil,
   901  				Config{
   902  					User: "myuser",
   903  				},
   904  			),
   905  		)
   906  
   907  		It("Fails if first tag isn't supported", func() {
   908  			_, err := New().
   909  				Load(`mykey: !wrong junk`).
   910  				Build()
   911  			Expect(err).To(HaveOccurred())
   912  			message := err.Error()
   913  			Expect(message).To(ContainSubstring("wrong"))
   914  		})
   915  
   916  		It("Fails if second tag isn't supported", func() {
   917  			_, err := New().
   918  				Load(`mykey: !script/wrong echo -n junk`).
   919  				Build()
   920  			Expect(err).To(HaveOccurred())
   921  			message := err.Error()
   922  			Expect(message).To(ContainSubstring("wrong"))
   923  		})
   924  
   925  		It("Fails if environment variable doesn't exist", func() {
   926  			_, err := New().
   927  				Load(`mykey: !variable DOESNOTEXIST`).
   928  				Build()
   929  			Expect(err).To(HaveOccurred())
   930  		})
   931  
   932  		It("Reports location of error for known source name", func() {
   933  			// Create a temporary file containing the configuration:
   934  			tmp, err := os.CreateTemp("", "*.test.yaml")
   935  			Expect(err).ToNot(HaveOccurred())
   936  			name := tmp.Name()
   937  			defer func() {
   938  				err = os.Remove(name)
   939  				Expect(err).ToNot(HaveOccurred())
   940  			}()
   941  			_, err = tmp.Write([]byte(`mykey: !file /doesnotexist`))
   942  			Expect(err).ToNot(HaveOccurred())
   943  			err = tmp.Close()
   944  			Expect(err).ToNot(HaveOccurred())
   945  
   946  			// Check that loading fails and that the error message contains the name of
   947  			// the source file:
   948  			_, err = New().
   949  				Load(name).
   950  				Build()
   951  			Expect(err).To(HaveOccurred())
   952  			message := err.Error()
   953  			Expect(message).To(ContainSubstring(name + ":1:8"))
   954  		})
   955  
   956  		It("Reports location of error for included file", func() {
   957  			// Create the a temporary file containing the text to be included:
   958  			tmp, err := os.CreateTemp("", "*.test.yaml")
   959  			Expect(err).ToNot(HaveOccurred())
   960  			name := tmp.Name()
   961  			defer func() {
   962  				err = os.Remove(name)
   963  				Expect(err).ToNot(HaveOccurred())
   964  			}()
   965  			_, err = tmp.Write([]byte(`yourkey: !file /doesnotexist`))
   966  			Expect(err).ToNot(HaveOccurred())
   967  			err = tmp.Close()
   968  			Expect(err).ToNot(HaveOccurred())
   969  
   970  			// Check that loading fails and that the error message contains the name of
   971  			// the included file:
   972  			_, err = New().
   973  				Load(`mykey: !file/yaml "` + name + `"`).
   974  				Build()
   975  			Expect(err).To(HaveOccurred())
   976  			message := err.Error()
   977  			Expect(message).To(ContainSubstring(name + ":1:10:"))
   978  		})
   979  
   980  		It("Reports location of error for unknown source name", func() {
   981  			_, err := New().
   982  				Load(`mykey: !file /doesnotexist.txt`).
   983  				Build()
   984  			Expect(err).To(HaveOccurred())
   985  			message := err.Error()
   986  			Expect(message).To(ContainSubstring("unknown:1:8:"))
   987  		})
   988  
   989  		It("Fails if script writes to stderr", func() {
   990  			_, err := New().
   991  				Load(`mykey: !script echo myerror 1>&2`).
   992  				Build()
   993  			Expect(err).To(HaveOccurred())
   994  		})
   995  
   996  		It("Script error contains stdout and stderr", func() {
   997  			_, err := New().
   998  				Load(`mykey: !script echo myoutput; echo myerror 1>&2; exit 1`).
   999  				Build()
  1000  			Expect(err).To(HaveOccurred())
  1001  			message := err.Error()
  1002  			Expect(message).To(ContainSubstring("myoutput"))
  1003  			Expect(message).To(ContainSubstring("myerror"))
  1004  		})
  1005  
  1006  		It("Fails if script command doesn't exist", func() {
  1007  			_, err := New().
  1008  				Load(`mykey: !script doesnotexist`).
  1009  				Build()
  1010  			Expect(err).To(HaveOccurred())
  1011  			message := err.Error()
  1012  			Expect(message).To(ContainSubstring("doesnotexist"))
  1013  		})
  1014  
  1015  		It("Fails if finds sequence when expecting scalar", func() {
  1016  			_, err := New().
  1017  				Load(`mykey: !variable []`).
  1018  				Build()
  1019  			Expect(err).To(HaveOccurred())
  1020  			message := err.Error()
  1021  			Expect(message).To(ContainSubstring("variable"))
  1022  			Expect(message).To(ContainSubstring("scalar"))
  1023  			Expect(message).To(ContainSubstring("sequence"))
  1024  		})
  1025  
  1026  		It("Fails if finds mapping when expecting scalar", func() {
  1027  			_, err := New().
  1028  				Load(`mykey: !file {}`).
  1029  				Build()
  1030  			Expect(err).To(HaveOccurred())
  1031  			message := err.Error()
  1032  			Expect(message).To(ContainSubstring("file"))
  1033  			Expect(message).To(ContainSubstring("scalar"))
  1034  			Expect(message).To(ContainSubstring("mapping"))
  1035  		})
  1036  	})
  1037  })