packages feed

hup-0.2.0.0: test/Distribution/Hup/UploadSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Distribution.Hup.UploadSpec where




import Control.Exception                      (throwIO)
import Control.Monad
import Control.Monad.IO.Class                 (liftIO)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy.Char8 as LBS   --   pack
import Data.Maybe                             (fromJust)
import Data.Monoid                            ( (<>) )
import Network.HTTP.Client.MultipartFormData  (renderParts,webkitBoundary)
import Network.HTTP.Types as T                (statusCode,methodPost
                                              ,StdMethod(..)) 
import Network.Wai.Parse as Parse             (FileInfo(..), fileName)
import Network.Wai.Test                       (simpleStatus,SResponse
                                              ,simpleBody)
import Test.Hspec
import qualified Test.Hspec.Wai as HWai       --(put, request)
import Test.Hspec.Wai.Internal                --(WaiSession,runWaiSession)
import Test.QuickCheck                        --(forAll)
import Test.QuickCheck.Monadic                --(assert, run, monadicIO )
import Web.Frank                              --(post, put)
import Web.Simple --(Application,controllerApp, ControllerT,Controller,ok,parseForm, queryParam, rawPathInfo, request, respond, routeMethod, routePattern)


import Distribution.Hup.Upload  
import Distribution.Hup.Parse 
import Distribution.Hup.Parse.Test

import qualified Distribution.Hup.Upload.Test 

import qualified Distribution.Hup.WebTest 

type ParsedTgz = Either String (IsDocumentation, Package) 


-- `main` is here so that this module can be run from GHCi on its own.  It is
-- not needed for automatic spec discovery.
main :: IO ()
main = 
  hspec spec 

spec :: Spec
spec = do
  describe "testing with mocked requests" $ 
    describe "mocked requests" $
      context "when processed by a mock hackage server" $ 
        it "should go to the right web app path" 
          httpMetadataRoundtripsOK'  
  describe "testing with live HTTP requests" $ 
    -- this will be replaced with a stub unless the WEB_TESTS macro
    -- is defined.
    Distribution.Hup.WebTest.liveTest webApp



httpMetadataRoundtripsOK' = 
  forAll arbUpload $ \upl -> httpMetadataRoundtripsOK upl 


httpMetadataRoundtripsOK upl = monadicIO $ do 
  upl <- return $ upl { fileConts = Just "" }
  testRequest <- run $ buildTestRequest "" upl 
  testResponse <- run $ sendTestRequest testRequest

  let resStatus = T.statusCode $ simpleStatus testResponse
      resBody :: String
      resBody =   LBS.unpack $ simpleBody testResponse
      _unserialized :: (IsDocumentation, IsCandidate, ParsedTgz)
      _unserialized@(recdIsDoc1, recdIsCand, parsedTgz) = read resBody

  let sentIsCand = isCandidate upl
      sentIsDoc  = uploadType  upl
      sentPkg    = package     upl

  assert (resStatus == 200)
  assert (sentIsCand == recdIsCand)
  assert (sentIsDoc  == recdIsDoc1)
  assert (parsedTgz == Right (sentIsDoc, sentPkg) )


sendTestRequest :: WaiSession SResponse -> IO SResponse
sendTestRequest testReq =   
  runWaiSession testReq webApp

--showInd x =
--  indent 4 (show x) 
--
--indent n = 
--  let wspace = replicate n ' ' 
--  in  unlines .  (map (wspace ++)) . lines

testPut :: String -> LBS.ByteString -> WaiSession SResponse
testPut url conts = 
  HWai.put (BS.pack url) conts

testPost
  :: String -> FilePath -> LBS.ByteString -> IO (WaiSession SResponse)
testPost url fileName fileConts = do
  boundary <- webkitBoundary 
  let part    = mkPart fileName fileConts
      headers = [("Content-Type", 
                    "multipart/form-data; boundary=" <> boundary)]
  body <- bodyToByteString <$> renderParts boundary [part]
  return $ HWai.request T.methodPost (BS.pack url) headers body


-- Only call when fileConts has something in it.
buildTestRequest
  :: String -> Upload -> IO (WaiSession SResponse)
buildTestRequest serverUrl upl  = do
  let (Upload _ filePath fileConts uploadType _pkgType ) = upl
      url = getUploadUrl serverUrl upl
  fileConts <- return (fromJust fileConts)
  case uploadType of
      IsPackage -> 
         testPost url filePath fileConts
      IsDocumentation -> 
         return $ testPut url fileConts 


ioAssert pred mesg = 
  unless pred $
        liftIO $ throwIO $ userError mesg

-- | We send the parsed results back as plain text,
-- parseable by 'Text.Read.read'.
webApp :: Application
webApp = controllerApp () $ do
    myReq <- request 
    routeMethod T.POST $ do path <- rawPathInfo <$> request
                            let isCand = if "/candidates/" `BS.isSuffixOf`path
                                         then CandidatePkg
                                         else NormalPkg  
                            when (path == "/packages/" || 
                                 path == "/packages/candidates/") $ do
                                    (_params, files) <- parseForm
                                    handlePost isCand files
    routeMethod T.PUT $ routePattern "/package/:pkgVer/:isCand" $ do
                            pkgVer <- fromJust <$> queryParam "pkgVer"  
                            let filename = pkgVer :: String
                            isCand <- fromJust <$> queryParam "isCand" 
                            let isCand' = if ("candidate" :: String) == isCand
                                         then CandidatePkg
                                         else NormalPkg
                            let remainingBit = if "candidate"== isCand
                                          then "docs"
                                          else ""
                            routePattern remainingBit $ 
                              handlePut isCand' filename
  where 
    handlePost :: IsCandidate -> [(a, FileInfo c)] -> ControllerT s IO b
    handlePost isCand files = do
      ioAssert (length files == 1)
               "posted package should have exactly 1 file" 
      let filename = BS.unpack $ Parse.fileName $ snd $ head files
          parsed :: Either String (IsDocumentation, Package)  
          parsed = parseTgzFilename' filename
      respond $ ok "text/plain" $ LBS.pack $ show (IsPackage, isCand, parsed)

    handlePut :: IsCandidate -> FilePath -> Controller s a
    handlePut isCand filename = do
      let parsed :: Either String (IsDocumentation, Package)  
          parsed = parseTgzFilename' (filename ++ "-docs.tar.gz")
      respond $ ok "text/plain" $ LBS.pack $ 
                show (IsDocumentation, isCand, parsed)