{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Clod.FileSystem.TransformationsSpec -- Description : Tests for the FileSystem.Transformations module -- Copyright : (c) Fuzz Leonard, 2025 -- License : MIT -- Maintainer : ink@fuzz.ink -- Stability : experimental -- -- This module tests the file transformations functionality. module Clod.FileSystem.TransformationsSpec (spec) where import Test.Hspec import Test.QuickCheck () import System.Directory (doesFileExist, createDirectory) import System.FilePath ((>)) import System.IO.Temp (withSystemTempDirectory) import Control.Exception () import Control.Monad () import Control.Monad.Except () import Control.Monad.Reader () import qualified Data.ByteString as BS import qualified Data.Text as T import qualified Data.Text.Encoding as TE import Data.Either () import Data.Function () import Data.List () import Clod.Types import Clod.FileSystem.Transformations import Clod.TestHelpers (defaultTestConfig) -- | Test the file transformations spec :: Spec spec = do describe "transformFilename" $ do it "transforms SVG files to XML with special suffix" $ do transformFilename "logo.svg" "logo.svg" `shouldBe` "logo-svg.xml" it "leaves non-SVG files unchanged" $ do transformFilename "main.js" "main.js" `shouldBe` "main.js" transformFilename "index.html" "index.html" `shouldBe` "index.html" it "transforms hidden files by removing dot and adding 'dot--' prefix" $ do transformFilename ".gitignore" ".gitignore" `shouldBe` "dot--gitignore" transformFilename ".tool-versions" ".tool-versions" `shouldBe` "dot--tool-versions" transformFilename ".config" ".config" `shouldBe` "dot--config" it "sanitizes filenames with special characters" $ do transformFilename "file with spaces.txt" "file with spaces.txt" `shouldBe` "filewithtxt" transformFilename "#weird$chars%.js" "#weird$chars%.js" `shouldBe` "weirdchars.js" transformFilename "$$$.svg" "$$$.svg" `shouldBe` "-svg.xml" it "returns a default name for empty filenames" $ do transformFilename "" "" `shouldBe` "unnamed" describe "flattenPath" $ do it "replaces directory separators with underscores" $ do flattenPath "dir/subdir/file.txt" `shouldBe` "dir_subdir_file.txt" flattenPath "some\\windows\\path.txt" `shouldBe` "some_windows_path.txt" it "leaves filenames without separators unchanged" $ do flattenPath "file.txt" `shouldBe` "file.txt" describe "sanitizeFilename" $ do it "removes invalid characters from filenames" $ do sanitizeFilename "hello world.txt" `shouldBe` "helloworld.txt" sanitizeFilename "test!@#$%^&*().txt" `shouldBe` "test.txt" sanitizeFilename "*special*.json" `shouldBe` "special.json" it "preserves valid characters" $ do sanitizeFilename "valid-name_123.js" `shouldBe` "valid-name_123.js" sanitizeFilename "a.b.c.d.e.f" `shouldBe` "a.b.c.d.e.f" it "returns 'unnamed' for empty strings" $ do sanitizeFilename "" `shouldBe` "unnamed" sanitizeFilename "#@$%^" `shouldBe` "unnamed" describe "transformFileContent" $ do it "transforms file content using the provided function" $ do -- Create a temp directory withSystemTempDirectory "clod-test" $ \tmpDir -> do -- Create a test file let srcPath = tmpDir > "source.txt" destPath = tmpDir > "dest.txt" content = "hello world" transformFn = T.toUpper . TE.decodeUtf8 BS.writeFile srcPath (TE.encodeUtf8 (T.pack content)) -- Create capabilities let readCap = fileReadCap [tmpDir] writeCap = fileWriteCap [tmpDir] config = defaultTestConfig tmpDir -- Run the function result <- runClodM config $ transformFileContent readCap writeCap transformFn srcPath destPath -- Verify the destination file has the transformed content result `shouldBe` Right () destContent <- TE.decodeUtf8 <$> BS.readFile destPath destContent `shouldBe` "HELLO WORLD" it "fails when source is outside read capability" $ do -- Create a temp directory structure withSystemTempDirectory "clod-test" $ \tmpDir -> do withSystemTempDirectory "clod-test-outside" $ \outsideDir -> do -- Create source file in the outside directory let srcPath = outsideDir > "source.txt" destPath = tmpDir > "dest.txt" content = "hello world" transformFn = T.toUpper . TE.decodeUtf8 BS.writeFile srcPath (TE.encodeUtf8 (T.pack content)) -- Create read capability that only includes the temp dir let readCap = fileReadCap [tmpDir] writeCap = fileWriteCap [tmpDir] config = defaultTestConfig tmpDir -- Run the function result <- runClodM config $ transformFileContent readCap writeCap transformFn srcPath destPath -- Verify the operation failed case result of Left (CapabilityError _) -> return () _ -> expectationFailure "Expected CapabilityError but got different result" -- Verify the destination file wasn't created destExists <- doesFileExist destPath destExists `shouldBe` False describe "SVG to XML transformation" $ do it "preserves SVG content with XML extension" $ do -- Create a temp directory structure withSystemTempDirectory "clod-test" $ \tmpDir -> do -- Create source and destination directories let srcDir = tmpDir > "src" destDir = tmpDir > "dest" svgPath = srcDir > "icon.svg" xmlPath = destDir > "icon-svg.xml" svgContent = "" createDirectory srcDir createDirectory destDir BS.writeFile svgPath (TE.encodeUtf8 (T.pack svgContent)) -- Create capabilities let readCap = fileReadCap [srcDir, destDir] writeCap = fileWriteCap [destDir] transformFn = TE.decodeUtf8 -- Transform ByteString to Text config = defaultTestConfig tmpDir -- Run the transformation _ <- runClodM config $ transformFileContent readCap writeCap transformFn svgPath xmlPath -- Verify the XML file was created with the same content xmlContent <- TE.decodeUtf8 <$> BS.readFile xmlPath T.unpack xmlContent `shouldBe` svgContent describe "End-to-end transformation" $ do it "works with complex transformations" $ do -- Create a temp directory withSystemTempDirectory "clod-test" $ \tmpDir -> do -- Create test files let jsPath = tmpDir > "script.js" htmlPath = tmpDir > "page.html" jsContent = "console.log('Hello, world!');" htmlContent = "