ollama-haskell-0.4.0.0: test/Test/Ollama/Unit/SchemaDerive.hs
module Test.Ollama.Unit.SchemaDerive (tests) where
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import GHC.Generics (Generic)
import Ollama.Types.Format
import Test.Tasty
import Test.Tasty.HUnit
-- ---------------------------------------------------------------------------
-- Test types
-- ---------------------------------------------------------------------------
data SimplePerson = SimplePerson
{ name :: Text
, age :: Int
}
deriving stock (Generic)
deriving anyclass (ToSchema)
data PersonWithOptional = PersonWithOptional
{ personName :: Text
, personAge :: Int
, personNickname :: Maybe Text
}
deriving stock (Generic)
deriving anyclass (ToSchema)
data Address = Address
{ city :: Text
, zipCode :: Text
}
deriving stock (Generic)
deriving anyclass (ToSchema, ToJsonType)
data PersonWithAddress = PersonWithAddress
{ fullName :: Text
, homeAddress :: Address
}
deriving stock (Generic)
deriving anyclass (ToSchema)
data PersonWithHobbies = PersonWithHobbies
{ hobbyName :: Text
, hobbies :: [Text]
}
deriving stock (Generic)
deriving anyclass (ToSchema)
data Color = Red | Green | Blue
deriving stock (Generic)
deriving anyclass (ToSchema)
data PersonWithMaybeInt = PersonWithMaybeInt
{ pmName :: Text
, pmScore :: Maybe Int
}
deriving stock (Generic)
deriving anyclass (ToSchema)
-- ---------------------------------------------------------------------------
-- Tests
-- ---------------------------------------------------------------------------
tests :: TestTree
tests =
testGroup
"Unit SchemaDerive Tests"
[ testSimpleRecord
, testOptionalFields
, testNestedRecord
, testArrayFields
, testEnumType
, testMaybeUnwrapsType
, testSchemaForAlias
, testFormatFor
, testMatchesManualSchema
]
testSimpleRecord :: TestTree
testSimpleRecord = testCase "Simple record produces correct schema" $ do
let schema = schemaFor @SimplePerson
props = schemaProperties schema
req = schemaRequired schema
assertEqual "Has 'name' property" (Just (Property JString)) (Map.lookup "name" props)
assertEqual "Has 'age' property" (Just (Property JInteger)) (Map.lookup "age" props)
assertEqual "Has 2 properties" 2 (Map.size props)
assertBool "'name' is required" ("name" `elem` req)
assertBool "'age' is required" ("age" `elem` req)
assertEqual "2 required fields" 2 (length req)
testOptionalFields :: TestTree
testOptionalFields = testCase "Maybe fields are not required" $ do
let schema = schemaFor @PersonWithOptional
props = schemaProperties schema
req = schemaRequired schema
assertEqual "Has 3 properties" 3 (Map.size props)
assertEqual
"'personNickname' maps to JString"
(Just (Property JString))
(Map.lookup "personNickname" props)
assertBool "'personName' is required" ("personName" `elem` req)
assertBool "'personAge' is required" ("personAge" `elem` req)
assertBool "'personNickname' is NOT required" ("personNickname" `notElem` req)
assertEqual "2 required fields" 2 (length req)
testNestedRecord :: TestTree
testNestedRecord = testCase "Nested record becomes JObject" $ do
let schema = schemaFor @PersonWithAddress
props = schemaProperties schema
case Map.lookup "homeAddress" props of
Just (Property (JObject innerSchema)) -> do
let innerProps = schemaProperties innerSchema
assertEqual "Inner has 'city'" (Just (Property JString)) (Map.lookup "city" innerProps)
assertEqual "Inner has 'zipCode'" (Just (Property JString)) (Map.lookup "zipCode" innerProps)
other -> assertFailure $ "Expected JObject for homeAddress, got: " <> show other
testArrayFields :: TestTree
testArrayFields = testCase "List fields become JArray" $ do
let schema = schemaFor @PersonWithHobbies
props = schemaProperties schema
assertEqual
"'hobbies' maps to JArray JString"
(Just (Property (JArray JString)))
(Map.lookup "hobbies" props)
testEnumType :: TestTree
testEnumType = testCase "Simple enum produces schema with constructor names" $ do
let schema = schemaFor @Color
req = schemaRequired schema
-- Enum constructors are stored in required list (as enum values)
assertBool "Contains 'red'" ("red" `elem` req)
assertBool "Contains 'green'" ("green" `elem` req)
assertBool "Contains 'blue'" ("blue" `elem` req)
assertEqual "3 enum values" 3 (length req)
assertEqual "No properties" 0 (Map.size $ schemaProperties schema)
testMaybeUnwrapsType :: TestTree
testMaybeUnwrapsType = testCase "Maybe Int field maps to JInteger" $ do
let schema = schemaFor @PersonWithMaybeInt
props = schemaProperties schema
assertEqual "'pmScore' maps to JInteger" (Just (Property JInteger)) (Map.lookup "pmScore" props)
testSchemaForAlias :: TestTree
testSchemaForAlias = testCase "schemaFor is equivalent to toSchema" $ do
let s1 = schemaFor @SimplePerson
s2 = toSchema @SimplePerson
assertEqual "schemaFor == toSchema" s1 s2
testFormatFor :: TestTree
testFormatFor = testCase "formatFor wraps in SchemaFormat" $ do
let fmt = formatFor @SimplePerson
expected = SchemaFormat (schemaFor @SimplePerson)
assertEqual "formatFor wraps schema" expected fmt
testMatchesManualSchema :: TestTree
testMatchesManualSchema = testCase "Derived schema matches manual DSL schema" $ do
let derived = schemaFor @SimplePerson
manual =
buildSchema $
emptyObject
|+ ("name", JString)
|+ ("age", JInteger)
|!! ["name", "age"]
assertEqual "Properties match" (schemaProperties manual) (schemaProperties derived)
-- Required fields should contain the same elements (order may differ)
let derivedReq = schemaRequired derived
manualReq = schemaRequired manual
assertBool "All derived required fields in manual" (all (`elem` manualReq) derivedReq)
assertBool "All manual required fields in derived" (all (`elem` derivedReq) manualReq)