Repository: nats-io/nsc (go)

Issue:
tests should not fail when: $TMPDIR is set
The new test `TestAllDir` fails when the env variable `TMPDIR` is set to point to within the home directory.

The stripped table output contains: `$NKEYS_PATH No ~/tmp/nsc_test1638242485`  
The test complains: `does not contain "$NKEYS_PATH Yes /home/myusercode/tmp/nsc_test1638242485"`

This has two different expectations of normalization.

Pull request:
Fixed env tests checking for --all-dirs flag
[TEST] changed tests to deduce the effect of the flag without relying on the env.

[FIX] deprecated NKEYS_PATH and NSC_HOME - installations should use XDG_DATA_HOME and XDG_CONFIG_HOME instead. Added these variables to the `nsc env` report

Fixes #563

Patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index da011a5..770a1d8 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -36,7 +36,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v2
         with:
-          go-version: 1.19
+          go-version: '1.19'
           check-latest: true
 
       - name: Install cosign
diff --git a/cmd/describeaccount.go b/cmd/describeaccount.go
index 0a25aad..c3472ed 100644
--- a/cmd/describeaccount.go
+++ b/cmd/describeaccount.go
@@ -102,7 +102,7 @@ func (p *DescribeAccountParams) PostInteractive(_ ActionCtx) error {
 
 func (p *DescribeAccountParams) Run(_ ActionCtx) (store.Status, error) {
 	if Raw || Json || JsonPath != "" {
-		if !IsStdOut(p.outputFile) {
+		if !IsStdOut(p.outputFile) && Raw {
 			var err error
 			p.raw, err = jwt.DecorateJWT(string(p.raw))
 			if err != nil {
diff --git a/cmd/describeoperator.go b/cmd/describeoperator.go
index 69191f2..1704827 100644
--- a/cmd/describeoperator.go
+++ b/cmd/describeoperator.go
@@ -120,7 +120,7 @@ func (p *DescribeOperatorParams) PostInteractive(_ ActionCtx) error {
 
 func (p *DescribeOperatorParams) Run(_ ActionCtx) (store.Status, error) {
 	if Raw || Json || JsonPath != "" {
-		if !IsStdOut(p.outputFile) {
+		if !IsStdOut(p.outputFile) && Raw {
 			var err error
 			p.raw, err = jwt.DecorateJWT(string(p.raw))
 			if err != nil {
diff --git a/cmd/describeuser.go b/cmd/describeuser.go
index a31ca43..ac7d7c7 100644
--- a/cmd/describeuser.go
+++ b/cmd/describeuser.go
@@ -129,7 +129,7 @@ func (p *DescribeUserParams) PostInteractive(_ ActionCtx) error {
 
 func (p *DescribeUserParams) Run(ctx ActionCtx) (store.Status, error) {
 	if Raw || Json || JsonPath != "" {
-		if !IsStdOut(p.outputFile) {
+		if !IsStdOut(p.outputFile) && Raw {
 			var err error
 			p.raw, err = jwt.DecorateJWT(string(p.raw))
 			if err != nil {
diff --git a/cmd/editaccount.go b/cmd/editaccount.go
index 250d75c..bc74465 100644
--- a/cmd/editaccount.go
+++ b/cmd/editaccount.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2022 The NATS Authors
+ * Copyright 2018-2023 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -18,16 +18,14 @@ package cmd
 import (
 	"errors"
 	"fmt"
-
 	"strconv"
 	"strings"
 
 	cli "github.com/nats-io/cliprompts/v2"
 	"github.com/nats-io/jwt/v2"
 	"github.com/nats-io/nkeys"
-	"github.com/spf13/cobra"
-
 	"github.com/nats-io/nsc/v2/cmd/store"
+	"github.com/spf13/cobra"
 )
 
 func validatorUrlOrEmpty() cli.Opt {
@@ -119,6 +117,8 @@ func createEditAccount() *cobra.Command {
 	cmd.Flags().VarP(&params.MaxAckPending, "js-max-ack-pending", "", "JetStream: set number of maximum acks that can be pending for a consumer in the account")
 
 	cmd.Flags().StringVarP(&params.AccountContextParams.Name, "name", "n", "", "account to edit")
+	cmd.Flags().BoolVarP(&params.disableJetStream, "js-disable", "", false, "disables all JetStream limits in the account by deleting any limits")
+
 	params.signingKeys.BindFlags("sk", "", nkeys.PrefixByteAccount, cmd)
 	params.TimeParams.BindFlags(cmd)
 	return cmd
@@ -148,21 +148,22 @@ type EditAccountParams struct {
 	GenericClaimsParams
 	PermissionsParams
 	JetStreamLimitParams
-	claim          *jwt.AccountClaims
-	token          string
-	infoUrl        string
-	description    string
-	conns          NumberParams
-	leafConns      NumberParams
-	exports        NumberParams
-	disallowBearer bool
-	exportsWc      bool
-	imports        NumberParams
-	subscriptions  NumberParams
-	payload        NumberParams
-	data           NumberParams
-	signingKeys    SigningKeysParams
-	rmSigningKeys  []string
+	claim            *jwt.AccountClaims
+	token            string
+	infoUrl          string
+	description      string
+	conns            NumberParams
+	leafConns        NumberParams
+	exports          NumberParams
+	disallowBearer   bool
+	exportsWc        bool
+	imports          NumberParams
+	subscriptions    NumberParams
+	payload          NumberParams
+	data             NumberParams
+	signingKeys      SigningKeysParams
+	rmSigningKeys    []string
+	disableJetStream bool
 }
 
 func (p *EditAccountParams) SetDefaults(ctx ActionCtx) error {
@@ -186,6 +187,10 @@ func (p *EditAccountParams) SetDefaults(ctx ActionCtx) error {
 		return fmt.Errorf("rm-js-tier is exclusive of all other js options")
 	}
 
+	if p.disableJetStream && (p.hasJSSetParams || hasDeleteTier) {
+		return fmt.Errorf("js-disable is exclusive of all other js options")
+	}
+
 	if !InteractiveFlag && ctx.NothingToDo(
 		"start", "expiry", "tag", "rm-tag", "conns", "leaf-conns", "exports", "imports", "subscriptions",
 		"payload", "data", "wildcard-exports", "sk", "rm-sk", "description", "info-url", "response-ttl", "allow-pub-response",
@@ -200,10 +205,12 @@ func (p *EditAccountParams) SetDefaults(ctx ActionCtx) error {
 		"js-max-mem-stream",
 		"js-max-disk-stream",
 		"js-max-bytes-required",
-		"js-max-ack-pending") {
+		"js-max-ack-pending",
+		"js-disable") {
 		ctx.CurrentCmd().SilenceUsage = false
 		return fmt.Errorf("specify an edit option")
 	}
+
 	return nil
 }
 
@@ -236,6 +243,19 @@ func (p *EditAccountParams) getTierLimits(tier int) (*jwt.JetStreamLimits, error
 	return src, nil
 }
 
+func (p *EditAccountParams) doDisableJetStream(r *store.Report) error {
+	if !p.disableJetStream {
+		return nil
+	}
+	p.claim.Limits.JetStreamLimits = jwt.JetStreamLimits{}
+	r.AddOK("deleted global limit")
+	for k := range p.claim.Limits.JetStreamTieredLimits {
+		r.AddOK("deleted tier limit %s", k)
+	}
+	p.claim.Limits.JetStreamTieredLimits = nil
+	return nil
+}
+
 func (p *EditAccountParams) loadLimits(ctx ActionCtx, tier int) error {
 	src, err := p.getTierLimits(tier)
 	if err != nil {
@@ -517,6 +537,63 @@ func (p *EditAccountParams) PostInteractive(ctx ActionCtx) error {
 	return nil
 }
 
+func (p *EditAccountParams) checkSystemAccount(ctx ActionCtx) error {
+	// if we are not editing system account ignore
+	if op, _ := ctx.StoreCtx().Store.ReadOperatorClaim(); op.SystemAccount != p.claim.Subject {
+		return nil
+	}
+
+	if p.claim.Limits.JetStreamTieredLimits != nil {
+		return errors.New("system account cannot have JetStream limits - please rerun with --js-disable")
+	}
+
+	var mustUnset []string
+
+	if p.Tier != 0 {
+		mustUnset = append(mustUnset, "--js-tier")
+	}
+	if p.MemStorage > 0 {
+		mustUnset = append(mustUnset, "--js-mem-storage")
+	}
+	if p.DiskStorage > 0 {
+		mustUnset = append(mustUnset, "--js-disk-storage")
+	}
+	if p.Streams > 0 {
+		mustUnset = append(mustUnset, "--js-streams")
+	}
+	if p.Consumer > 0 {
+		mustUnset = append(mustUnset, "--js-consumer")
+	}
+	if p.MemMaxStreamBytes > 0 {
+		mustUnset = append(mustUnset, "--js-max-mem-stream")
+	}
+	if p.DiskMaxStreamBytes > 0 {
+		mustUnset = append(mustUnset, "--js-max-disk-stream")
+	}
+	if p.MaxBytesRequired {
+		mustUnset = append(mustUnset, "--js-max-bytes-required")
+	}
+	if p.MaxAckPending > 0 {
+		mustUnset = append(mustUnset, "--js-max-ack-pending")
+	}
+	if len(mustUnset) == 0 {
+		// no user specified values set - so set to zero/false
+		// reset all the flags to zero
+		p.Tier = 0
+		p.Consumer = 0
+		p.Streams = 0
+		p.DiskStorage = 0
+		p.MemStorage = 0
+		p.MemMaxStreamBytes = 0
+		p.DiskMaxStreamBytes = 0
+		p.MaxBytesRequired = false
+		p.MaxAckPending = 0
+	} else {
+		return fmt.Errorf("system account cannot have jetstream related settings - please rerun without: %s", strings.Join(mustUnset, " "))
+	}
+	return nil
+}
+
 func (p *EditAccountParams) Validate(ctx ActionCtx) error {
 	var err error
 	if err = p.signingKeys.Valid(); err != nil {
@@ -528,10 +605,8 @@ func (p *EditAccountParams) Validate(ctx ActionCtx) error {
 	if err = p.SignerParams.ResolveWithPriority(ctx, p.claim.Issuer); err != nil {
 		return err
 	}
-	if op, _ := ctx.StoreCtx().Store.ReadOperatorClaim(); op.SystemAccount == p.claim.Subject {
-		if p.MemStorage != 0 || p.DiskStorage != 0 || p.Consumer != 0 || p.Streams != 0 {
-			return fmt.Errorf("jetstream not available for system account")
-		}
+	if err = p.checkSystemAccount(ctx); err != nil {
+		return err
 	}
 	if err := p.PermissionsParams.Validate(); err != nil {
 		return err
@@ -594,11 +669,14 @@ func (p *EditAccountParams) applyLimits(ctx ActionCtx, r *store.Report) error {
 			return errors.New("account doesn't have tier limits")
 		}
 	}
-
 	if p.DeleteTier != -1 {
 		return nil
 	}
 
+	if p.disableJetStream {
+		return p.doDisableJetStream(r)
+	}
+
 	label := p.tierLabel()
 
 	limits.Streams = params.Streams.Int64()
diff --git a/cmd/editoperator.go b/cmd/editoperator.go
index 2cd3277..233c000 100644
--- a/cmd/editoperator.go
+++ b/cmd/editoperator.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2022 The NATS Authors
+ * Copyright 2018-2023 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -189,6 +189,32 @@ func (p *EditOperatorParams) PostInteractive(ctx ActionCtx) error {
 	return p.SignerParams.Edit(ctx)
 }
 
+func (p *EditOperatorParams) setSystemAccount(ctx ActionCtx) error {
+	if p.sysAcc != "" {
+		if !nkeys.IsValidPublicAccountKey(p.sysAcc) {
+			if acc, err := ctx.StoreCtx().Store.ReadAccountClaim(p.sysAcc); err != nil {
+				return err
+			} else {
+				if acc.Limits.JetStreamTieredLimits != nil {
+					return fmt.Errorf("system accounts cannot have tiered JetStream limits - run %q first", fmt.Sprintf("nsc edit account %s --js-disable", acc.Name))
+				}
+				if acc.Limits.JetStreamLimits.Streams != 0 ||
+					acc.Limits.JetStreamLimits.Consumer != 0 ||
+					acc.Limits.JetStreamLimits.MaxAckPending != 0 ||
+					acc.Limits.JetStreamLimits.DiskMaxStreamBytes != 0 ||
+					acc.Limits.JetStreamLimits.MemoryMaxStreamBytes != 0 ||
+					acc.Limits.JetStreamLimits.DiskStorage != 0 ||
+					acc.Limits.JetStreamLimits.MemoryStorage != 0 ||
+					acc.Limits.JetStreamLimits.MaxBytesRequired {
+					return fmt.Errorf("system accounts cannot have JetStream limits - run %q first", fmt.Sprintf("nsc edit account %s --js-disable", acc.Name))
+				}
+				p.sysAcc = acc.Subject
+			}
+		}
+	}
+	return nil
+}
+
 func (p *EditOperatorParams) Validate(ctx ActionCtx) error {
 	var err error
 	if err = p.GenericClaimsParams.Valid(); err != nil {
@@ -200,14 +226,8 @@ func (p *EditOperatorParams) Validate(ctx ActionCtx) error {
 			return err
 		}
 	}
-	if p.sysAcc != "" {
-		if !nkeys.IsValidPublicAccountKey(p.sysAcc) {
-			if acc, err := ctx.StoreCtx().Store.ReadAccountClaim(p.sysAcc); err != nil {
-				return err
-			} else {
-				p.sysAcc = acc.Subject
-			}
-		}
+	if err := p.setSystemAccount(ctx); err != nil {
+		return err
 	}
 	if p.reqSk {
 		accounts, err := ctx.StoreCtx().Store.ListSubContainers(store.Accounts)
diff --git a/cmd/env.go b/cmd/env.go
index 7778329..42f3b16 100644
--- a/cmd/env.go
+++ b/cmd/env.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2022 The NATS Authors
+ * Copyright 2018-2023 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -21,7 +21,6 @@ import (
 
 	"github.com/nats-io/nsc/v2/cmd/store"
 	"github.com/nats-io/nsc/v2/home"
-
 	"github.com/spf13/cobra"
 	"github.com/xlab/tablewriter"
 )
@@ -95,10 +94,12 @@ func (p *SetContextParams) PrintEnv(cmd *cobra.Command) {
 	table := tablewriter.CreateTable()
 	table.AddTitle("NSC Environment")
 	table.AddHeaders("Setting", "Set", "Effective Value")
+	table.AddRow("$"+home.XdgConfigHome, envSet(home.XdgConfigHome), os.Getenv(home.XdgConfigHome))
+	table.AddRow("$"+home.XdgDataHome, envSet(home.XdgDataHome), os.Getenv(home.XdgDataHome))
 	table.AddRow("$"+NscCwdOnlyEnv, envSet(NscCwdOnlyEnv), "If set, default operator/account from cwd only")
 	table.AddRow("$"+NscNoGitIgnoreEnv, envSet(NscNoGitIgnoreEnv), "If set, no .gitignore files written")
-	table.AddRow("$"+store.NKeysPathEnv, envSet(store.NKeysPathEnv), AbbrevHomePaths(store.GetKeysDir()))
-	table.AddRow("$"+NscHomeEnv, envSet(NscHomeEnv), AbbrevHomePaths(ConfigDirFlag))
+	table.AddRow("$"+store.NKeysPathEnv+" (deprecated)", envSet(store.NKeysPathEnv), AbbrevHomePaths(store.GetKeysDir()))
+	table.AddRow("$"+NscHomeEnv+" (deprecated)", envSet(NscHomeEnv), AbbrevHomePaths(ConfigDirFlag))
 	table.AddRow("$"+NscRootCasNatsEnv, envSet(NscRootCasNatsEnv),
 		"If set, root CAs in the referenced file will be used for nats connections")
 	table.AddRow("", "", "If not set, will default to the system trust store")
diff --git a/home/home.go b/home/home.go
index a455f72..78d4967 100644
--- a/home/home.go
+++ b/home/home.go
@@ -24,10 +24,12 @@ import (
 
 const StoresSubDirName = "stores"
 const KeysSubDirName = "keys"
+const XdgConfigHome = "XDG_CONFIG_HOME"
+const XdgDataHome = "XDG_DATA_HOME"
 
 var home, _ = homedir.Dir()
-var config = envOrValue("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
-var data = envOrValue("XDG_DATA_HOME", filepath.Join(home, ".local", "share"))
+var config = envOrValue(XdgConfigHome, filepath.Join(home, ".config"))
+var data = envOrValue(XdgDataHome, filepath.Join(home, ".local", "share"))
 
 func envOrValue(name, value string) string {
 	ev := os.Getenv(name)


Test patch:
diff --git a/cmd/describeaccount_test.go b/cmd/describeaccount_test.go
index 139ac4c..06ed07d 100644
--- a/cmd/describeaccount_test.go
+++ b/cmd/describeaccount_test.go
@@ -18,6 +18,9 @@ package cmd
 import (
 	"encoding/json"
 	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/nats-io/jwt/v2"
@@ -250,3 +253,34 @@ func TestDescribeAccount_SubjectEncoding(t *testing.T) {
 	require.NoError(t, err)
 	require.Contains(t, out, "foo.>")
 }
+
+func TestDescribeAccount_Output(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	ts.AddAccount(t, "A")
+
+	p := filepath.Join(ts.Dir, "A.json")
+	_, _, err := ExecuteCmd(rootCmd, "describe", "account", "--json", "--output-file", p)
+	require.NoError(t, err)
+	data, err := os.ReadFile(p)
+	require.NoError(t, err)
+
+	ac := jwt.AccountClaims{}
+	require.NoError(t, json.Unmarshal(data, &ac))
+	require.Equal(t, "A", ac.Name)
+
+	p = filepath.Join(ts.Dir, "A.txt")
+	_, _, err = ExecuteCmd(rootCmd, "describe", "account", "--output-file", p)
+	require.NoError(t, err)
+	data, err = os.ReadFile(p)
+	require.NoError(t, err)
+	strings.Contains(string(data), "Account Details")
+
+	p = filepath.Join(ts.Dir, "A.jwt")
+	_, _, err = ExecuteCmd(rootCmd, "describe", "account", "--raw", "--output-file", p)
+	require.NoError(t, err)
+	data, err = os.ReadFile(p)
+	require.NoError(t, err)
+	require.Contains(t, string(data), "-----BEGIN NATS ACCOUNT JWT-----\ney")
+}
diff --git a/cmd/describeoperator_test.go b/cmd/describeoperator_test.go
index 9b7ebb4..0d1f073 100644
--- a/cmd/describeoperator_test.go
+++ b/cmd/describeoperator_test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2020 The NATS Authors
+ * Copyright 2018-2023 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -18,6 +18,9 @@ package cmd
 import (
 	"encoding/json"
 	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/nats-io/jwt/v2"
@@ -179,3 +182,32 @@ func TestDescribeOperator_JsonPath(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, fmt.Sprintf("\"%s\"\n", oc.Subject), out)
 }
+
+func TestDescribeOperator_Output(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	p := filepath.Join(ts.Dir, "O.json")
+	_, _, err := ExecuteCmd(rootCmd, "describe", "operator", "--json", "--output-file", p)
+	require.NoError(t, err)
+	data, err := os.ReadFile(p)
+	require.NoError(t, err)
+
+	oc := jwt.OperatorClaims{}
+	require.NoError(t, json.Unmarshal(data, &oc))
+	require.Equal(t, "O", oc.Name)
+
+	p = filepath.Join(ts.Dir, "O.txt")
+	_, _, err = ExecuteCmd(rootCmd, "describe", "operator", "--output-file", p)
+	require.NoError(t, err)
+	data, err = os.ReadFile(p)
+	require.NoError(t, err)
+	strings.Contains(string(data), "Operator Details")
+
+	p = filepath.Join(ts.Dir, "O.jwt")
+	_, _, err = ExecuteCmd(rootCmd, "describe", "operator", "--raw", "--output-file", p)
+	require.NoError(t, err)
+	data, err = os.ReadFile(p)
+	require.NoError(t, err)
+	require.Contains(t, string(data), "-----BEGIN NATS OPERATOR JWT-----\ney")
+}
diff --git a/cmd/describeuser_test.go b/cmd/describeuser_test.go
index 080545a..2ad8317 100644
--- a/cmd/describeuser_test.go
+++ b/cmd/describeuser_test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2020 The NATS Authors
+ * Copyright 2018-2023 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -18,6 +18,9 @@ package cmd
 import (
 	"encoding/json"
 	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/nats-io/jwt/v2"
@@ -243,5 +246,35 @@ func TestDescribeUser_Times(t *testing.T) {
 	stdout, _, err := ExecuteCmd(createDescribeUserCmd(), "--account", "A", "--name", "aa")
 	require.NoError(t, err)
 	require.Contains(t, stdout, "16:04:05-17:04:09")
+}
+
+func TestDescribeUser_Output(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
 
+	ts.AddAccount(t, "A")
+	ts.AddUser(t, "A", "aa")
+
+	p := filepath.Join(ts.Dir, "aa.json")
+	_, _, err := ExecuteCmd(rootCmd, "describe", "user", "-a", "A", "--json", "--output-file", p)
+	require.NoError(t, err)
+	data, err := os.ReadFile(p)
+	require.NoError(t, err)
+	uc := jwt.UserClaims{}
+	require.NoError(t, json.Unmarshal(data, &uc))
+	require.Equal(t, "aa", uc.Name)
+
+	p = filepath.Join(ts.Dir, "aa.txt")
+	_, _, err = ExecuteCmd(rootCmd, "describe", "user", "-a", "A", "--output-file", p)
+	require.NoError(t, err)
+	data, err = os.ReadFile(p)
+	require.NoError(t, err)
+	strings.Contains(string(data), "User Details")
+
+	p = filepath.Join(ts.Dir, "aa.jwt")
+	_, _, err = ExecuteCmd(rootCmd, "describe", "user", "-a", "A", "--raw", "--output-file", p)
+	require.NoError(t, err)
+	data, err = os.ReadFile(p)
+	require.NoError(t, err)
+	require.Contains(t, string(data), "-----BEGIN NATS USER JWT-----\ney")
 }
diff --git a/cmd/editaccount_test.go b/cmd/editaccount_test.go
index 8b1c2a9..f9b2fa6 100644
--- a/cmd/editaccount_test.go
+++ b/cmd/editaccount_test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2022 The NATS Authors
+ * Copyright 2018-2023 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -16,11 +16,11 @@
 package cmd
 
 import (
+	"fmt"
 	"testing"
 	"time"
 
 	"github.com/nats-io/nkeys"
-
 	"github.com/stretchr/testify/require"
 )
 
@@ -354,3 +354,50 @@ func Test_EditOperatorDisallowBearerToken(t *testing.T) {
 	require.Error(t, err)
 	require.Equal(t, err.Error(), "account disallows bearer token")
 }
+
+func Test_EditSysAccount(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	ts.AddAccount(t, "SYS")
+	_, _, err := ExecuteCmd(createEditOperatorCmd(), "--system-account", "SYS")
+	require.NoError(t, err)
+
+	// test setting any flag will generate an error and the flag is reported
+	jsOptions := []string{
+		"js-max-bytes-required",
+		"js-tier",
+		"js-mem-storage",
+		"js-disk-storage",
+		"js-streams",
+		"js-consumer",
+		"js-max-mem-stream",
+		"js-max-disk-stream",
+		"js-max-ack-pending",
+	}
+	// setting any JS flags, will fail the edit
+	for idx, n := range jsOptions {
+		flag := fmt.Sprintf("--%s", n)
+		if idx > 0 {
+			_, _, err = ExecuteCmd(createEditAccount(), "SYS", "--tag", "A", flag, "1")
+			require.Error(t, err)
+			require.Contains(t, err.Error(), flag)
+		} else {
+			_, _, err = ExecuteCmd(createEditAccount(), "SYS", "--tag", "A", flag)
+			require.Error(t, err)
+			require.Contains(t, err.Error(), flag)
+		}
+	}
+	// defaults are removed automatically
+	_, _, err = ExecuteCmd(createEditAccount(), "SYS", "--tag", "A")
+	require.NoError(t, err)
+}
+
+func Test_TierRmAndDisabled(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+	ts.AddAccount(t, "A")
+
+	_, _, err := ExecuteCmd(createEditAccount(), "A", "--rm-js-tier", "1", "--js-disable")
+	require.Error(t, err)
+	require.Equal(t, err.Error(), "js-disable is exclusive of all other js options")
+}
diff --git a/cmd/editoperator_test.go b/cmd/editoperator_test.go
index c498015..9aabe2c 100644
--- a/cmd/editoperator_test.go
+++ b/cmd/editoperator_test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2022 The NATS Authors
+ * Copyright 2018-2023 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -348,3 +348,30 @@ func Test_SKGenerateAddsOneKey(t *testing.T) {
 	require.NoError(t, err)
 	assert.Equal(t, 2, len(keys))
 }
+
+func Test_SysAccountCannotHaveJetStream(t *testing.T) {
+	ts := NewTestStore(t, "0")
+	defer ts.Done(t)
+
+	// global JS limits shouldn't work
+	ts.AddAccount(t, "A")
+	_, _, err := ExecuteCmd(createEditAccount(), "A", "--js-disk-storage", "1024")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--system-account", "A")
+	require.Error(t, err)
+	require.Equal(t, err.Error(), "system accounts cannot have JetStream limits - run \"nsc edit account A --js-disable\" first")
+
+	// tiered JS limits shouldn't work
+	_, _, err = ExecuteCmd(createEditAccount(), "A", "--js-disable")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createEditAccount(), "A", "--js-tier", "3", "--js-disk-storage", "1024")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--system-account", "A")
+	require.Error(t, err)
+	require.Equal(t, err.Error(), "system accounts cannot have tiered JetStream limits - run \"nsc edit account A --js-disable\" first")
+
+	_, _, err = ExecuteCmd(createEditAccount(), "A", "--js-disable")
+	require.NoError(t, err)
+	_, _, err = ExecuteCmd(createEditOperatorCmd(), "--system-account", "A")
+	require.NoError(t, err)
+}
diff --git a/cmd/env_test.go b/cmd/env_test.go
index d709f25..2f112c3 100644
--- a/cmd/env_test.go
+++ b/cmd/env_test.go
@@ -18,10 +18,12 @@ package cmd
 import (
 	"fmt"
 	"os"
+	"path"
 	"path/filepath"
 	"testing"
 
 	"github.com/nats-io/nsc/v2/cmd/store"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -34,7 +36,7 @@ func TestEnv_DefaultOutput(t *testing.T) {
 	require.NoError(t, err)
 	stderr = StripTableDecorations(stderr)
 	require.NoError(t, err)
-	require.Contains(t, stderr, fmt.Sprintf("$NKEYS_PATH Yes %s", AbbrevHomePaths(store.GetKeysDir())))
+	require.Contains(t, stderr, fmt.Sprintf("$NKEYS_PATH (deprecated) Yes %s", AbbrevHomePaths(store.GetKeysDir())))
 	require.Contains(t, stderr, fmt.Sprintf("Current Store Dir %s", AbbrevHomePaths(filepath.Dir(ts.Store.Dir))))
 	require.Contains(t, stderr, "Current Operator test")
 }
@@ -49,7 +51,7 @@ func TestEnv_SetAccountOutput(t *testing.T) {
 	_, stderr, err := ExecuteCmd(createEnvCmd(), "--operator", "test", "--account", "B")
 	require.NoError(t, err)
 	stderr = StripTableDecorations(stderr)
-	require.Contains(t, stderr, fmt.Sprintf("$NKEYS_PATH Yes %s", AbbrevHomePaths(store.GetKeysDir())))
+	require.Contains(t, stderr, fmt.Sprintf("$NKEYS_PATH (deprecated) Yes %s", AbbrevHomePaths(store.GetKeysDir())))
 	require.Contains(t, stderr, fmt.Sprintf("Current Store Dir %s", AbbrevHomePaths(filepath.Dir(ts.Store.Dir))))
 	require.Contains(t, stderr, "Current Operator test")
 	require.Contains(t, stderr, "Current Account B")
@@ -77,10 +79,14 @@ func TestAllDir(t *testing.T) {
 	p := MakeTempDir(t)
 	defer os.RemoveAll(p)
 
-	_, stderr, err := ExecuteCmd(rootCmd, "env", "--all-dirs", p)
+	_, _, err := ExecuteCmd(rootCmd, "env", "--all-dirs", p)
 	require.NoError(t, err)
-	stderr = StripTableDecorations(stderr)
-	require.Contains(t, stderr, fmt.Sprintf("$NKEYS_PATH Yes %s", p))
-	require.Contains(t, stderr, fmt.Sprintf("$NSC_HOME Yes %s", p))
-	require.Contains(t, stderr, fmt.Sprintf("Current Store Dir %s", p))
+
+	_, _, err = ExecuteCmd(rootCmd, "add", "operator", "O", "--all-dirs", p)
+	require.NoError(t, err)
+
+	assert.FileExists(t, path.Join(p, "nsc.json"))
+	assert.DirExists(t, path.Join(p, "creds"))
+	assert.DirExists(t, path.Join(p, "keys"))
+	assert.DirExists(t, path.Join(p, "O"))
 }
diff --git a/cmd/test.go b/cmd/test.go
index 84e5a1e..7abfe48 100644
--- a/cmd/test.go
+++ b/cmd/test.go
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018-2022 The NATS Authors
+ * Copyright 2018-2023 The NATS Authors
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -225,6 +225,44 @@ func (t *flagTable) render() string {
 	return buf.String()
 }
 
+func (t *flagTable) ResetFlagDefaults() string {
+	var buf bytes.Buffer
+
+	allFlags := make(map[string]bool)
+	for _, c := range t.commands {
+		for n := range c.flagMap {
+			allFlags[n] = true
+		}
+	}
+	var cols []string
+	for k := range allFlags {
+		cols = append(cols, k)
+	}
+	sort.Strings(cols)
+	cols = append([]string{"cmd"}, cols...)
+
+	buf.WriteString(strings.Join(cols, ","))
+	buf.WriteString("\n")
+
+	for _, c := range t.commands {
+		sf := []string{c.name}
+		for i := 1; i < len(cols); i++ {
+			v, ok := c.flagMap[cols[i]]
+			if v != "" {
+				sf = append(sf, v)
+			} else if ok {
+				sf = append(sf, "-")
+			} else {
+				sf = append(sf, "")
+			}
+		}
+		buf.WriteString(strings.Join(sf, ","))
+		buf.WriteString("\n")
+	}
+
+	return buf.String()
+}
+
 func init() {
 	GetRootCmd().AddCommand(testCmd)
 	testCmd.AddCommand(createGenerateNKeyCmd())
diff --git a/cmd/util_test.go b/cmd/util_test.go
index ed50fb3..156622d 100644
--- a/cmd/util_test.go
+++ b/cmd/util_test.go
@@ -30,6 +30,8 @@ import (
 	"testing"
 	"time"
 
+	flag "github.com/spf13/pflag"
+
 	cli "github.com/nats-io/cliprompts/v2"
 	"github.com/nats-io/jwt/v2"
 	jwt1 "github.com/nats-io/jwt/v2/v1compat"
@@ -74,6 +76,25 @@ func ResetSharedFlags() {
 	ConfigDirFlag = ""
 	DataDirFlag = ""
 	KeysDirFlag = ""
+	var buf Stack
+	buf.Push(rootCmd)
+	for {
+		c := buf.Pop()
+		if c == nil {
+			break
+		}
+		if c.HasSubCommands() {
+			for _, sub := range c.Commands() {
+				buf.Push(sub)
+			}
+		}
+		flags := c.Flags()
+		flags.VisitAll(func(f *flag.Flag) {
+			if f.Changed {
+				_ = f.Value.Set(f.DefValue)
+			}
+		})
+	}
 }
 
 func NewEmptyStore(t *testing.T) *TestStore {