Repository: nats-io/nsc (go)

Issue:
NSC does not allow multiple clusters with 100% weight subject mapping
### What version were you using?

nsc version 2.8.6

### What environment was the server running in?

darwin-arm64

### Is this defect reproducible?

Run the following:
```
nsc add account APP
nsc edit account APP --sk generate
nsc add mapping --account APP --cluster east --from "foo" --to "foo.east" --weight 100
nsc add mapping --account APP --cluster west --from "foo" --to "foo.west" --weight 100
```

### Given the capability you are leveraging, describe your expectation?

That the mapping command would add another element to the mapping slice instead of not allowing it. Trying to do the following with nsc: https://github.com/synadia-labs/eventually-consistent-virtual-global-stream/blob/main/mappings.cfg
But so far no luck

### Given the expectation, what is the defect you are observing?

When the commands run I get the following:
```
$ nsc add mapping --account APP --cluster east --from "foo" --to "foo.east" --weight 100
[ OK ] added mapping foo -> foo.east, weight 100%
$ nsc add mapping --account APP --cluster west --from "foo" --to "foo.west" --weight 100
Error: mapping validation failed: [Mapping "foo" exceeds 100% among all of it's weighted to mappings]
```

Pull request:
[FIX] mapping add/delete commands related to the `--cluster` flag
- [BUMP] bumped dependencies (including JWT to support cluster allocation on checks)
- [FIX] fixed an issue in add mapping where the `--cluster` was not taken into account when deciding whether to add or replace, and the first mapping matching to `--to` was replaced. 
- [FEAT] delete mapping now supports --cluster

Fixes #657 - Note that this fix [requires the server to adopt the updated JWT](https://github.com/nats-io/nats-server/pull/5618) library as otherwise, validation by the server will reject the mapping configuration

