package tools_test import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "git.d-ma.be/mathias/gitea-mcp/internal/allowlist" "git.d-ma.be/mathias/gitea-mcp/internal/gitea" "git.d-ma.be/mathias/gitea-mcp/internal/tools" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // #38: `repo` (and `number`) are the canonical, idiomatic gitea/GitHub arg names // that identifier tools advertise. `name` is kept as a back-compat alias via the // bidirectional shim, so old `name` callers still work. `index`->`number` stays. // An explicit canonical (`repo`) always wins over its alias (`name`). func TestRepoAndIndexAliasesResolve(t *testing.T) { tests := []struct { name string args string wantPath string }{ {"canonical repo+number", `{"owner":"mathias","repo":"infra","number":7}`, "/api/v1/repos/mathias/infra/issues/7"}, {"repo+index alias", `{"owner":"mathias","repo":"infra","index":7}`, "/api/v1/repos/mathias/infra/issues/7"}, {"legacy name alias", `{"owner":"mathias","name":"infra","number":7}`, "/api/v1/repos/mathias/infra/issues/7"}, {"explicit repo wins over name", `{"owner":"mathias","name":"ignored","repo":"infra","number":7}`, "/api/v1/repos/mathias/infra/issues/7"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotPath string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"number":7,"title":"t"}`)) })) defer srv.Close() tool := tools.NewIssueGet(gitea.NewClient(srv.URL, "tok"), allowlist.New([]string{"mathias"})) out, err := tool.Call(context.Background(), json.RawMessage(tc.args)) require.NoError(t, err) assert.Equal(t, tc.wantPath, gotPath) assert.Contains(t, string(out), `"number":7`) }) } } // #38: identifier tools must ADVERTISE `repo` (not `name`) in their schema, so // the contract a client reads matches what every caller sends. The two create // tools keep `name` because there it means "name of the new repo", not an // existing-repo identifier. func TestRepoIsCanonicalInAdvertisedSchema(t *testing.T) { c := gitea.NewClient("http://unused", "") a := allowlist.New([]string{"mathias"}) identifier := map[string]json.RawMessage{ "repo_get": tools.NewRepoGet(c, a).Descriptor().InputSchema, "issue_get": tools.NewIssueGet(c, a).Descriptor().InputSchema, "pr_merge": tools.NewPRMerge(c, a).Descriptor().InputSchema, "repo_delete": tools.NewRepoDelete(c, a).Descriptor().InputSchema, "file_read": tools.NewFileRead(c, a).Descriptor().InputSchema, } for name, sch := range identifier { s := string(sch) assert.Contains(t, s, `"repo":`, name+" must advertise repo") assert.NotContains(t, s, `"name":`, name+" must not advertise name") } // create tools keep `name` (new resource name, not an existing-repo id) assert.Contains(t, string(tools.NewRepoCreate(c, a).Descriptor().InputSchema), `"name":`) }