packages feed

valiant-cli-0.1.0.0: test/Valiant/CLI/DiscoverSpec.hs

module Valiant.CLI.DiscoverSpec (spec) where

import Data.ByteString.Char8 qualified as BS8
import Data.List (sort)
import Valiant.CLI.Discover
import System.Directory (createDirectoryIfMissing)
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
import Test.Hspec

spec :: Spec
spec = do
  describe "discoverSqlFiles" $ do
    it "finds .sql files recursively" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        createDirectoryIfMissing True (tmpDir </> "users")
        BS8.writeFile (tmpDir </> "users" </> "find.sql") "SELECT 1"
        BS8.writeFile (tmpDir </> "list.sql") "SELECT 2"
        files <- discoverSqlFiles tmpDir
        sort (map sqlRelPath files) `shouldBe` ["list.sql", "users/find.sql"]

    it "returns empty for non-existent directory" $ do
      files <- discoverSqlFiles "/tmp/valiant-nonexistent-dir-12345"
      map sqlRelPath files `shouldBe` []

    it "ignores non-.sql files" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        BS8.writeFile (tmpDir </> "query.sql") "SELECT 1"
        BS8.writeFile (tmpDir </> "notes.txt") "not sql"
        BS8.writeFile (tmpDir </> "readme.md") "not sql"
        files <- discoverSqlFiles tmpDir
        map sqlRelPath files `shouldBe` ["query.sql"]

    it "reads file content correctly" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        let content = "SELECT id FROM users WHERE id = $1"
        BS8.writeFile (tmpDir </> "test.sql") content
        files <- discoverSqlFiles tmpDir
        case files of
          [f] -> sqlContent f `shouldBe` content
          _ -> expectationFailure "expected exactly one file"

  describe "discoverInlineSql" $ do
    it "extracts query calls from Haskell source" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        let hsFile = tmpDir </> "Queries.hs"
        writeFile hsFile $ unlines
          [ "module Queries where"
          , "import Valiant"
          , "findById = query \"SELECT id, name FROM users WHERE id = $1\""
          , "listAll = query \"SELECT id, name FROM users\""
          ]
        files <- discoverInlineSql [hsFile]
        length files `shouldBe` 2

    it "extracts SQL content correctly" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        let hsFile = tmpDir </> "Q.hs"
        writeFile hsFile "q = query \"SELECT 1\""
        files <- discoverInlineSql [hsFile]
        case files of
          [f] -> sqlContent f `shouldBe` "SELECT 1"
          _ -> expectationFailure $ "expected 1 file, got " <> show (length files)

    it "sets path to <inline>" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        let hsFile = tmpDir </> "Q.hs"
        writeFile hsFile "q = query \"SELECT 1\""
        files <- discoverInlineSql [hsFile]
        case files of
          [f] -> sqlRelPath f `shouldBe` "<inline>"
          _ -> expectationFailure "expected 1 file"

    it "deduplicates identical SQL across files" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        let f1 = tmpDir </> "A.hs"
            f2 = tmpDir </> "B.hs"
        writeFile f1 "a = query \"SELECT 1\""
        writeFile f2 "b = query \"SELECT 1\""
        files <- discoverInlineSql [f1, f2]
        length files `shouldBe` 1

    it "handles multiple queries on separate lines" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        let hsFile = tmpDir </> "Q.hs"
        writeFile hsFile $ unlines
          [ "a = query \"SELECT 1\""
          , "b = query \"SELECT 2\""
          , "c = query \"SELECT 3\""
          ]
        files <- discoverInlineSql [hsFile]
        length files `shouldBe` 3

    it "ignores queryFile calls" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        let hsFile = tmpDir </> "Q.hs"
        writeFile hsFile "q = queryFile \"users/find.sql\""
        files <- discoverInlineSql [hsFile]
        length files `shouldBe` 0

    it "ignores lines without query calls" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        let hsFile = tmpDir </> "Q.hs"
        writeFile hsFile $ unlines
          [ "module Q where"
          , "import Valiant"
          , "-- this is a comment with query in it"
          , "x = 42"
          ]
        files <- discoverInlineSql [hsFile]
        length files `shouldBe` 0

    it "returns empty for no .hs files" $ do
      files <- discoverInlineSql []
      length files `shouldBe` 0

    it "extracts SQL with named params" $ do
      withSystemTempDirectory "valiant-test" $ \tmpDir -> do
        let hsFile = tmpDir </> "Q.hs"
        writeFile hsFile "q = query \"SELECT id FROM users WHERE name = :name AND active = :active\""
        files <- discoverInlineSql [hsFile]
        case files of
          [f] -> sqlContent f `shouldBe` "SELECT id FROM users WHERE name = :name AND active = :active"
          _ -> expectationFailure $ "expected 1 file, got " <> show (length files)