packages feed

bloodhound-1.0.0.0: tests/Test/AnalyzeSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns -Wno-x-partial #-}

module Test.AnalyzeSpec (spec) where

import Data.Aeson.Key (fromText)
import Data.Aeson.Types (parseMaybe)
import Data.ByteString.Lazy.Char8 qualified as BL8
import TestsUtils.Common
import TestsUtils.Import

spec :: Spec
spec = do
  describe "Analyze JSON round-trip" $ do
    let lookupField :: Value -> Text -> Maybe Value
        lookupField json field = parseMaybe (withObject "req" $ \o -> o .: fromText field) json

    it "encodes a minimal AnalyzeRequest (text only)" $
      let req =
            AnalyzeRequest
              { analyzeRequestText = ["foo"],
                analyzeRequestAnalyzer = Just "standard",
                analyzeRequestTokenizer = Nothing,
                analyzeRequestFilter = Nothing,
                analyzeRequestCharFilter = Nothing,
                analyzeRequestField = Nothing,
                analyzeRequestNormalizer = Nothing
              }
          json = toJSON req
       in do
            lookupField json "text" `shouldBe` Just (toJSON ["foo" :: Text])
            lookupField json "analyzer" `shouldBe` Just (String "standard")

    it "omits optional fields that are Nothing" $
      let req =
            AnalyzeRequest
              { analyzeRequestText = ["foo"],
                analyzeRequestAnalyzer = Nothing,
                analyzeRequestTokenizer = Nothing,
                analyzeRequestFilter = Nothing,
                analyzeRequestCharFilter = Nothing,
                analyzeRequestField = Nothing,
                analyzeRequestNormalizer = Nothing
              }
          json = toJSON req
       in do
            lookupField json "tokenizer" `shouldBe` (Nothing :: Maybe Value)
            lookupField json "analyzer" `shouldBe` (Nothing :: Maybe Value)
            lookupField json "filter" `shouldBe` (Nothing :: Maybe Value)

    it "includes filter, char_filter and field when provided" $
      let req =
            AnalyzeRequest
              { analyzeRequestText = ["foo"],
                analyzeRequestAnalyzer = Nothing,
                analyzeRequestTokenizer = Just "standard",
                analyzeRequestFilter = Just ["lowercase", "stop"],
                analyzeRequestCharFilter = Just ["html_strip"],
                analyzeRequestField = Just (FieldName "message"),
                analyzeRequestNormalizer = Nothing
              }
          json = toJSON req
       in do
            lookupField json "filter" `shouldBe` Just (toJSON ["lowercase", "stop" :: Text])
            lookupField json "char_filter" `shouldBe` Just (toJSON ["html_strip" :: Text])
            lookupField json "field" `shouldBe` Just (String "message")

    it "includes normalizer when provided" $
      let req =
            AnalyzeRequest
              { analyzeRequestText = ["foo"],
                analyzeRequestAnalyzer = Nothing,
                analyzeRequestTokenizer = Nothing,
                analyzeRequestFilter = Nothing,
                analyzeRequestCharFilter = Nothing,
                analyzeRequestField = Nothing,
                analyzeRequestNormalizer = Just "lowercase"
              }
          json = toJSON req
       in lookupField json "normalizer" `shouldBe` Just (String "lowercase")

    it "decodes an Elasticsearch AnalyzeResponse with tokens" $
      let bytes =
            BL8.pack
              "{\"tokens\":[{\"token\":\"foo\",\"start_offset\":0,\"end_offset\":3,\"type\":\"<ALPHANUM>\",\"position\":0},{\"token\":\"bar\",\"start_offset\":4,\"end_offset\":7,\"type\":\"<ALPHANUM>\",\"position\":1}]}"
          Just resp = decode bytes
       in do
            length (analyzeResponseTokens resp) `shouldBe` 2
            analyzeTokenToken (head (analyzeResponseTokens resp)) `shouldBe` "foo"
            analyzeTokenPosition (analyzeResponseTokens resp !! 1) `shouldBe` 1

    it "decodes an OpenSearch-style token with a kind field" $
      let bytes =
            BL8.pack
              "{\"tokens\":[{\"token\":\"foo\",\"start_offset\":0,\"end_offset\":3,\"type\":\"ALPHANUM\",\"position\":0,\"kind\":\"WORD\"}]}"
          Just resp = decode bytes
       in analyzeTokenKind (head (analyzeResponseTokens resp)) `shouldBe` TokenKindWord

    it "decodes an unknown kind value as TokenKindOther preserving the string" $
      let bytes =
            BL8.pack
              "{\"tokens\":[{\"token\":\"foo\",\"start_offset\":0,\"end_offset\":3,\"type\":\"ALPHANUM\",\"position\":0,\"kind\":\"CUSTOM\"}]}"
          Just resp = decode bytes
       in analyzeTokenKind (head (analyzeResponseTokens resp)) `shouldBe` TokenKindOther "CUSTOM"

  describe "Analyze endpoint" $ do
    it "analyzes text with the built-in standard analyzer (lowercases) via POST /_analyze" $
      withTestEnv $ do
        let req =
              AnalyzeRequest
                { analyzeRequestText = ["Foo Bar"],
                  analyzeRequestAnalyzer = Just "standard",
                  analyzeRequestTokenizer = Nothing,
                  analyzeRequestFilter = Nothing,
                  analyzeRequestCharFilter = Nothing,
                  analyzeRequestField = Nothing,
                  analyzeRequestNormalizer = Nothing
                }
        resp <- performBHRequest $ analyzeText Nothing req
        let toks = analyzeResponseTokens resp
        liftIO $ do
          length toks `shouldBe` 2
          map analyzeTokenToken toks `shouldBe` ["foo", "bar"]
          map analyzeTokenPosition toks `shouldBe` [0, 1]
          map analyzeTokenStartOffset toks `shouldBe` [0, 4]
          map analyzeTokenEndOffset toks `shouldBe` [3, 7]

    it "preserves case with the whitespace analyzer via POST /_analyze" $
      withTestEnv $ do
        let req =
              AnalyzeRequest
                { analyzeRequestText = ["Foo Bar"],
                  analyzeRequestAnalyzer = Just "whitespace",
                  analyzeRequestTokenizer = Nothing,
                  analyzeRequestFilter = Nothing,
                  analyzeRequestCharFilter = Nothing,
                  analyzeRequestField = Nothing,
                  analyzeRequestNormalizer = Nothing
                }
        resp <- performBHRequest $ analyzeText Nothing req
        let toks = analyzeResponseTokens resp
        liftIO $ map analyzeTokenToken toks `shouldBe` ["Foo", "Bar"]

    it "analyzes text with a lowercase filter chain via POST /_analyze" $
      withTestEnv $ do
        let req =
              AnalyzeRequest
                { analyzeRequestText = ["Foo Bar"],
                  analyzeRequestAnalyzer = Nothing,
                  analyzeRequestTokenizer = Just "standard",
                  analyzeRequestFilter = Just ["lowercase"],
                  analyzeRequestCharFilter = Nothing,
                  analyzeRequestField = Nothing,
                  analyzeRequestNormalizer = Nothing
                }
        resp <- performBHRequest $ analyzeText Nothing req
        let toks = analyzeResponseTokens resp
        liftIO $ do
          length toks `shouldBe` 2
          map analyzeTokenToken toks `shouldBe` ["foo", "bar"]

    it "returns no tokens for an empty text value" $
      withTestEnv $ do
        let req =
              AnalyzeRequest
                { analyzeRequestText = [""],
                  analyzeRequestAnalyzer = Just "standard",
                  analyzeRequestTokenizer = Nothing,
                  analyzeRequestFilter = Nothing,
                  analyzeRequestCharFilter = Nothing,
                  analyzeRequestField = Nothing,
                  analyzeRequestNormalizer = Nothing
                }
        resp <- performBHRequest $ analyzeText Nothing req
        liftIO $ analyzeResponseTokens resp `shouldBe` []

    it "analyzes text via POST /{index}/_analyze against an existing index" $
      withTestEnv $ do
        _ <- createExampleIndex
        let req =
              AnalyzeRequest
                { analyzeRequestText = ["Foo Bar"],
                  analyzeRequestAnalyzer = Just "standard",
                  analyzeRequestTokenizer = Nothing,
                  analyzeRequestFilter = Nothing,
                  analyzeRequestCharFilter = Nothing,
                  analyzeRequestField = Nothing,
                  analyzeRequestNormalizer = Nothing
                }
        resp <- performBHRequest $ analyzeText (Just testIndex) req
        _ <- deleteExampleIndex
        let toks = analyzeResponseTokens resp
        liftIO $ do
          length toks `shouldBe` 2
          map analyzeTokenToken toks `shouldBe` ["foo", "bar"]