packages feed

mcp-server-0.1.0.9: test/Spec/SchemaValidation.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell   #-}

module Spec.SchemaValidation (spec) where

import Control.Monad (forM_)
import Data.Text (Text)
import qualified Data.Text as T
import MCP.Server
import MCP.Server.Derive
import Test.Hspec
import TestTypes
import TestData

-- Generate handlers for testing
testToolHandlers :: (ToolListHandler IO, ToolCallHandler IO)
testToolHandlers = $(deriveToolHandler ''TestTool 'handleTestTool)

testToolHandlersWithDescriptions :: (ToolListHandler IO, ToolCallHandler IO)
testToolHandlersWithDescriptions = $(deriveToolHandlerWithDescription ''TestTool 'handleTestTool testDescriptions)

-- Helper functions for schema validation
findTool :: Text -> [ToolDefinition] -> ToolDefinition
findTool toolName toolDefs = 
  case filter (\def -> toolDefinitionName def == toolName) toolDefs of
    [def] -> def
    [] -> error $ "Tool not found: " ++ T.unpack toolName
    _ -> error $ "Multiple tools found with name: " ++ T.unpack toolName

getProperty :: Text -> ToolDefinition -> InputSchemaDefinitionProperty
getProperty propertyName toolDef = 
  case toolDefinitionInputSchema toolDef of
    InputSchemaDefinitionObject props _ -> 
      case lookup propertyName props of
        Just prop -> prop
        Nothing -> error $ "Property not found: " ++ T.unpack propertyName
    other -> error $ "Expected InputSchemaDefinitionObject but got: " ++ show other

assertToolExists :: Text -> [ToolDefinition] -> IO ()
assertToolExists toolName toolDefs = do
  toolDefs `shouldSatisfy` any (\def -> toolDefinitionName def == toolName)

assertHasProperty :: Text -> Text -> ToolDefinition -> IO ()
assertHasProperty propertyName expectedType toolDef = do
  let prop = getProperty propertyName toolDef
  propertyType prop `shouldBe` expectedType

assertPropertyDescription :: Text -> ToolDefinition -> Text -> IO ()
assertPropertyDescription propertyName toolDef expectedDesc = do
  let prop = getProperty propertyName toolDef
  propertyDescription prop `shouldBe` expectedDesc

assertRequiredFields :: [Text] -> ToolDefinition -> IO ()
assertRequiredFields expectedFields toolDef = do
  case toolDefinitionInputSchema toolDef of
    InputSchemaDefinitionObject _ required -> 
      all (`elem` required) expectedFields `shouldBe` True
    other -> expectationFailure $ "Expected InputSchemaDefinitionObject but got: " ++ show other

assertToolDescription :: ToolDefinition -> Text -> IO ()
assertToolDescription toolDef expectedDesc = 
  toolDefinitionDescription toolDef `shouldBe` expectedDesc

spec :: Spec
spec = describe "Schema Validation and Custom Descriptions" $ do
  
  describe "Schema Generation" $ do
    let (listHandler, _) = testToolHandlers
    
    forM_ schemaTestCases $ \testCase -> do
      it (T.unpack $ schemaTestDescription testCase) $ do
        toolDefs <- listHandler
        
        assertToolExists (schemaToolName testCase) toolDefs
        let toolDef = findTool (schemaToolName testCase) toolDefs
        
        -- Validate property types
        forM_ (expectedProperties testCase) $ \(propName, expectedType) ->
          assertHasProperty propName expectedType toolDef
        
        -- Validate required fields
        assertRequiredFields (requiredFields testCase) toolDef

  describe "Custom Descriptions" $ do
    it "applies correct tool descriptions" $ do
      let (listHandler, _) = testToolHandlersWithDescriptions
      toolDefs <- listHandler
      
      assertToolExists "echo" toolDefs
      let echoDef = findTool "echo" toolDefs
      assertToolDescription echoDef "Echoes the input text back to the user"
      
      assertToolExists "calculate" toolDefs
      let calculateDef = findTool "calculate" toolDefs
      assertToolDescription calculateDef "Performs mathematical calculations"
    
    it "applies correct field descriptions for Calculate tool" $ do
      let (listHandler, _) = testToolHandlersWithDescriptions
      toolDefs <- listHandler
      
      assertToolExists "calculate" toolDefs
      let calculateDef = findTool "calculate" toolDefs
      
      assertPropertyDescription "operation" calculateDef "The mathematical operation to perform"
      assertPropertyDescription "x" calculateDef "The first number"  
      assertPropertyDescription "y" calculateDef "The second number"