mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-30 09:48:27 -05:00
Compare commits
2 Commits
processing
...
get-resour
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd716a725a | ||
|
|
9bc91a6cd8 |
74
internal/server/admin.go
Normal file
74
internal/server/admin.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2025 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 server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// adminRouter creates a router that represents the routes under /admin
|
||||||
|
func adminRouter(s *Server) (chi.Router, error) {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
|
||||||
|
r.Use(middleware.AllowContentType("application/json"))
|
||||||
|
r.Use(middleware.StripSlashes)
|
||||||
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||||
|
|
||||||
|
r.Get("/{resource}", func(w http.ResponseWriter, r *http.Request) { adminGetHandler(s, w, r) })
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// adminGetHandler handles requests for a list of specific resource
|
||||||
|
func adminGetHandler(s *Server, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
resource := chi.URLParam(r, "resource")
|
||||||
|
|
||||||
|
var resourceList []string
|
||||||
|
switch resource {
|
||||||
|
case "source":
|
||||||
|
sourcesMap := s.ResourceMgr.GetSourcesMap()
|
||||||
|
for n := range sourcesMap {
|
||||||
|
resourceList = append(resourceList, n)
|
||||||
|
}
|
||||||
|
case "authservice":
|
||||||
|
authServicesMap := s.ResourceMgr.GetAuthServiceMap()
|
||||||
|
for n := range authServicesMap {
|
||||||
|
resourceList = append(resourceList, n)
|
||||||
|
}
|
||||||
|
case "tool":
|
||||||
|
toolsMap := s.ResourceMgr.GetToolsMap()
|
||||||
|
for n := range toolsMap {
|
||||||
|
resourceList = append(resourceList, n)
|
||||||
|
}
|
||||||
|
case "toolset":
|
||||||
|
toolsetsMap := s.ResourceMgr.GetToolsetsMap()
|
||||||
|
for n := range toolsetsMap {
|
||||||
|
resourceList = append(resourceList, n)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf(`invalid resource %s, please provide one of "source", "authservice", "tool", or "toolset"`, resource)
|
||||||
|
s.logger.DebugContext(ctx, err.Error())
|
||||||
|
_ = render.Render(w, r, newErrResponse(err, http.StatusNotFound))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.JSON(w, r, resourceList)
|
||||||
|
}
|
||||||
222
internal/server/admin_test.go
Normal file
222
internal/server/admin_test.go
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
// Copyright 2025 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 server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"encoding/json"
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/auth"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ sources.Source = &MockSource{}
|
||||||
|
var _ sources.SourceConfig = &MockSourceConfig{}
|
||||||
|
|
||||||
|
// MockSource is used to mock sources in tests
|
||||||
|
type MockSource struct {
|
||||||
|
name string
|
||||||
|
kind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s MockSource) SourceKind() string {
|
||||||
|
return s.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockSourceConfig struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Kind string `yaml:"kind"`
|
||||||
|
Project string `yaml:"project"`
|
||||||
|
User string `yaml:"user"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
Database string `yaml:"database"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc MockSourceConfig) SourceConfigKind() string {
|
||||||
|
return "mock-source"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc MockSourceConfig) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
|
||||||
|
s := MockSource{
|
||||||
|
name: sc.Name,
|
||||||
|
kind: sc.Kind,
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceConfig1 = MockSourceConfig{
|
||||||
|
Name: "source1",
|
||||||
|
Kind: "mock-source",
|
||||||
|
Project: "my-project",
|
||||||
|
User: "my-user",
|
||||||
|
Password: "my-password",
|
||||||
|
Database: "my-db",
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockAuthService struct {
|
||||||
|
name string
|
||||||
|
kind string
|
||||||
|
clientID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as MockAuthService) AuthServiceKind() string {
|
||||||
|
return as.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as MockAuthService) GetName() string {
|
||||||
|
return as.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as MockAuthService) GetClaimsFromHeader(context.Context, http.Header) (map[string]any, error) {
|
||||||
|
return map[string]any{"foo": "bar"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockASConfig struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Kind string `yaml:"kind"`
|
||||||
|
ClientID string `yaml:"clientId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac MockASConfig) AuthServiceConfigKind() string {
|
||||||
|
return "mock-auth-service"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac MockASConfig) Initialize() (auth.AuthService, error) {
|
||||||
|
a := MockAuthService{
|
||||||
|
name: ac.Name,
|
||||||
|
kind: ac.Kind,
|
||||||
|
clientID: ac.ClientID,
|
||||||
|
}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var authService1 = MockASConfig{
|
||||||
|
Name: "auth-service1",
|
||||||
|
Kind: "mock-auth-service",
|
||||||
|
ClientID: "foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminGetResourceEndpoint(t *testing.T) {
|
||||||
|
source1, _ := sourceConfig1.Initialize(context.Background(), nil)
|
||||||
|
mockSources := []MockSource{source1.(MockSource)}
|
||||||
|
as1, _ := authService1.Initialize()
|
||||||
|
mockAuthServices := []MockAuthService{as1.(MockAuthService)}
|
||||||
|
mockTools := []MockTool{tool1, tool2}
|
||||||
|
sourcesMap, authServicesMap, toolsMap, toolsets := setUpResources(t, mockSources, mockAuthServices, mockTools)
|
||||||
|
r, shutdown := setUpServer(t, "admin", sourcesMap, authServicesMap, toolsMap, toolsets)
|
||||||
|
defer shutdown()
|
||||||
|
ts := runServer(r, false)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
// wantResponse is a struct for checks against test cases
|
||||||
|
type wantResponse struct {
|
||||||
|
statusCode int
|
||||||
|
isErr bool
|
||||||
|
errString string
|
||||||
|
resourcesList []string
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
want wantResponse
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "get source",
|
||||||
|
url: "/source",
|
||||||
|
want: wantResponse{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
resourcesList: []string{"source1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get auth services",
|
||||||
|
url: "/authservice",
|
||||||
|
want: wantResponse{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
resourcesList: []string{"auth-service1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get tool",
|
||||||
|
url: "/tool",
|
||||||
|
want: wantResponse{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
resourcesList: []string{"no_params", "some_params"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get toolset",
|
||||||
|
url: "/toolset",
|
||||||
|
want: wantResponse{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
resourcesList: []string{"", "tool1_only", "tool2_only"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get invalid",
|
||||||
|
url: "/invalid",
|
||||||
|
want: wantResponse{
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
isErr: true,
|
||||||
|
errString: `invalid resource invalid, please provide one of "source", "authservice", "tool", or "toolset"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
resp, body, err := runRequest(ts, http.MethodGet, tc.url, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error during request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentType := resp.Header.Get("Content-type"); contentType != "application/json" {
|
||||||
|
t.Fatalf("unexpected content-type header: want %s, got %s", "application/json", contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.want.statusCode != resp.StatusCode {
|
||||||
|
t.Fatalf("unexpected status code: want %d, got %d", tc.want.statusCode, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.want.isErr {
|
||||||
|
var res errResponse
|
||||||
|
err = json.Unmarshal(body, &res)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error unmarshaling body: %s", err)
|
||||||
|
}
|
||||||
|
if tc.want.errString != res.ErrorText {
|
||||||
|
t.Fatalf("unexpected error message: want %s, got %s", tc.want.errString, res.ErrorText)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []string
|
||||||
|
err = json.Unmarshal(body, &res)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error unmarshaling body: %s", err)
|
||||||
|
}
|
||||||
|
slices.Sort(res)
|
||||||
|
if !reflect.DeepEqual(tc.want.resourcesList, res) {
|
||||||
|
t.Fatalf("unexpected response: want %+v, got %+v", tc.want.resourcesList, res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -297,29 +297,3 @@ func (rr resultResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ render.Renderer = &errResponse{} // Renderer interface for managing response payloads.
|
|
||||||
|
|
||||||
// newErrResponse is a helper function initializing an ErrResponse
|
|
||||||
func newErrResponse(err error, code int) *errResponse {
|
|
||||||
return &errResponse{
|
|
||||||
Err: err,
|
|
||||||
HTTPStatusCode: code,
|
|
||||||
|
|
||||||
StatusText: http.StatusText(code),
|
|
||||||
ErrorText: err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// errResponse is the response sent back when an error has been encountered.
|
|
||||||
type errResponse struct {
|
|
||||||
Err error `json:"-"` // low-level runtime error
|
|
||||||
HTTPStatusCode int `json:"-"` // http response status code
|
|
||||||
|
|
||||||
StatusText string `json:"status"` // user-level status message
|
|
||||||
ErrorText string `json:"error,omitempty"` // application-level error message, for debugging
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
render.Status(r, e.HTTPStatusCode)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ import (
|
|||||||
|
|
||||||
func TestToolsetEndpoint(t *testing.T) {
|
func TestToolsetEndpoint(t *testing.T) {
|
||||||
mockTools := []MockTool{tool1, tool2}
|
mockTools := []MockTool{tool1, tool2}
|
||||||
toolsMap, toolsets := setUpResources(t, mockTools)
|
sourcesMap, asMap, toolsMap, toolsets := setUpResources(t, nil, nil, mockTools)
|
||||||
r, shutdown := setUpServer(t, "api", toolsMap, toolsets)
|
r, shutdown := setUpServer(t, "api", sourcesMap, asMap, toolsMap, toolsets)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
ts := runServer(r, false)
|
ts := runServer(r, false)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
@@ -125,8 +125,8 @@ func TestToolsetEndpoint(t *testing.T) {
|
|||||||
|
|
||||||
func TestToolGetEndpoint(t *testing.T) {
|
func TestToolGetEndpoint(t *testing.T) {
|
||||||
mockTools := []MockTool{tool1, tool2}
|
mockTools := []MockTool{tool1, tool2}
|
||||||
toolsMap, toolsets := setUpResources(t, mockTools)
|
sourcesMap, asMap, toolsMap, toolsets := setUpResources(t, nil, nil, mockTools)
|
||||||
r, shutdown := setUpServer(t, "api", toolsMap, toolsets)
|
r, shutdown := setUpServer(t, "api", sourcesMap, asMap, toolsMap, toolsets)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
ts := runServer(r, false)
|
ts := runServer(r, false)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
@@ -213,8 +213,8 @@ func TestToolGetEndpoint(t *testing.T) {
|
|||||||
|
|
||||||
func TestToolInvokeEndpoint(t *testing.T) {
|
func TestToolInvokeEndpoint(t *testing.T) {
|
||||||
mockTools := []MockTool{tool1, tool2, tool4, tool5}
|
mockTools := []MockTool{tool1, tool2, tool4, tool5}
|
||||||
toolsMap, toolsets := setUpResources(t, mockTools)
|
sourcesMap, asMap, toolsMap, toolsets := setUpResources(t, nil, nil, mockTools)
|
||||||
r, shutdown := setUpServer(t, "api", toolsMap, toolsets)
|
r, shutdown := setUpServer(t, "api", sourcesMap, asMap, toolsMap, toolsets)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
ts := runServer(r, false)
|
ts := runServer(r, false)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/auth"
|
||||||
"github.com/googleapis/genai-toolbox/internal/log"
|
"github.com/googleapis/genai-toolbox/internal/log"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/telemetry"
|
"github.com/googleapis/genai-toolbox/internal/telemetry"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
)
|
)
|
||||||
@@ -129,7 +131,17 @@ var tool5 = MockTool{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setUpResources setups resources to test against
|
// setUpResources setups resources to test against
|
||||||
func setUpResources(t *testing.T, mockTools []MockTool) (map[string]tools.Tool, map[string]tools.Toolset) {
|
func setUpResources(t *testing.T, mockSources []MockSource, mockAuthServices []MockAuthService, mockTools []MockTool) (map[string]sources.Source, map[string]auth.AuthService, map[string]tools.Tool, map[string]tools.Toolset) {
|
||||||
|
sourcesMap := make(map[string]sources.Source)
|
||||||
|
for _, s := range mockSources {
|
||||||
|
sourcesMap[s.name] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
authServicesMap := make(map[string]auth.AuthService)
|
||||||
|
for _, a := range mockAuthServices {
|
||||||
|
authServicesMap[a.name] = a
|
||||||
|
}
|
||||||
|
|
||||||
toolsMap := make(map[string]tools.Tool)
|
toolsMap := make(map[string]tools.Tool)
|
||||||
var allTools []string
|
var allTools []string
|
||||||
for _, tool := range mockTools {
|
for _, tool := range mockTools {
|
||||||
@@ -151,11 +163,11 @@ func setUpResources(t *testing.T, mockTools []MockTool) (map[string]tools.Tool,
|
|||||||
}
|
}
|
||||||
toolsets[name] = m
|
toolsets[name] = m
|
||||||
}
|
}
|
||||||
return toolsMap, toolsets
|
return sourcesMap, authServicesMap, toolsMap, toolsets
|
||||||
}
|
}
|
||||||
|
|
||||||
// setUpServer create a new server with tools and toolsets that are given
|
// setUpServer create a new server with tools and toolsets that are given
|
||||||
func setUpServer(t *testing.T, router string, tools map[string]tools.Tool, toolsets map[string]tools.Toolset) (chi.Router, func()) {
|
func setUpServer(t *testing.T, router string, sources map[string]sources.Source, authServices map[string]auth.AuthService, tools map[string]tools.Tool, toolsets map[string]tools.Toolset) (chi.Router, func()) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
testLogger, err := log.NewStdLogger(os.Stdout, os.Stderr, "info")
|
testLogger, err := log.NewStdLogger(os.Stdout, os.Stderr, "info")
|
||||||
@@ -175,7 +187,7 @@ func setUpServer(t *testing.T, router string, tools map[string]tools.Tool, tools
|
|||||||
|
|
||||||
sseManager := newSseManager(ctx)
|
sseManager := newSseManager(ctx)
|
||||||
|
|
||||||
resourceManager := NewResourceManager(nil, nil, tools, toolsets)
|
resourceManager := NewResourceManager(sources, authServices, tools, toolsets)
|
||||||
|
|
||||||
server := Server{
|
server := Server{
|
||||||
version: fakeVersionString,
|
version: fakeVersionString,
|
||||||
@@ -197,6 +209,11 @@ func setUpServer(t *testing.T, router string, tools map[string]tools.Tool, tools
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to initialize mcp router: %s", err)
|
t.Fatalf("unable to initialize mcp router: %s", err)
|
||||||
}
|
}
|
||||||
|
case "admin":
|
||||||
|
r, err = adminRouter(&server)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to initialize admin router: %s", err)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unknown router")
|
t.Fatalf("unknown router")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/auth"
|
||||||
"github.com/googleapis/genai-toolbox/internal/log"
|
"github.com/googleapis/genai-toolbox/internal/log"
|
||||||
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
|
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
"github.com/googleapis/genai-toolbox/internal/telemetry"
|
"github.com/googleapis/genai-toolbox/internal/telemetry"
|
||||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
)
|
)
|
||||||
@@ -68,8 +70,8 @@ var tool3InputSchema = map[string]any{
|
|||||||
|
|
||||||
func TestMcpEndpointWithoutInitialized(t *testing.T) {
|
func TestMcpEndpointWithoutInitialized(t *testing.T) {
|
||||||
mockTools := []MockTool{tool1, tool2, tool3, tool4, tool5}
|
mockTools := []MockTool{tool1, tool2, tool3, tool4, tool5}
|
||||||
toolsMap, toolsets := setUpResources(t, mockTools)
|
sourcesMap, asMap, toolsMap, toolsets := setUpResources(t, nil, nil, mockTools)
|
||||||
r, shutdown := setUpServer(t, "mcp", toolsMap, toolsets)
|
r, shutdown := setUpServer(t, "mcp", sourcesMap, asMap, toolsMap, toolsets)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
ts := runServer(r, false)
|
ts := runServer(r, false)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
@@ -337,8 +339,8 @@ func runInitializeLifecycle(t *testing.T, ts *httptest.Server, protocolVersion s
|
|||||||
|
|
||||||
func TestMcpEndpoint(t *testing.T) {
|
func TestMcpEndpoint(t *testing.T) {
|
||||||
mockTools := []MockTool{tool1, tool2, tool3, tool4, tool5}
|
mockTools := []MockTool{tool1, tool2, tool3, tool4, tool5}
|
||||||
toolsMap, toolsets := setUpResources(t, mockTools)
|
sourcesMap, asMap, toolsMap, toolsets := setUpResources(t, nil, nil, mockTools)
|
||||||
r, shutdown := setUpServer(t, "mcp", toolsMap, toolsets)
|
r, shutdown := setUpServer(t, "mcp", sourcesMap, asMap, toolsMap, toolsets)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
ts := runServer(r, false)
|
ts := runServer(r, false)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
@@ -743,8 +745,8 @@ func TestMcpEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidProtocolVersionHeader(t *testing.T) {
|
func TestInvalidProtocolVersionHeader(t *testing.T) {
|
||||||
toolsMap, toolsets := map[string]tools.Tool{}, map[string]tools.Toolset{}
|
sourcesMap, asMap, toolsMap, toolsets := map[string]sources.Source{}, map[string]auth.AuthService{}, map[string]tools.Tool{}, map[string]tools.Toolset{}
|
||||||
r, shutdown := setUpServer(t, "mcp", toolsMap, toolsets)
|
r, shutdown := setUpServer(t, "mcp", sourcesMap, asMap, toolsMap, toolsets)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
ts := runServer(r, false)
|
ts := runServer(r, false)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
@@ -770,8 +772,8 @@ func TestInvalidProtocolVersionHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteEndpoint(t *testing.T) {
|
func TestDeleteEndpoint(t *testing.T) {
|
||||||
toolsMap, toolsets := map[string]tools.Tool{}, map[string]tools.Toolset{}
|
sourcesMap, asMap, toolsMap, toolsets := map[string]sources.Source{}, map[string]auth.AuthService{}, map[string]tools.Tool{}, map[string]tools.Toolset{}
|
||||||
r, shutdown := setUpServer(t, "mcp", toolsMap, toolsets)
|
r, shutdown := setUpServer(t, "mcp", sourcesMap, asMap, toolsMap, toolsets)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
ts := runServer(r, false)
|
ts := runServer(r, false)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
@@ -786,8 +788,8 @@ func TestDeleteEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetEndpoint(t *testing.T) {
|
func TestGetEndpoint(t *testing.T) {
|
||||||
toolsMap, toolsets := map[string]tools.Tool{}, map[string]tools.Toolset{}
|
sourcesMap, asMap, toolsMap, toolsets := map[string]sources.Source{}, map[string]auth.AuthService{}, map[string]tools.Tool{}, map[string]tools.Toolset{}
|
||||||
r, shutdown := setUpServer(t, "mcp", toolsMap, toolsets)
|
r, shutdown := setUpServer(t, "mcp", sourcesMap, asMap, toolsMap, toolsets)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
ts := runServer(r, false)
|
ts := runServer(r, false)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
@@ -810,7 +812,7 @@ func TestGetEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSseEndpoint(t *testing.T) {
|
func TestSseEndpoint(t *testing.T) {
|
||||||
r, shutdown := setUpServer(t, "mcp", nil, nil)
|
r, shutdown := setUpServer(t, "mcp", nil, nil, nil, nil)
|
||||||
defer shutdown()
|
defer shutdown()
|
||||||
ts := runServer(r, false)
|
ts := runServer(r, false)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
@@ -925,7 +927,7 @@ func TestStdioSession(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
mockTools := []MockTool{tool1, tool2, tool3}
|
mockTools := []MockTool{tool1, tool2, tool3}
|
||||||
toolsMap, toolsets := setUpResources(t, mockTools)
|
sourcesMap, asMap, toolsMap, toolsets := setUpResources(t, nil, nil, mockTools)
|
||||||
|
|
||||||
pr, pw, err := os.Pipe()
|
pr, pw, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -955,7 +957,7 @@ func TestStdioSession(t *testing.T) {
|
|||||||
|
|
||||||
sseManager := newSseManager(ctx)
|
sseManager := newSseManager(ctx)
|
||||||
|
|
||||||
resourceManager := NewResourceManager(nil, nil, toolsMap, toolsets)
|
resourceManager := NewResourceManager(sourcesMap, asMap, toolsMap, toolsets)
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
version: fakeVersionString,
|
version: fakeVersionString,
|
||||||
|
|||||||
@@ -111,6 +111,12 @@ func (r *ResourceManager) SetResources(sourcesMap map[string]sources.Source, aut
|
|||||||
r.toolsets = toolsetsMap
|
r.toolsets = toolsetsMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ResourceManager) GetSourcesMap() map[string]sources.Source {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
return r.sources
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ResourceManager) GetAuthServiceMap() map[string]auth.AuthService {
|
func (r *ResourceManager) GetAuthServiceMap() map[string]auth.AuthService {
|
||||||
r.mu.RLock()
|
r.mu.RLock()
|
||||||
defer r.mu.RUnlock()
|
defer r.mu.RUnlock()
|
||||||
@@ -123,6 +129,12 @@ func (r *ResourceManager) GetToolsMap() map[string]tools.Tool {
|
|||||||
return r.tools
|
return r.tools
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ResourceManager) GetToolsetsMap() map[string]tools.Toolset {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
return r.toolsets
|
||||||
|
}
|
||||||
|
|
||||||
func InitializeConfigs(ctx context.Context, cfg ServerConfig) (
|
func InitializeConfigs(ctx context.Context, cfg ServerConfig) (
|
||||||
map[string]sources.Source,
|
map[string]sources.Source,
|
||||||
map[string]auth.AuthService,
|
map[string]auth.AuthService,
|
||||||
@@ -337,6 +349,11 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) {
|
|||||||
}
|
}
|
||||||
r.Mount("/ui", webR)
|
r.Mount("/ui", webR)
|
||||||
}
|
}
|
||||||
|
adminR, err := adminRouter(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.Mount("/admin", adminR)
|
||||||
// default endpoint for validating server is running
|
// default endpoint for validating server is running
|
||||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = w.Write([]byte("🧰 Hello, World! 🧰"))
|
_, _ = w.Write([]byte("🧰 Hello, World! 🧰"))
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -152,6 +153,26 @@ func TestUpdateServer(t *testing.T) {
|
|||||||
t.Errorf("error updating server: %s", err)
|
t.Errorf("error updating server: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gotSourcesMap := s.ResourceMgr.GetSourcesMap()
|
||||||
|
if !reflect.DeepEqual(gotSourcesMap, newSources) {
|
||||||
|
t.Errorf("error retrieving sources map: got %+v, want %+v", gotSourcesMap, newSources)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotAuthServiceMap := s.ResourceMgr.GetAuthServiceMap()
|
||||||
|
if !reflect.DeepEqual(gotAuthServiceMap, newAuth) {
|
||||||
|
t.Errorf("error retrieving auth servies map: got %+v, want %+v", gotAuthServiceMap, newAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotToolsMap := s.ResourceMgr.GetToolsMap()
|
||||||
|
if !reflect.DeepEqual(gotToolsMap, newTools) {
|
||||||
|
t.Errorf("error retrieving tools map: got %+v, want %+v", gotToolsMap, newTools)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotToolsetsMap := s.ResourceMgr.GetToolsetsMap()
|
||||||
|
if !reflect.DeepEqual(gotToolsetsMap, newToolsets) {
|
||||||
|
t.Errorf("error retrieving toolsets map: got %+v, want %+v", gotToolsetsMap, newToolsets)
|
||||||
|
}
|
||||||
|
|
||||||
gotSource, _ := s.ResourceMgr.GetSource("example-source")
|
gotSource, _ := s.ResourceMgr.GetSource("example-source")
|
||||||
if diff := cmp.Diff(gotSource, newSources["example-source"]); diff != "" {
|
if diff := cmp.Diff(gotSource, newSources["example-source"]); diff != "" {
|
||||||
t.Errorf("error updating server, sources (-want +got):\n%s", diff)
|
t.Errorf("error updating server, sources (-want +got):\n%s", diff)
|
||||||
|
|||||||
48
internal/server/util.go
Normal file
48
internal/server/util.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2025 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 server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ render.Renderer = &errResponse{} // Renderer interface for managing response payloads.
|
||||||
|
|
||||||
|
// newErrResponse is a helper function initializing an ErrResponse
|
||||||
|
func newErrResponse(err error, code int) *errResponse {
|
||||||
|
return &errResponse{
|
||||||
|
Err: err,
|
||||||
|
HTTPStatusCode: code,
|
||||||
|
|
||||||
|
StatusText: http.StatusText(code),
|
||||||
|
ErrorText: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errResponse is the response sent back when an error has been encountered.
|
||||||
|
type errResponse struct {
|
||||||
|
Err error `json:"-"` // low-level runtime error
|
||||||
|
HTTPStatusCode int `json:"-"` // http response status code
|
||||||
|
|
||||||
|
StatusText string `json:"status"` // user-level status message
|
||||||
|
ErrorText string `json:"error,omitempty"` // application-level error message, for debugging
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
render.Status(r, e.HTTPStatusCode)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user