Files
wehub-resource-sync e04ed9c211
CF: Deploy Dev Docs / deploy (push) Has been cancelled
Sync Labels / build (push) Has been cancelled
tests / unit tests (macos-latest) (push) Has been cancelled
tests / unit tests (windows-latest) (push) Has been cancelled
tests / unit tests (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:32:45 +08:00

173 lines
5.6 KiB
Go

// Copyright 2026 Google LLC
//
// 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
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tools_test
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/mcp-toolbox/internal/embeddingmodels"
"github.com/googleapis/mcp-toolbox/internal/tools"
"github.com/googleapis/mcp-toolbox/internal/util"
"github.com/googleapis/mcp-toolbox/internal/util/parameters"
)
// stubConfig and stubTool exercise the path of embedding BaseTool with only
// the extra methods (Invoke, ToConfig) needed to satisfy the Tool interface.
// Real tools embed ConfigBase in their Config; the stub does too so it
// satisfies ToolMeta if/when wired into BaseTool.
type stubConfig struct {
tools.ConfigBase
}
func (stubConfig) ToolConfigType() string { return "stub" }
func (stubConfig) Initialize(context.Context) (tools.Tool, error) {
return nil, nil
}
type stubTool struct {
tools.BaseTool[stubConfig]
}
func (stubTool) Invoke(_ context.Context, _ tools.SourceProvider, _ parameters.ParamValues, _ tools.AccessToken) (any, util.ToolboxError) {
return nil, nil
}
func (stubTool) ToConfig() tools.ToolConfig { return stubConfig{} }
// Compile-time check: embedding BaseTool plus Invoke + ToConfig satisfies Tool.
var _ tools.Tool = stubTool{}
// Compile-time check: ConfigBase satisfies ToolMeta on its own.
var _ tools.ToolMeta = tools.ConfigBase{}
func newBaseTool() (tools.BaseTool[tools.ConfigBase], tools.Manifest) {
cfg := tools.ConfigBase{
Name: "my-tool",
Description: "my tool description",
AuthRequired: []string{"google"},
ScopesRequired: []string{"scope-a", "scope-b"},
}
manifest := tools.Manifest{
Description: "manifest description",
AuthRequired: []string{"google"},
}
b := tools.NewBaseTool(
cfg,
tools.NewReadOnlyAnnotations(),
manifest,
parameters.Parameters{parameters.NewStringParameter("p1", "first param")},
)
return b, manifest
}
func TestBaseToolGetters(t *testing.T) {
b, wantManifest := newBaseTool()
if got, want := b.GetName(), "my-tool"; got != want {
t.Errorf("GetName() = %q, want %q", got, want)
}
if got, want := b.GetDescription(), "my tool description"; got != want {
t.Errorf("GetDescription() = %q, want %q", got, want)
}
if diff := cmp.Diff([]string{"google"}, b.GetAuthRequired()); diff != "" {
t.Errorf("GetAuthRequired() mismatch (-want +got):\n%s", diff)
}
got := b.GetAnnotations()
if got == nil || got.ReadOnlyHint == nil || !*got.ReadOnlyHint {
t.Errorf("GetAnnotations() = %+v, want ReadOnlyHint=true", got)
}
gotManifest, err := b.Manifest(nil)
if err != nil {
t.Fatalf("Manifest() error = %v", err)
}
if diff := cmp.Diff(wantManifest, gotManifest); diff != "" {
t.Errorf("Manifest() mismatch (-want +got):\n%s", diff)
}
p, err := b.GetParameters(nil)
if err != nil {
t.Fatalf("GetParameters() error = %v", err)
}
if len(p) != 1 || p[0].GetName() != "p1" {
t.Errorf("GetParameters() = %+v, want one param named p1", p)
}
if diff := cmp.Diff([]string{"scope-a", "scope-b"}, b.GetScopesRequired()); diff != "" {
t.Errorf("GetScopesRequired() mismatch (-want +got):\n%s", diff)
}
}
func TestBaseToolAuthorized(t *testing.T) {
tcs := []struct {
desc string
authRequired []string
verified []string
want bool
}{
{"empty required is always authorized", nil, nil, true},
{"empty required ignores verified", nil, []string{"foo"}, true},
{"verified includes required", []string{"google"}, []string{"google", "github"}, true},
{"verified missing required", []string{"google"}, []string{"github"}, false},
{"verified empty when required non-empty", []string{"google"}, nil, false},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
b := tools.NewBaseTool(tools.ConfigBase{AuthRequired: tc.authRequired}, nil, tools.Manifest{}, nil)
if got := b.Authorized(tc.verified); got != tc.want {
t.Errorf("Authorized(%v) = %v, want %v", tc.verified, got, tc.want)
}
})
}
}
func TestBaseToolRequiresClientAuthorization(t *testing.T) {
b := tools.NewBaseTool(tools.ConfigBase{}, nil, tools.Manifest{}, nil)
got, err := b.RequiresClientAuthorization(nil)
if err != nil {
t.Fatalf("RequiresClientAuthorization() error = %v", err)
}
if got {
t.Errorf("RequiresClientAuthorization() = true, want false")
}
}
func TestBaseToolGetAuthTokenHeaderName(t *testing.T) {
b := tools.NewBaseTool(tools.ConfigBase{}, nil, tools.Manifest{}, nil)
got, err := b.GetAuthTokenHeaderName(nil)
if err != nil {
t.Fatalf("GetAuthTokenHeaderName() error = %v", err)
}
if got != "Authorization" {
t.Errorf("GetAuthTokenHeaderName() = %q, want %q", got, "Authorization")
}
}
func TestBaseToolEmbedParamsPassthrough(t *testing.T) {
b := tools.NewBaseTool(
tools.ConfigBase{},
nil,
tools.Manifest{},
parameters.Parameters{parameters.NewStringParameter("p1", "first")},
)
values := parameters.ParamValues{{Name: "p1", Value: "hello"}}
got, err := b.EmbedParams(context.Background(), values, map[string]embeddingmodels.EmbeddingModel{})
if err != nil {
t.Fatalf("EmbedParams() error = %v", err)
}
if diff := cmp.Diff(values, got); diff != "" {
t.Errorf("EmbedParams() mismatch (-want +got):\n%s", diff)
}
}