github.com/hernad/nomad@v1.6.112/command/var_put_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package command 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "regexp" 10 "strings" 11 "testing" 12 13 "github.com/hernad/nomad/api" 14 "github.com/hernad/nomad/ci" 15 "github.com/mitchellh/cli" 16 "github.com/posener/complete" 17 "github.com/shoenig/test/must" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestVarPutCommand_Implements(t *testing.T) { 22 ci.Parallel(t) 23 var _ cli.Command = &VarPutCommand{} 24 } 25 func TestVarPutCommand_Fails(t *testing.T) { 26 ci.Parallel(t) 27 t.Run("bad_args", func(t *testing.T) { 28 ci.Parallel(t) 29 ui := cli.NewMockUi() 30 cmd := &VarPutCommand{Meta: Meta{Ui: ui}} 31 code := cmd.Run([]string{"-bad-flag"}) 32 out := ui.ErrorWriter.String() 33 require.Equal(t, 1, code, "expected exit code 1, got: %d") 34 require.Contains(t, out, commandErrorText(cmd), "expected help output, got: %s", out) 35 }) 36 t.Run("bad_address", func(t *testing.T) { 37 ci.Parallel(t) 38 ui := cli.NewMockUi() 39 cmd := &VarPutCommand{Meta: Meta{Ui: ui}} 40 code := cmd.Run([]string{"-address=nope", "foo", "-"}) 41 out := ui.ErrorWriter.String() 42 require.Equal(t, 1, code, "expected exit code 1, got: %d") 43 require.Contains(t, out, "Error creating variable", "expected error creating variable, got: %s", out) 44 }) 45 t.Run("missing_template", func(t *testing.T) { 46 ci.Parallel(t) 47 ui := cli.NewMockUi() 48 cmd := &VarPutCommand{Meta: Meta{Ui: ui}} 49 code := cmd.Run([]string{`-out=go-template`, "foo", "-"}) 50 out := strings.TrimSpace(ui.ErrorWriter.String()) 51 require.Equal(t, 1, code, "expected exit code 1, got: %d", code) 52 require.Equal(t, errMissingTemplate+"\n"+commandErrorText(cmd), out) 53 }) 54 t.Run("unexpected_template", func(t *testing.T) { 55 ci.Parallel(t) 56 ui := cli.NewMockUi() 57 cmd := &VarPutCommand{Meta: Meta{Ui: ui}} 58 code := cmd.Run([]string{`-out=json`, `-template="bad"`, "foo", "-"}) 59 out := strings.TrimSpace(ui.ErrorWriter.String()) 60 require.Equal(t, 1, code, "expected exit code 1, got: %d", code) 61 require.Equal(t, errUnexpectedTemplate+"\n"+commandErrorText(cmd), out) 62 }) 63 t.Run("bad_in", func(t *testing.T) { 64 ci.Parallel(t) 65 ui := cli.NewMockUi() 66 cmd := &VarPutCommand{Meta: Meta{Ui: ui}} 67 code := cmd.Run([]string{`-in=bad`, "foo", "-"}) 68 out := strings.TrimSpace(ui.ErrorWriter.String()) 69 require.Equal(t, 1, code, "expected exit code 1, got: %d", code) 70 require.Equal(t, errInvalidInFormat+"\n"+commandErrorText(cmd), out) 71 }) 72 t.Run("wildcard_namespace", func(t *testing.T) { 73 ci.Parallel(t) 74 ui := cli.NewMockUi() 75 cmd := &VarPutCommand{Meta: Meta{Ui: ui}} 76 code := cmd.Run([]string{`-namespace=*`, "foo", "-"}) 77 out := strings.TrimSpace(ui.ErrorWriter.String()) 78 require.Equal(t, 1, code, "expected exit code 1, got: %d", code) 79 require.Equal(t, errWildcardNamespaceNotAllowed, out) 80 }) 81 } 82 83 func TestVarPutCommand_GoodJson(t *testing.T) { 84 ci.Parallel(t) 85 86 // Create a server 87 srv, client, url := testServer(t, true, nil) 88 defer srv.Shutdown() 89 90 ui := cli.NewMockUi() 91 cmd := &VarPutCommand{Meta: Meta{Ui: ui}} 92 93 // Get the variable 94 code := cmd.Run([]string{"-address=" + url, "-out=json", "test/var", "k1=v1", "k2=v2"}) 95 require.Equal(t, 0, code, "expected exit 0, got: %d; %v", code, ui.ErrorWriter.String()) 96 97 t.Cleanup(func() { 98 _, _ = client.Variables().Delete("test/var", nil) 99 }) 100 101 var outVar api.Variable 102 b := ui.OutputWriter.Bytes() 103 err := json.Unmarshal(b, &outVar) 104 require.NoError(t, err, "error unmarshaling json: %v\nb: %s", err, b) 105 require.Equal(t, "default", outVar.Namespace) 106 require.Equal(t, "test/var", outVar.Path) 107 require.Equal(t, api.VariableItems{"k1": "v1", "k2": "v2"}, outVar.Items) 108 } 109 110 func TestVarPutCommand_AutocompleteArgs(t *testing.T) { 111 ci.Parallel(t) 112 srv, client, url := testServer(t, true, nil) 113 defer srv.Shutdown() 114 115 ui := cli.NewMockUi() 116 cmd := &VarPutCommand{Meta: Meta{Ui: ui, flagAddress: url}} 117 118 // Create a var 119 sv := testVariable() 120 _, _, err := client.Variables().Create(sv, nil) 121 require.NoError(t, err) 122 123 args := complete.Args{Last: "t"} 124 predictor := cmd.AutocompleteArgs() 125 126 res := predictor.Predict(args) 127 require.Equal(t, 1, len(res)) 128 require.Equal(t, sv.Path, res[0]) 129 } 130 131 func TestVarPutCommand_KeyWarning(t *testing.T) { 132 // Extract invalid characters from warning message. 133 r := regexp.MustCompile(`contains characters \[(.*)\]`) 134 135 tcs := []struct { 136 name string 137 goodKeys []string 138 badKeys []string 139 badChars []string 140 }{ 141 { 142 name: "simple", 143 goodKeys: []string{"simple"}, 144 }, 145 { 146 name: "hasDot", 147 badKeys: []string{"has.Dot"}, 148 badChars: []string{`"."`}, 149 }, 150 { 151 name: "unicode_letters", 152 goodKeys: []string{"世界"}, 153 }, 154 { 155 name: "unicode_numbers", 156 goodKeys: []string{"٣٢١"}, 157 }, 158 { 159 name: "two_good", 160 goodKeys: []string{"aardvark", "beagle"}, 161 }, 162 { 163 name: "one_good_one_bad", 164 goodKeys: []string{"aardvark"}, 165 badKeys: []string{"bad.key"}, 166 badChars: []string{`"."`}, 167 }, 168 { 169 name: "one_good_two_bad", 170 goodKeys: []string{"aardvark"}, 171 badKeys: []string{"bad.key", "also-bad"}, 172 badChars: []string{`"."`, `"-"`}, 173 }, 174 { 175 name: "repeated_bad_char", 176 goodKeys: []string{"aardvark"}, 177 badKeys: []string{"bad.key", "also.bad"}, 178 badChars: []string{`"."`, `"."`}, 179 }, 180 { 181 name: "repeated_bad_char_same_key", 182 goodKeys: []string{"aardvark"}, 183 badKeys: []string{"bad.key."}, 184 badChars: []string{`"."`}, 185 }, 186 { 187 name: "dont_escape", 188 goodKeys: []string{"aardvark"}, 189 badKeys: []string{"bad\\key"}, 190 badChars: []string{`"\"`}, 191 }, 192 } 193 194 ci.Parallel(t) 195 _, _, url := testServer(t, false, nil) 196 197 for _, tc := range tcs { 198 t.Run(tc.name, func(t *testing.T) { 199 tc := tc // capture test case 200 ci.Parallel(t) // make the subtests parallel 201 202 keys := append(tc.goodKeys, tc.badKeys...) // combine keys into a single slice 203 for i, k := range keys { 204 keys[i] = k + "=value" // Make each key into a k=v pair; value is not part of test 205 } 206 207 ui := cli.NewMockUi() 208 cmd := &VarPutCommand{Meta: Meta{Ui: ui}} 209 args := append([]string{"-address=" + url, "-force", "-out=json", "test/var"}, keys...) 210 code := cmd.Run(args) 211 errOut := ui.ErrorWriter.String() 212 213 must.Eq(t, 0, code) // the command should always succeed 214 215 badKeysLen := len(tc.badKeys) 216 switch badKeysLen { 217 case 0: 218 must.Eq(t, "", errOut) // cases with no bad keys shouldn't put anything to stderr 219 return 220 case 1: 221 must.StrContains(t, errOut, "1 warning:") // header should be singular 222 default: 223 must.StrContains(t, errOut, fmt.Sprintf("%d warnings:", badKeysLen)) // header should be plural 224 } 225 226 for _, k := range tc.badKeys { 227 must.StrContains(t, errOut, k) // every bad key should appear in the warning output 228 } 229 230 if len(tc.badChars) > 0 { 231 invalid := r.FindAllStringSubmatch(errOut, -1) 232 for i, k := range tc.badChars { 233 must.Eq(t, invalid[i][1], k) // every bad char should appear in the warning output 234 } 235 } 236 237 for _, k := range tc.goodKeys { 238 must.StrNotContains(t, errOut, k) // good keys should not be emitted 239 } 240 }) 241 } 242 }