packages feed

bloodhound-1.0.0.0: tests/Test/ReindexSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Test.ReindexSpec (spec) where

import Control.Concurrent
import Numeric.Natural
import TestsUtils.Common
import TestsUtils.Import

spec :: Spec
spec =
  describe "Reindex" $ do
    it "reindexes synchronously" $ withTestEnv $ do
      _ <- insertData
      let newIndex = [qqIndexName|bloodhound-tests-twitter-index-1-reindexed|]
      _ <- tryPerformBHRequest $ deleteIndex newIndex
      _ <- performBHRequest $ createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0) defaultIndexMappingsLimits) newIndex
      _ <- assertRight =<< tryPerformBHRequest (reindex $ mkReindexRequest testIndex newIndex)
      _ <- performBHRequest $ refreshIndex newIndex
      searchResult <- assertRight =<< tryPerformBHRequest (searchByIndex newIndex $ mkSearch Nothing Nothing)
      liftIO $
        map hitSource (hits (searchHits searchResult)) `shouldBe` [Just exampleTweet]

    it "reindexes asynchronously" $ withTestEnv $ do
      _ <- insertData
      let newIndex = [qqIndexName|bloodhound-tests-twitter-index-1-reindexed|]
      _ <- tryPerformBHRequest $ deleteIndex newIndex
      _ <- performBHRequest $ createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0) defaultIndexMappingsLimits) newIndex
      taskNodeId <- assertRight =<< tryPerformBHRequest (reindexAsync $ mkReindexRequest testIndex newIndex)
      _ <- waitForTaskToComplete 10 taskNodeId
      _ <- performBHRequest $ refreshIndex newIndex
      searchResult <- assertRight =<< tryPerformBHRequest (searchByIndex newIndex (mkSearch Nothing Nothing))
      liftIO $
        (map hitSource (hits (searchHits searchResult))) `shouldBe` [Just exampleTweet]

    it "reindexWith forwards slices/max_docs URI params and reindexes synchronously" $ withTestEnv $ do
      -- Seed the source with two distinct documents so rioMaxDocs has
      -- something to cap. (insertData resets the index each call, so
      -- use insertTweetWithDocId for the second insertion.)
      _ <- insertData
      _ <- insertTweetWithDocId (exampleTweetWithAge 99) "2"
      let newIndex = [qqIndexName|bloodhound-tests-twitter-index-1-reindexed|]
          opts = defaultReindexOptions {rioSlices = Just SlicesAuto, rioMaxDocs = Just 1}
      _ <- tryPerformBHRequest $ deleteIndex newIndex
      _ <- performBHRequest $ createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0) defaultIndexMappingsLimits) newIndex
      _ <- assertRight =<< tryPerformBHRequest (reindexWith opts $ mkReindexRequest testIndex newIndex)
      _ <- performBHRequest $ refreshIndex newIndex
      searchResult <- assertRight =<< tryPerformBHRequest (searchByIndex @Tweet newIndex (mkSearch Nothing Nothing))
      -- rioMaxDocs = 1 caps the destination to one document even though
      -- the source has two.
      liftIO $ length (hits (searchHits searchResult)) `shouldBe` 1

    it "rethrottleReindex unthrottles an in-progress async reindex" $ withTestEnv $ do
      _ <- insertData
      let newIndex = [qqIndexName|bloodhound-tests-twitter-rethrottle-target|]
      _ <- tryPerformBHRequest $ deleteIndex newIndex
      _ <-
        performBHRequest $
          createIndex (IndexSettings (ShardCount 1) (ReplicaCount 0) defaultIndexMappingsLimits) newIndex
      -- Start the reindex throttled to 1 doc/sec so the task is still
      -- running when the rethrottle lands.
      let opts = defaultReindexOptions {rioRequestsPerSecond = Just 1}
      taskNodeId <- assertRight =<< tryPerformBHRequest (reindexAsyncWith opts $ mkReindexRequest testIndex newIndex)
      -- Race window: the single-doc reindex may finish before the
      -- rethrottle reaches the server, in which case the response
      -- carries an empty node list (the docstring on 'rethrottleReindex'
      -- spells this out). When the task is still running the rethrottle
      -- response lists it (>= 1 node). Either outcome is acceptable
      -- here — the test exercises the wire path and decoder regardless.
      _ <- performBHRequest $ rethrottleReindex taskNodeId RethrottleUnlimited
      _ <- waitForTaskToComplete 10 taskNodeId
      _ <- performBHRequest $ refreshIndex newIndex
      searchResult <- assertRight =<< tryPerformBHRequest (searchByIndex newIndex (mkSearch Nothing Nothing))
      liftIO $
        map hitSource (hits (searchHits searchResult)) `shouldBe` [Just exampleTweet]

assertRight :: (Show a, MonadFail m) => Either a b -> m b
assertRight (Left x) = fail $ "Expected Right, got Left: " <> show x
assertRight (Right x) = pure x

-- | The response is not used, but make sure we return it so it gets parsed
waitForTaskToComplete :: (MonadBH m, MonadFail m) => Natural -> TaskNodeId -> m (TaskResponse ReindexResponse)
waitForTaskToComplete 0 taskNodeId = fail $ "Timed out waiting for task to complete, taskNodeId = " <> show taskNodeId
waitForTaskToComplete n taskNodeId = do
  task <- assertRight =<< tryPerformBHRequest (getTask taskNodeId)
  if taskResponseCompleted task
    then return task
    else liftIO (threadDelay 100000) >> waitForTaskToComplete (n - 1) taskNodeId