packages feed

effectful-data-cache-0.1.0.1: test/Effectful/CacheSpec.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}

module Effectful.CacheSpec (spec) where

import Data.List (sort)
import Effectful
import Effectful.Cache
import System.Clock (TimeSpec (..))
import Test.Hspec
import Prelude hiding (lookup)

run :: Maybe TimeSpec -> Eff '[Cache String Int, IOE] a -> IO a
run ts = runEff . runCache ts

expired :: Maybe TimeSpec
expired = Just (TimeSpec (-1) 0)

spec :: Spec
spec = do
  it "looks up what it inserted" $ do
    r <- run Nothing $ do
      insert "a" (1 :: Int)
      lookup @String @Int "a"
    r `shouldBe` Just 1

  it "misses on an absent key" $ do
    r <- run Nothing $ lookup @String @Int "nope"
    r `shouldBe` Nothing

  it "deletes" $ do
    r <- run Nothing $ do
      insert "a" (1 :: Int)
      delete @String @Int "a"
      lookup @String @Int "a"
    r `shouldBe` Nothing

  it "lists keys and size" $ do
    (ks, n) <- run Nothing $ do
      insert "a" (1 :: Int)
      insert "b" (2 :: Int)
      (,) <$> keys @String @Int <*> size @String @Int
    sort ks `shouldBe` ["a", "b"]
    n `shouldBe` 2

  it "honours the default expiration" $ do
    r <- run expired $ do
      insert "a" (1 :: Int)
      lookup @String @Int "a"
    r `shouldBe` Nothing

  it "honours a per-entry expiration" $ do
    r <- run Nothing $ do
      insert' expired "a" (1 :: Int)
      lookup @String @Int "a"
    r `shouldBe` Nothing

  it "evicts on lookup but not on lookup'" $ do
    (afterLookup', afterLookup) <- run expired $ do
      insert "a" (1 :: Int)
      _ <- lookup' @String @Int "a"
      n1 <- size @String @Int
      _ <- lookup @String @Int "a"
      n2 <- size @String @Int
      pure (n1, n2)
    afterLookup' `shouldBe` 1
    afterLookup `shouldBe` 0

  it "filters with key" $ do
    ks <- run Nothing $ do
      insert "keep" (1 :: Int)
      insert "drop" (2 :: Int)
      filterWithKey @String @Int (\k _ -> k == "keep")
      keys @String @Int
    ks `shouldBe` ["keep"]

  it "purges everything" $ do
    n <- run Nothing $ do
      insert "a" (1 :: Int)
      insert "b" (2 :: Int)
      purge @String @Int
      size @String @Int
    n `shouldBe` 0

  it "purges only expired entries" $ do
    n <- run Nothing $ do
      insert' expired "old" (1 :: Int)
      insert "new" (2 :: Int)
      purgeExpired @String @Int
      size @String @Int
    n `shouldBe` 1

  it "round-trips the default expiration" $ do
    (old, new) <- run Nothing $ do
      b <- defaultExpiration @String @Int
      setDefaultExpiration @String @Int (Just (TimeSpec 5 0))
      a <- defaultExpiration @String @Int
      pure (b, a)
    old `shouldBe` Nothing
    new `shouldBe` Just (TimeSpec 5 0)