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  }