ollama-haskell-0.3.0.0: test-integration/Main.hs
module Main (main) where
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Text qualified as T
import Data.Time (getCurrentTime)
import Ollama
import Test.Tasty
import Test.Tasty.HUnit
testModel :: ModelName
testModel = "qwen3.5:2b"
fastOptions :: Maybe ModelOptions
fastOptions = Just (defaultOptions {optNumPredict = Just 15})
tests :: TestTree
tests =
testGroup
"ollama-haskell Live Server End-to-End Test Suite"
[ testCase "GET /api/version — getVersion" $ do
client <- defaultClient
res <- getVersion client
case res of
Left err -> assertFailure $ "Version request failed: " <> show err
Right ver -> assertBool "Version non-empty" (not $ T.null $ unVersion ver)
, testCase "GET /api/tags — listModels" $ do
client <- defaultClient
res <- listModels client
case res of
Left err -> assertFailure $ "List models failed: " <> show err
Right (ListResponse ms) ->
assertBool "Has installed models" (not $ null ms)
, testCase "POST /api/show — showModel" $ do
client <- defaultClient
res <- showModel client testModel
case res of
Left err -> assertFailure $ "Show model failed: " <> show err
Right resp ->
assertBool "Modelfile or details present" (not $ T.null $ srsModelfile resp)
, testCase "GET /api/ps — listRunning" $ do
client <- defaultClient
res <- listRunning client
case res of
Left err -> assertFailure $ "List running failed: " <> show err
Right _ -> pure ()
, testCase "POST /api/chat — non-streaming chat" $ do
client <- defaultClient
let msgs = systemMessage "You are a helpful assistant." :| [userMessage "Say hello in one word."]
req = (chatRequest testModel msgs) {chatOptions = fastOptions, chatThink = Just ThinkDisabled}
res <- chat client req
case res of
Left err -> assertFailure $ "Chat request failed: " <> show err
Right resp -> do
assertBool "Chat response done" (crDone resp)
case crMessage resp of
Nothing -> assertFailure "Expected message in response"
Just msg ->
assertBool
"Message content or thinking present"
(not (T.null (messageContent msg)) || maybe False (not . T.null) (messageThinking msg))
, testCase "POST /api/chat — streaming chat with conduit" $ do
client <- defaultClient
let req = (chatRequest testModel (userMessage "Count from 1 to 5." :| [])) {chatThink = Just ThinkDisabled}
chunks <- collectStream (chatStream client req)
assertBool "Stream produced response chunks" (not $ null chunks)
, testCase "POST /api/generate — non-streaming generate" $ do
client <- defaultClient
let req =
(generateRequest testModel "Write 3 words.")
{ genOptions = fastOptions
, genThink = Just ThinkDisabled
}
res <- generate client req
case res of
Left err -> assertFailure $ "Generate request failed: " <> show err
Right resp -> do
assertBool "Generate response done" (grDone resp)
assertBool "Generated response non-empty" (not $ T.null $ grResponse resp)
, testCase "POST /api/generate — streaming generate with conduit" $ do
client <- defaultClient
let req = (generateRequest testModel "Say hi.") {genThink = Just ThinkDisabled}
chunks <- collectStream (generateStream client req)
assertBool "Stream produced generate chunks" (not $ null chunks)
, testCase "POST /api/generate — thinking model support" $ do
client <- defaultClient
let req =
(generateRequest testModel "What is 2 + 2?")
{ genThink = Just ThinkEnabled
, genOptions = fastOptions
}
res <- generate client req
case res of
Left err -> assertFailure $ "Thinking generate failed: " <> show err
Right resp -> assertBool "Response done" (grDone resp)
, testCase "POST /api/chat — tool calling definition and execution" $ do
client <- defaultClient
let weatherTool =
Tool
{ toolType = "function"
, toolFunction =
FunctionDef
{ fnName = "get_current_weather"
, fnDescription = Just "Get current weather for a city"
, fnParameters =
Just
FunctionParameters
{ fpType = "object"
, fpProperties = Nothing
, fpRequired = Just ["location"]
, fpAdditionalProperties = Nothing
, fpDescription = Nothing
, fpEnum = Nothing
}
, fnStrict = Nothing
}
}
req =
(chatRequest testModel (userMessage "What is the weather in Tokyo?" :| []))
{ chatTools = Just [weatherTool]
, chatOptions = fastOptions
, chatThink = Just ThinkDisabled
}
res <- chat client req
case res of
Left err -> assertFailure $ "Tool chat request failed: " <> show err
Right resp -> assertBool "Chat response completed" (crDone resp)
, testCase "POST /api/chat — structured output format" $ do
client <- defaultClient
let req =
(chatRequest testModel (userMessage "Respond with JSON listing 2 colors" :| []))
{ chatFormat = Just JsonFormat
, chatOptions = fastOptions
, chatThink = Just ThinkDisabled
}
res <- chat client req
case res of
Left err -> assertFailure $ "Structured chat failed: " <> show err
Right resp -> assertBool "Response done" (crDone resp)
, testCase "POST /api/embed — vector embeddings" $ do
client <- defaultClient
let req = embedRequest testModel ["Hello world", "Haskell LLM client"]
res <- embed client req
case res of
Left (ApiError 501 _) -> pure ()
Left err -> assertFailure $ "Embed request failed: " <> show err
Right resp ->
assertBool "Embeddings non-empty" (not $ null $ erEmbeddings resp)
, testCase "POST /api/copy & DELETE /api/delete — model lifecycle" $ do
client <- defaultClient
let copyTarget = "qwen3.5:2b-test-copy"
copyRes <- copyModel client testModel copyTarget
case copyRes of
Left err -> assertFailure $ "Copy model failed: " <> show err
Right () -> do
delRes <- deleteModel client copyTarget
case delRes of
Left err -> assertFailure $ "Delete model failed: " <> show err
Right () -> pure ()
, testCase "ConversationStore — InMemoryStore with real conversation" $ do
store <- initInMemoryStore
now <- getCurrentTime
let cid = "test-conv-1"
conv =
Conversation
cid
[systemMessage "You are a concise assistant.", userMessage "My favorite color is green."]
testModel
now
now
saveConversationInMemory store conv
mConv <- loadConversationInMemory store cid
assertEqual "Loaded saved conversation" (Just conv) mConv
]
main :: IO ()
main = defaultMain tests