packages feed

spire-0.1.0.0: test/Main.hs

module Main (main) where

import Spire
import Data.IORef


-- ===================================================================
-- Test helpers
-- ===================================================================

assert :: String -> Bool -> IO ()
assert label True  = putStrLn $ "  OK: " ++ label
assert label False = error   $ "FAIL: " ++ label


-- ===================================================================
-- 1. Basic Service creation and execution
-- ===================================================================

testServiceBasic :: IO ()
testServiceBasic = do
  -- Pure echo
  let echo :: Service IO String String
      echo = Service pure
  result <- runService echo "hello"
  assert "echo service returns input" (result == "hello")

  -- Service with computation
  let double :: Service IO Int Int
      double = Service $ \n -> pure (n * 2)
  result2 <- runService double 21
  assert "double service computes" (result2 == 42)


-- ===================================================================
-- 2. mapResponse
-- ===================================================================

testMapResponse :: IO ()
testMapResponse = do
  let svc :: Service IO Int Int
      svc = Service $ \n -> pure (n + 1)
      svc' = mapResponse (* 10) svc
  result <- runService svc' 5
  assert "mapResponse transforms output" (result == 60)  -- (5+1)*10


-- ===================================================================
-- 3. mapRequest
-- ===================================================================

testMapRequest :: IO ()
testMapRequest = do
  let svc :: Service IO Int String
      svc = Service $ \n -> pure (show n)
      svc' = mapRequest length svc  -- String -> Int via length
  result <- runService svc' "hello"
  assert "mapRequest transforms input" (result == "5")


-- ===================================================================
-- 4. dimap
-- ===================================================================

testDimap :: IO ()
testDimap = do
  let svc :: Service IO Int Int
      svc = Service $ \n -> pure (n * 2)
      svc' = dimap length show svc  -- String -> show(length*2)
  result <- runService svc' "hey"
  assert "dimap transforms both" (result == "6")  -- length "hey" = 3, *2 = 6


-- ===================================================================
-- 5. Middleware via `middleware` constructor
-- ===================================================================

testMiddleware :: IO ()
testMiddleware = do
  ref <- newIORef ([] :: [String])
  let svc :: Service IO String String
      svc = Service $ \req -> do
        modifyIORef' ref (++ ["handler:" ++ req])
        pure ("resp:" ++ req)

      logging :: Middleware IO String String
      logging = middleware $ \inner -> Service $ \req -> do
        modifyIORef' ref (++ ["before"])
        resp <- runService inner req
        modifyIORef' ref (++ ["after"])
        pure resp

  let svc' = svc |> logging
  result <- runService svc' "test"
  log <- readIORef ref

  assert "middleware wraps service" (result == "resp:test")
  assert "middleware runs before/after" (log == ["before", "handler:test", "after"])


-- ===================================================================
-- 6. before combinator
-- ===================================================================

testBefore :: IO ()
testBefore = do
  ref <- newIORef ([] :: [String])
  let svc :: Service IO String String
      svc = Service $ \req -> do
        modifyIORef' ref (++ ["handler"])
        pure req

      logBefore :: Middleware IO String String
      logBefore = before $ \req -> modifyIORef' ref (++ ["before:" ++ req])

  let svc' = svc |> logBefore
  _ <- runService svc' "x"
  log <- readIORef ref
  assert "before runs before handler" (log == ["before:x", "handler"])


-- ===================================================================
-- 7. after combinator
-- ===================================================================

testAfter :: IO ()
testAfter = do
  ref <- newIORef ([] :: [String])
  let svc :: Service IO String String
      svc = Service $ \req -> do
        modifyIORef' ref (++ ["handler"])
        pure ("resp:" ++ req)

      logAfter :: Middleware IO String String
      logAfter = after $ \_ resp -> modifyIORef' ref (++ ["after:" ++ resp])

  let svc' = svc |> logAfter
  _ <- runService svc' "x"
  log <- readIORef ref
  assert "after runs after handler" (log == ["handler", "after:resp:x"])


-- ===================================================================
-- 8. around combinator
-- ===================================================================

