packages feed

tricorder-0.2.0.0: test/Unit/Tricorder/SocketSpec.hs

module Unit.Tricorder.SocketSpec (spec_Socket) where

import Atelier.Effects.File (File, runFile)
import Effectful (IOE, runEff)
import System.IO (hClose, hGetLine, openFile, writeFile)
import Test.Hspec (Spec, describe, it, shouldBe)

import Tricorder.Socket.Client (isDaemonReady)
import Tricorder.Socket.UnixSocket
    ( SocketScript (..)
    , UnixSocket
    , acceptHandle
    , bindSocket
    , removeSocketFile
    , runUnixSocketIO
    , runUnixSocketScripted
    , socketFileExists
    )


spec_Socket :: Spec
spec_Socket = do
    describe "runUnixSocketScripted" testScripted
    describe "isDaemonReady" testReady


--------------------------------------------------------------------------------
-- Scripted interpreter tests
--------------------------------------------------------------------------------

testScripted :: Spec
testScripted = do
    describe "socketFileExists" do
        it "returns True when scripted" do
            result <- runScripted [NextFileCheck True] $ socketFileExists "/"
            result `shouldBe` True

        it "returns False when scripted" do
            result <- runScripted [NextFileCheck False] $ socketFileExists "/"
            result `shouldBe` False

    describe "removeSocketFile" do
        it "is always a no-op" do
            -- No NextFileCheck/NextAccept needed; just returns ()
            runScripted [] $ removeSocketFile "/nonexistent/path"

    describe "acceptHandle" do
        it "returns the scripted handle, readable from a file" do
            let tmpPath = "/tmp/tricorder-socket-accept-test.txt"
            writeFile tmpPath "hello from test\n"
            h <- liftIO $ openFile tmpPath ReadMode
            line <- runScripted [NextAccept h] $ do
                sock <- bindSocket "/"
                h' <- acceptHandle sock
                liftIO $ hGetLine h'
            liftIO $ hClose h
            line `shouldBe` "hello from test"


--------------------------------------------------------------------------------
-- isDaemonReady (real IO interpreter)
--------------------------------------------------------------------------------

testReady :: Spec
testReady = do
    it "returns False when nothing is listening on the path" do
        -- A connect to a non-existent socket must be caught, not thrown: this is
        -- the race the start/status path hit before the socket was bound.
        result <- runIO' $ isDaemonReady "/tmp/tricorder-isdaemonready-absent.sock"
        result `shouldBe` False

    it "returns True once a socket is bound and listening" do
        let path = "/tmp/tricorder-isdaemonready-bound.sock"
        result <- runIO' do
            removeSocketFile path
            _ <- bindSocket path
            isDaemonReady path
        runIO' $ removeSocketFile path
        result `shouldBe` True


--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------

-- | Run scripted socket operations (no Delay needed).
runScripted :: [SocketScript] -> Eff '[UnixSocket, File, IOE] a -> IO a
runScripted script = runEff . runFile . runUnixSocketScripted script


-- | Run socket operations against the real IO interpreter.
runIO' :: Eff '[UnixSocket, File, IOE] a -> IO a
runIO' = runEff . runFile . runUnixSocketIO