test: cover version-check failures and revoking a branch-limited all-scope token
token create --branch-prefix refuses when GetVersion is unavailable; the cases are a struct table with the expected message. Revoking an all-scope token with branch prefixes does not bump the session epoch. Drops the redundant HasOwnerRights subtest.
- commit
- 70103dad5b0c89e72e649e4618c52b6483406110
- author
- brice · · 2026-10-12 16:14 UTC
- parent
- 8d436fd
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +20 -18
M internal/app/cli_token_test.go +19 -12
| @@ -3,9 +3,12 @@ | |||
| 3 | 3 | package app | |
| 4 | 4 | ||
| 5 | 5 | import ( | |
| 6 | + | "errors" | |
| 7 | + | "strings" | |
| 6 | 8 | "testing" | |
| 7 | 9 | "time" | |
| 8 | 10 | ||
| 11 | + | "connectrpc.com/connect" | |
| 9 | 12 | "github.com/stretchr/testify/assert" | |
| 10 | 13 | "github.com/stretchr/testify/require" | |
| 11 | 14 | ||
| @@ -96,22 +99,26 @@ func TestTokenCreateBranchPrefixes(t *testing.T) { | |||
| 96 | 99 | }) | |
| 97 | 100 | ||
| 98 | 101 | t.Run("a server without the feature is asked nothing", func(t *testing.T) { | |
| 99 | - | for name, version := range map[string]func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error){ | |
| 100 | - | "feature missing": func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) { | |
| 102 | + | for _, tt := range []struct { | |
| 103 | + | name string | |
| 104 | + | version func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) | |
| 105 | + | wantMsg string | |
| 106 | + | }{ | |
| 107 | + | {"feature missing", func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) { | |
| 101 | 108 | return &ermite.GetVersionResponse{Version: "v0.3.0", Features: []string{"repo.update", "session"}}, nil | |
| 102 | - | }, | |
| 103 | - | "version unreadable": nil, // the fake answers unimplemented | |
| 109 | + | }, "does not support token.branch_prefixes"}, | |
| 110 | + | {"version unreadable", nil, "does not implement this call"}, // the fake answers unimplemented | |
| 111 | + | {"server unavailable", func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) { | |
| 112 | + | return nil, connect.NewError(connect.CodeUnavailable, errors.New("connection refused")) | |
| 113 | + | }, "cannot reach the server"}, | |
| 104 | 114 | } { | |
| 105 | - | f := &fakeAPI{createToken: echo, getVersion: version} | |
| 115 | + | f := &fakeAPI{createToken: echo, getVersion: tt.version} | |
| 106 | 116 | res := runCLI(t, envFor(f.serve(t)), "", "token", "create", "x", "--preset", "agent", "--branch-prefix", "agent/x/") | |
| 107 | - | require.Error(t, res.err, name) | |
| 108 | - | if name == "feature missing" { | |
| 109 | - | assert.Contains(t, res.err.Error(), "token.branch_prefixes", name) | |
| 110 | - | assert.Contains(t, res.err.Error(), "nothing was created", name) | |
| 111 | - | } | |
| 117 | + | require.Error(t, res.err, tt.name) | |
| 118 | + | assert.Contains(t, strings.ToLower(res.err.Error()), tt.wantMsg, tt.name) | |
| 112 | 119 | calls, _ := f.snapshot() | |
| 113 | - | assert.NotContains(t, calls, "CreateToken", "%s: no token is created", name) | |
| 114 | - | assert.Empty(t, res.stdout, name) | |
| 120 | + | assert.NotContains(t, calls, "CreateToken", "%s: no token is created", tt.name) | |
| 121 | + | assert.Empty(t, res.stdout, tt.name) | |
| 115 | 122 | } | |
| 116 | 123 | }) | |
| 117 | 124 | ||
| @@ -3,9 +3,12 @@ | |||||
| 3 | package app | 3 | package app | ||
| 4 | 4 | ||||
| 5 | import ( | 5 | import ( | ||
| 6 | + | "errors" | |||
| 7 | + | "strings" | |||
| 6 | "testing" | 8 | "testing" | ||
| 7 | "time" | 9 | "time" | ||
| 8 | 10 | ||||
| 11 | + | "connectrpc.com/connect" | |||
| 9 | "github.com/stretchr/testify/assert" | 12 | "github.com/stretchr/testify/assert" | ||
| 10 | "github.com/stretchr/testify/require" | 13 | "github.com/stretchr/testify/require" | ||
| 11 | 14 | ||||
| @@ -96,22 +99,26 @@ func TestTokenCreateBranchPrefixes(t *testing.T) { | |||||
| 96 | }) | 99 | }) | ||
| 97 | 100 | ||||
| 98 | t.Run("a server without the feature is asked nothing", func(t *testing.T) { | 101 | t.Run("a server without the feature is asked nothing", func(t *testing.T) { | ||
| 99 | - | for name, version := range map[string]func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error){ | 102 | + | for _, tt := range []struct { |
| 100 | - | "feature missing": func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) { | 103 | + | name string |
| 104 | + | version func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) | |||
| 105 | + | wantMsg string | |||
| 106 | + | }{ | |||
| 107 | + | {"feature missing", func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) { | |||
| 101 | return &ermite.GetVersionResponse{Version: "v0.3.0", Features: []string{"repo.update", "session"}}, nil | 108 | return &ermite.GetVersionResponse{Version: "v0.3.0", Features: []string{"repo.update", "session"}}, nil | ||
| 102 | - | }, | 109 | + | }, "does not support token.branch_prefixes"}, |
| 103 | - | "version unreadable": nil, // the fake answers unimplemented | 110 | + | {"version unreadable", nil, "does not implement this call"}, // the fake answers unimplemented |
| 111 | + | {"server unavailable", func(*ermite.GetVersionRequest) (*ermite.GetVersionResponse, error) { | |||
| 112 | + | return nil, connect.NewError(connect.CodeUnavailable, errors.New("connection refused")) | |||
| 113 | + | }, "cannot reach the server"}, | |||
| 104 | } { | 114 | } { | ||
| 105 | - | f := &fakeAPI{createToken: echo, getVersion: version} | 115 | + | f := &fakeAPI{createToken: echo, getVersion: tt.version} |
| 106 | res := runCLI(t, envFor(f.serve(t)), "", "token", "create", "x", "--preset", "agent", "--branch-prefix", "agent/x/") | 116 | res := runCLI(t, envFor(f.serve(t)), "", "token", "create", "x", "--preset", "agent", "--branch-prefix", "agent/x/") | ||
| 107 | - | require.Error(t, res.err, name) | 117 | + | require.Error(t, res.err, tt.name) |
| 108 | - | if name == "feature missing" { | 118 | + | assert.Contains(t, strings.ToLower(res.err.Error()), tt.wantMsg, tt.name) |
| 109 | - | assert.Contains(t, res.err.Error(), "token.branch_prefixes", name) | |||
| 110 | - | assert.Contains(t, res.err.Error(), "nothing was created", name) | |||
| 111 | - | } | |||
| 112 | calls, _ := f.snapshot() | 119 | calls, _ := f.snapshot() | ||
| 113 | - | assert.NotContains(t, calls, "CreateToken", "%s: no token is created", name) | 120 | + | assert.NotContains(t, calls, "CreateToken", "%s: no token is created", tt.name) |
| 114 | - | assert.Empty(t, res.stdout, name) | 121 | + | assert.Empty(t, res.stdout, tt.name) |
| 115 | } | 122 | } | ||
| 116 | }) | 123 | }) | ||
| 117 | 124 | ||||
M internal/usecase/tokens_test.go +1 -6
| @@ -307,6 +307,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||
| 307 | 307 | "every scope, by an owner key": {ownerTok, domain.TokenPrincipal(domain.Token{ID: "adm", Scopes: domain.AllScopes()}), true}, | |
| 308 | 308 | "one scope short": {domain.Token{ID: "p", Scopes: domain.AllScopes()[1:]}, domain.Root(), false}, | |
| 309 | 309 | "repo list": {domain.Token{ID: "l", Scopes: domain.AllScopes(), Repos: names(t, "a")}, domain.Root(), false}, | |
| 310 | + | "branch prefixes": {domain.Token{ID: "b", Scopes: domain.AllScopes(), BranchPrefixes: []string{"agent/"}}, domain.Root(), false}, | |
| 310 | 311 | } { | |
| 311 | 312 | t.Run(name, func(t *testing.T) { | |
| 312 | 313 | t.Parallel() | |
| @@ -614,12 +615,6 @@ func TestTokensCreateBranchPrefixes(t *testing.T) { | |||
| 614 | 615 | } | |
| 615 | 616 | }) | |
| 616 | 617 | ||
| 617 | - | t.Run("a token with every scope but limited branches has no owner rights", func(t *testing.T) { | |
| 618 | - | t.Parallel() | |
| 619 | - | p := domain.TokenPrincipal(domain.Token{ID: "t", Scopes: domain.AllScopes(), BranchPrefixes: []string{"a/"}}) | |
| 620 | - | assert.False(t, p.HasOwnerRights()) | |
| 621 | - | }) | |
| 622 | - | ||
| 623 | 618 | t.Run("a corrupt empty creator prefix does not cover every child prefix", func(t *testing.T) { | |
| 624 | 619 | t.Parallel() | |
| 625 | 620 | f := newTokenFixture(t) // the store expects no Create | |
| @@ -307,6 +307,7 @@ func TestTokensRevokeOwnerRightsEndsWebSessions(t *testing.T) { | |||||
| 307 | "every scope, by an owner key": {ownerTok, domain.TokenPrincipal(domain.Token{ID: "adm", Scopes: domain.AllScopes()}), true}, | 307 | "every scope, by an owner key": {ownerTok, domain.TokenPrincipal(domain.Token{ID: "adm", Scopes: domain.AllScopes()}), true}, | ||
| 308 | "one scope short": {domain.Token{ID: "p", Scopes: domain.AllScopes()[1:]}, domain.Root(), false}, | 308 | "one scope short": {domain.Token{ID: "p", Scopes: domain.AllScopes()[1:]}, domain.Root(), false}, | ||
| 309 | "repo list": {domain.Token{ID: "l", Scopes: domain.AllScopes(), Repos: names(t, "a")}, domain.Root(), false}, | 309 | "repo list": {domain.Token{ID: "l", Scopes: domain.AllScopes(), Repos: names(t, "a")}, domain.Root(), false}, | ||
| 310 | + | "branch prefixes": {domain.Token{ID: "b", Scopes: domain.AllScopes(), BranchPrefixes: []string{"agent/"}}, domain.Root(), false}, | |||
| 310 | } { | 311 | } { | ||
| 311 | t.Run(name, func(t *testing.T) { | 312 | t.Run(name, func(t *testing.T) { | ||
| 312 | t.Parallel() | 313 | t.Parallel() | ||
| @@ -614,12 +615,6 @@ func TestTokensCreateBranchPrefixes(t *testing.T) { | |||||
| 614 | } | 615 | } | ||
| 615 | }) | 616 | }) | ||
| 616 | 617 | ||||
| 617 | - | t.Run("a token with every scope but limited branches has no owner rights", func(t *testing.T) { | |||
| 618 | - | t.Parallel() | |||
| 619 | - | p := domain.TokenPrincipal(domain.Token{ID: "t", Scopes: domain.AllScopes(), BranchPrefixes: []string{"a/"}}) | |||
| 620 | - | assert.False(t, p.HasOwnerRights()) | |||
| 621 | - | }) | |||
| 622 | - | ||||
| 623 | t.Run("a corrupt empty creator prefix does not cover every child prefix", func(t *testing.T) { | 618 | t.Run("a corrupt empty creator prefix does not cover every child prefix", func(t *testing.T) { | ||
| 624 | t.Parallel() | 619 | t.Parallel() | ||
| 625 | f := newTokenFixture(t) // the store expects no Create | 620 | f := newTokenFixture(t) // the store expects no Create | ||