github.com/kbehouse/nsc@v0.0.6/cmd/addimport_test.go (about)

     1  /*
     2   * Copyright 2018-2020 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  	"net/http"
    21  	"net/http/httptest"
    22  	"os"
    23  	"path/filepath"
    24  	"testing"
    25  
    26  	"github.com/nats-io/jwt/v2"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func Test_AddImport(t *testing.T) {
    31  	ts := NewTestStore(t, "test")
    32  	defer ts.Done(t)
    33  
    34  	ts.AddAccount(t, "A")
    35  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", false)
    36  
    37  	ts.AddAccount(t, "B")
    38  
    39  	token := ts.GenerateActivation(t, "A", "foobar.>", "B")
    40  	fp := filepath.Join(ts.Dir, "token.jwt")
    41  	require.NoError(t, Write(fp, []byte(token)))
    42  
    43  	tests := CmdTests{
    44  		//{createAddImportCmd(), []string{"add", "import", "--account", "B"}, nil, []string{"token is required"}, true},
    45  		{createAddImportCmd(), []string{"add", "import", "--account", "B", "--token", fp}, nil, []string{"added stream import"}, false},
    46  	}
    47  
    48  	tests.Run(t, "root", "add")
    49  }
    50  
    51  func Test_AddImportNoDefaultAccount(t *testing.T) {
    52  	ts := NewTestStore(t, "test")
    53  	defer ts.Done(t)
    54  
    55  	ts.AddAccount(t, "A")
    56  	ts.AddAccount(t, "B")
    57  
    58  }
    59  
    60  func Test_AddImportSelfImportsRejected(t *testing.T) {
    61  	ts := NewTestStore(t, "test")
    62  	defer ts.Done(t)
    63  
    64  	ts.AddAccount(t, "A")
    65  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", false)
    66  
    67  	token := ts.GenerateActivation(t, "A", "foobar.>", "A")
    68  	fp := filepath.Join(ts.Dir, "token.jwt")
    69  	require.NoError(t, Write(fp, []byte(token)))
    70  
    71  	_, _, err := ExecuteCmd(createAddImportCmd(), "--token", fp)
    72  	require.Error(t, err)
    73  	require.Equal(t, "export issuer is this account", err.Error())
    74  }
    75  
    76  func Test_AddImportFromURL(t *testing.T) {
    77  	ts := NewTestStore(t, "test")
    78  	defer ts.Done(t)
    79  
    80  	ts.AddAccount(t, "A")
    81  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", false)
    82  
    83  	ts.AddAccount(t, "B")
    84  
    85  	token := ts.GenerateActivation(t, "A", "foobar.>", "B")
    86  
    87  	ht := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    88  		fmt.Fprint(w, token)
    89  	}))
    90  	defer ht.Close()
    91  
    92  	_, _, err := ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", ht.URL)
    93  	require.NoError(t, err)
    94  
    95  	ac, err := ts.Store.ReadAccountClaim("B")
    96  	require.NoError(t, err)
    97  	require.Len(t, ac.Imports, 1)
    98  	require.Equal(t, token, ac.Imports[0].Token)
    99  }
   100  
   101  func Test_AddImportInteractive(t *testing.T) {
   102  	ts := NewTestStore(t, "test")
   103  	defer ts.Done(t)
   104  
   105  	ts.AddAccount(t, "A")
   106  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", false)
   107  
   108  	akp := ts.GetAccountKey(t, "A")
   109  	require.NotNil(t, akp)
   110  	apub, err := akp.PublicKey()
   111  	require.NoError(t, err)
   112  
   113  	ts.AddAccount(t, "B")
   114  
   115  	token := ts.GenerateActivation(t, "A", "foobar.>", "B")
   116  	fp := filepath.Join(ts.Dir, "token.jwt")
   117  	require.NoError(t, Write(fp, []byte(token)))
   118  
   119  	cmd := createAddImportCmd()
   120  	HoistRootFlags(cmd)
   121  	input := []interface{}{1, false, false, fp, "my import", "barfoo.>", 0}
   122  	_, _, err = ExecuteInteractiveCmd(cmd, input, "-i")
   123  	require.NoError(t, err)
   124  
   125  	ac, err := ts.Store.ReadAccountClaim("B")
   126  	require.NoError(t, err)
   127  	require.Len(t, ac.Imports, 1)
   128  	require.Equal(t, "my import", ac.Imports[0].Name)
   129  	require.Equal(t, "barfoo.>", string(ac.Imports[0].LocalSubject))
   130  	require.Equal(t, "foobar.>", string(ac.Imports[0].Subject))
   131  	require.Equal(t, apub, ac.Imports[0].Account)
   132  }
   133  
   134  func Test_AddImportGeneratingTokenInteractive(t *testing.T) {
   135  	ts := NewTestStore(t, "test")
   136  	defer ts.Done(t)
   137  
   138  	ts.AddAccount(t, "A")
   139  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", false)
   140  
   141  	akp := ts.GetAccountKey(t, "A")
   142  	require.NotNil(t, akp)
   143  	apub, err := akp.PublicKey()
   144  	require.NoError(t, err)
   145  
   146  	ts.AddAccount(t, "B")
   147  
   148  	cmd := createAddImportCmd()
   149  	HoistRootFlags(cmd)
   150  	input := []interface{}{1, true, 1, "my import", "barfoo.>", 0}
   151  	_, _, err = ExecuteInteractiveCmd(cmd, input)
   152  	require.NoError(t, err)
   153  
   154  	ac, err := ts.Store.ReadAccountClaim("B")
   155  	require.NoError(t, err)
   156  	require.Len(t, ac.Imports, 1)
   157  	require.Equal(t, "my import", ac.Imports[0].Name)
   158  	require.Equal(t, "barfoo.>", string(ac.Imports[0].LocalSubject))
   159  	require.Equal(t, "foobar.>", string(ac.Imports[0].Subject))
   160  	require.Equal(t, apub, ac.Imports[0].Account)
   161  }
   162  
   163  func Test_AddServiceImportGeneratingTokenInteractive(t *testing.T) {
   164  	ts := NewTestStore(t, "test")
   165  	defer ts.Done(t)
   166  
   167  	ts.AddAccount(t, "A")
   168  	ts.AddExport(t, "A", jwt.Service, "foobar.>", false)
   169  
   170  	akp := ts.GetAccountKey(t, "A")
   171  	require.NotNil(t, akp)
   172  	apub, err := akp.PublicKey()
   173  	require.NoError(t, err)
   174  
   175  	ts.AddAccount(t, "B")
   176  
   177  	cmd := createAddImportCmd()
   178  	HoistRootFlags(cmd)
   179  	input := []interface{}{1, true, 1, "barfoo.>", true, "my import", "foobar.>"}
   180  	_, _, err = ExecuteInteractiveCmd(cmd, input)
   181  	require.NoError(t, err)
   182  
   183  	ac, err := ts.Store.ReadAccountClaim("B")
   184  	require.NoError(t, err)
   185  	require.Len(t, ac.Imports, 1)
   186  	require.Equal(t, true, ac.Imports[0].Share)
   187  	require.Equal(t, "my import", ac.Imports[0].Name)
   188  	require.Equal(t, "foobar.>", string(ac.Imports[0].LocalSubject))
   189  	require.Equal(t, "barfoo.>", string(ac.Imports[0].Subject))
   190  	require.Equal(t, apub, ac.Imports[0].Account)
   191  }
   192  
   193  func Test_AddPublicImport(t *testing.T) {
   194  	ts := NewTestStore(t, "test")
   195  	defer ts.Done(t)
   196  
   197  	ts.AddAccount(t, "A")
   198  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", true)
   199  	ts.AddAccount(t, "B")
   200  
   201  	_, _, err := ExecuteCmd(createAddImportCmd(), "--account", "B", "--src-account", "A", "--remote-subject", "foobar.>")
   202  	require.NoError(t, err)
   203  
   204  	ac, err := ts.Store.ReadAccountClaim("B")
   205  	require.NoError(t, err)
   206  	require.Len(t, ac.Imports, 1)
   207  }
   208  
   209  func Test_AddImport_TokenAndPublic(t *testing.T) {
   210  	ts := NewTestStore(t, "test")
   211  	defer ts.Done(t)
   212  
   213  	ts.AddAccount(t, "A")
   214  	_, _, err := ExecuteCmd(createAddImportCmd(), "--token", "/foo", "--remote-subject", "foobar.>")
   215  	require.Error(t, err)
   216  	require.Contains(t, err.Error(), "private imports require src-account")
   217  }
   218  
   219  func Test_AddImport_MoreForPublic(t *testing.T) {
   220  	ts := NewTestStore(t, "test")
   221  	defer ts.Done(t)
   222  
   223  	ts.AddAccount(t, "A")
   224  	_, _, err := ExecuteCmd(createAddImportCmd(), "--remote-subject", "foobar.>")
   225  	require.Error(t, err)
   226  	require.Contains(t, err.Error(), "public imports require src-account, remote-subject")
   227  }
   228  
   229  func Test_AddImport_PublicInteractive(t *testing.T) {
   230  	ts := NewTestStore(t, "test")
   231  	defer ts.Done(t)
   232  
   233  	ts.AddAccount(t, "A")
   234  	ts.AddExport(t, "A", jwt.Service, "foobar.>", true)
   235  
   236  	akp := ts.GetAccountKey(t, "A")
   237  	require.NotNil(t, akp)
   238  	apub, err := akp.PublicKey()
   239  	require.NoError(t, err)
   240  
   241  	ts.AddAccount(t, "B")
   242  
   243  	cmd := createAddImportCmd()
   244  	HoistRootFlags(cmd)
   245  	// B, public, A's pubkey, local sub, service, name test, remote subj "test.foobar.alberto, key
   246  	input := []interface{}{1, false, true, apub, "foobar.x.*", true, "test", "test.foobar.alberto.*", 0}
   247  	_, _, err = ExecuteInteractiveCmd(cmd, input, "-i")
   248  	require.NoError(t, err)
   249  
   250  	ac, err := ts.Store.ReadAccountClaim("B")
   251  	require.NoError(t, err)
   252  	require.Len(t, ac.Imports, 1)
   253  	require.Equal(t, "test", ac.Imports[0].Name)
   254  	// for services remote local is subject, remote is to
   255  	require.Equal(t, "foobar.x.*", string(ac.Imports[0].Subject))
   256  	require.Equal(t, "test.foobar.alberto.*", string(ac.Imports[0].LocalSubject))
   257  	require.Equal(t, jwt.Service, ac.Imports[0].Type)
   258  	require.Equal(t, apub, ac.Imports[0].Account)
   259  }
   260  
   261  func Test_AddImport_PublicImportsInteractive(t *testing.T) {
   262  	ts := NewTestStore(t, "test")
   263  	defer ts.Done(t)
   264  
   265  	ts.AddAccount(t, "A")
   266  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", true)
   267  	ts.AddExport(t, "A", jwt.Service, "q.*", true)
   268  
   269  	akp := ts.GetAccountKey(t, "A")
   270  	require.NotNil(t, akp)
   271  	apub, err := akp.PublicKey()
   272  	require.NoError(t, err)
   273  
   274  	ts.AddAccount(t, "B")
   275  
   276  	cmd := createAddImportCmd()
   277  	HoistRootFlags(cmd)
   278  	// B, don't pick, public, A's pubkey, remote sub, stream, name test, local subj "test.foobar.>, key
   279  	input := []interface{}{1, false, true, apub, "foobar.>", false, "test", "test.foobar.>", 0}
   280  	_, _, err = ExecuteInteractiveCmd(cmd, input)
   281  	require.NoError(t, err)
   282  
   283  	ac, err := ts.Store.ReadAccountClaim("B")
   284  	require.NoError(t, err)
   285  	require.Len(t, ac.Imports, 1)
   286  	require.Equal(t, "test", ac.Imports[0].Name)
   287  	require.Equal(t, "test.foobar.>", string(ac.Imports[0].LocalSubject))
   288  	require.Equal(t, "foobar.>", string(ac.Imports[0].Subject))
   289  	require.True(t, ac.Imports[0].IsStream())
   290  	require.Equal(t, apub, ac.Imports[0].Account)
   291  
   292  	// B, don't pick, public, A's pubkey, remote sub, service, name test, local subj "test.foobar.>, key
   293  	input = []interface{}{1, false, true, apub, "q.*", true, "q", "qq.*", 0}
   294  	_, _, err = ExecuteInteractiveCmd(cmd, input)
   295  	require.NoError(t, err)
   296  
   297  	ac, err = ts.Store.ReadAccountClaim("B")
   298  	require.NoError(t, err)
   299  	require.Len(t, ac.Imports, 2)
   300  	require.Equal(t, "q", ac.Imports[1].Name)
   301  	require.Equal(t, "qq.*", string(ac.Imports[1].LocalSubject))
   302  	require.Equal(t, "q.*", string(ac.Imports[1].Subject))
   303  	require.True(t, ac.Imports[1].IsService())
   304  	require.Equal(t, apub, ac.Imports[1].Account)
   305  }
   306  
   307  func Test_AddImportWithSigningKeyToken(t *testing.T) {
   308  	ts := NewTestStore(t, "test")
   309  	defer ts.Done(t)
   310  
   311  	_, pk, sk := CreateAccountKey(t)
   312  	ts.AddAccount(t, "A")
   313  	_, _, err := ExecuteCmd(createEditAccount(), "--sk", pk)
   314  	require.NoError(t, err)
   315  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", false)
   316  
   317  	ts.AddAccount(t, "B")
   318  	token := ts.GenerateActivationWithSigner(t, "A", "foobar.>", "B", sk)
   319  	tp := filepath.Join(ts.Dir, "token.jwt")
   320  	require.NoError(t, Write(tp, []byte(token)))
   321  	bc, err := ts.Store.ReadAccountClaim("B")
   322  	require.NoError(t, err)
   323  
   324  	// decode the activation
   325  	acc, err := jwt.DecodeActivationClaims(token)
   326  	require.NoError(t, err)
   327  	// issuer is the signing key
   328  	require.Equal(t, acc.Issuer, pk)
   329  	// issuer account is account A
   330  	ac, err := ts.Store.ReadAccountClaim("A")
   331  	require.NoError(t, err)
   332  	require.Equal(t, acc.IssuerAccount, ac.Subject)
   333  	// account to import is B
   334  	require.Equal(t, acc.Subject, bc.Subject)
   335  
   336  	_, _, err = ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", tp)
   337  	require.NoError(t, err)
   338  	acb, err := ts.Store.ReadAccountClaim("B")
   339  	require.NoError(t, err)
   340  	require.Len(t, acb.Imports, 1)
   341  	require.Equal(t, acb.Imports[0].Account, ac.Subject)
   342  }
   343  
   344  func Test_AddDecoratedToken(t *testing.T) {
   345  	ts := NewTestStore(t, "test")
   346  	defer ts.Done(t)
   347  
   348  	_, pk, sk := CreateAccountKey(t)
   349  	ts.AddAccount(t, "A")
   350  	_, _, err := ExecuteCmd(createEditAccount(), "--sk", pk)
   351  	require.NoError(t, err)
   352  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", false)
   353  
   354  	ts.AddAccount(t, "B")
   355  	token := ts.GenerateActivationWithSigner(t, "A", "foobar.>", "B", sk)
   356  	d, err := jwt.DecorateJWT(token)
   357  	require.NoError(t, err)
   358  	token = string(d)
   359  	tp := filepath.Join(ts.Dir, "token.jwt")
   360  	require.NoError(t, Write(tp, []byte(token)))
   361  
   362  	_, _, err = ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", tp)
   363  	require.NoError(t, err)
   364  	acb, err := ts.Store.ReadAccountClaim("B")
   365  	require.NoError(t, err)
   366  	require.Len(t, acb.Imports, 1)
   367  	require.Equal(t, string(acb.Imports[0].Subject), "foobar.>")
   368  }
   369  
   370  func Test_AddImport_LocalImportsInteractive(t *testing.T) {
   371  	ts := NewTestStore(t, "test")
   372  	defer ts.Done(t)
   373  
   374  	ts.AddAccount(t, "A")
   375  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", true)
   376  	ts.AddExport(t, "A", jwt.Service, "q", true)
   377  
   378  	akp := ts.GetAccountKey(t, "A")
   379  	require.NotNil(t, akp)
   380  	apub, err := akp.PublicKey()
   381  	require.NoError(t, err)
   382  
   383  	ts.AddAccount(t, "B")
   384  
   385  	cmd := createAddImportCmd()
   386  	HoistRootFlags(cmd)
   387  
   388  	// B, pick, stream foobar, name test, local subj "test.foobar.>, key
   389  	input := []interface{}{1, true, 1, "test", "test.foobar.>"}
   390  	_, _, err = ExecuteInteractiveCmd(cmd, input)
   391  	require.NoError(t, err)
   392  
   393  	ac, err := ts.Store.ReadAccountClaim("B")
   394  	require.NoError(t, err)
   395  	require.Len(t, ac.Imports, 1)
   396  	require.Equal(t, false, ac.Imports[0].Share)
   397  	require.Equal(t, "test", ac.Imports[0].Name)
   398  	require.Equal(t, "test.foobar.>", string(ac.Imports[0].LocalSubject))
   399  	require.Equal(t, "foobar.>", string(ac.Imports[0].Subject))
   400  	require.True(t, ac.Imports[0].IsStream())
   401  	require.Equal(t, apub, ac.Imports[0].Account)
   402  
   403  	// B, pick, service q, name q service, local subj qq
   404  	input = []interface{}{1, true, 2, true, "q service", "qq", 0}
   405  	_, _, err = ExecuteInteractiveCmd(cmd, input)
   406  	require.NoError(t, err)
   407  
   408  	ac, err = ts.Store.ReadAccountClaim("B")
   409  	require.NoError(t, err)
   410  	require.Len(t, ac.Imports, 2)
   411  	require.Equal(t, true, ac.Imports[1].Share)
   412  	require.Equal(t, "q service", ac.Imports[1].Name)
   413  	require.Equal(t, "qq", string(ac.Imports[1].LocalSubject))
   414  	require.Equal(t, "q", string(ac.Imports[1].Subject))
   415  	require.True(t, ac.Imports[1].IsService())
   416  	require.Equal(t, apub, ac.Imports[1].Account)
   417  }
   418  
   419  func Test_ImportStreamHandlesDecorations(t *testing.T) {
   420  	ts := NewTestStore(t, "test")
   421  	defer ts.Done(t)
   422  
   423  	ts.AddAccount(t, "A")
   424  	ts.AddExport(t, "A", jwt.Stream, "foobar.>", false)
   425  
   426  	ts.AddAccount(t, "B")
   427  	ac := ts.GenerateActivation(t, "A", "foobar.>", "B")
   428  	// test util removed the decoration
   429  	d, err := jwt.DecorateJWT(ac)
   430  	require.NoError(t, err)
   431  
   432  	ap := filepath.Join(ts.Dir, "activation.jwt")
   433  	Write(ap, d)
   434  	_, _, err = ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", ap)
   435  	require.NoError(t, err)
   436  
   437  	bc, err := ts.Store.ReadAccountClaim("B")
   438  	require.NoError(t, err)
   439  	require.Len(t, bc.Imports, 1)
   440  	require.Empty(t, bc.Imports[0].LocalSubject)
   441  }
   442  
   443  func Test_ImportServiceHandlesDecorations(t *testing.T) {
   444  	ts := NewTestStore(t, "test")
   445  	defer ts.Done(t)
   446  
   447  	ts.AddAccount(t, "A")
   448  	ts.AddExport(t, "A", jwt.Service, "q", false)
   449  
   450  	ts.AddAccount(t, "B")
   451  	ac := ts.GenerateActivation(t, "A", "q", "B")
   452  	// test util removed the decoration
   453  	d, err := jwt.DecorateJWT(ac)
   454  	require.NoError(t, err)
   455  
   456  	ap := filepath.Join(ts.Dir, "activation.jwt")
   457  	Write(ap, d)
   458  	_, _, err = ExecuteCmd(createAddImportCmd(), "--account", "B", "--token", ap)
   459  	require.NoError(t, err)
   460  
   461  	bc, err := ts.Store.ReadAccountClaim("B")
   462  	require.NoError(t, err)
   463  	require.Len(t, bc.Imports, 1)
   464  	require.Equal(t, jwt.Subject(bc.Imports[0].LocalSubject), bc.Imports[0].Subject)
   465  }
   466  
   467  func Test_AddImportToAccount(t *testing.T) {
   468  	ts := NewTestStore(t, t.Name())
   469  	defer ts.Done(t)
   470  
   471  	ts.AddAccount(t, "A")
   472  	ts.AddAccount(t, "B")
   473  
   474  	bpk := ts.GetAccountPublicKey(t, "B")
   475  
   476  	_, _, err := ExecuteCmd(createAddImportCmd(), "--account", "A", "--src-account", bpk, "--remote-subject", "s.>")
   477  	require.NoError(t, err)
   478  
   479  	bc, err := ts.Store.ReadAccountClaim("A")
   480  	require.NoError(t, err)
   481  	require.Len(t, bc.Imports, 1)
   482  }
   483  
   484  func Test_AddWilcdardImport(t *testing.T) {
   485  	ts := NewTestStore(t, "test")
   486  	defer ts.Done(t)
   487  
   488  	ts.AddAccount(t, "B")
   489  	ts.AddAccount(t, "A")
   490  	ts.AddExport(t, "A", jwt.Service, "priv-srvc.>", false)
   491  	ts.AddExport(t, "A", jwt.Stream, "priv-strm.>", false)
   492  	ts.AddExport(t, "A", jwt.Service, "pub-srvc.>", true)
   493  	ts.AddExport(t, "A", jwt.Stream, "pub-strm.>", true)
   494  
   495  	aPub := ts.GetAccountPublicKey(t, "A")
   496  
   497  	srvcToken := ts.GenerateActivation(t, "A", "priv-srvc.>", "B")
   498  	srvcFp := filepath.Join(ts.Dir, "srvc-token.jwt")
   499  	require.NoError(t, Write(srvcFp, []byte(srvcToken)))
   500  	defer os.Remove(srvcFp)
   501  
   502  	strmToken := ts.GenerateActivation(t, "A", "priv-strm.>", "B")
   503  	strmFp := filepath.Join(ts.Dir, "strm-token.jwt")
   504  	require.NoError(t, Write(strmFp, []byte(strmToken)))
   505  	defer os.Remove(strmFp)
   506  
   507  	tests := CmdTests{
   508  		{createAddImportCmd(), []string{"add", "import", "--account", "B", "--token", srvcFp}, nil,
   509  			[]string{"added service import"}, false},
   510  		{createAddImportCmd(), []string{"add", "import", "--account", "B", "--token", strmFp}, nil,
   511  			[]string{"added stream import"}, false},
   512  		{createAddImportCmd(), []string{"add", "import", "--account", "B", "--src-account", aPub, "--service",
   513  			"--remote-subject", "pub-srvc.>"}, nil, []string{"added service import"}, false},
   514  		{createAddImportCmd(), []string{"add", "import", "--account", "B", "--src-account", aPub,
   515  			"--remote-subject", "pub-strm.>"}, nil, []string{"added stream import"}, false},
   516  	}
   517  
   518  	tests.Run(t, "root", "add")
   519  }