packages feed

shikumi-tools-0.1.0.0: test/ToolSpec.hs

{-# LANGUAGE OverloadedStrings #-}

-- | M1: the typed wire round-trip. A valid 'Baikai.ToolCall' decodes, runs, and
-- encodes its result; malformed arguments and an unknown tool name each yield a
-- typed 'ToolError' (a value), never an exception.
module ToolSpec (tests) where

import Baikai (ToolCall, _ToolCall)
import Control.Lens ((&), (.~))
import Data.Aeson (Value, object, (.=))
import Data.Generics.Labels ()
import Data.Text (Text)
import Data.Text qualified as T
import Fixtures (weatherArgs, weatherRegistry)
import MockLLM (runEffMock)
import Shikumi.Tool (ToolError (..), runToolCall)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))

tc :: Text -> Value -> ToolCall
tc nm args = _ToolCall & #name .~ nm & #arguments .~ args

tests :: TestTree
tests =
  testGroup
    "Tool"
    [ testCase "decodes valid args and runs the body" $ do
        res <- runEffMock [] (runToolCall weatherRegistry (tc "get_weather" weatherArgs))
        case res of
          Right (Right obs) -> assertBool "observation mentions the forecast" ("mild" `T.isInfixOf` obs)
          other -> assertFailure ("expected a tool result, got " <> show other),
      testCase "returns ToolArgsInvalid for a missing required field" $ do
        res <- runEffMock [] (runToolCall weatherRegistry (tc "get_weather" (object ["city" .= ("Paris" :: Text)])))
        case res of
          Right (Left (ToolArgsInvalid nm _)) -> nm @?= "get_weather"
          other -> assertFailure ("expected ToolArgsInvalid, got " <> show other),
      testCase "returns ToolNotFound for an unknown name" $ do
        res <- runEffMock [] (runToolCall weatherRegistry (tc "nope" (object [])))
        case res of
          Right (Left (ToolNotFound nm)) -> nm @?= "nope"
          other -> assertFailure ("expected ToolNotFound, got " <> show other)
    ]