reload-0.0.0.1: test/Language/Haskell/Reload/FileBrowserSpec.hs
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
module Language.Haskell.Reload.FileBrowserSpec (spec) where
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Network.Wai.Test
import Data.Aeson hiding (json)
import Language.Haskell.Reload (app)
import Language.Haskell.Reload.Config
import System.Directory
import System.FilePath
import Data.List (sortBy, isPrefixOf)
import Data.Text (Text,toLower)
import qualified Data.HashMap.Strict as HM
import Data.Ord
import Data.Maybe
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString.Char8 as B8
import Data.Monoid
import Control.Monad
spec :: Spec
spec = after cleanUp $ with (app False)$ do
describe "GET /files" $ do
it "responds with files and folders at root" $ do
js <- liftIO $ do
root <- getCurrentDirectory
fs <- getDirectoryContents root
mv <- config root
let showHidden = showHiddenFiles mv
let filt = if showHidden
then (\x->x/="." && x/="..")
else (not . ("." `isPrefixOf`))
let visible = filter (filt . takeFileName) fs
sortBy sortFs <$> mapM (fileToJson root) visible
get "/files" `shouldRespondWith` (fromValue $ toJSON js)
get "/files/" `shouldRespondWith` (fromValue $ toJSON js)
it "get source root folder" $ do
get "/files/src" `shouldRespondWith` [json|[{"type":"dir","path":"src/Language"}]|]
get "/files/src/" `shouldRespondWith` [json|[{"type":"dir","path":"src/Language"}]|]
it "doesn't accept .." $ do
get "/files/../reload/src" `shouldRespondWith` 404
it "doesn't accept absolutes" $ do
get "/files//tmp" `shouldRespondWith` 404
describe "GET /file" $ do
it "get root plain file" $ do
testFile "LICENSE" "text/plain"
it "get root cabal file" $ do
testFile "reload.cabal" "text/x-cabal"
it "get root yaml file" $ do
testFile "stack.yaml" "text/x-yaml"
it "get src haskell file" $ do
testFile "src/Language/Haskell/Reload.hs" "text/x-haskell"
it "doesn't accept .." $ do
get "/file/../reload/LICENSE" `shouldRespondWith` 404
it "doesn't accept absolutes" $ do
get "/files//etc/passwd" `shouldRespondWith` 404
describe "PUT /files" $ do
it "creates a new folder" $ do
liftIO $ do
root <- getCurrentDirectory
ex <- doesDirectoryExist (root </> "tempTest")
when ex $ do
removeDirectoryRecursive (root </> "tempTest")
ex2 <- doesDirectoryExist (root </> "tempTest")
ex2 `shouldBe` False
put "/files/tempTest" B.empty `shouldRespondWith` [json|null|] {matchStatus = 201}
liftIO $ do
root <- getCurrentDirectory
ex <- doesDirectoryExist (root </> "tempTest")
ex `shouldBe` True
put "/files/tempTest" B.empty `shouldRespondWith` [json|null|] {matchStatus = 204}
it "doesn't accept .." $ do
put "/files/../tempTest" B.empty `shouldRespondWith` 404
it "doesn't accept absolutes" $ do
put "/files//tmp/tempTest" B.empty `shouldRespondWith` 404
describe "DELETE /files" $ do
it "deletes an existing folder" $ do
liftIO $ do
root <- getCurrentDirectory
createDirectoryIfMissing True (root </> "tempTest")
ex <- doesDirectoryExist (root </> "tempTest")
ex `shouldBe` True
delete "/files/tempTest" `shouldRespondWith` [json|null|] {matchStatus = 200}
liftIO $ do
root <- getCurrentDirectory
ex <- doesDirectoryExist (root </> "tempTest")
ex `shouldBe` False
delete "/files/tempTest" `shouldRespondWith` [json|null|] {matchStatus = 204}
it "doesn't accept .." $ do
delete "/files/../tempTest" `shouldRespondWith` 404
it "doesn't accept absolutes" $ do
delete "/files//tmp/tempTest" `shouldRespondWith` 404
describe "PUT /file" $ do
it "creates and modifies a new file" $ do
liftIO $ do
root <- getCurrentDirectory
createDirectoryIfMissing True (root </> "tempTest")
ex <- doesDirectoryExist (root </> "tempTest")
ex `shouldBe` True
put "/file/tempTest/f.txt" "content" `shouldRespondWith` 201
liftIO $ do
root <- getCurrentDirectory
f <- B.readFile (root </> "tempTest" </> "f.txt")
f `shouldBe` "content"
put "/file/tempTest/f.txt" "dummy" `shouldRespondWith` 200
liftIO $ do
root <- getCurrentDirectory
f <- B.readFile (root </> "tempTest" </> "f.txt")
f `shouldBe` "dummy"
it "doesn't accept .." $ do
put "/file/../tempTest/f.txt" "dummy" `shouldRespondWith` 404
it "doesn't accept absolutes" $ do
put "/file//tmp/f.txt" "dummy" `shouldRespondWith` 404
it "doesn't accept directories" $ do
put "/file/test" "dummy" `shouldRespondWith` 403
describe "DELETE /file" $ do
it "deletes a file" $ do
liftIO $ do
root <- getCurrentDirectory
createDirectoryIfMissing True (root </> "tempTest")
ex <- doesDirectoryExist (root </> "tempTest")
ex `shouldBe` True
let p = root </> "tempTest" </> "f.txt"
B.writeFile p "content"
exf <- doesFileExist p
exf `shouldBe` True
delete "/file/tempTest/f.txt" `shouldRespondWith` [json|null|] {matchStatus = 200}
liftIO $ do
root <- getCurrentDirectory
let p = root </> "tempTest" </> "f.txt"
exf <- doesFileExist p
exf `shouldBe` False
delete "/file/tempTest/f.txt" `shouldRespondWith` [json|null|] {matchStatus = 204}
it "doesn't accept .." $ do
liftIO $ do
root <- getCurrentDirectory
createDirectoryIfMissing True (root </> "tempTest")
ex <- doesDirectoryExist (root </> "tempTest")
ex `shouldBe` True
let p = root </> "tempTest" </> "f.txt"
B.writeFile p "content"
exf <- doesFileExist p
exf `shouldBe` True
delete "/file/../reload/tempTest/f.txt" `shouldRespondWith` 404
it "doesn't accept absolutes" $ do
delete "/file//tmp/f.txt" `shouldRespondWith` 404
cleanUp :: () -> IO()
cleanUp _ = do
root <- getCurrentDirectory
ex <- doesDirectoryExist (root </> "tempTest")
when ex $ do
removeDirectoryRecursive (root </> "tempTest")
--testFile :: FilePath -> ByteString -
testFile fp ct = do
l <- liftIO $ B.readFile fp
let fpb = B8.pack fp
get ("/file/" <> fpb) `shouldRespondWith` 200 {matchHeaders = ["Content-Type" <:> ct]}
r <- simpleBody <$> get ("/file/" <> fpb)
liftIO $ r `shouldBe` l
fileToJson :: FilePath -> FilePath -> IO Value
fileToJson root fs = do
let full =root</> fs
isf <- doesFileExist full
return $ if isf
then let
m = HM.lookupDefault "text/plain" (takeExtension fs) myMimes
in object ["type" .= ("file"::Text), "path" .= fs, "mime" .= m]
else object ["type" .= ("dir"::Text), "path" .= fs]
sortFs :: Value -> Value -> Ordering
sortFs (Object m1) (Object m2) =
let (String t1) = fromJust $ HM.lookup "type" m1
(String t2) = fromJust $ HM.lookup "type" m2
tc = t1 `compare` t2
in if tc == EQ
then
let (String p1) = fromJust $ HM.lookup "path" m1
(String p2) = fromJust $ HM.lookup "path" m2
in (toLower p1) `compare` (toLower p2)
else tc
myMimes :: HM.HashMap String Text
myMimes = HM.insert ".yaml" "text/x-yaml" $
HM.insert ".cabal" "text/x-cabal" $
HM.insert ".lhs" "text/x-haskell" $
HM.insert ".hs" "text/x-haskell" $
HM.insert ".json" "application/json" HM.empty