Skip to main content
Glama

MCPJungle mcp gateway

by mcpjungle
Mozilla Public License 2.0
638
  • Apple
middleware_test.go13.2 kB
package api import ( "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/mcpjungle/mcpjungle/internal/model" "github.com/mcpjungle/mcpjungle/internal/service/config" "github.com/mcpjungle/mcpjungle/internal/service/mcpclient" "github.com/mcpjungle/mcpjungle/internal/service/user" "github.com/mcpjungle/mcpjungle/pkg/testhelpers" "github.com/mcpjungle/mcpjungle/pkg/types" "gorm.io/gorm" ) func TestRequireInitialized(t *testing.T) { gin.SetMode(gin.TestMode) tests := []struct { name string setupConfig func(*gorm.DB) error expectedStatus int expectedBody string }{ { name: "server is initialized", setupConfig: func(testDB *gorm.DB) error { configService := config.NewServerConfigService(testDB) _, err := configService.Init(model.ModeDev) return err }, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "server is not initialized", setupConfig: func(testDB *gorm.DB) error { cfg := model.ServerConfig{ Initialized: false, Mode: model.ModeDev, } return testDB.Create(&cfg).Error }, expectedStatus: http.StatusForbidden, expectedBody: `{"error":"server is not initialized"}`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { setup := testhelpers.SetupTestDB(t) defer setup.Cleanup() testDB := setup.DB configService := config.NewServerConfigService(testDB) err := tt.setupConfig(testDB) if err != nil { t.Fatalf("Setup config failed: %v", err) } server := &Server{configService: configService} router := gin.New() router.Use(server.requireInitialized()) router.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "success"}) }) req := httptest.NewRequest(http.MethodGet, "/test", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != tt.expectedStatus { t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code) } if tt.expectedBody != "" && w.Body.String() != tt.expectedBody { t.Errorf("Expected body %s, got %s", tt.expectedBody, w.Body.String()) } }) } } func TestVerifyUserAuthForAPIAccess(t *testing.T) { gin.SetMode(gin.TestMode) setup := testhelpers.SetupTestDB(t) defer setup.Cleanup() testDB := setup.DB userService := user.NewUserService(testDB) tests := []struct { name string mode model.ServerMode authHeader string setupUser func() error expectedStatus int expectedBody string }{ { name: "dev mode - no auth required", mode: model.ModeDev, authHeader: "", setupUser: func() error { return nil }, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "enterprise mode - valid token", mode: model.ModeEnterprise, authHeader: "Bearer test-token", setupUser: func() error { _, err := userService.CreateAdminUser() if err != nil { return err } var u model.User err = testDB.Where("username = ?", "admin").First(&u).Error if err != nil { return err } u.AccessToken = "test-token" return testDB.Save(&u).Error }, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "enterprise mode - missing token", mode: model.ModeEnterprise, authHeader: "", setupUser: func() error { return nil }, expectedStatus: http.StatusUnauthorized, expectedBody: `{"error":"missing access token"}`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.setupUser() if err != nil { t.Fatalf("Setup user failed: %v", err) } router := gin.New() router.Use(func(c *gin.Context) { if tt.mode != "" { c.Set("mode", tt.mode) } }) server := &Server{userService: userService} router.Use(server.verifyUserAuthForAPIAccess()) router.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "success"}) }) req := httptest.NewRequest(http.MethodGet, "/test", nil) if tt.authHeader != "" { req.Header.Set("Authorization", tt.authHeader) } w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != tt.expectedStatus { t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code) } if tt.expectedBody != "" && w.Body.String() != tt.expectedBody { t.Errorf("Expected body %s, got %s", tt.expectedBody, w.Body.String()) } }) } } func TestRequireAdminUser(t *testing.T) { gin.SetMode(gin.TestMode) testDB := testhelpers.SetupTestDB(t).DB userService := user.NewUserService(testDB) tests := []struct { name string mode model.ServerMode user any expectedStatus int expectedBody string }{ { name: "dev mode - no admin check required", mode: model.ModeDev, user: nil, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "enterprise mode - admin user", mode: model.ModeEnterprise, user: &model.User{ Model: gorm.Model{ID: 1}, Username: "admin", Role: types.UserRoleAdmin, }, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "enterprise mode - regular user", mode: model.ModeEnterprise, user: &model.User{ Model: gorm.Model{ID: 1}, Username: "user", Role: types.UserRoleUser, }, expectedStatus: http.StatusForbidden, expectedBody: `{"error":"user is not authorized to perform this action"}`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { router := gin.New() router.Use(func(c *gin.Context) { if tt.mode != "" { c.Set("mode", tt.mode) } if tt.user != nil { c.Set("user", tt.user) } }) server := &Server{userService: userService} router.Use(server.requireAdminUser()) router.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "success"}) }) req := httptest.NewRequest(http.MethodGet, "/test", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != tt.expectedStatus { t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code) } if tt.expectedBody != "" && w.Body.String() != tt.expectedBody { t.Errorf("Expected body %s, got %s", tt.expectedBody, w.Body.String()) } }) } } func TestRequireServerMode(t *testing.T) { gin.SetMode(gin.TestMode) tests := []struct { name string contextMode model.ServerMode requiredMode model.ServerMode expectedStatus int expectedBody string }{ { name: "matching mode - dev", contextMode: model.ModeDev, requiredMode: model.ModeDev, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "non-matching mode - dev required, enterprise context", contextMode: model.ModeEnterprise, requiredMode: model.ModeDev, expectedStatus: http.StatusForbidden, expectedBody: `{"error":"this request is only allowed in development mode"}`, }, { name: "non-matching mode - dev required, prod context", contextMode: model.ModeProd, requiredMode: model.ModeDev, expectedStatus: http.StatusForbidden, expectedBody: `{"error":"this request is only allowed in development mode"}`, }, { name: "enterprise required, prod context (deprecated)", contextMode: model.ModeProd, requiredMode: model.ModeEnterprise, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "prod required, enterprise context (deprecated)", contextMode: model.ModeEnterprise, requiredMode: model.ModeProd, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "prod required, prod context (deprecated)", contextMode: model.ModeProd, requiredMode: model.ModeProd, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "enterprise required, enterprise context", contextMode: model.ModeEnterprise, requiredMode: model.ModeEnterprise, expectedStatus: http.StatusOK, expectedBody: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { router := gin.New() router.Use(func(c *gin.Context) { if tt.contextMode != "" { c.Set("mode", tt.contextMode) } }) server := &Server{} router.Use(server.requireServerMode(tt.requiredMode)) router.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "success"}) }) req := httptest.NewRequest(http.MethodGet, "/test", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != tt.expectedStatus { t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code) } if tt.expectedBody != "" && w.Body.String() != tt.expectedBody { t.Errorf("Expected body %s, got %s", tt.expectedBody, w.Body.String()) } }) } } func TestCheckAuthForMcpProxyAccess(t *testing.T) { gin.SetMode(gin.TestMode) setup := testhelpers.SetupTestDB(t) defer setup.Cleanup() testDB := setup.DB mcpClientService := mcpclient.NewMCPClientService(testDB) tests := []struct { name string mode model.ServerMode authHeader string setupClient func() error expectedStatus int expectedBody string }{ { name: "dev mode - no auth required", mode: model.ModeDev, authHeader: "", setupClient: func() error { return nil }, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "enterprise mode - valid token", mode: model.ModeEnterprise, authHeader: "Bearer test-token", setupClient: func() error { client := model.McpClient{ Name: "test-client", Description: "Test client", AllowList: []byte("[]"), } _, err := mcpClientService.CreateClient(client) if err != nil { return err } var c model.McpClient err = testDB.Where("name = ?", "test-client").First(&c).Error if err != nil { return err } c.AccessToken = "test-token" return testDB.Save(&c).Error }, expectedStatus: http.StatusOK, expectedBody: "", }, { name: "enterprise mode - missing token", mode: model.ModeEnterprise, authHeader: "", setupClient: func() error { return nil }, expectedStatus: http.StatusUnauthorized, expectedBody: `{"error":"missing MCP client access token"}`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.setupClient() if err != nil { t.Fatalf("Setup client failed: %v", err) } router := gin.New() router.Use(func(c *gin.Context) { if tt.mode != "" { c.Set("mode", tt.mode) } }) server := &Server{mcpClientService: mcpClientService} router.Use(server.checkAuthForMcpProxyAccess()) router.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "success"}) }) req := httptest.NewRequest(http.MethodGet, "/test", nil) if tt.authHeader != "" { req.Header.Set("Authorization", tt.authHeader) } w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != tt.expectedStatus { t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code) } if tt.expectedBody != "" && w.Body.String() != tt.expectedBody { t.Errorf("Expected body %s, got %s", tt.expectedBody, w.Body.String()) } }) } } func TestMiddlewareIntegration(t *testing.T) { gin.SetMode(gin.TestMode) setup := testhelpers.SetupTestDB(t) defer setup.Cleanup() testDB := setup.DB configService := config.NewServerConfigService(testDB) userService := user.NewUserService(testDB) // Setup config _, err := configService.Init(model.ModeEnterprise) if err != nil { t.Fatalf("Setup config failed: %v", err) } // Setup user _, err = userService.CreateAdminUser() if err != nil { t.Fatalf("Setup user failed: %v", err) } // Update user with known token and admin role var u model.User err = testDB.Where("username = ?", "admin").First(&u).Error if err != nil { t.Fatalf("Failed to find admin user: %v", err) } u.AccessToken = "valid-token" u.Role = types.UserRoleAdmin err = testDB.Save(&u).Error if err != nil { t.Fatalf("Failed to save admin user: %v", err) } server := &Server{ configService: configService, userService: userService, } router := gin.New() router.Use(server.requireInitialized()) router.Use(server.verifyUserAuthForAPIAccess()) router.Use(server.requireAdminUser()) router.GET("/admin", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "admin access granted"}) }) req := httptest.NewRequest(http.MethodGet, "/admin", nil) req.Header.Set("Authorization", "Bearer valid-token") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code) } expectedBody := `{"status":"admin access granted"}` if w.Body.String() != expectedBody { t.Errorf("Expected body %s, got %s", expectedBody, w.Body.String()) } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mcpjungle/MCPJungle'

If you have feedback or need assistance with the MCP directory API, please join our Discord server