github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/addimport_test.go (about)

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