Patch:
diff --git a/cmd/addmapping.go b/cmd/addmapping.go
index 0547c86..30937d4 100644
--- a/cmd/addmapping.go
+++ b/cmd/addmapping.go
@@ -120,7 +120,7 @@ func (p *AddMappingParams) Validate(ctx ActionCtx) error {
 	if v, ok := (p.claim.Mappings)[p.from]; ok {
 		set := false
 		for i, w := range v {
-			if w.Subject == m.Subject {
+			if w.Subject == m.Subject && w.Cluster == m.Cluster {
 				v[i] = m
 				set = true
 				break
diff --git a/cmd/context_ui_utils.go b/cmd/context_ui_utils.go
index eba7f49..e850e6b 100644
--- a/cmd/context_ui_utils.go
+++ b/cmd/context_ui_utils.go
@@ -2,6 +2,7 @@ package cmd
 
 import (
 	"fmt"
+
 	cli "github.com/nats-io/cliprompts/v2"
 	"github.com/nats-io/nsc/v2/cmd/store"
 )
diff --git a/cmd/deletemapping.go b/cmd/deletemapping.go
index 1609862..6cb7543 100644
--- a/cmd/deletemapping.go
+++ b/cmd/deletemapping.go
@@ -37,6 +37,8 @@ func createDeleteMappingCmd() *cobra.Command {
 	}
 	cmd.Flags().StringVarP((*string)(&params.from), "from", "f", "", "map from subject (required)")
 	cmd.Flags().StringVarP((*string)(&params.to), "to", "t", "", "to subject. When present, only that particular mapping is removed. Otherwise all mappings for from subject are.")
+	cmd.Flags().StringVarP(&params.cluster, "cluster", "", "", "in which cluster this mapping should apply")
+
 	params.AccountContextParams.BindFlags(cmd)
 	return cmd
 }
@@ -48,9 +50,10 @@ func init() {
 type DeleteMappingParams struct {
 	AccountContextParams
 	SignerParams
-	claim *jwt.AccountClaims
-	from  jwt.Subject
-	to    jwt.Subject
+	claim   *jwt.AccountClaims
+	from    jwt.Subject
+	to      jwt.Subject
+	cluster string
 }
 
 func (p *DeleteMappingParams) SetDefaults(ctx ActionCtx) error {
@@ -101,7 +104,7 @@ func (p *DeleteMappingParams) Run(ctx ActionCtx) (store.Status, error) {
 	if p.to != "" {
 		list := p.claim.Mappings[p.from]
 		for i, l := range list {
-			if l.Subject == p.to {
+			if l.Subject == p.to && l.Cluster == p.cluster {
 				if i == 0 {
 					if len(list) == 1 {
 						delete(p.claim.Mappings, p.from)
@@ -126,9 +129,17 @@ func (p *DeleteMappingParams) Run(ctx ActionCtx) (store.Status, error) {
 		return nil, err
 	}
 	if p.to == "" {
-		r.AddOK("deleted all mapping for %s", p.from)
+		if p.cluster != "" {
+			r.AddOK("deleted all mapping in cluster %s for %s", p.cluster, p.from)
+		} else {
+			r.AddOK("deleted all mapping for %s", p.from)
+		}
 	} else {
-		r.AddOK("deleted mapping %s -> %s", p.from, p.to)
+		if p.cluster != "" {
+			r.AddOK("deleted mapping %s -> %s in cluster %s", p.cluster, p.from, p.cluster)
+		} else {
+			r.AddOK("deleted mapping %s -> %s", p.from, p.to)
+		}
 	}
 	rs, err := ctx.StoreCtx().Store.StoreClaim([]byte(token))
 	if rs != nil {
diff --git a/go.mod b/go.mod
index 3549ec6..d12eafa 100644
--- a/go.mod
+++ b/go.mod
@@ -4,22 +4,22 @@ go 1.21
 
 require (
 	github.com/blang/semver v3.5.1+incompatible
-	github.com/briandowns/spinner v1.23.0
+	github.com/briandowns/spinner v1.23.1
 	github.com/dustin/go-humanize v1.0.1
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/nats-io/cliprompts/v2 v2.0.0-20231014115920-801ca035562a
 	github.com/nats-io/jsm.go v0.1.1
-	github.com/nats-io/jwt/v2 v2.5.7
+	github.com/nats-io/jwt/v2 v2.5.8
 	github.com/nats-io/nats-server/v2 v2.10.16
-	github.com/nats-io/nats.go v1.35.0
+	github.com/nats-io/nats.go v1.36.0
 	github.com/nats-io/nkeys v0.4.7
 	github.com/nats-io/nuid v1.0.1
 	github.com/rhysd/go-github-selfupdate v1.2.3
-	github.com/spf13/cobra v1.8.0
+	github.com/spf13/cobra v1.8.1
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.9.0
 	github.com/xlab/tablewriter v0.0.0-20160610135559-80b567a11ad5
-	golang.org/x/text v0.15.0
+	golang.org/x/text v0.16.0
 )
 
 require (
@@ -32,7 +32,7 @@ require (
 	github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
-	github.com/klauspost/compress v1.17.8 // indirect
+	github.com/klauspost/compress v1.17.9 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
@@ -43,11 +43,11 @@ require (
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/tcnksm/go-gitconfig v0.1.2 // indirect
 	github.com/ulikunitz/xz v0.5.12 // indirect
-	golang.org/x/crypto v0.23.0 // indirect
-	golang.org/x/net v0.25.0 // indirect
-	golang.org/x/oauth2 v0.20.0 // indirect
-	golang.org/x/sys v0.20.0 // indirect
-	golang.org/x/term v0.20.0 // indirect
+	golang.org/x/crypto v0.25.0 // indirect
+	golang.org/x/net v0.27.0 // indirect
+	golang.org/x/oauth2 v0.21.0 // indirect
+	golang.org/x/sys v0.22.0 // indirect
+	golang.org/x/term v0.22.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 52bf4ac..7cfd403 100644
--- a/go.sum
+++ b/go.sum
@@ -6,9 +6,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n
 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
-github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A=
-github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE=
-github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650=
+github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
 github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
 github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
@@ -41,8 +40,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
-github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
-github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -70,12 +69,12 @@ github.com/nats-io/cliprompts/v2 v2.0.0-20231014115920-801ca035562a h1:28qvB6peS
 github.com/nats-io/cliprompts/v2 v2.0.0-20231014115920-801ca035562a/go.mod h1:oweZn7AeaVJYKlNHfCIhznJVsdySLSng55vfuINE/d0=
 github.com/nats-io/jsm.go v0.1.1 h1:6vjllz276SdC+3Fb3XI71p9B6toxkCruuB1K6unQEr0=
 github.com/nats-io/jsm.go v0.1.1/go.mod h1:cFz5wR1pW0zLFotntS4HA7V8Wm+sf8zpF+iQJHbsS6M=
-github.com/nats-io/jwt/v2 v2.5.7 h1:j5lH1fUXCnJnY8SsQeB/a/z9Azgu2bYIDvtPVNdxe2c=
-github.com/nats-io/jwt/v2 v2.5.7/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A=
+github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE=
+github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A=
 github.com/nats-io/nats-server/v2 v2.10.16 h1:2jXaiydp5oB/nAx/Ytf9fdCi9QN6ItIc9eehX8kwVV0=
 github.com/nats-io/nats-server/v2 v2.10.16/go.mod h1:Pksi38H2+6xLe1vQx0/EA4bzetM0NqyIHcIbmgXSkIU=
-github.com/nats-io/nats.go v1.35.0 h1:XFNqNM7v5B+MQMKqVGAyHwYhyKb48jrenXNxIU20ULk=
-github.com/nats-io/nats.go v1.35.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
+github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU=
+github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
 github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
 github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
 github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
@@ -90,8 +89,8 @@ github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7
 github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
-github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
+github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
+github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -111,8 +110,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
-golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
+golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -121,12 +120,12 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
-golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
-golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -143,20 +142,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
-golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
+golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
-golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=


Test patch:
diff --git a/cmd/addmapping_test.go b/cmd/addmapping_test.go
new file mode 100644
index 0000000..9742256
--- /dev/null
+++ b/cmd/addmapping_test.go
@@ -0,0 +1,81 @@
+package cmd
+
+import (
+	"testing"
+
+	"github.com/nats-io/jwt/v2"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_AddMappingCannotExceed100(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	ts.AddAccount(t, "A")
+	// add a default mapping at a 100, default mapping at max
+	_, _, err := ExecuteCmd(createAddMappingCmd(), "--from", "q", "--to", "qq", "--weight", "100")
+	require.NoError(t, err)
+
+	// default mapping cannot be incremented
+	_, _, err = ExecuteCmd(createAddMappingCmd(), "--from", "q", "--to", "qqq", "--weight", "10")
+	require.Error(t, err)
+	require.ErrorContains(t, err, "Mapping \"q\" exceeds 100%")
+
+	// the cli didn't add the extra mapping
+	ac, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	mappings := ac.Mappings["q"]
+	require.Len(t, mappings, 1)
+	require.Equal(t, jwt.WeightedMapping{
+		Subject: "qq",
+		Cluster: "",
+		Weight:  100,
+	}, mappings[0])
+
+	// can add another mapping but has to be for cluster
+	_, _, err = ExecuteCmd(createAddMappingCmd(), "--from", "q", "--to", "qa", "--weight", "100", "--cluster", "A")
+	require.NoError(t, err)
+	// and for another cluster
+	_, _, err = ExecuteCmd(createAddMappingCmd(), "--from", "q", "--to", "qb", "--weight", "100", "--cluster", "B")
+	require.NoError(t, err)
+
+	// incrementing one of the above maxed clusters fails
+	_, _, err = ExecuteCmd(createAddMappingCmd(), "--from", "q", "--to", "qaa", "--weight", "10", "--cluster", "A")
+	require.Error(t, err)
+	require.ErrorContains(t, err, "Mapping \"q\" in cluster \"A\" exceeds 100%")
+}
+
+func Test_AddMappingCrud(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	ts.AddAccount(t, "A")
+	// add a default mapping at a 100, default mapping at max
+	_, _, err := ExecuteCmd(createAddMappingCmd(), "--from", "q", "--to", "qq", "--weight", "100")
+	require.NoError(t, err)
+
+	ac, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	m := ac.Mappings["q"]
+	require.NotNil(t, m)
+	require.Equal(t, jwt.WeightedMapping{
+		Subject: "qq",
+		Weight:  100,
+		Cluster: "",
+	}, m[0])
+
+	ts.AddAccount(t, "B")
+	// add a default mapping at a 100, default mapping at max
+	_, _, err = ExecuteCmd(createAddMappingCmd(), "--from", "qq", "--to", "rr", "--weight", "100", "--cluster", "B")
+	require.NoError(t, err)
+
+	ac, err = ts.Store.ReadAccountClaim("B")
+	require.NoError(t, err)
+	m = ac.Mappings["qq"]
+	require.NotNil(t, m)
+	require.Equal(t, jwt.WeightedMapping{
+		Subject: "rr",
+		Weight:  100,
+		Cluster: "B",
+	}, m[0])
+}
diff --git a/cmd/deletemapping_test.go b/cmd/deletemapping_test.go
index 09a4b38..b5ba925 100644
--- a/cmd/deletemapping_test.go
+++ b/cmd/deletemapping_test.go
@@ -18,6 +18,8 @@ package cmd
 import (
 	"testing"
 
+	"github.com/nats-io/jwt/v2"
+
 	"github.com/stretchr/testify/require"
 )
 
@@ -77,3 +79,67 @@ func Test_DeleteMapping(t *testing.T) {
 	require.NotNil(t, ac)
 	require.Len(t, ac.Mappings, 0)
 }
+
+func TestDeleteMappingWithCluster(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	ts.AddAccount(t, "A")
+	// add a default mapping at a 100, default mapping at max
+	_, _, err := ExecuteCmd(createAddMappingCmd(), "--from", "q", "--to", "qq", "--weight", "100")
+	require.NoError(t, err)
+
+	// add a mapping that has the same subject, but using a cluster
+	_, _, err = ExecuteCmd(createAddMappingCmd(), "--from", "q", "--to", "qq", "--weight", "100", "--cluster", "A")
+	require.NoError(t, err)
+
+	// verify we have both mappings
+	ac, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	m := ac.Mappings["q"]
+	require.NotNil(t, m)
+	require.Len(t, m, 2)
+	require.Equal(t, jwt.WeightedMapping{
+		Subject: "qq",
+		Weight:  100,
+		Cluster: "",
+	}, m[0])
+	require.Equal(t, jwt.WeightedMapping{
+		Subject: "qq",
+		Weight:  100,
+		Cluster: "A",
+	}, m[1])
+
+	_, _, err = ExecuteCmd(createDeleteMappingCmd(), "--from", "q", "--to", "qq")
+	require.NoError(t, err)
+
+	// deleted the one without the cluster
+	ac, err = ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	m = ac.Mappings["q"]
+	require.NotNil(t, m)
+	require.Len(t, m, 1)
+	require.Equal(t, jwt.WeightedMapping{
+		Subject: "qq",
+		Weight:  100,
+		Cluster: "A",
+	}, m[0])
+
+	// add the default mapping again
+	_, _, err = ExecuteCmd(createAddMappingCmd(), "--from", "q", "--to", "qq", "--weight", "100")
+	require.NoError(t, err)
+
+	_, _, err = ExecuteCmd(createDeleteMappingCmd(), "--from", "q", "--to", "qq", "--cluster", "A")
+	require.NoError(t, err)
+
+	ac, err = ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	m = ac.Mappings["q"]
+	require.NotNil(t, m)
+	require.Len(t, m, 1)
+	require.Equal(t, jwt.WeightedMapping{
+		Subject: "qq",
+		Weight:  100,
+		Cluster: "",
+	}, m[0])
+}
diff --git a/cmd/editaccount_test.go b/cmd/editaccount_test.go
index 420109f..5bb8464 100644
--- a/cmd/editaccount_test.go
+++ b/cmd/editaccount_test.go
@@ -17,10 +17,11 @@ package cmd
 
 import (
 	"fmt"
-	"github.com/nats-io/jwt/v2"
 	"testing"
 	"time"
 
+	"github.com/nats-io/jwt/v2"
+
 	"github.com/nats-io/nkeys"
 	"github.com/stretchr/testify/require"
 )