testAround :: IO ()
testAround = do
  ref <- newIORef ([] :: [String])
  let svc :: Service IO String String
      svc = Service $ \req -> do
        modifyIORef' ref (++ ["handler:" ++ req])
        pure req

      wrap :: Middleware IO String String
      wrap = around $ \req callInner -> do
        modifyIORef' ref (++ ["around:before"])
        resp <- callInner (req ++ "!")
        modifyIORef' ref (++ ["around:after"])
        pure (resp ++ "!!")

  let svc' = svc |> wrap
  result <- runService svc' "hi"
  log <- readIORef ref

  assert "around wraps call" (result == "hi!!!")
  assert "around sees modified request" (log == ["around:before", "handler:hi!", "around:after"])


-- ===================================================================
-- 9. Layer composition with |>
-- ===================================================================

testPipeComposition :: IO ()
testPipeComposition = do
  ref <- newIORef ([] :: [String])
  let svc :: Service IO String String
      svc = Service $ \req -> do
        modifyIORef' ref (++ ["handler"])
        pure req

      layer1 :: Middleware IO String String
      layer1 = before $ \_ -> modifyIORef' ref (++ ["L1"])

      layer2 :: Middleware IO String String
      layer2 = before $ \_ -> modifyIORef' ref (++ ["L2"])

  -- svc |> layer1 |> layer2
  -- layer2 wraps (layer1 wraps svc)
  -- Request flow: layer2 -> layer1 -> handler
  let svc' = svc |> layer1 |> layer2
  _ <- runService svc' "x"
  log <- readIORef ref
  assert "|> applies layers inside-out" (log == ["L2", "L1", "handler"])


-- ===================================================================
-- 10. composeLayer
-- ===================================================================

testComposeLayer :: IO ()
testComposeLayer = do
  ref <- newIORef ([] :: [String])
  let svc :: Service IO String String
      svc = Service $ \req -> do
        modifyIORef' ref (++ ["handler"])
        pure req

      layer1 :: Middleware IO String String
      layer1 = before $ \_ -> modifyIORef' ref (++ ["L1"])

      layer2 :: Middleware IO String String
      layer2 = before $ \_ -> modifyIORef' ref (++ ["L2"])

      -- composed applies layer1 first (inner), then layer2 (outer)
      composed = composeLayer layer2 layer1

  let svc' = svc |> composed
  _ <- runService svc' "x"
  log <- readIORef ref
  assert "composeLayer: outer wraps inner" (log == ["L2", "L1", "handler"])


-- ===================================================================
-- 11. identity layer
-- ===================================================================

testIdentity :: IO ()
testIdentity = do
  let svc :: Service IO Int Int
      svc = Service $ \n -> pure (n + 1)
      svc' = svc |> identity
  result <- runService svc' 5
  assert "identity layer is no-op" (result == 6)


-- ===================================================================
-- 12. <| operator (right-to-left)
-- ===================================================================

testRightToLeft :: IO ()
testRightToLeft = do
  ref <- newIORef ([] :: [String])
  let svc :: Service IO String String
      svc = Service $ \req -> do
        modifyIORef' ref (++ ["handler"])
        pure req

      layer1 :: Middleware IO String String
      layer1 = before $ \_ -> modifyIORef' ref (++ ["L1"])

  let svc' = layer1 <| svc
  _ <- runService svc' "x"
  log <- readIORef ref
  assert "<| applies layer" (log == ["L1", "handler"])


-- ===================================================================
-- Main
-- ===================================================================

main :: IO ()
main = do
  putStrLn "Spire tests:"
  putStrLn ""
  putStrLn "Service basics:"
  testServiceBasic
  putStrLn ""
  putStrLn "Transformers:"
  testMapResponse
  testMapRequest
  testDimap
  putStrLn ""
  putStrLn "Middleware:"
  testMiddleware
  testBefore
  testAfter
  testAround
  putStrLn ""
  putStrLn "Composition:"
  testPipeComposition
  testComposeLayer
  testIdentity
  testRightToLeft
  putStrLn ""
  putStrLn "All spire tests passed."