packages feed

pfile-0.1.0.0: test/PFile/TrashCan/Tests.hs

{-# LANGUAGE BlockArguments    #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE NamedFieldPuns    #-}
{-# LANGUAGE OverloadedStrings #-}

module PFile.TrashCan.Tests
  ( tests
  ) where

import           Control.Monad.Writer     (execWriterT)
import qualified Data.HashSet             as Set
import           PFile.Path               (doesPathExist, (<//>))
import qualified PFile.Path               as Path
import           PFile.Path.Tests         (ensureWriteFile)
import           PFile.Tests.Env          (withTempDirectory)
import           PFile.TrashCan           (TrashCan (..))
import qualified PFile.TrashCan           as TrashCan
import           PFile.TrashCan.Tests.Env ()
import           Protolude
import           System.FilePath          ((</>))
import           Test.Hspec
  ( around
  , describe
  , it
  , shouldBe
  , shouldReturn
  )
import           Test.HUnit               (assertFailure)
import           Test.Tasty               (TestTree, testGroup)
import           Test.Tasty.Hspec         (testSpec)

tests :: IO TestTree
tests = do
  unitTests_ <- unitTests
  pure $ testGroup "PFile.TrashCan" [unitTests_]

unitTests :: IO TestTree
unitTests = testSpec "Unit Tests" $
  around withTempDirectory do
    describe "trash" do
      it "trashes a file properly" \temporaryRoot ->
        (,) <$> filesNames <*> filesContents & traverse_ \(name, contents) ->
          trashFileTest (temporaryRoot <//> "origin" </> name) contents
    describe "restore" do
      it "restores a file properly" \temporaryRoot ->
        (,) <$> filesNames <*> filesContents & traverse_ \(name, contents) ->
          restoreFileTest (temporaryRoot <//> "origin" </> name) contents
    describe "restoreAll" do
      it "restores all files properly" \temporaryRoot ->
        restoreAllTest
          $ zip
            (filesNames <&> \name -> temporaryRoot <//> "origin" </> name)
            filesContents
    describe "remove" do
      it "removes empty trashCan properly" \_ ->
        removeTest []
      it "removes non-empty trashCan properly" \temporaryRoot ->
        removeTest
          $ zip
            (filesNames <&> \name -> temporaryRoot <//> "origin" </> name)
            filesContents
    describe "dumpTrashed" do
      it "dumps trashed list properly" \temporaryRoot ->
        dumpTrashedTest
          $ zip
            (filesNames <&> \name -> temporaryRoot <//> "origin" </> name)
            filesContents
  where
    filesNames :: [FilePath]
    filesNames =
      [ "9h(ILN:/b<t{mg2"
      , "'*_"
      , "*CDy/|\rHh?/!@"
      , "C~(?3|(y/8&"
      , "1t=_rL'/%JB0\"8"
      , "ej/s$\\K.5ZT/Z*6rn-j\t3/x':@bj"
      , "YJ/%X4C$KMr=/z\r"
      , "f~NO*a j@7"
      , "swSgYJUM9r/.EYR\"wADf/*DJd:("
      , " Y(7*/|/<-[6nv3"
      , "yfF~"
      , "zETbVl w7./]^/[m>+"
      , ";)A/:H'^og@GB"
      , "\\g2\n( M#/scIU/o^A/Nj>/nC,ASzz7q"
      , "SB$N/;F"
      , "?,@Zo\tz/*|1%%\tWm}/ k>oAV_7M/yGn$+c 7AS"
      , "\"\\_"
      , "4g/M;FD901"
      , "\t$sL;G9/3zZ|p[1/w\\6f]/=926UD"
      ]

    filesContents :: [Text]
    filesContents =
      [ "\54951\170056NU+\"\92881a-\142680\177134\SOXKE\1097408\63631hq\1021187\ETBd\1019100#\1028440\1041796\1000925Gat"
      , "RD\177342\1036654\1084661\189502\1096769 \1011758\96759\67362\1058870\96334\74438o\1094509>\1095213[,\37703SFb\147814\25819F\153345#b"
      , "\SUB\170178\178560yv\1011719Q/\\$>L\GS7\1048669\&1z\1093012\1086214r\199200P,\nOrG{\1002826L"
      , "et\39933\152786\71222\51388WFY\DC1'il9kH\132409E\\C\1024119\68094]\991083B\11343\DC1Ez\18164"
      , "!dB\999918\SUBo`-<QrS<9+MT\1044433\DELt\96208\17044\1090040@1PP\147330]K"
      , "/7!SH\98989\183950J\"\138051\161420\35056WE<2F8&~)c\181084&!\78704N_$\15030"
      , "#[&)xPg$nH]9mt60\94378jU=*B^F[\29380x\199030D\92604"
      , "CD\149540\&1TqCL\132951H:,P)^4\2636I&{r?CmS\179807Nf*'"
      , "\SIhFN:CH=OS(2?{A+\GS\SOA\STXG{|nL?8!L_"
      , "CpH`8a?\NAK,\GS\ETXqG\ETXN9\EMa)\\1AYEco3Fd_"
      ]

trashFileTest :: Path.Absolute -> Text -> IO ()
trashFileTest path contents = withTrashCan \trashCan -> do
  ensureWriteFile path contents
  ensureTrash path trashCan

restoreFileTest :: Path.Absolute -> Text -> IO ()
restoreFileTest originPath contents = withTrashCan \trashCan -> do
  ensureWriteFile originPath contents
  ensureTrash originPath trashCan
  TrashCan.trashed trashCan >>= \case
    [mountPath] -> TrashCan.restore trashCan mountPath & runExceptT >>= \case
      Left error -> assertFailure
        $ "Expected restore to succeed, instead got an error: " <> show error
      Right () -> readFile (Path.unAbsolute originPath) `shouldReturn` contents
    xs -> assertFailure
      $ "Expected single path in trashed list, instead got: " <> show xs

restoreAllTest :: [(Path.Absolute, Text)] -> IO ()
restoreAllTest files = withTrashCan \trashCan -> do
  files & traverse_ (uncurry ensureWriteFile)
  files & traverse_ \(originPath, _) -> ensureTrash originPath trashCan
  length <$> TrashCan.trashed trashCan `shouldReturn` length files
  TrashCan.restoreAll trashCan & execWriterT >>= \case
    errors@(_:_) -> assertFailure
      $ "Expected restoreAll to succeed, instead got errors: " <> show errors
    [] -> files & traverse_ \(originPath, contents) ->
      readFile (Path.unAbsolute originPath) `shouldReturn` contents

removeTest :: [(Path.Absolute, Text)] -> IO ()
removeTest files = withTrashCan \trashCan -> do
  files & traverse_ (uncurry ensureWriteFile)
  files & traverse_ \(originPath, _) -> ensureTrash originPath trashCan
  ensureRemove trashCan

dumpTrashedTest :: [(Path.Absolute, Text)] -> IO ()
dumpTrashedTest files = withTrashCan \trashCan -> do
  files & traverse_ (uncurry ensureWriteFile)
  files & traverse_ \(originPath, _) -> ensureTrash originPath trashCan
  TrashCan.dumpTrashed trashCan & runExceptT >>= \case
    Left error -> assertFailure
      $ "Expected dumpTrashed to succeed, instead got an error: " <> show error
    Right trashedPath -> do
      unlessM (doesPathExist trashedPath) . assertFailure
        $ "Expected file \"" <> Path.unAbsolute trashedPath <> "\" to exist"
      actualPaths <-
        readFile (Path.unAbsolute trashedPath)
        <&> fmap (Path.Absolute . toS) . lines
      expectedPaths <- TrashCan.trashed trashCan
      Set.fromList actualPaths `shouldBe` Set.fromList expectedPaths

withTrashCan :: (TrashCan -> IO a) -> IO a
withTrashCan callback = do
  trashCan <- ensureCreate
  result <- callback trashCan
  ensureRemove trashCan
  pure result

ensureCreate :: IO TrashCan
ensureCreate = runExceptT TrashCan.create >>= \case
  Left error -> assertFailure
    $ "Expected create to succeed, instead got an error: " <> show error
  Right trashCan -> pure trashCan

ensureRemove :: TrashCan -> IO ()
ensureRemove trashCan@TrashCan {root} =
  TrashCan.remove trashCan & runExceptT >>= \case
    Left error -> assertFailure
      $ "Expected remove to succeed, instead got an error: " <> show error
    Right () -> whenM (doesPathExist root) . assertFailure
      $ "Expected directory \"" <> Path.unAbsolute root <> "\" to not exist"

ensureTrash :: Path.Absolute -> TrashCan -> IO ()
ensureTrash path trashCan = TrashCan.trash path trashCan & runExceptT >>= \case
  Left error -> assertFailure
    $ "Expected trash to succeed, instead got an error: " <> show error
  Right () -> whenM (doesPathExist path) . assertFailure
    $ "Expected file \"" <> Path.unAbsolute path <> "\" to not exist"