packages feed

bloodhound-1.0.0.0: tests/Test/QuerySpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.QuerySpec where

import Data.Aeson.KeyMap qualified as X
import TestsUtils.Common
import TestsUtils.Import

spec :: Spec
spec =
  describe "query API" $ do
    it "returns document for term query and identity filter" $
      withTestEnv $ do
        _ <- insertData
        let query = TermQuery (Term "user" "bitemyapp") Nothing
        let filter' = Filter $ MatchAllQuery Nothing
        let search = mkSearch (Just query) (Just filter')
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    it "handles constant score queries" $
      withTestEnv $ do
        _ <- insertData
        let query = TermsQuery "user" ("bitemyapp" :| [])
        let cfQuery = ConstantScoreQuery query (Boost 1.0)
        let filter' = Filter $ MatchAllQuery Nothing
        let search = mkSearch (Just cfQuery) (Just filter')
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    it "returns document for terms query and identity filter" $
      withTestEnv $ do
        _ <- insertData
        let query = TermsQuery "user" ("bitemyapp" :| [])
        let filter' = Filter $ MatchAllQuery Nothing
        let search = mkSearch (Just query) (Just filter')
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    it "returns document for match query" $
      withTestEnv $ do
        _ <- insertData
        let query = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
        let search = mkSearch (Just query) Nothing
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    it "returns document for match query with fuzziness" $
      withTestEnv $ do
        _ <- insertData
        let match = mkMatchQuery (FieldName "user") (QueryString "bidemyapp")
        let query = QueryMatchQuery $ match {matchQueryFuzziness = Just FuzzinessAuto}
        let search = mkSearch (Just query) Nothing
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    it "returns document for multi-match query" $
      withTestEnv $ do
        _ <- insertData
        let flds = [FieldName "user", FieldName "message"]
        let query = QueryMultiMatchQuery $ mkMultiMatchQuery flds (QueryString "bitemyapp")
        let search = mkSearch (Just query) Nothing
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    it "returns document for multi-match query with a custom tiebreaker" $
      withTestEnv $ do
        _ <- insertData
        let tiebreaker = Just $ Tiebreaker 0.3
            flds = [FieldName "user", FieldName "message"]
            multiQuery' = mkMultiMatchQuery flds (QueryString "bitemyapp")
            query = QueryMultiMatchQuery $ multiQuery' {multiMatchQueryTiebreaker = tiebreaker}
            search = mkSearch (Just query) Nothing
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    it "returns document for bool query" $
      withTestEnv $ do
        _ <- insertData
        let innerQuery =
              QueryMatchQuery $
                mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
        let query =
              QueryBoolQuery $
                mkBoolQuery [innerQuery] [] [] []
        let search = mkSearch (Just query) Nothing
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    it "returns document for boosting query" $
      withTestEnv $ do
        _ <- insertData
        let posQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "bitemyapp")
        let negQuery = QueryMatchQuery $ mkMatchQuery (FieldName "user") (QueryString "notmyapp")
        let query = QueryBoostingQuery $ BoostingQuery posQuery negQuery (Boost 0.2)
        let search = mkSearch (Just query) Nothing
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    -- The @common@ terms query was deprecated in ES 6.1 and removed in
    -- ES 8.0 (OpenSearch, which forked from ES 7.10, still supports it).
    -- Gate the live round-trip to backends whose major version is below 8.
    esCommonIt <- runIO (withMajorVersionIT (< 8))
    esCommonIt "returns document for common terms query" $
      withTestEnv $ do
        _ <- insertData
        let query =
              QueryCommonTermsQuery $
                CommonTermsQuery
                  (FieldName "user")
                  (QueryString "bitemyapp")
                  (Just (CutoffFrequency 0.0001))
                  (Just Or)
                  (Just Or)
                  Nothing
                  Nothing
                  Nothing
                  Nothing
        let search = mkSearch (Just query) Nothing
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet

    it "returns document for template query" $
      withTestEnv $ do
        _ <- insertData
        let query = SearchTemplateSource "{\"query\": { \"match\" : { \"{{my_field}}\" : \"{{my_value}}\" } } }"
            templateParams =
              TemplateQueryKeyValuePairs $
                X.fromList
                  [ ("my_field", "user"),
                    ("my_value", "bitemyapp")
                  ]
            search = mkSearchTemplate (Right query) templateParams
        response <- performBHRequest $ searchByIndexTemplate testIndex search
        let myTweet = grabFirst response
        liftIO $ myTweet `shouldBe` Right exampleTweet

    it "can save, use, read and delete template queries" $
      withTestEnv $ do
        _ <- insertData
        let query = SearchTemplateSource "{\"query\": { \"match\" : { \"{{my_field}}\" : \"{{my_value}}\" } } }"
            templateParams =
              TemplateQueryKeyValuePairs $
                X.fromList
                  [ ("my_field", "user"),
                    ("my_value", "bitemyapp")
                  ]
            tid = SearchTemplateId "myTemplate"
            search = mkSearchTemplate (Left tid) templateParams
        _ <- performBHRequest $ storeSearchTemplate tid query
        t1 <- performBHRequest $ getSearchTemplate tid
        liftIO $ t1 `shouldBe` GetTemplateScript {getTemplateScriptLang = Just "mustache", getTemplateScriptSource = Just (SearchTemplateSource "{\"query\": { \"match\" : { \"{{my_field}}\" : \"{{my_value}}\" } } }"), getTemplateScriptOptions = Nothing, getTemplateScriptId = "myTemplate", getTemplateScriptFound = True}
        response <- performBHRequest $ searchByIndexTemplate testIndex search
        _ <- performBHRequest $ deleteSearchTemplate tid
        t2 <- performBHRequest $ getSearchTemplate tid
        let myTweet = grabFirst response
        liftIO $ do
          t2 `shouldBe` GetTemplateScript {getTemplateScriptLang = Nothing, getTemplateScriptSource = Nothing, getTemplateScriptOptions = Nothing, getTemplateScriptId = "myTemplate", getTemplateScriptFound = False}
          myTweet `shouldBe` Right exampleTweet

    it "renderTemplate renders an inline template's mustache substitutions without executing it" $
      withTestEnv $ do
        -- renderTemplate renders the body server-side without executing
        -- a search, so no document needs to be indexed for this test.
        let query = SearchTemplateSource "{\"query\": { \"match\" : { \"{{my_field}}\" : \"{{my_value}}\" } } }"
            templateParams =
              TemplateQueryKeyValuePairs $
                X.fromList
                  [ ("my_field", "user"),
                    ("my_value", "bitemyapp")
                  ]
            search = mkSearchTemplate (Right query) templateParams
        rendered <- performBHRequest $ renderTemplate search
        liftIO $ case rendered of
          Left err ->
            expectationFailure ("renderTemplate failed: " <> show err)
          Right body -> do
            -- The response wraps the resolved query under
            -- @template_output@. Both the key (@{{my_field}}@) and the
            -- value (@{{my_value}}@) must be substituted by mustache.
            let expectedOutput =
                  object
                    [ "query"
                        .= object ["match" .= object ["user" .= String "bitemyapp"]]
                    ]
            case body of
              Object top -> X.lookup "template_output" top `shouldBe` Just expectedOutput
              _ -> expectationFailure "expected a JSON object body"

    it "renderTemplate renders a stored template by id without executing it" $
      withTestEnv $ do
        -- renderTemplate renders the body server-side without executing
        -- a search, so no document needs to be indexed for this test.
        let query = SearchTemplateSource "{\"query\": { \"match\" : { \"{{my_field}}\" : \"{{my_value}}\" } } }"
            tid = SearchTemplateId "bh-render-template-spec"
            templateParams =
              TemplateQueryKeyValuePairs $
                X.fromList
                  [ ("my_field", "user"),
                    ("my_value", "bitemyapp")
                  ]
            search = mkSearchTemplate (Left tid) templateParams
        _ <- performBHRequest $ storeSearchTemplate tid query
        rendered <- performBHRequest $ renderTemplate search
        _ <- performBHRequest $ deleteSearchTemplate tid
        liftIO $ case rendered of
          Left err ->
            expectationFailure ("renderTemplate by id failed: " <> show err)
          Right body -> do
            let expectedOutput =
                  object
                    [ "query"
                        .= object ["match" .= object ["user" .= String "bitemyapp"]]
                    ]
            case body of
              Object top -> X.lookup "template_output" top `shouldBe` Just expectedOutput
              _ -> expectationFailure "expected a JSON object body"

    it "returns document for wildcard query" $
      withTestEnv $ do
        _ <- insertData
        let query = QueryWildcardQuery $ WildcardQuery (FieldName "user") (Just "bitemy*") Nothing Nothing Nothing Nothing
        let search = mkSearch (Just query) Nothing
        myTweet <- searchTweet search
        liftIO $
          myTweet `shouldBe` Right exampleTweet