packages feed

tricorder-0.1.0.0: test/Unit/Tricorder/SourceLookupSpec.hs

module Unit.Tricorder.SourceLookupSpec (spec_SourceLookup) where

import Atelier.Effects.Cache (Cache, runCacheForever)
import Atelier.Effects.FileSystem (FileSystem (..))
import Atelier.Effects.Log (Log, runLogNoOp)
import Effectful (IOE, runEff)
import Effectful.Concurrent (Concurrent, runConcurrent)
import Effectful.Dispatch.Dynamic (interpret_)
import Test.Hspec

import Data.Map.Strict qualified as Map

import Tricorder.Effects.GhcPkg (GhcPkg, GhcPkgScript (..), runGhcPkgScripted)
import Tricorder.GhcPkg.Types (ModuleName, PackageId, SourceQuery (..))
import Tricorder.SourceLookup (ModuleSourceResult (..), ReExport, lookupModuleSource)


spec_SourceLookup :: Spec
spec_SourceLookup = do
    describe "lookupModuleSource" testLookupModuleSource


testLookupModuleSource :: Spec
testLookupModuleSource = do
    it "returns SourceFound when module, haddock-html, and file are present" do
        result <-
            runTest
                [ NextFindModule (Just "pkg-1.0")
                , NextGetHaddockHtml (Just "/haddock/pkg")
                ]
                (Map.singleton "/haddock/pkg/src/Foo.html" sampleHtml)
                (lookupModuleSource (wholeModule "Foo"))
        result `shouldBe` SourceFound (wholeModule "Foo") "module Foo where" []

    it "returns SourceFound on second call without re-querying GhcPkg (cache hit)" do
        -- Only one NextFindModule and one NextGetHaddockHtml in the script.
        -- The second call must come entirely from cache (no script pop).
        (r1, r2) <- runTest
            [ NextFindModule (Just "pkg-1.0")
            , NextGetHaddockHtml (Just "/haddock/pkg")
            ]
            (Map.singleton "/haddock/pkg/src/Foo.html" sampleHtml)
            $ do
                r1 <- lookupModuleSource (wholeModule "Foo")
                r2 <- lookupModuleSource (wholeModule "Foo")
                pure (r1, r2)
        r1 `shouldBe` SourceFound (wholeModule "Foo") "module Foo where" []
        r2 `shouldBe` SourceFound (wholeModule "Foo") "module Foo where" []

    it "returns SourceNotFound when findModule returns Nothing" do
        result <-
            runTest
                [NextFindModule Nothing]
                Map.empty
                (lookupModuleSource (wholeModule "Unknown"))
        result `shouldBe` SourceNotFound (wholeModule "Unknown")

    it "returns SourceNoHaddock when getHaddockHtml returns Nothing" do
        result <-
            runTest
                [ NextFindModule (Just "no-docs-1.0")
                , NextGetHaddockHtml Nothing
                ]
                Map.empty
                (lookupModuleSource (wholeModule "Foo"))
        result `shouldBe` SourceNoHaddock (wholeModule "Foo") "no-docs-1.0"

    it "returns SourceNoHaddock when the html file does not exist" do
        result <-
            runTest
                [ NextFindModule (Just "pkg-1.0")
                , NextGetHaddockHtml (Just "/haddock/pkg")
                ]
                Map.empty
                (lookupModuleSource (wholeModule "Foo"))
        result `shouldBe` SourceNoHaddock (wholeModule "Foo") "pkg-1.0"

    it "handles two different module names independently" do
        (r1, r2) <- runTest
            [ NextFindModule (Just "pkg-a-1.0")
            , NextGetHaddockHtml (Just "/haddock/pkg-a")
            , NextFindModule (Just "pkg-b-1.0")
            , NextGetHaddockHtml (Just "/haddock/pkg-b")
            ]
            ( Map.fromList
                [ ("/haddock/pkg-a/src/Foo.html", sampleHtml)
                , ("/haddock/pkg-b/src/Bar.html", barHtml)
                ]
            )
            $ do
                r1 <- lookupModuleSource (wholeModule "Foo")
                r2 <- lookupModuleSource (wholeModule "Bar")
                pure (r1, r2)
        r1 `shouldBe` SourceFound (wholeModule "Foo") "module Foo where" []
        r2 `shouldBe` SourceFound (wholeModule "Bar") "module Bar where" []


--------------------------------------------------------------------------------
-- Fixtures
--------------------------------------------------------------------------------

sampleHtml :: LByteString
sampleHtml = "<html><body><pre id=\"src\"><span>module</span> Foo <span>where</span></pre></body></html>"


barHtml :: LByteString
barHtml = "<html><body><pre id=\"src\"><span>module</span> Bar <span>where</span></pre></body></html>"


-- | Helper: a whole-module query (no function filter).
wholeModule :: ModuleName -> SourceQuery
wholeModule m = SourceQuery {moduleName = m, function = Nothing}


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

runFileSystemScripted :: Map FilePath LByteString -> Eff (FileSystem : es) a -> Eff es a
runFileSystemScripted files = interpret_ \case
    DoesFileExist path -> pure $ Map.member path files
    ReadFileLbsFrom path _ -> pure $ fromMaybe "" (Map.lookup path files)
    _ -> error "FileSystemScripted: unexpected operation"


runTest
    :: [GhcPkgScript]
    -> Map FilePath LByteString
    -> Eff '[Cache ModuleName PackageId, Cache (PackageId, SourceQuery) (Text, [ReExport]), FileSystem, GhcPkg, Log, Concurrent, IOE] a
    -> IO a
runTest pkgScript files action =
    runEff
        . runConcurrent
        . runLogNoOp
        . runGhcPkgScripted pkgScript
        . runFileSystemScripted files
        . runCacheForever @(PackageId, SourceQuery) @(Text, [ReExport])
        . runCacheForever @ModuleName @PackageId
        $ action