bloodhound-1.0.0.0: tests/Test/NeuralClearCacheSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Test.NeuralClearCacheSpec (spec) where
import Database.Bloodhound.OpenSearch3.Client qualified as OS3.Client
import Database.Bloodhound.OpenSearch3.Requests qualified as OS3Requests
import TestsUtils.Common
import TestsUtils.Import
import Prelude
-- | Pure endpoint-shape tests for the Neural Search plugin Clear Cache API
-- (@POST /_plugins/_neural/clear_cache\/{index}@) for OpenSearch 3. These
-- mirror 'Test.NeuralWarmupSpec's shape tests but assert the
-- @\/_plugins\/_neural\/clear_cache@ path, the POST method, and the
-- empty-object body. JSON decoding of the shared 'ShardsResult' response
-- is re-verified here on the clear-cache response shape
-- (@{\"_shards\":{...}}@). The wire shape for the neural clear-cache API
-- is live-verified against OS 3.7.0 in bead bloodhound-at5. The
-- neural-sparse clear-cache route is OS 3.x only: OS 1.3.x does not ship
-- the neural-search plugin (bloodhound-ie4j) and OS 2.19.5 does not
-- register the route or the sparse_vector field type (bloodhound-sh7o),
-- so neither OS1 nor OS2 ships a builder and no cross-backend parity
-- block is meaningful.
spec :: Spec
spec = describe "Neural Clear Cache API" $ do
describe "clearNeuralCache endpoint shape (OpenSearch 3)" $ do
it "POSTs to /_plugins/_neural/clear_cache/{index} when given an IndexName" $ do
let req = OS3Requests.clearNeuralCache [[qqIndexName|my-index|]]
bhRequestMethod req `shouldBe` "POST"
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_plugins", "_neural", "clear_cache", "my-index"]
getRawEndpointQueries (bhRequestEndpoint req) `shouldBe` []
it "attaches an empty JSON object as the body" $ do
let req = OS3Requests.clearNeuralCache [[qqIndexName|my-index|]]
bhRequestBody req `shouldBe` Just (encode (object []))
it "uses a distinct index in the path when given a different IndexName" $ do
let req = OS3Requests.clearNeuralCache [[qqIndexName|other-index|]]
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_plugins", "_neural", "clear_cache", "other-index"]
it "keeps the index name as a single opaque path segment" $ do
let req = OS3Requests.clearNeuralCache [[qqIndexName|my-index_42|]]
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_plugins", "_neural", "clear_cache", "my-index_42"]
-- ---------------------------------------------------------------- --
-- Multi-index (comma-separated) path rendering: bloodhound-6jy. --
-- The Neural Clear Cache API documents comma-separated lists and --
-- index patterns in the {index} path segment; the [IndexName] --
-- argument is rendered as a single comma-joined segment, matching --
-- the OpenSearch multi-target syntax. --
-- ---------------------------------------------------------------- --
describe "clearNeuralCache multi-index path rendering (OpenSearch 3)" $ do
it "renders [i1,i2,i3] as a single comma-joined path segment" $ do
let req =
OS3Requests.clearNeuralCache
[[qqIndexName|index1|], [qqIndexName|index2|], [qqIndexName|index3|]]
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_plugins", "_neural", "clear_cache", "index1,index2,index3"]
it "preserves index name order in the comma-joined segment" $ do
let req =
OS3Requests.clearNeuralCache
[[qqIndexName|zebra|], [qqIndexName|alpha|], [qqIndexName|mike|]]
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_plugins", "_neural", "clear_cache", "zebra,alpha,mike"]
it "renders an empty segment for an empty index list (server will error)" $ do
let req = OS3Requests.clearNeuralCache []
getRawEndpoint (bhRequestEndpoint req)
`shouldBe` ["_plugins", "_neural", "clear_cache", ""]
describe "clearNeuralCache is distinct from warmupNeuralIndex" $ do
-- Guards against an accidental copy-paste regression where
-- clearNeuralCache silently re-uses the warmup endpoint. The two APIs
-- share an identical wire shape (POST + empty body + ShardsResult
-- response), differing only in the @warmup@ vs @clear_cache@ path
-- segment, so the path is the single distinguishing feature.
let idx = [qqIndexName|shared-index|]
it "OS3 clear_cache path differs from warmup path" $ do
let clearPath = getRawEndpoint (bhRequestEndpoint (OS3Requests.clearNeuralCache [idx]))
warmupPath = getRawEndpoint (bhRequestEndpoint (OS3Requests.warmupNeuralIndex [idx]))
clearPath `shouldNotBe` warmupPath
describe "ShardsResult response decoding" $ do
-- Pure unit tests (no OS round-trip): lock the 'FromJSON ShardsResult'
-- decoder against the wire shape returned by
-- @POST /_plugins/_neural/clear_cache\/{index}@. Live OS 3.7.0
-- confirmed the response is the @_shards@ envelope
-- (@{"_shards":{"total":2,"successful":1,"failed":0}}@), matching
-- the warmup response shape (bead bloodhound-at5).
it "decodes {\"_shards\":{\"total\":2,\"successful\":1,\"skipped\":0,\"failed\":0}} into ShardsResult" $ do
let payload = "{\"_shards\":{\"total\":2,\"successful\":1,\"skipped\":0,\"failed\":0}}"
Just decoded = decode payload :: Maybe ShardsResult
decoded `shouldBe` ShardsResult (ShardResult 2 1 0 0)
it "rejects a payload missing the _shards envelope" $ do
let payload = "{\"acknowledged\":true}"
decoded = decode payload :: Maybe ShardsResult
decoded `shouldBe` Nothing
-- ---------------------------------------------------------------- --
-- Live round-trip: gated per-OS with os{n}OnlyIT (the --
-- 'Test.KnnClearCacheSpec' pattern). --
-- --
-- Wire shape live-verified against OS 3.7.0 (port 9205) in bead --
-- bloodhound-at5: the response is --
-- @{"_shards":{"total":2,"successful":1,"failed":0}}@ — the --
-- @_shards@ envelope, decoded into 'ShardsResult'. The endpoint --
-- requires an index with @index.sparse: true@ and a @sparse_vector@ --
-- field mapping. --
-- --
-- The neural-sparse clear-cache route is OS 3.x only: OS 1.3.x does --
-- not ship the neural-search plugin (bloodhound-ie4j), and OS 2.19.5 --
-- does not register the clear-cache route or the @sparse_vector@ --
-- field type (bloodhound-sh7o), so the live round-trip can only be --
-- exercised on OS 3. --
-- ---------------------------------------------------------------- --
describe "clearNeuralCache live round-trip" $ do
os3It <- runIO os3OnlyIT
let assertShards :: ShardsResult -> IO ()
assertShards (ShardsResult sr) = do
shardTotal sr `shouldSatisfy` (>= 1)
shardsFailed sr `shouldBe` 0
os3It "OS3: returns a ShardsResult with at least one shard and no failures" $
withTestEnv $ do
_ <- tryEsError resetOsNeuralSparseIndex
_ <- OS3.Client.warmupNeuralIndex osNeuralSparseTestIndex
result <- OS3.Client.clearNeuralCache osNeuralSparseTestIndex
liftIO $ assertShards result