packages feed

langchain-hs-0.0.5.0: test/Test/Langchain/PromptTemplate/PromptSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.Langchain.PromptTemplate.PromptSpec (tests) where

import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import Test.Tasty
import Test.Tasty.HUnit

import Langchain.PromptTemplate.Prompt

tests :: TestTree
tests =
  testGroup
    "PromptTemplate"
    [ testCase "correctly interpolates all variables" $
        renderPrompt greetingTemplate vars @?= Right "Hello, Alice! Welcome to Wonderland."
    , testCase "handles templates with no variables" $
        let noVarTemplate = fromTemplate "Hello, world!"
         in renderPrompt noVarTemplate Map.empty @?= Right "Hello, world!"
    , testCase "handles templates with repeated variables" $
        let repeatTemplate = fromTemplate "{name} likes {food}. {name} eats {food} every day."
            repeatVars = Map.fromList [("name", "Bob"), ("food", "pizza")]
         in renderPrompt repeatTemplate repeatVars @?= Right "Bob likes pizza. Bob eats pizza every day."
    , testCase "returns an error for missing variables" $
        let missingVars = Map.fromList [("name", "Charlie")]
         in case renderPrompt greetingTemplate missingVars of
              Left err -> "place" `T.isInfixOf` T.pack (show err) @? "Expected error to contain 'place'"
              Right _ -> assertFailure "Expected an error for missing variable"
    , testCase "renders escaped f-string braces" $
        let promptTemplate = fromTemplate "Hello {{name}}, {name}!"
         in renderPrompt promptTemplate (Map.singleton "name" "Alice") @?= Right "Hello {name}, Alice!"
    , testCase "renders f-string format specs" $
        let promptTemplate = fromTemplate "Hello, {name:~u}!"
         in renderPrompt promptTemplate (Map.singleton "name" "Alice") @?= Right "Hello, ALICE!"
    , testCase "infers f-string variables without escaped braces" $
        let promptTemplate = fromTemplate "Hello {{name}}, {name}!"
         in inputVariables promptTemplate @?= ["name"]
    , testCase "rejects f-string positional fields" $
        assertRenderErrorContains "Positional arguments are not supported" $
          renderPrompt (fromTemplate "Hello, {0}!") (Map.singleton "0" "Alice")
    , testCase "rejects f-string attribute access" $
        assertRenderErrorContains "Attribute access is not supported" $
          renderPrompt (fromTemplate "Hello, {user.name}!") (Map.singleton "user.name" "Alice")
    , testCase "rejects nested f-string replacement fields" $
        assertRenderErrorContains "Nested replacement fields are not allowed" $
          renderPrompt
            (fromTemplate "Hello, {name:{width}}!")
            (Map.fromList [("name", "Alice"), ("width", "10")])
    ]
  where
    greetingTemplate = fromTemplate "Hello, {name}! Welcome to {place}."
    vars = Map.fromList [("name", "Alice"), ("place", "Wonderland")]

assertRenderErrorContains :: (Show err) => T.Text -> Either err T.Text -> Assertion
assertRenderErrorContains expected result =
  case result of
    Left err -> expected `T.isInfixOf` T.pack (show err) @? "Expected error to contain expected text"
    Right _ -> assertFailure "Expected render error"