github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/common_test.go (about) 1 /* 2 * Copyright 2018-2021 The NATS Authors 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package cmd 17 18 import ( 19 "fmt" 20 "io" 21 "net/http" 22 "net/http/httptest" 23 "net/url" 24 "os" 25 "path/filepath" 26 "testing" 27 "time" 28 29 "github.com/nats-io/jwt/v2" 30 31 "github.com/spf13/cobra" 32 33 "github.com/mitchellh/go-homedir" 34 "github.com/nats-io/nkeys" 35 "github.com/stretchr/testify/require" 36 ) 37 38 func TestCommon_ResolvePath(t *testing.T) { 39 v := ResolvePath("bar", "foo") 40 require.Equal(t, v, "bar", "non defined variable") 41 42 v = ResolvePath("bar", "") 43 require.Equal(t, v, "bar", "empty variable") 44 45 os.Setenv("foo", "foobar") 46 v = ResolvePath("bar", "foo") 47 require.Equal(t, v, "foobar", "env set") 48 } 49 50 func TestCommon_GetOutput(t *testing.T) { 51 dir, err := os.MkdirTemp("", "") 52 if err != nil { 53 t.Fatal("error creating tmpdir", err) 54 } 55 56 type testd struct { 57 fp string 58 create bool 59 isError bool 60 isDir bool 61 } 62 tests := []testd{ 63 {"--", false, false, false}, 64 {filepath.Join(dir, "dir"), true, true, true}, 65 {filepath.Join(dir, "nonExisting"), false, false, false}, 66 {filepath.Join(dir, "existing"), false, false, false}, 67 } 68 for _, d := range tests { 69 if d.isDir { 70 os.MkdirAll(d.fp, 0777) 71 } else if d.create { 72 os.Create(d.fp) 73 } 74 file, err := GetOutput(d.fp) 75 if file != nil && d.fp != "--" { 76 file.Close() 77 } 78 if d.isError && err == nil { 79 t.Errorf("expected error creating %#q, but didn't", d.fp) 80 } 81 if !d.isError && err != nil { 82 t.Errorf("unexpected error creating %#q: %v", d.fp, err) 83 } 84 } 85 } 86 87 func createWriteCmd(t *testing.T) *cobra.Command { 88 var out string 89 cmd := &cobra.Command{ 90 Use: "test", 91 RunE: func(cmd *cobra.Command, args []string) error { 92 return Write(out, []byte("hello")) 93 }, 94 } 95 cmd.Flags().StringVarP(&out, "out", "", "--", "") 96 return cmd 97 } 98 99 func Test_WriteDestinations(t *testing.T) { 100 stdout, _, err := ExecuteCmd(createWriteCmd(t), "--out", "--") 101 require.NoError(t, err) 102 require.Contains(t, stdout, "hello") 103 dir := MakeTempDir(t) 104 fn := filepath.Join(dir, "test.txt") 105 _, _, err = ExecuteCmd(createWriteCmd(t), "--out", fn) 106 require.NoError(t, err) 107 require.FileExists(t, fn) 108 d, err := os.ReadFile(fn) 109 require.NoError(t, err) 110 require.Contains(t, string(d), "hello") 111 } 112 113 func TestCommon_IsStdOut(t *testing.T) { 114 require.True(t, IsStdOut("--")) 115 require.False(t, IsStdOut("/tmp/foo.txt")) 116 } 117 118 func TestCommon_ResolveKeyEmpty(t *testing.T) { 119 old := KeyPathFlag 120 KeyPathFlag = "" 121 122 rkp, err := ResolveKeyFlag() 123 KeyPathFlag = old 124 125 require.NoError(t, err) 126 require.Nil(t, rkp) 127 } 128 129 func TestCommon_ResolveKeyFromSeed(t *testing.T) { 130 seed, p, _ := CreateAccountKey(t) 131 old := KeyPathFlag 132 KeyPathFlag = string(seed) 133 134 rkp, err := ResolveKeyFlag() 135 KeyPathFlag = old 136 137 require.NoError(t, err) 138 139 pp, err := rkp.PublicKey() 140 require.NoError(t, err) 141 142 require.Equal(t, pp, p) 143 } 144 145 func TestCommon_ResolveKeyFromFile(t *testing.T) { 146 dir := MakeTempDir(t) 147 _, p, kp := CreateAccountKey(t) 148 old := KeyPathFlag 149 KeyPathFlag = StoreKey(t, kp, dir) 150 rkp, err := ResolveKeyFlag() 151 KeyPathFlag = old 152 153 require.NoError(t, err) 154 155 pp, err := rkp.PublicKey() 156 require.NoError(t, err) 157 158 require.Equal(t, pp, p) 159 } 160 161 func TestCommon_ParseNumber(t *testing.T) { 162 type testd struct { 163 input string 164 output int64 165 isError bool 166 } 167 tests := []testd{ 168 {"", 0, false}, 169 {"0", 0, false}, 170 {"1000", 1000, false}, 171 {"1K", 1000, false}, 172 {"1k", 1000, false}, 173 {"1M", 1000 * 1000, false}, 174 {"1m", 1000 * 1000, false}, 175 {"1G", 1000 * 1000 * 1000, false}, 176 {"1g", 1000 * 1000 * 1000, false}, 177 {"1KIB", 1024, false}, 178 {"1kib", 1024, false}, 179 {"1MIB", 1024 * 1024, false}, 180 {"1mib", 1024 * 1024, false}, 181 {"1GIB", 1024 * 1024 * 1024, false}, 182 {"1gib", 1024 * 1024 * 1024, false}, 183 {"32a", 0, true}, 184 } 185 for _, d := range tests { 186 v, err := ParseNumber(d.input) 187 if err != nil && !d.isError { 188 t.Errorf("%s didn't expect error: %v", d.input, err) 189 continue 190 } 191 if err == nil && d.isError { 192 t.Errorf("expected error from %s", d.input) 193 continue 194 } 195 if v != d.output { 196 t.Errorf("%s expected %d but got %d", d.input, d.output, v) 197 } 198 } 199 } 200 201 func TestCommon_NKeyValidatorActualKey(t *testing.T) { 202 aSeed, _, _ := CreateAccountKey(t) 203 fn := NKeyValidator(nkeys.PrefixByteAccount) 204 require.NoError(t, fn(string(aSeed))) 205 206 oSeed, _, _ := CreateOperatorKey(t) 207 require.Error(t, fn(string(oSeed))) 208 } 209 210 func TestCommon_NKeyValidatorKeyInFile(t *testing.T) { 211 dir := MakeTempDir(t) 212 aSeed, _, _ := CreateAccountKey(t) 213 oSeed, _, _ := CreateOperatorKey(t) 214 215 require.NoError(t, Write(filepath.Join(dir, "as.nk"), aSeed)) 216 require.NoError(t, Write(filepath.Join(dir, "os.nk"), oSeed)) 217 218 fn := NKeyValidator(nkeys.PrefixByteAccount) 219 require.NoError(t, fn(filepath.Join(dir, "as.nk"))) 220 221 require.Error(t, fn(filepath.Join(dir, "os.nk"))) 222 } 223 224 func TestCommon_LoadFromURL(t *testing.T) { 225 v := "1,2,3" 226 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 227 fmt.Fprint(w, v) 228 })) 229 defer ts.Close() 230 231 d, err := LoadFromURL(ts.URL) 232 require.NoError(t, err) 233 require.Equal(t, v, string(d)) 234 } 235 236 func TestCommon_LoadFromURLTimeout(t *testing.T) { 237 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 238 time.Sleep(time.Second * 6) 239 })) 240 defer ts.Close() 241 242 _, err := LoadFromURL(ts.URL) 243 require.Error(t, err) 244 require.Contains(t, err.Error(), "Timeout exceeded") 245 } 246 247 func TestCommon_IsValidDir(t *testing.T) { 248 d := MakeTempDir(t) 249 require.NoError(t, IsValidDir(d)) 250 251 tp := filepath.Join(d, "foo") 252 err := IsValidDir(tp) 253 require.Error(t, err) 254 require.True(t, os.IsNotExist(err)) 255 256 err = os.WriteFile(tp, []byte("hello"), 0600) 257 require.NoError(t, err) 258 err = IsValidDir(tp) 259 require.Error(t, err) 260 require.Equal(t, "not a directory", err.Error()) 261 } 262 263 func TestCommon_MaybeMakeDir(t *testing.T) { 264 d := MakeTempDir(t) 265 dir := filepath.Join(d, "foo") 266 _, err := os.Stat(dir) 267 require.True(t, os.IsNotExist(err)) 268 err = MaybeMakeDir(dir) 269 require.NoError(t, err) 270 require.DirExists(t, dir) 271 272 // test no fail if exists 273 err = MaybeMakeDir(dir) 274 require.NoError(t, err) 275 } 276 277 func TestCommon_MaybeMakeDir_FileExists(t *testing.T) { 278 d := MakeTempDir(t) 279 fp := filepath.Join(d, "foo") 280 err := Write(fp, []byte("hello")) 281 require.NoError(t, err) 282 283 err = MaybeMakeDir(fp) 284 require.Error(t, err) 285 require.Contains(t, err.Error(), "is not a dir") 286 } 287 288 func TestCommon_Read(t *testing.T) { 289 d := MakeTempDir(t) 290 dir := filepath.Join(d, "foo", "bar", "baz") 291 err := MaybeMakeDir(dir) 292 require.NoError(t, err) 293 294 fp := filepath.Join(dir, "..", "..", "foo.txt") 295 err = Write(fp, []byte("hello")) 296 require.NoError(t, err) 297 298 require.DirExists(t, dir) 299 require.FileExists(t, filepath.Join(d, "foo", "foo.txt")) 300 data, err := Read(fp) 301 require.NoError(t, err) 302 require.Equal(t, "hello", string(data)) 303 } 304 305 func TestCommon_WriteJSON(t *testing.T) { 306 d := MakeTempDir(t) 307 fp := filepath.Join(d, "foo") 308 309 n := struct { 310 Name string `json:"name"` 311 }{} 312 n.Name = "test" 313 314 err := WriteJson(fp, n) 315 require.NoError(t, err) 316 317 v, err := Read(fp) 318 require.NoError(t, err) 319 require.JSONEq(t, `{"name": "test"}`, string(v)) 320 } 321 322 func TestCommon_ReadJSON(t *testing.T) { 323 d := MakeTempDir(t) 324 fp := filepath.Join(d, "foo") 325 err := Write(fp, []byte(`{"name": "test"}`)) 326 require.NoError(t, err) 327 328 n := struct { 329 Name string `json:"name"` 330 }{} 331 332 err = ReadJson(fp, &n) 333 require.NoError(t, err) 334 require.Equal(t, "test", n.Name) 335 } 336 337 func TestCommon_AbbrevHomePaths(t *testing.T) { 338 require.Equal(t, "", AbbrevHomePaths("")) 339 require.Equal(t, "/foo/bar", AbbrevHomePaths("/foo/bar")) 340 v, err := homedir.Dir() 341 if err != nil { 342 require.Equal(t, "~/bar", AbbrevHomePaths(filepath.Join(v, "bar"))) 343 } 344 } 345 346 func Test_NKeyValidator(t *testing.T) { 347 ts := NewTestStore(t, "O") 348 defer ts.Done(t) 349 350 oSeed, opk, _ := CreateOperatorKey(t) 351 aSeed, pk, _ := CreateAccountKey(t) 352 asf := filepath.Join(ts.Dir, "account_seed_file.nk") 353 require.NoError(t, os.WriteFile(asf, aSeed, 0700)) 354 pkf := filepath.Join(ts.Dir, "account_public_file.nk") 355 require.NoError(t, os.WriteFile(pkf, []byte(pk), 0700)) 356 nff := filepath.Join(ts.Dir, "not_exist.nk") 357 358 var keyTests = []struct { 359 arg string 360 ok bool 361 }{ 362 {asf, true}, 363 {pkf, true}, 364 {nff, false}, 365 {ts.Dir, false}, 366 {string(aSeed), true}, 367 {string(aSeed), true}, 368 {pk, true}, 369 {string(oSeed), false}, 370 {opk, false}, 371 {"", false}, 372 {"foo", false}, 373 } 374 375 fun := NKeyValidator(nkeys.PrefixByteAccount) 376 for i, kt := range keyTests { 377 err := fun(kt.arg) 378 var failed bool 379 message := fmt.Sprintf("unexpected error on test %q (%d): %v", kt.arg, i, err) 380 if err != nil { 381 failed = true 382 } 383 require.Equal(t, !kt.ok, failed, message) 384 } 385 } 386 387 func Test_SeedNKeyValidatorMatching(t *testing.T) { 388 ts := NewTestStore(t, "O") 389 defer ts.Done(t) 390 391 oSeed, opk, _ := CreateOperatorKey(t) 392 aSeed, pk, _ := CreateAccountKey(t) 393 as1, pk1, _ := CreateAccountKey(t) 394 as2, pk2, _ := CreateAccountKey(t) 395 396 validPubs := []string{string(pk), string(pk1)} 397 398 var keyTests = []struct { 399 arg string 400 ok bool 401 }{ 402 {string(oSeed), false}, 403 {"", false}, 404 {"foo", false}, 405 {pk, false}, 406 {pk2, false}, 407 {opk, false}, 408 {filepath.Join(ts.Dir, "notexist.nk"), false}, 409 {string(as2), false}, 410 {string(aSeed), true}, 411 {string(as1), true}, 412 } 413 414 fun := SeedNKeyValidatorMatching(validPubs, nkeys.PrefixByteAccount) 415 for i, kt := range keyTests { 416 err := fun(kt.arg) 417 var failed bool 418 message := fmt.Sprintf("unexpected error on test %q (%d): %v", kt.arg, i, err) 419 if err != nil { 420 failed = true 421 } 422 require.Equal(t, !kt.ok, failed, message) 423 } 424 } 425 426 func TestPushAccount(t *testing.T) { 427 _, opk, okp := CreateOperatorKey(t) 428 429 // create an http server to accept the request 430 hts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 431 defer r.Body.Close() 432 433 body, err := io.ReadAll(r.Body) 434 if err != nil { 435 require.NoError(t, err) 436 } 437 ac, err := jwt.DecodeAccountClaims(string(body)) 438 require.NoError(t, err) 439 440 token, err := ac.Encode(okp) 441 require.NoError(t, err) 442 443 w.Header().Add("Content-Type", "application/jwt") 444 w.WriteHeader(200) 445 w.Write([]byte(token)) 446 })) 447 defer hts.Close() 448 449 // self sign a jwt 450 _, apk, akp := CreateAccountKey(t) 451 ac := jwt.NewAccountClaims(apk) 452 araw, err := ac.Encode(akp) 453 require.NoError(t, err) 454 455 u, err := url.Parse(hts.URL) 456 require.NoError(t, err) 457 _, sraw, err := PushAccount(u.String(), []byte(araw)) 458 require.NoError(t, err) 459 require.NotNil(t, sraw) 460 461 ac, err = jwt.DecodeAccountClaims(string(sraw)) 462 require.NoError(t, err) 463 require.Equal(t, apk, ac.Subject) 464 require.Equal(t, opk, ac.Issuer) 465 } 466 467 func Test_NameFlagArgOnlyOnEmpty(t *testing.T) { 468 var tests = []struct { 469 n string 470 a []string 471 out string 472 }{ 473 {"", nil, ""}, 474 {"a", nil, "a"}, 475 {"a", []string{}, "a"}, 476 {"a", []string{"b", "c"}, "a"}, 477 {"", []string{"b", "c"}, "b"}, 478 } 479 480 for i, v := range tests { 481 r := nameFlagOrArgument(v.n, v.a) 482 require.Equal(t, v.out, r, "failed test %d", i) 483 } 484 }