package localtools_test import ( "context" "encoding/json" "testing" "git.d-ma.be/mathias/gitea-mcp/internal/adminbootstrap" "git.d-ma.be/mathias/gitea-mcp/internal/localtools" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // This tool lives in its own package, never internal/tools, on purpose: it // resets whatever password its target account has, which is far too // dangerous a capability to risk it ever being wired into RegisterAll and // exposed on the shared HTTP gitea-mcp server. Only cmd/gitea-admin-mcp (a // separate, local-only, stdio binary) imports this package. type fakeBootstrapper struct { gotReq adminbootstrap.Request result adminbootstrap.Result err error } func (f *fakeBootstrapper) Run(_ context.Context, req adminbootstrap.Request) (adminbootstrap.Result, error) { f.gotReq = req return f.result, f.err } func TestMintServiceAccountToken_ParsesArgsAndCallsBootstrapper(t *testing.T) { fb := &fakeBootstrapper{result: adminbootstrap.Result{ Username: "dispatch-bot", Created: true, SecretRef: "op://AgentSecrets/gitea-dispatch-bot-ci-readonly/token", }} tool := localtools.NewMintServiceAccountToken(fb) args, _ := json.Marshal(map[string]any{ "username": "dispatch-bot", "email": "dispatch@d-ma.be", "scopes": []string{"read:repository", "read:issue"}, "token_name": "dispatch-ci-readonly", "secret_name": "gitea-dispatch-bot-ci-readonly", "grant_read_repos": []string{"mathias/cobalt-dingo"}, "confirm_service_account": true, }) out, err := tool.Call(context.Background(), args) require.NoError(t, err) assert.Equal(t, "dispatch-bot", fb.gotReq.Username) assert.Equal(t, []string{"read:repository", "read:issue"}, fb.gotReq.Scopes) assert.Equal(t, true, fb.gotReq.ConfirmServiceAccount) require.Equal(t, []adminbootstrap.RepoRef{{Owner: "mathias", Name: "cobalt-dingo"}}, fb.gotReq.GrantReadRepos) var result map[string]any require.NoError(t, json.Unmarshal(out, &result)) assert.Equal(t, "op://AgentSecrets/gitea-dispatch-bot-ci-readonly/token", result["SecretRef"]) // The tool's own output must never carry a raw token or password field -- // adminbootstrap.Result structurally can't (it has none), which this pins. _, hasToken := result["Token"] _, hasPassword := result["Password"] assert.False(t, hasToken) assert.False(t, hasPassword) } func TestMintServiceAccountToken_RejectsMalformedRepoRef(t *testing.T) { fb := &fakeBootstrapper{} tool := localtools.NewMintServiceAccountToken(fb) args, _ := json.Marshal(map[string]any{ "username": "dispatch-bot", "email": "dispatch@d-ma.be", "scopes": []string{"read:repository"}, "token_name": "x", "secret_name": "x", "grant_read_repos": []string{"not-owner-slash-repo"}, "confirm_service_account": true, }) _, err := tool.Call(context.Background(), args) require.Error(t, err) assert.Contains(t, err.Error(), "owner/repo") } func TestMintServiceAccountToken_PropagatesBootstrapperError(t *testing.T) { fb := &fakeBootstrapper{err: assert.AnError} tool := localtools.NewMintServiceAccountToken(fb) args, _ := json.Marshal(map[string]any{ "username": "dispatch-bot", "email": "dispatch@d-ma.be", "scopes": []string{"read:repository"}, "token_name": "x", "secret_name": "x", "confirm_service_account": true, }) _, err := tool.Call(context.Background(), args) require.Error(t, err) } func TestMintServiceAccountToken_Descriptor(t *testing.T) { tool := localtools.NewMintServiceAccountToken(&fakeBootstrapper{}) d := tool.Descriptor() assert.Equal(t, "mint_service_account_token", d.Name) assert.NotEmpty(t, d.Description) }